@bims-ad/tdm-client
v0.1.2
Published
TypeScript client for the Broadcom Test Data Manager (TDM) REST API — typed clients for all 13 servlet services, plus an auth-refreshing session, correct pagination, and job-domain helpers.
Maintainers
Readme
@bims-ad/tdm-client
TypeScript clients for the Broadcom Test Data Manager REST API, generated from
the OpenAPI 3.1 specs in swagger/.
Shape
The thirteen TDM specs are thirteen servlet context roots on one host
(job-engine → /TDMJobService, publish → /TDMPublisherService, …). They
share an origin and a JWT but each needs its own base URL. Two specs declaring
POST /api/ca/v1/jobs are therefore different URLs, not a conflict.
createTdmClients() takes the origin and returns one configured client per
service. Pass it into any generated operation:
import { createTdmClients, jobEngine } from '@bims-ad/tdm-client';
const tdm = createTdmClients({
origin: 'https://tdm.example.com:8443',
token,
insecureTls: true, // lab hosts ship self-signed / expired certs
});
const jobs = await jobEngine.getAllJobs({ client: tdm.jobEngine, query: { size: 50 } });Auth: core owns /user/login and issues the JWT the other twelve consume.
Use setTdmToken(tdm, token) to swap it on already-built clients.
Domain layer
Above the raw generated clients sits a thin hand-written domain layer that encodes the operational knowledge the generated types can't (auth lifecycle, the endpoint gotchas). Prefer it over wiring auth yourself.
createTdmSession — auth that stays fresh
For anything long-running (a monitor, a poller), use a session instead of a one-shot login. It takes credentials, logs in, and keeps the JWT current — proactively before the ~24h expiry and reactively on a 401 — so your calls never fail on a stale token. The clients it exposes always carry a valid token; you never rebuild them.
import { createTdmSession, jobEngine } from '@bims-ad/tdm-client';
const session = await createTdmSession({
origin: 'https://tdm.example.com:8443',
username, password,
insecureTls: true,
});
while (running) {
await session.ensureFresh(); // no-op until the token nears expiry, then re-logs-in
const { data } = await jobEngine.getAllJobs({ client: session.clients.jobEngine });
// ...
}loginToTdm() (returning a TdmLoginResult) remains available for the one-shot
case, but createTdmSession is the right default for a live integration. See the
JSDoc on TdmSession for the full contract.
paginate() — correct list paging
TDM list endpoints are 1-indexed (page=0 aliases page 1) and can repeat a record
across pages. paginate() encodes that once: give it a fetchPage(page, size)
adapter and a keyOf, and it handles 1-indexing, dedup, and the stop conditions
for any list endpoint. See its JSDoc for the shape.
createJobsApi() + job helpers
Job-domain operations and field semantics, so you don't re-derive the model's
traps. createJobsApi(session.clients) gives recent(), get() (the singular
/job/{id}), and children() (the plural /jobs/{id}). Pure helpers encode what
the generated types can't:
import { createJobsApi, isTerminal, statusOf, jobFilter } from '@bims-ad/tdm-client';
const jobs = createJobsApi(session.clients);
const recent = await jobs.recent({ total: 500, q: jobFilter({ origin: 'flow_origin' }) });
const done = recent.filter(isTerminal); // terminal = status, NOT endTimeLKey rule baked in: terminality is decided by status, never by endTimeL — a
job cancelled before it started has no endTimeL; one cancelled mid-run has one.
See jobs.ts JSDoc for isTerminal, durationMs, queueLatencyMs,
wasCancelledBeforeStart, and the singular/plural endpoint distinction.
Generated code
src/generated/ is gitignored. It is produced by tools/generate-clients.mjs
via the generate-clients Nx target, which build, typecheck, test, and
lint all dependsOn — so a fresh clone materializes it on the first command.
Never edit it; the next run overwrites it.
Regenerate explicitly:
bun run generate:clientsWhy axios, not fetch
@hey-api/client-axios, not client-fetch, because TDM hosts commonly present
self-signed or expired certificates and may sit behind a proxy. There is no
single fetch code path that handles that on both Node and Bun:
| Approach | Node 24 | Bun 1.3 |
|---|---|---|
| fetch + tls: {} | not an API | works |
| fetch + undici Agent | UND_ERR_INVALID_ARG | ignored |
| undici.fetch + Agent | works | fails |
| axios + httpsAgent | works | works |
axios gives per-instance TLS and proxy control on both runtimes. The
alternative — NODE_TLS_REJECT_UNAUTHORIZED=0 — disables verification
process-wide, which is the wrong scope for one bad certificate.
Known deviation
exactOptionalPropertyTypes is false in tsconfig.lib.json, against the
workspace default. hey-api's output cannot satisfy it and the files are
machine-written. Scoped to this project; everything else inherits true.
