Releases: makegov/tango-node
v1.0.0
First stable release. tango-node is now at full feature parity with both the Tango API and tango-python for the surface that remains after the subject-based webhook removal (see "Removed" below). Every read method and every endpoint/alert/signing helper available on tango_python.TangoClient has an idiomatic camelCase counterpart on TangoClient, the SDK's docs are auto-published to docs.makegov.com/sdks/node/ via the composer pipeline (makegov/docs#15 / makegov/docs#16), and from 1.x on we'll only ship breaking changes on a major bump.
Changed (breaking)
createWebhookEndpoint({ name, ... })—nameis now required. Previously the SDK silently fell back to the callback URL's host whennamewas omitted, which masked the server'sunique(user, name)constraint until the second duplicate endpoint. Raising client-side gives a clearer error and matchestango-python1.0.0's behavior.createWebhookAlert({ filters, ... })—filtersis now validated as a non-empty plain object. Previously{}and arrays passed the check; the error message claimed "non-empty object" but didn't enforce it. Matches the server-side validation.
Fixed
tests/unit/client.iterate.test.ts— corrected the comment above the firstpageassertion; previous text claimed the first call should NOT carry a page, but the assertion (correctly) expectspage=1sincelistContractsdefaults to it.docs/DEVELOPERS.md—listContracts({ offset: 25 })example replaced with{ page: 2 }and "manual offset tracking" → "manual page/cursor tracking" (the method has never acceptedoffset).CHANGELOG.md— corrected "4 new unit test files" to "5"; the parenthesized list always contained 5 paths.- Lint cleanup ahead of the npm publish — 44 redundant
as AnyRecord/as <T>type-assertion casts dropped acrosssrc/(@typescript-eslint/no-unnecessary-type-assertionfires under the newer plugin minor that CI resolves).src/webhooks/receiver.ts: dropped the unusedAddressInfoimport, simplifiedDelivery.bodyJson: unknown | null→unknown(the latter already includesnull), and restructuredWebhookReceiver.run()to avoidconst receiver = this(replaced with arrow-function closures overgetUrl/getDeliveries/stop). No behavior change — tests still pass 220/220. eslint.config.js— disabled the coreno-undefrule for TS files. TypeScript itself does undefined-symbol checking with knowledge of TS-only types (AsyncDisposable,Disposable, etc.); the core rule double-fires and false-flags those. Matches the typescript-eslint upstream guidance.
Internal
- Pinned
devDependenciesto exact versions (dropped^frompackage.json). The previous unpinned ranges + gitignoredpackage-lock.jsonmeant CI re-resolved deps on every run and could pick up minor versions with stricter behavior that local installs hadn't seen yet — that's how the lint mismatch above slipped past local checks.
Docs
- README updated for the docs-review sweep:
- Added
TangoTimeoutErrorto the documented error class list (it has been exported fromsrc/errors.tssince v0.4 but the README omitted it). - Replaced the "(Coming Soon!)" marker on the docs link with the live
https://docs.makegov.com/sdks/node/URL. - Rewrote the "Comprehensive API Coverage" feature bullet — the old enumeration listed fewer than half of the actually-implemented domains. New bullet points at the canonical "API Methods" section for the full surface.
- Added
- New
docs/CLIENT.md—TangoClientconstructor reference, environment variables, full retry/backoff semantics (includingRetry-Afterhandling), error-handling patterns,fetchImplinjection, and staging/local targeting. Ported fromdocs.makegov.com/sdks/node/client.mdahead of the docs-site auto-pull cutover (makegov/docs#15 / makegov/docs#16). docs/API_REFERENCE.mdenriched with notes from the docs-sitemethods.mdthat hadn't been folded in yet:listContractspage/cursor mutual exclusion,getIdvSummary/listIdvSummaryAwardsdeprecation (server returns 404),listIdvLcatsclarification,listOrganizationslevelsemantics,createWebhookEndpointsnake_case canonical vs camelCase legacy aliases (nameis required either way per the 1.0.0 change above),testWebhookEndpointpost-#2252 cleanup ({ endpoint: <id> }is canonical), andcreateWebhookAlertfield-rename notes (namevssubscription_name,filtersvsfilter_definition, singularquery_type, update-writable field list).
CI
- New
.github/workflows/docs-dispatch.yml— fires on push tomainwhendocs/**,README.md, orCHANGELOG.mdchanges and dispatchesexternal_updatedatmakegov/docsso the public docs site rebuilds with the latest SDK content. Required for the makegov/docs#15 auto-pull pipeline.
Parity closure — all previously-tracked gaps addressed
Every gap surfaced in the May 2026 parity audit is now closed:
Typed filter Options interfaces
listForecasts,listOpportunities,listNotices,listGrants— previouslyListOptionsBase & Record<string, unknown>with zero typed filters; now ship fullOptionsinterfaces (ListForecastsOptions,ListOpportunitiesOptions,ListNoticesOptions,ListGrantsOptions) enumerating every filter kwarg from the Python signatures.ListNoticesOptionsdeliberately omitsordering(server rejects it).ListContractsOptionsexpanded to enumerate all 27+ Python kwargs (award_date*,awarding_agency,funding_agency,obligated_gte/lte,pop_*,expiring_*,keyword/recipient_name/recipient_uei/set_aside_type/naics_code/psc_codealiases,sort+order→ordering, etc.).ListEntitiesOptionsexpanded with the 12 Python kwargs (cage_code,naics,name,psc,purpose_of_registration_code,socioeconomic,state,total_awards_obligated_gte/lte,uei,zip_code).ListVehiclesOptionsexpanded with all 21 filter kwargs (vehicle_type,type_of_idc,contract_type,who_can_use,total_obligated_min/max, etc.).ListIdvsOptionsexpanded with the full IDV filter surface.ListOtasOptions/ListOtidvsOptionsexpanded with the missing_gte/_lteranges (award_date_gte/lte,fiscal_year_gte/lte,expiring_gte/lte,pop_start_date_gte/lte,pop_end_date_gte/lte).ListAgenciesOptionsadded (was inline{ page?, limit? }).
All Options interfaces keep a [key: string]: unknown index signature for forward-compatibility with new server-side filters that haven't been ported yet — typed fields give autocomplete and typo-protection on known filters; unknown fields still pass through.
ShapeConfig preset parity
Added: PROTESTS_MINIMAL, OTAS_MINIMAL, OTIDVS_MINIMAL, SUBAWARDS_MINIMAL, GSA_ELIBRARY_CONTRACTS_MINIMAL, ORGANIZATIONS_MINIMAL, VEHICLE_ORDERS_MINIMAL, ITDASHBOARD_INVESTMENTS_MINIMAL, ITDASHBOARD_INVESTMENTS_COMPREHENSIVE.
Updated to match Python field lists: ENTITIES_COMPREHENSIVE (now uses federal_obligations(*) expansion), VEHICLES_MINIMAL (extended to mirror Python's larger field list), VEHICLES_COMPREHENSIVE (dropped competition_details(*) per SDK v0.6.0; added program_acronym, is_synthetic_solicitation, metrics(*), organization expansion).
Explicit schemas
Added 11 schemas + registry entries: ORGANIZATION_OFFICE_SCHEMA, VEHICLE_METRICS_SCHEMA, ORGANIZATION_SCHEMA, OTA_SCHEMA, OTIDV_SCHEMA, SUBAWARD_SCHEMA, PROTEST_DOCKET_SCHEMA, PROTEST_SCHEMA, GSA_ELIBRARY_IDV_REF_SCHEMA, GSA_ELIBRARY_CONTRACT_SCHEMA, ITDASHBOARD_INVESTMENT_SCHEMA. Wired into the EXPLICIT_SCHEMAS registry under canonical model names.
Also extended VEHICLE_SCHEMA with the fields the new VEHICLES_* presets reference: is_synthetic_solicitation, program_acronym, organization (expandable to OrganizationOffice), metrics (expandable to VehicleMetrics), idv_count, total_obligated, latest_award_date, opportunity_id, description.
Typed return models
client.resolve(input)→Promise<ResolveResult>with typedResolveCandidate[](wasPromise<{ candidates: AnyRecord[]; count: number }>).client.validate(input)→Promise<ValidateResult>(wasPromise<AnyRecord>).client.getAgency(code)→Promise<AgencyRecord>(wasPromise<AnyRecord>).client.getProtest(caseNumber)→Promise<ProtestRecord>with typeddocket,resolved_*, etc.
All new types exported from package root.
Observability — rateLimitInfo + lastResponseHeaders
Two new instance properties on TangoClient mirroring Python's rate_limit_info / last_response_headers:
await client.listContracts({ limit: 5 });
console.log(client.rateLimitInfo);
// { remaining: 98, limit: 100, resetIn: 60, retryAfter: null, limitType: "per_minute" }
console.log(client.lastResponseHeaders?.["x-request-id"]);Both null until the first request completes; populated after every successful request. HttpClient parses X-RateLimit-{Remaining,Limit,Reset,Type} + Retry-After headers into a RateLimitInfo snapshot.
listContracts cursor pagination (non-breaking)
ListContractsOptions now accepts cursor alongside page. When cursor is supplied, the request omits page and uses keyset pagination (Python parity); otherwise falls back to page-based. PaginatedResponse gained a cursor field auto-extracted from next so callers can client.listContracts({ cursor: resp.cursor }) for the next page without parsing the URL themselves.
WebhookReceiver framework
New src/webhooks/receiver.ts. WebhookReceiver dataclass-style class with start(), stop(), url, deliveries, onDelivery callback, optional forwardTo mirror, history cap, signature verification (via existing verifySignature). Two run patterns shipped:
await using rx = await new WebhookReceiver(opts).run();(modern,Symbol.asyncDispose).- `await WebhookReceiver.wit...
v0.3.0
Added
- Vehicles endpoints:
listVehicles,getVehicle, andlistVehicleAwardees(supports shaping + flattening) - IDV endpoints:
listIdvs,getIdv,listIdvAwards,listIdvChildIdvs,listIdvTransactions,getIdvSummary,listIdvSummaryAwards - Webhooks v2 client support: event type discovery, subscription CRUD, endpoint management, test delivery, sample payload helpers
Changed
- HTTP client now supports PATCH/PUT/DELETE for non-GET endpoints
joineris now respected when unflatteningflat=trueresponses on supported endpoints
v0.1.4 (2025-11-21)
This is the Node/TypeScript port of the official Tango Python SDK.