On this page

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.

MethodRouteReturns
whoami()GET /v1/whoamiYour user id, role, organization, department, project and grants.
approvals(status?)GET /v1/approvalsPending or decided approvals. status is URL-encoded for you.
approve(approvalId)POST /v1/approvals/{id}/approveThe decision result.
reject(approvalId, reason)POST /v1/approvals/{id}/rejectThe 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.

OptionDefaultEffect
retryfalseRetry UpstreamError and QuotaExceeded, up to the client's retries.
waitForApprovalfalseBlock and poll until a person decides, then resume automatically.
approvalTimeout600Seconds to wait for that decision.
poll5Seconds between approval checks.
resumeToken—Resume an approved call. Takes no payload.
timeoutclient's 70Seconds, 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.