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.
Match Patterns
Section titled “Match Patterns”The match option accepts a glob pattern, but what it filters against depends on which method you call:
client.thingand repository list calls — the glob filters against full wrefs such asLocation/cave.client.shape.list— the glob filters against bare shape names only (e.g.GameState), not full wrefs. A pattern likeGameState/**will not match theGameStateshape itself; useGameStateorGameState*to match it.
Glob rules:
*matches a single path segment.**matches zero or more path segments. When used as a trailing/**suffix (e.g.Location/**), it matches descendants only and does not match the node at the pattern boundary itself. The bare**pattern remains a match-all and does match the boundary node.
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.
Field-Value Filters
Section titled “Field-Value Filters”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.
Collection Summary Reads
Section titled “Collection Summary Reads”WarmHub ships six built-in collection shapes — Arc, Bond, Pair, Triple, Set, and List — each of which groups related things into an ordered or unordered structure. For background on how collections are modeled, see Collections.
For things whose shape is one of those six, client.thing.get(...) and client.thing.getMany(...) elide large collection bodies by default. In that case the read returns a collection summary — a lightweight payload that includes the member count and a short preview of the first few members — rather than the full member list. This default keeps routine reads fast when a collection has grown large. Pass dataMode: "full" to force the complete body:
// Default — large collection bodies may be elided; summary data presentconst result = await client.thing.get('acme', 'world', 'Set/favorites')
// Full body — complete data always present, no elisionconst full = await client.thing.get('acme', 'world', 'Set/favorites', undefined, { dataMode: 'full',})The same dataMode option is accepted by client.thing.getMany(...).
Batch Reads
Section titled “Batch Reads”client.thing.getMany(...) accepts any number of wrefs and auto-chunks requests above the backend’s 500-wref limit.
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 to tune how many wrefs are fetched together — lower values help avoid timeouts when individual records are large, while higher values (up to the 500 maximum) reduce round-trips when records are small. Use chunkConcurrency to fetch multiple chunks at the same time, which shortens wall-clock time when scanning large sets of refs; the merged result order is deterministic regardless of which chunks finish first.
Read Result Metadata
Section titled “Read Result Metadata”Thing read results include a metadata object alongside the record’s data:
| Field | What it tells you |
|---|---|
durableId | A stable id for the thing that never changes across rename, revision, or retraction. See Durable Ids. |
createdOn | When the thing was first created, as a Unix timestamp in milliseconds. Stable across later revisions and renames. |
revisedOn | When the current version was created, as a Unix timestamp in milliseconds. |
Because createdOn 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 createdOn.
Wrefs in read results
Section titled “Wrefs in read results”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:
| Field | What it is |
|---|---|
wref | Always present and unpinned (Shape or Shape/name). It names the shape or shaped thing without locking to a version — use it to look up or show the target as it is now. |
pinnedWref | The same name pinned to the exact version you just read (Shape@vN or 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 shapes or shaped 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 preserves that version selection. Either way the stored reference keeps resolving to that exact version, which is what makes references reproducible. If the target is renamed, reads use its new name with the same pin. A reference in another repo comes back fully qualified (wh:org/repo/Shape@vN or wh:org/repo/Shape/name@vN).
The two are independent: pinnedWref is the version of this result; a pinned wref in data is the version of the shape or shaped 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 a shape or shaped thing, pinned when 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.
Reference Queries
Section titled “Reference Queries”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, collection, or shape target. The method accepts any string wref — including shape wrefs such as 'Player' — and returns assertions whose target is that wref. For example, client.thing.about(org, repo, 'Player') returns assertions whose target is the Player shape. Use refs(...) when you specifically need field-level wref links.
thing.about(...) returns a page of assertions, each of which may include nested assertion children (replies or sub-assertions attached to a top-level assertion). For thing.about traversal, includeRetracted: true applies to top-level assertions and every populated level of nested assertion children.
Search Modes
Section titled “Search Modes”client.thing.search(...) supports text, vector, and hybrid modes.
| Mode | Behavior |
|---|---|
text | Full-text search and the full read-filter set |
vector | Embedding-based semantic search |
hybrid | Combined 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.
Pagination
Section titled “Pagination”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.
Iteration
Section titled “Iteration”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.
Anonymous Pagination Caps
Section titled “Anonymous Pagination Caps”Tokenless clients reading public repositories have narrower paging on shared read procedures:
limitis capped at 25 items per page.- Omitted
limitdefaults to 25. - The page size is bound to the cursor. Follow-up requests must omit
limitor 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, thing search, 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.