Using the TypeScript SDK
Everything the SDK does falls into three groups: tool calls, which all share one shape; the handful of non-tool methods that are spelled out explicitly because they are not tools; and the options that govern retry, approval and cancellation.
Calling a tool
Every tool has a typed method. Field names in the payload are the gateway's own wire names, verbatim — there is no case conversion, because the gateway's own format is mixed.
const rows = await fabric.queryContext({
query: 'MATCH (c:Customer) WHERE c.project_id = $project_id RETURN c.name AS name LIMIT 5',
})
Every result is the uniform envelope, with the correlation ids attached to the object rather than buried in it:
console.log(rows) // { columns: …, rows: …, row_count: 0, stats: {…}, truncated: false }
console.log(rows.lifecycleId) // qlc_000013
console.log(rows.traceId) // 73d1ab052c9e3371ae3b38098c4b34ac
$project_id is bound for you from the credential. Never pass it yourself — see
below.
Or call by name
call() takes the tool's name, and accepts either spelling. These are the same call:
await fabric.call('run_process_flow', { goal: 'reconcile' })
await fabric.call('run-process-flow', { goal: 'reconcile' })
An unrecognised name is sent to the wire verbatim rather than rejected, so a tool the gateway gained after your SDK was published is still reachable without an upgrade.
The same call on every surface
curl -s -X POST "$BASE/v1/query-context" \
-H "Authorization: Bearer $WEXA_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"query":"MATCH (c:Customer) WHERE c.project_id = $project_id RETURN c.name AS name LIMIT 5"}'All three produced the identical envelope against a live gateway. Per-tool arguments are in the tool reference.
The SDK exports every tool as a method, named as the tool's page shows it.
Server-bound arguments
Four argument names are never yours to send: project_id, projectID, organization_id and
executed_by. The platform fills them in from your credential's scope.
The SDK refuses them client-side, before the request is built:
await fabric.queryContext({ query: 'RETURN 1', project_id: 'proj_other', organization_id: 'o' })
ValidationError: ['organization_id', 'project_id'] are bound server-side from your token scope; sending them is rejected
The reason it is refused rather than quietly dropped is that the gateway does not refuse it for
you. Sending a body project_id of another project alongside a valid query-context request
returned HTTP 200 and a normal envelope when it was tried against a live gateway: the handler
reads only query and parameters, and binds $project_id from your credential's scope. Your
value was neither honoured nor rejected — it was ignored, and the call succeeded against the scope
you were always pinned to rather than the one you asked for.
That is the failure the client-side guard exists to prevent, and it is the worse of the two possible ones: a refusal is visible, a silently ignored argument is not. For a REST caller with no SDK, this guard is the only thing standing between you and that outcome.
The Python SDK applies the same guard over the same four names. See server-bound arguments for the concept.
The non-tool methods
Five methods are not tools and are spelled out explicitly, because they map onto conventional REST
routes rather than the POST /v1/<tool> convention.
| Method | Route | Returns |
|---|---|---|
whoami() | GET /v1/whoami | Your user id, role, organization, department, project and grants. |
approvals(status?) | GET /v1/approvals | Pending or decided approvals. status is URL-encoded for you. |
approve(approvalId) | POST /v1/approvals/{id}/approve | The decision result. |
reject(approvalId, reason) | POST /v1/approvals/{id}/reject | The decision result. reason is required; without one the call throws ValidationError before any request. |
lifecycle(lifecycleId) | GET /v1/lifecycles/{id} | The stage-by-stage record of one governed call. |
const pending = await fabric.approvals('pending')
for (const a of pending) console.log(a.id, a.tool, a.what, a.why, a.expires_at)
await fabric.approve(pending[0].id)
const record = await fabric.lifecycle(rows.lifecycleId)
There is a sixth explicit method, resume(tool, resumeToken, options?), which exists because a
typed payload cannot simply be omitted for tools with required fields. It is covered on
error handling alongside the rest of the approval path.
Call options
Every tool method takes a second argument.
| Option | Default | Effect |
|---|---|---|
retry | false | Retry UpstreamError and QuotaExceeded, up to the client's retries. |
waitForApproval | false | Block and poll until a person decides, then resume automatically. |
approvalTimeout | 600 | Seconds to wait for that decision. |
poll | 5 | Seconds between approval checks. |
resumeToken | — | Resume an approved call. Takes no payload. |
timeout | client's 70 | Seconds, for this call only. |
signal | — | An AbortSignal that cancels this call and any approval poll it is waiting on. |
await fabric.queryContext({ query }, { retry: true }) // safe: it is a read
await fabric.saveContext({ nodes }, { waitForApproval: true }) // blocks on a person
retry is off by default on purpose: the gateway has no idempotency key, so a retried runAgent
starts a second real run.
waitForApproval ends on the first final status of the approval. approved resumes the call. rejected,
expired, overdue and consumed throw ApprovalError with the status in the message (approval rejected)
and the approval in raw. An overdue approval can still be approved in the console; to keep waiting for it,
catch ApprovalRequired, keep the resumeToken and resume later.
Consequential writes wait by default
Breaking change. Every project now holds consequential writes made with an API key or an SDK session until a
person approves them, admin API keys included. Table row inserts and updates (insertTableRows,
updateTableRow), table create and rename (createTable, renameTable), trigger changes (setTriggers)
and connector actions (runConnectorAction) are consequential now, next to the tools that already were, such as
deleteTableRows and saveContext. They throw ApprovalRequired unless the gate is off. A project admin
turns the gate off, or back on, in the project's settings. Actions taken by a person in the console are not held.
Cancellation
const controller = new AbortController()
setTimeout(() => controller.abort(), 2_000)
await fabric.queryContext({ query }, { signal: controller.signal })
A caller-triggered abort rejects with the standard AbortError, not with a Wexa error class —
an abort is your own action rather than a failure of the call. A timeout, by contrast, is wrapped as
a TransportError, so the two stay distinguishable in one handler.
close() aborts everything in flight at once, which is what you want on shutdown.
A typed result
Result<T> is the tool payload with lifecycleId and traceId attached. Pass a type argument when
you know the shape:
type Row = { name: string }
const rows = await fabric.queryContext<{ rows: Row[]; row_count: number }>({ query })
Payload types are exported with the methods — QueryContextPayload, SaveContextPayload,
CreateOntologyPayload and the rest — as are WhoAmI, Approval, FabricOptions, CallOptions
and ToolCallOptions.