Client Surfaces
The WarmHubClient organizes operations into typed surfaces accessed as properties on the client instance. Each surface groups related methods for a specific domain.
import { WarmHubClient } from '@warmhub/sdk-ts'
const client = new WarmHubClient({ auth: { getToken: async () => process.env.WARMHUB_TOKEN },})
// Access surfaces as propertiesconst orgs = await client.org.list()const head = await client.thing.head('acme', 'world')All methods return promises. All methods throw WarmHubError on failure. See SDK Overview for how to create a token and full client setup.
Constructor options
Section titled “Constructor options”| Option | Type | Default | Description |
|---|---|---|---|
auth.getToken | () => Promise<string | undefined> | — | Token acquisition hook — required for authenticated endpoints |
accessToken | string | () => string | undefined | Promise<string | undefined> | — | Static token or sync/async provider (alternative to auth.getToken) |
apiUrl | string | https://api.warmhub.ai | Override the API URL (not needed for most users) |
fetch | typeof fetch | — | Custom fetch implementation |
functionLogs | 'raw' | 'off' | 'off' | Control replay of backend console.* lines. Set to 'off' to suppress backend log noise. |
client.org
Section titled “client.org”Manage organizations — the top-level namespace for repositories.
| Method | Description |
|---|---|
get(orgName) | Get an organization by name |
list(opts?) | List organizations (archived hidden by default). Pass { includeArchived: true } to include archived. |
create(name, displayName?, description?) | Create a new organization. Description is trimmed; max 2000 characters. |
getOrCreate(name, displayName?) | Get if exists, otherwise create |
addMember(orgName, email, role?) | Add a member or send an invite (role defaults to 'editor'). Returns OrgMember with status?: 'active' | 'pending' and optional invitedBy (inviter’s email). If the email is not a WarmHub user, a pending invite is created and an invite email is attempted (best-effort, async). Only owners can assign 'owner'. |
removeMember(orgName, email) | Remove a member or revoke a pending invite |
listMembers(orgName, opts?) | List members and caller’s role. Pass { pending: true } to filter to pending invites only. Returns { members: OrgMember[], callerRole: OrgRole | null }. Each member has status?: 'active' | 'pending' and optional invitedBy (email). callerRole is null for non-members or unauthenticated callers (members will be empty). |
changeMemberRole(orgName, email, role) | Change a member’s role. Only owners can promote to or demote from 'owner'. Cannot demote the last owner. |
setDescription(orgName, description?) | Set or clear the org description. Trimmed; empty strings clear the value; max 2000 characters. |
archive(orgName) | Archive an organization (blocks new repos and members) |
unarchive(orgName) | Unarchive an organization |
Roles: 'owner', 'admin', 'editor', 'viewer' (type: OrgRole).
const org = await client.org.getOrCreate('acme', 'Acme Corp')const orgs = await client.org.list()
// Membershipawait client.org.addMember('acme', 'alice@example.com', 'editor')const { members, callerRole } = await client.org.listMembers('acme')await client.org.changeMemberRole('acme', 'alice@example.com', 'viewer')await client.org.removeMember('acme', 'alice@example.com')client.repo
Section titled “client.repo”Manage repositories within an organization.
| Method | Description |
|---|---|
get(orgName, repoName) | Get a repository |
list(orgName, opts?) | List repos in an org (archived hidden by default). Pass { includeArchived: true } to include archived. |
create(orgName, repoName, description?, visibility?) | Create a repository. visibility is 'public' or 'private' (default: 'private'). Description is trimmed; max 2000 characters. |
setVisibility(orgName, repoName, visibility) | Set 'public' or 'private' |
setDescription(orgName, repoName, description?) | Set or clear the repo description. Trimmed; empty strings clear the value; max 2000 characters. |
resolve(orgName, repoName) | Resolve to a RepoLocator |
archive(orgName, repoName) | Archive a repository (blocks new commits) |
unarchive(orgName, repoName) | Unarchive a repository |
const ref = await client.repo.create('acme', 'world', 'Game world')const pubRef = await client.repo.create('acme', 'datasets', 'Public datasets', 'public')const repos = await client.repo.list('acme')client.shape
Section titled “client.shape”Manage shapes — the schemas that define the structure of things and assertions.
| Method | Description |
|---|---|
get(orgName, repoName, shapeName) | Get a shape definition |
list(orgName, repoName, opts?) | List all shapes. Options include componentId to filter by owner and excludeComponents to hide component-owned shapes. |
create(orgName, repoName, shapeName, fields, opts?) | Create a shape. opts: { description?: string } |
revise(orgName, repoName, shapeName, newFields, opts?) | Update shape fields (creates new version). opts: { description?: string } |
remove(orgName, repoName, shapeName) | Remove a shape |
await client.shape.create('acme', 'world', 'Location', { x: 'number', y: 'number', label: 'string',})
const shapes = await client.shape.list('acme', 'world')client.thing
Section titled “client.thing”Read things and assertions — the core data in a repository.
| Method | Description |
|---|---|
head(orgName, repoName, opts?) | Current HEAD snapshot, optionally filtered by shape/kind/match. HeadOptions includes workspaceName, componentId, excludeComponents, and excludeInfraShapes (hides system-managed component infrastructure records). |
get(orgName, repoName, wref, version?, opts?) | Get one thing by wref, with optional pinned version. opts: { workspaceName?, includeInactive? }. |
history(orgName, repoName, opts?) | Version history and timeline metadata. HistoryOptions includes resolveCollections and workspaceName. |
about(orgName, repoName, wref, opts?) | Assertions about a thing, with optional shape/match filter. AboutOptions includes resolveCollections and workspaceName. |
query(orgName, repoName, opts?) | Query by shape, kind, about, or text filters. FilterOptions includes resolveCollections, workspaceName, componentId, excludeComponents, and excludeInfraShapes. |
resolve(orgName, repoName, wref, opts?) | Resolve a wref to its projected record. opts: { workspaceName? } |
search(orgName, repoName, query, opts?) | Full-text, vector, or hybrid search. SearchOptions includes resolveCollections (text mode only), workspaceName, componentId, excludeComponents, and excludeInfraShapes (text mode only — passing these with vector/hybrid mode throws). When searching with about or resolveCollections, pages may be sparse — paginate until nextCursor is absent. |
refs(orgName, repoName, wref, opts?) | Query wref-typed field backlinks (inbound) or forward references (outbound) for a thing. RefsOptions includes workspaceName. Default direction is 'inbound'. |
count(orgName, repoName, opts?) | Count matching items without returning data. CountOptions accepts shape, kind, match, about, includeInactive, resolveCollections, workspaceName, componentId, excludeComponents, and excludeInfraShapes. Returns { count: number }. Passing workspaceName currently returns a VALIDATION_ERROR — workspace-scoped counts are not yet supported. |
// Read HEAD, filtered to Location thingsconst head = await client.thing.head('acme', 'world', { shape: 'Location' })
// Get a specific thingconst cave = await client.thing.get('acme', 'world', 'Location/cave')
// Get assertions about the caveconst beliefs = await client.thing.about('acme', 'world', 'Location/cave', { shape: 'Belief',})
// Filter with glob pattern (match applies to full wrefs: Shape/name)const dungeonRooms = await client.thing.head('acme', 'world', { shape: 'Location', match: 'Location/dungeon/*',})
// Query with matchconst dungeonBeliefs = await client.thing.query('acme', 'world', { shape: 'Belief', about: 'Location/cave', match: 'Belief/dungeon/*',})
// Searchconst results = await client.thing.search('acme', 'world', 'cave', { mode: 'text',})
// Count itemsconst total = await client.thing.count('acme', 'world', { shape: 'Location' })console.log(total.count) // 42The match parameter
Section titled “The match parameter”The match option accepts a glob pattern that filters against full wrefs (Shape/name). It is supported on head(), query(), and about().
*matches a single path segment**matches zero or more segments
// All locations under dungeon/client.thing.head('acme', 'world', { match: 'Location/dungeon/*' })
// Nested assertions about a thingclient.thing.about('acme', 'world', 'Location/cave', { match: 'Belief/*' })See Filtering and Lookup for CLI and MCP equivalents.
thing.refs() queries backlinks created by "wref"-typed fields in shape definitions. For example, if a Route shape has a field destination: "wref" pointing to Location/cave, then thing.refs('Location/cave') returns that Route as an inbound reference. It does not return about-based assertion relationships (use thing.about() for those).
| Option | Type | Default | Description |
|---|---|---|---|
direction | 'inbound' | 'outbound' | 'inbound' | Inbound: things whose wref fields point to this thing. Outbound: things this thing’s wref fields point to. |
fieldPath | string | — | Filter by field path (inbound only) |
workspaceName | string | — | Scope query to a workspace |
limit | number | — | Page size |
cursor | string | — | Pagination cursor |
// What things reference Location/cave in their wref-typed fields?const inbound = await client.thing.refs('acme', 'world', 'Location/cave')
// What does Location/cave reference in its own wref-typed fields?const outbound = await client.thing.refs('acme', 'world', 'Location/cave', { direction: 'outbound',})Search modes
Section titled “Search modes”| Mode | Description |
|---|---|
'text' | Full-text search (default) |
'vector' | Embedding-based semantic search |
'hybrid' | Combined text + vector search |
client.commit
Section titled “client.commit”Write data through atomic commits.
| Method | Description |
|---|---|
apply(orgName, repoName, author, message, operations, opts?) | Apply a commit with one or more operations. See apply options below. |
log(orgName, repoName, opts?) | Read the commit log. LogOptions includes workspaceName to scope to a workspace’s commit history. |
commit.apply options
Section titled “commit.apply options”| Option | Type | Default | Description |
|---|---|---|---|
workspaceName | string | — | Commit to a workspace instead of baseline |
componentId | string | — | Scope the commit to a specific component |
chunkSize | number | 1000 | Override the default chunk size for large commits sent via ingest. Clamped to [1, 2000] and truncated to an integer. |
await client.commit.apply('acme', 'world', 'sdk', 'seed cave', [ { operation: 'add', kind: 'thing', name: 'Location/cave', data: { x: 3, y: 7, label: 'Dark Cave' }, },])
// Commit to a workspaceawait client.commit.apply( 'acme', 'world', 'sdk', 'bulk import', operations, { workspaceName: 'q4-refresh' },)
const log = await client.commit.log('acme', 'world', { limit: 5 })Operations follow the same model as CLI commit operations — add and revise, each for things, assertions, shapes, or collections.
client.commit.apply() vs CommitBuilder
Section titled “client.commit.apply() vs CommitBuilder”There are two ways to commit:
client.commit.apply() | CommitBuilder | |
|---|---|---|
| Best for | Simple commits, raw operation arrays | Multi-operation commits that benefit from validation |
| Client-side validation | Minimal (structural checks only) | Extensive — duplicate names, missing refs, shape validation |
| Builder pattern | No — pass operations directly | Yes — chainable add() / revise() / deactivate() |
| Kind inference | No — you must specify kind | Yes — inferred from name, about, and type fields |
See CommitBuilder below for the builder API and AddOp fields for the simplified input format.
client.workspace
Section titled “client.workspace”Manage workspaces — isolated branches for data changes.
| Method | Description |
|---|---|
create(orgName, repoName, name, description?) | Create a workspace pinned to current repo HEAD |
list(orgName, repoName, opts?) | List workspaces. opts: { status?, includeDefault? } |
get(orgName, repoName, workspaceName) | Get workspace details |
remove(orgName, repoName, workspaceName) | Delete (abandon) a workspace |
status(orgName, repoName, workspaceName) | Rich status: metadata, changes, conflicts, expiry |
validate(orgName, repoName, workspaceName) | Check for conflicts with baseline |
rebase(orgName, repoName, workspaceName) | Advance baseline to current HEAD |
revert(orgName, repoName, workspaceName, wref) | Undo workspace changes for a baseline thing |
discard(orgName, repoName, workspaceName, wref) | Remove a workspace-added thing |
// Create a workspaceconst ws = await client.workspace.create('acme', 'world', 'q4-refresh', 'Q4 data update')
// Commit to the workspaceawait client.commit.apply('acme', 'world', 'sdk', 'add location', [ { operation: 'add', kind: 'thing', name: 'Location/office', data: { x: 0, y: 0 } },], { workspaceName: 'q4-refresh' })
// Check statusconst status = await client.workspace.status('acme', 'world', 'q4-refresh')
// Validate before publishingconst validation = await client.workspace.validate('acme', 'world', 'q4-refresh')
// Rebase if behind HEADconst rebase = await client.workspace.rebase('acme', 'world', 'q4-refresh')Workspace status fields
Section titled “Workspace status fields”| Field | Type | Description |
|---|---|---|
name | string | Workspace name |
description | string? | Workspace description |
status | string | 'open', 'published', 'abandoned', 'permanent' |
ownerEmail | string? | Owner’s email address |
baselineCommitId | string? | Baseline commit the workspace forked from |
commitsBehind | number | How many baseline commits ahead of the workspace |
changes | array | List of changes with wref, changeKind, shapeName, draftCount |
conflictStatus | string | 'clean' or 'conflicted' |
conflicts | array | Individual conflict details |
createdAt | number | Creation timestamp (ms since epoch) |
expiresAt | number? | Expiry timestamp (ms since epoch) |
changesetNumber | number? | Changeset number if published |
client.changeset
Section titled “client.changeset”Manage changesets — the review and merge workflow for workspace changes.
| Method | Description |
|---|---|
publish(orgName, repoName, workspaceName, message, rationale?, opts?) | Publish a workspace as a changeset for review. opts: { squash?: boolean } |
list(orgName, repoName, opts?) | List changesets. opts: { status?, limit? } |
get(orgName, repoName, changesetNumber) | Get changeset details. Open, accepting, and failed changesets include operations; accepted, rejected, and withdrawn changesets return an empty operations array. |
accept(orgName, repoName, changesetNumber) | Accept a changeset (replays operations into baseline) |
reject(orgName, repoName, changesetNumber, reason?) | Reject a changeset (reopens workspace) |
withdraw(orgName, repoName, changesetNumber) | Withdraw a changeset (author only, reopens workspace) |
validate(orgName, repoName, changesetNumber) | Re-validate a changeset against current HEAD |
// Publish workspace as changesetconst cs = await client.changeset.publish( 'acme', 'world', 'q4-refresh', 'Q4 data refresh', 'Annual revenue update')
// Reviewconst detail = await client.changeset.get('acme', 'world', cs.changesetNumber)
// Acceptawait client.changeset.accept('acme', 'world', cs.changesetNumber)
// Or reject with reasonawait client.changeset.reject('acme', 'world', cs.changesetNumber, 'Needs citations')
// Or withdraw (author only)await client.changeset.withdraw('acme', 'world', cs.changesetNumber)Changeset types
Section titled “Changeset types”| Type | Description |
|---|---|
ChangesetRef | Reference returned on publish (includes changesetNumber) |
ChangesetDetail | Full changeset metadata with status, validation, and optional acceptProgress |
ChangesetListItem | Summary for list views |
ChangesetAcceptResult | Accept result with the queued status |
ChangesetValidation | Validation result with conflict details |
ChangesetStatus | 'open' | 'accepting' | 'failed' | 'accepted' | 'rejected' | 'withdrawn' |
ChangesetDetail.operations is populated for open, accepting, and failed changesets.
It is empty for accepted, rejected, and withdrawn changesets.
ChangesetDetail.acceptProgress includes the chunk cursor and replay diagnostics:
nextChunkIndex, totalChunks, startedAt, optional lastProcessedAt, and optional lastError.
client.subscription
Section titled “client.subscription”Manage webhook and sprite subscriptions scoped to a repository. Sprites are server-side functions that WarmHub executes automatically when matching commits occur.
| Method | Description |
|---|---|
create(args) | Create a webhook or sprite subscription |
update(args) | Update an existing subscription |
list(orgName, repoName, opts?) | List subscriptions |
get(orgName, repoName, name) | Get a subscription by name |
pause(orgName, repoName, name) | Pause a subscription |
resume(orgName, repoName, name) | Resume a paused subscription |
remove(orgName, repoName, name) | Delete a subscription |
The create method accepts a CreateSubscriptionOptions object. The workspacePolicy field controls whether workspace commits trigger the subscription:
| Option | Type | Default | Description |
|---|---|---|---|
workspacePolicy | 'ignore' | 'include' | 'ignore' | When 'ignore', only baseline commits trigger the subscription. When 'include', workspace commits also trigger it. |
await client.subscription.create({ orgName: 'acme', repoName: 'world', name: 'location-hook', shapeName: 'Location', filterJson: { shape: 'Location' }, kind: 'webhook', webhookUrl: 'https://example.com/hook', workspacePolicy: 'ignore', // default — only baseline commits})
const subs = await client.subscription.list('acme', 'world')
await client.subscription.update({ orgName: 'acme', repoName: 'world', name: 'location-hook', webhookUrl: 'https://example.com/new-hook', workspacePolicy: 'include',})client.actions
Section titled “client.actions”Manage action leases and delivery tracking for subscription consumers. These are the low-level primitives that sprites and custom consumers use to coordinate processing.
| Method | Description |
|---|---|
acquireLease(orgName, repoName, subscriptionName, holderId, holderType, opts?) | Acquire an exclusive processing lease |
heartbeatLease(orgName, repoName, subscriptionName, holderId, opts?) | Extend a lease’s TTL |
releaseLease(orgName, repoName, subscriptionName, holderId) | Release a lease |
claimDelivery(orgName, repoName, subscriptionName, commitId, holderId) | Claim a delivery for processing |
completeDelivery(orgName, repoName, subscriptionName, commitId, holderId) | Mark a delivery as complete |
liveFeed(orgName, repoName, subscriptionName, opts?) | Query the live delivery feed |
client.diagnostics
Section titled “client.diagnostics”Health checks and capability discovery.
| Method | Description |
|---|---|
ping() | Round-trip connectivity check. Returns { ok, latencyMs } or { ok: false, error }. |
capabilities() | Returns { apiVersion, minSupportedSdk, features } for compatibility and feature discovery. |
const ping = await client.diagnostics.ping()if (!ping.ok) { console.error('Backend unreachable:', ping.error.message)}CommitBuilder
Section titled “CommitBuilder”CommitBuilder provides a builder pattern for constructing multi-operation commits with client-side validation. It is single-use — after a successful commit(), the builder is sealed.
import { CommitBuilder } from '@warmhub/sdk-ts'
const cb = new CommitBuilder()cb.add({ name: 'Location/cave', data: { x: 0, y: 0 } })cb.add({ name: 'Location/forest', data: { x: 5, y: 3 } })
// Validate before committing (optional, no server call)const check = cb.validate()if (!check.valid) { console.error(check.errors)}
const result = await cb.commit({ client, orgName: 'acme', repoName: 'world', author: 'sdk', message: 'seed locations',})| Method | Description |
|---|---|
add(op) | Add a new thing, assertion, shape, or collection. Chainable. |
revise(op) | Revise an existing entity. Chainable. |
deactivate(op) | Shorthand for revise({ active: false }). Chainable. |
validate() | Client-side validation (duplicate names, missing refs). No server call. |
commit(params) | Terminal — sends the commit and seals the builder. |
operations | Read-only access to accumulated operations. |
size | Number of operations. |
has(name) | Check if a name was added in this commit. |
AddOp fields
Section titled “AddOp fields”| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | For things and assertions: Shape/localName format (e.g., 'Location/cave'). For shapes: plain shape name (e.g., 'Location'). |
data | unknown | No | Data payload. For things and assertions, the shape-validated data object. For shapes, use { fields: {...} }. Not used for collections. |
about | string | CollectionAbout | No | Wref of the target thing, or inline collection syntax. Presence of about automatically makes the operation an assertion add. |
kind | string | No | Explicit kind override: 'thing', 'assertion', 'shape', or 'collection'. Normally omitted — CommitBuilder infers it from other fields. |
type | string | No | Collection type ('pair', 'triple', 'set', 'list'). Only used when kind is 'collection'. |
members | string[] | No | Collection member wrefs. Only used when kind is 'collection'. |
CommitResult
Section titled “CommitResult”client.commit.apply() returns a CommitResult with these fields. CommitBuilder.commit() returns a CommitBuilderResult with the same shape but looser types (commitId may be undefined).
| Field | Type | Description |
|---|---|---|
commitId | string | Unique hash identifier for the commit |
author | string | Author of the commit |
message | string | undefined | Commit message, if provided |
operationCount | number | Total number of operations in the commit |
operations | Array<{...}> | Per-operation results with name, operation, dataHash, and version |
Pagination
Section titled “Pagination”All list operations support cursor-based pagination:
import type { Page, PageRequest } from '@warmhub/sdk-ts'
// First pageconst page1 = await client.org.list({ limit: 10 })
// Next page (if available)if (page1.nextCursor) { const page2 = await client.org.list({ cursor: page1.nextCursor, limit: 10 })}Pass { limit, cursor? } to any method that accepts PageRequest. The response includes items and an optional nextCursor.
Next Steps
Section titled “Next Steps”- Data Modeling — wrefs, shapes, things, assertions, collections
- Commits — atomic writes and the operation model
- Queries — HEAD, filtering, and search
- API Reference — auto-generated TypeDoc reference for all types