Skip to content

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 properties
const 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.

OptionTypeDefaultDescription
auth.getToken() => Promise<string | undefined>Token acquisition hook — required for authenticated endpoints
accessTokenstring | () => string | undefined | Promise<string | undefined>Static token or sync/async provider (alternative to auth.getToken)
apiUrlstringhttps://api.warmhub.aiOverride the API URL (not needed for most users)
fetchtypeof fetchCustom fetch implementation
functionLogs'raw' | 'off''off'Control replay of backend console.* lines. Set to 'off' to suppress backend log noise.

Manage organizations — the top-level namespace for repositories.

MethodDescription
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()
// Membership
await 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')

Manage repositories within an organization.

MethodDescription
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')

Manage shapes — the schemas that define the structure of things and assertions.

MethodDescription
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')

Read things and assertions — the core data in a repository.

MethodDescription
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 things
const head = await client.thing.head('acme', 'world', { shape: 'Location' })
// Get a specific thing
const cave = await client.thing.get('acme', 'world', 'Location/cave')
// Get assertions about the cave
const 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 match
const dungeonBeliefs = await client.thing.query('acme', 'world', {
shape: 'Belief',
about: 'Location/cave',
match: 'Belief/dungeon/*',
})
// Search
const results = await client.thing.search('acme', 'world', 'cave', {
mode: 'text',
})
// Count items
const total = await client.thing.count('acme', 'world', { shape: 'Location' })
console.log(total.count) // 42

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 thing
client.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).

OptionTypeDefaultDescription
direction'inbound' | 'outbound''inbound'Inbound: things whose wref fields point to this thing. Outbound: things this thing’s wref fields point to.
fieldPathstringFilter by field path (inbound only)
workspaceNamestringScope query to a workspace
limitnumberPage size
cursorstringPagination 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',
})
ModeDescription
'text'Full-text search (default)
'vector'Embedding-based semantic search
'hybrid'Combined text + vector search

Write data through atomic commits.

MethodDescription
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.
OptionTypeDefaultDescription
workspaceNamestringCommit to a workspace instead of baseline
componentIdstringScope the commit to a specific component
chunkSizenumber1000Override 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 workspace
await 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 operationsadd and revise, each for things, assertions, shapes, or collections.

There are two ways to commit:

client.commit.apply()CommitBuilder
Best forSimple commits, raw operation arraysMulti-operation commits that benefit from validation
Client-side validationMinimal (structural checks only)Extensive — duplicate names, missing refs, shape validation
Builder patternNo — pass operations directlyYes — chainable add() / revise() / deactivate()
Kind inferenceNo — you must specify kindYes — inferred from name, about, and type fields

See CommitBuilder below for the builder API and AddOp fields for the simplified input format.

Manage workspaces — isolated branches for data changes.

MethodDescription
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 workspace
const ws = await client.workspace.create('acme', 'world', 'q4-refresh', 'Q4 data update')
// Commit to the workspace
await client.commit.apply('acme', 'world', 'sdk', 'add location', [
{ operation: 'add', kind: 'thing', name: 'Location/office', data: { x: 0, y: 0 } },
], { workspaceName: 'q4-refresh' })
// Check status
const status = await client.workspace.status('acme', 'world', 'q4-refresh')
// Validate before publishing
const validation = await client.workspace.validate('acme', 'world', 'q4-refresh')
// Rebase if behind HEAD
const rebase = await client.workspace.rebase('acme', 'world', 'q4-refresh')
FieldTypeDescription
namestringWorkspace name
descriptionstring?Workspace description
statusstring'open', 'published', 'abandoned', 'permanent'
ownerEmailstring?Owner’s email address
baselineCommitIdstring?Baseline commit the workspace forked from
commitsBehindnumberHow many baseline commits ahead of the workspace
changesarrayList of changes with wref, changeKind, shapeName, draftCount
conflictStatusstring'clean' or 'conflicted'
conflictsarrayIndividual conflict details
createdAtnumberCreation timestamp (ms since epoch)
expiresAtnumber?Expiry timestamp (ms since epoch)
changesetNumbernumber?Changeset number if published

Manage changesets — the review and merge workflow for workspace changes.

MethodDescription
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 changeset
const cs = await client.changeset.publish(
'acme', 'world', 'q4-refresh', 'Q4 data refresh', 'Annual revenue update'
)
// Review
const detail = await client.changeset.get('acme', 'world', cs.changesetNumber)
// Accept
await client.changeset.accept('acme', 'world', cs.changesetNumber)
// Or reject with reason
await client.changeset.reject('acme', 'world', cs.changesetNumber, 'Needs citations')
// Or withdraw (author only)
await client.changeset.withdraw('acme', 'world', cs.changesetNumber)
TypeDescription
ChangesetRefReference returned on publish (includes changesetNumber)
ChangesetDetailFull changeset metadata with status, validation, and optional acceptProgress
ChangesetListItemSummary for list views
ChangesetAcceptResultAccept result with the queued status
ChangesetValidationValidation 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.

Manage webhook and sprite subscriptions scoped to a repository. Sprites are server-side functions that WarmHub executes automatically when matching commits occur.

MethodDescription
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:

OptionTypeDefaultDescription
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',
})

Manage action leases and delivery tracking for subscription consumers. These are the low-level primitives that sprites and custom consumers use to coordinate processing.

MethodDescription
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

Health checks and capability discovery.

MethodDescription
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 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',
})
MethodDescription
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.
operationsRead-only access to accumulated operations.
sizeNumber of operations.
has(name)Check if a name was added in this commit.
FieldTypeRequiredDescription
namestringYesFor things and assertions: Shape/localName format (e.g., 'Location/cave'). For shapes: plain shape name (e.g., 'Location').
dataunknownNoData payload. For things and assertions, the shape-validated data object. For shapes, use { fields: {...} }. Not used for collections.
aboutstring | CollectionAboutNoWref of the target thing, or inline collection syntax. Presence of about automatically makes the operation an assertion add.
kindstringNoExplicit kind override: 'thing', 'assertion', 'shape', or 'collection'. Normally omitted — CommitBuilder infers it from other fields.
typestringNoCollection type ('pair', 'triple', 'set', 'list'). Only used when kind is 'collection'.
membersstring[]NoCollection member wrefs. Only used when kind is 'collection'.

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).

FieldTypeDescription
commitIdstringUnique hash identifier for the commit
authorstringAuthor of the commit
messagestring | undefinedCommit message, if provided
operationCountnumberTotal number of operations in the commit
operationsArray<{...}>Per-operation results with name, operation, dataHash, and version

All list operations support cursor-based pagination:

import type { Page, PageRequest } from '@warmhub/sdk-ts'
// First page
const 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.

  • 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