Skip to content

Read Semantics

SDK read methods share a few behavior patterns across client.thing, client.shape, and repository list calls. Use the generated WarmHubClient API reference for exact signatures.

The match option accepts a glob pattern that filters against full wrefs such as Location/cave.

  • * matches a single path segment.
  • ** matches zero or more path segments.
await client.thing.head('acme', 'world', {
match: 'Location/dungeon/*',
})
await client.thing.about('acme', 'world', 'Location/cave', {
match: 'Observation/**',
})

Reads with a match filter may lag briefly after a write while WarmHub updates read indexes. Subsequent reads after the index catches up will see the new state.

Where match filters by a record’s identity (its wref), the where option filters on the values of a thing’s declared fields. It is accepted by client.thing.query, client.thing.head, client.thing.about, and client.thing.count. Each predicate is a structured { fieldPath, op, rhs } object:

await client.thing.query('acme', 'world', {
shape: 'Observation',
where: [
{ fieldPath: 'status', op: 'eq', rhs: 'active' },
{ fieldPath: 'severity', op: 'gte', rhs: 3 },
],
})

op is one of eq, ne, gt, gte, lt, lte, prefix (scalar rhs), in (array rhs), or exists (omit rhs). Predicates are ANDed, up to 8 per call, and resolve against a shape’s typed fields — so the query must set a shape. For the full operator reference, value-typing rules, and constraints, see Field-Value Predicates.

client.thing.getMany(...) accepts any number of wrefs and auto-chunks requests above the backend’s 500-wref transport cap.

The result preserves duplicates: each duplicate counts toward the requested count and produces a duplicate result or missing entry. Dedupe upstream when you want one row per unique wref.

The optional top-level version pins every requested wref that does not already include @vN or @HEAD. Per-wref pins remain intact.

Missing and inaccessible refs are reported in missing rather than throwing per item. With includeRetracted: true, retracted records can appear in items; without it, retracted refs land in missing.

Use chunkSize when you want smaller backend requests for memory or latency shaping; values above 500 are clamped to the transport cap. Use chunkConcurrency to run multiple chunks in parallel while keeping the merged result order deterministic.

Thing read results include a metadata object alongside the record’s data:

FieldWhat it tells you
durableIdA stable id for the thing that never changes across rename, revision, or retraction. See Durable Ids.
thingCreatedAtWhen the thing was first created, as a Unix timestamp in milliseconds. Stable across later revisions and renames.
versionCreatedAtWhen the current version was created, as a Unix timestamp in milliseconds.

Because thingCreatedAt is the original creation time, you can order things by when they were first created from a single read — without walking each thing’s history. History rows carry durableId and thingCreatedAt.

Two kinds of wref in a read result pin differently: the record’s own identity, and the wref-typed fields inside its data. (Reads also expose context wrefs such as aboutWref and committerWref; those are not covered here.)

The thing’s own identity is reported as two fields:

FieldWhat it is
wrefAlways present and unpinned (Shape/name). It names the thing without locking to a version — use it to look up or show the thing as it is now.
pinnedWrefThe same name pinned to the exact version you just read (Shape/name@vN). Use it to record or re-fetch that precise version.

Wrefs stored in the thing’s data — fields the shape declares as wref-typed — point at other things, and they come back pinned (@vN). A bare or @HEAD value is pinned to the version current when the data was written; an explicit @vN you wrote is preserved as-is. Either way the stored reference keeps resolving to that exact version, which is what makes cross-thing references reproducible. A reference to a thing in another repo comes back fully qualified (wh:org/repo/Shape/name@vN).

The two are independent: pinnedWref is the version of this thing; a pinned wref in data is the version of a thing it points to.

To compare or index a wref without caring whether it is pinned, strip the version with normalizeWref:

import { normalizeWref } from '@warmhub/sdk-ts'
const result = await client.thing.get('acme', 'world', 'Location/cave')
result.wref // 'Location/cave' (unpinned name)
result.pinnedWref // 'Location/cave@v3' (the version you read)
// `data` is typed `unknown` — narrow it to your shape to read its fields.
// A wref-typed field points at another thing, pinned when it was written:
const data = result.data as { region: string }
data.region // 'Region/north@v2'
normalizeWref(data.region) // 'Region/north'

normalizeWref removes a trailing @vN, @HEAD, or @ALL. Reach for it wherever a wref may arrive pinned — for example results from client.thing.get and client.thing.getMany.

client.thing.refs(...) queries wref-typed fields.

  • Inbound refs find records whose wref fields point to the supplied wref.
  • Outbound refs find records the supplied record points to.
  • Inbound queries can be narrowed to a field path.

Use client.thing.about(...) for assertions about a thing or collection target. Use refs(...) when you specifically need field-level wref links.

client.thing.search(...) supports text, vector, and hybrid modes.

ModeBehavior
textFull-text search and the full read-filter set
vectorEmbedding-based semantic search
hybridCombined text and vector search

See the WarmHubClient API reference for the per-mode option set.

When searching with an assertion target or collection resolution, pages may be sparse. Keep paginating until nextCursor is absent.

Paginated methods follow a shared cursor pattern: the request accepts limit and cursor, and the response includes items plus an optional nextCursor.

const page1 = await client.repo.listPage('acme', { limit: 10 })
if (page1.nextCursor) {
const page2 = await client.repo.listPage('acme', {
cursor: page1.nextCursor,
limit: 10,
})
}

Some methods return a generic page shape and others return method-specific envelopes. The cursor pattern is the same; methods that do not paginate return their full result set in one call.

For “scan everything matching this filter” reads, prefer the SDK iterator helpers over manual cursor loops:

for await (const item of client.thing.queryIter('acme', 'world', {
shape: 'Location',
limit: 500,
})) {
console.log(item.wref)
}

thing.refsIter, thing.queryIter, thing.headIter, and thing.aboutIter handle the cursor internally and yield one item at a time. Their *All variants materialize the full scan into an array and accept max to prevent accidental unbounded reads:

const refs = await client.thing.refsAll('acme', 'world', 'Location/cave', {
direction: 'inbound',
max: 10_000,
})

The underlying paginated methods remain available when you need explicit page envelopes, nextCursor storage, or custom page scheduling.

Tokenless clients reading public repositories have narrower paging on shared read procedures:

  • limit is capped at 25 items per page.
  • Omitted limit defaults to 25.
  • The page size is bound to the cursor. Follow-up requests must omit limit or pass the same value used to mint the cursor.
  • Anonymous pagination stops after 2 pages for repository list pages, shape history, thing HEAD, thing about, thing query, and wref-form thing history.
  • Shape- or about-filtered thing history allows only page 0 anonymously.

The SDK surfaces these boundaries as WarmHubError kinds such as VALIDATION_ERROR or UNAUTHENTICATED. REST query endpoints can collapse equivalent deny paths to opaque 404 responses to keep repository existence private.

Authenticated callers see no anonymous pagination narrowing.