Client SDK
Install and use @tarski/client for observation, query, subscription, schedule, blob, session, and platform-builder contracts.
@tarski/client is the generated TypeScript client for local tarski serve and Tarski
Cloud. Every request, response, receipt, diagnostic, and error type comes from the same
contract inventory the runtime enforces. It is ESM, includes declarations, requires
Node 18+ or a modern browser bundler, and has zero runtime dependencies.
Availability and installation
The first public registry release is @tarski/client 0.8.0. It accompanies
tarski 0.5.9-preview.8 and is release-gated by a clean consumer install, package
integrity, generated-contract digest, local runtime conformance, and hosted parity.
npm install @tarski/client@^0.8.0
# or
pnpm add @tarski/client@^0.8.0
Create a client
import { createTarskiClient } from '@tarski/client';
const client = createTarskiClient({
baseUrl: 'http://127.0.0.1:8080',
token: () => sessionToken,
});
Auth options are deliberately in-memory:
tokensupplies a runtime bearer as a value or function. The SDK never persists it.endUserTokenre-reads your application’s rotating IdP token for each request; the runtime verifies it and constructs trusted actor context.localDevActorContextmaps to local fixture headers and fails closed against any other authority.
Point a separate client at the management origin for platform-builder operations. Never expose its credential to a browser.
Capability discovery
An older or differently profiled runtime may omit a generated operation. Discover the surface first:
import {
clientSdkCapabilityIsAvailable,
resolveClientSdkCapability,
} from '@tarski/client';
const capabilities = await client.api.discoverCapabilities({
operation: 'discover_capabilities',
operationContract: 'tarski-client-sdk-api:v1/discover_capabilities',
});
if (!clientSdkCapabilityIsAvailable(capabilities, 'createSchedule')) {
const state = resolveClientSdkCapability(capabilities, 'createSchedule');
// state.state === 'unsupported_runtime' when the operation was omitted
}
The SDK never probes a guessed legacy route. Omitted operations resolve to typed
unsupported_runtime; explicitly advertised unavailable operations preserve the
runtime’s reason and minimum contract.
Append evidence
const receipt = await client.api.appendObservation({
operation: 'append_observation',
operationContract: 'tarski-client-sdk-api:v1/append_observation',
params: { lineage_id: 'lin_orders' },
body: {
kind: 'order.placed',
payload: { order_id: 'o_42' },
source: 'storefront',
write_policy: 'customer_order',
},
idempotency: { idempotency_key: 'order-o_42' },
});
A matching idempotent retry returns the original observation address and observed_at.
Conflicting key reuse fails. The generated batch operation appends one ordered,
linearizable batch with contiguous observation ids, but its SDK contract has no
idempotency: retrying the batch appends it again. Use an expected head for optimistic
concurrency and single idempotent appends when transport retry is possible.
Queries and coherent batches
const page = await client.api.executeDeclaredQuery({
operation: 'execute_declared_query',
operationContract: 'tarski-client-sdk-api:v1/execute_declared_query',
params: { query_name: 'open-orders' },
body: { bindings: {} },
});
Pages pin query, binding, result, snapshot, and projection identities. When several query windows must describe the same committed worldview, execute one coherent batch:
const dashboard = await client.api.executeQueryBatch({
operation: 'execute_query_batch',
operationContract: 'tarski-client-sdk-api:v1/execute_query_batch',
body: {
lineage: 'org:acme',
queries: [
{ name: 'open-tickets', bindings: {} },
{ name: 'on-call-summary', bindings: {} },
],
},
});
Every result carries dashboard.snapshot: the same lineage, evaluator, projection, and
head. This is the multi-window coherence boundary; independent calls do not promise to
share a head.
Subscriptions
subscribeQuery() is a typed async iterable over an initial full result, exact row
deltas, explicit full-result rehydrates, and terminal closure:
for await (const update of client.subscribeQuery('open-tickets', {
lineage: 'org:acme',
})) {
switch (update.kind) {
case 'initial':
case 'rehydrate':
rows = update.rows;
break;
case 'delta':
rows = applyDelta(rows, update.added, update.removed);
persistResumeCursor(update.updateId);
break;
case 'closed':
return;
}
}
Resume uses the opaque, per-subscription updateId. The SDK prefers held SSE and falls
back to a batch resume door after an open failure while preserving that cursor. A stale
window or oversized delta yields rehydrate; replace the whole visible set. See
Reactive queries.
subscribeLineageEvents() and subscribeSessionEvents() provide the corresponding
cursor/rehydrate contract for runtime and agent-session events.
Schedules and blobs
The generated API includes schedule list/read/create/replace/pause/resume/remove/trigger, fire history, and pure preview:
await client.api.createSchedule({
operation: 'create_schedule',
operationContract: 'tarski-client-sdk-api:v1/create_schedule',
params: { lineage_id: 'org:acme' },
body: {
schedule_id: 'daily-digest',
definition: { cron: '0 7 * * *', timezone: 'Europe/Rome' },
},
});
Blob operations cover upload, list, metadata, and ranged raw download:
const uploaded = await client.api.uploadBlob({
operation: 'upload_blob',
operationContract: 'tarski-client-sdk-api:v1/upload_blob',
body: {
lineage_id: 'org:acme',
content: 'hello',
media_type: 'text/plain',
},
});
const bytes = await client.downloadBlob(String(uploaded.digest));
downloadBlob() is a behavior method because successful content is raw bytes rather than
the generated JSON envelope. See Storage for auth,
quota, size, range, and error rules.
Sessions and errors
Generated session operations cover create-or-start, start, preview, messages, catalog,
auth state, lifecycle, transcript, timeline, and provenance. Session start is
idempotent; conflicting reuse has the stable session_start.idempotency_conflict code.
All failures use TarskiApiError:
try {
await client.api.appendObservation(/* generated request */);
} catch (error) {
if (error instanceof TarskiApiError) {
error.code;
error.retryability; // retryable | non_retryable | rehydrate_required
error.retryAfterMs;
error.contracted;
}
}
Unknown future server codes pass through with contracted: false; configuration errors
minted by the SDK use the separate sdk.* code space.
Contract identity
import { CLIENT_SDK_CONTRACT_IDENTITY } from '@tarski/client';
CLIENT_SDK_CONTRACT_IDENTITY.contractVersion;
CLIENT_SDK_CONTRACT_IDENTITY.sourceContractDigest;
CLIENT_SDK_CONTRACT_IDENTITY.generatorVersion;
The published tarball embeds its generated contract sources under dist/contract/.
Changing the source contract digest requires at least a minor package version and is
blocked by the release gate otherwise.