Tarski/ docsJoin the waitlist

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:

  • token supplies a runtime bearer as a value or function. The SDK never persists it.
  • endUserToken re-reads your application’s rotating IdP token for each request; the runtime verifies it and constructs trusted actor context.
  • localDevActorContext maps 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.