@polyweave/state (1.0.13)
Installation
@polyweave:registry=https://hub.kl1.tenere.ai/api/packages/Polyweave/npm/npm install @polyweave/state@1.0.13"@polyweave/state": "1.0.13"About this package
@polyweave/state
Transactionally versioned state stores with DVVSet conflict detection, lazy MVCC snapshot isolation, and kind-driven entity projections.
Synopsis
import {
createFactGraph,
createDocumentStore,
createTreeStore,
createTextStore,
createClusterTreeStore,
createTextEmbeddingVectorStore,
createVectorIndex,
createMultiStateStore,
createMultiStateStoreFromSpec,
dotsDominate,
dotsConflicting,
createFlatDotsRegistry,
FrameTypes,
CursorRegistry,
createQueryFrame
} from '@polyweave/state';
const docs = createDocumentStore();
docs.sink.write({ type: 'DOC_UPDATE', content: { sectionId: 'a', text: 'Hello world' } });
const result = docs.getSection('a');
Install
npm install @polyweave/state
Why
Polyweave state stores provide transactionally versioned, conflict-aware storage with DVVSet-based version vectors, lazy MVCC snapshot isolation, and kind-driven entity projections. Eight specialized stores serve different data shapes — facts (triples), documents (sections), trees (wormhole-trie paths), full-text (BM25), cluster trees (QT clustering), embedding vectors, vector indexes (JAG/RNG/KDT/BKT), and multi-state federation — while sharing a unified push-stream protocol and transaction contract. Use them as the persistent, queryable backbone for agent blackboards, memory systems, and structured data pipelines.
Architecture
All stores share a push-stream protocol: write frames via sink.write(frame), subscribe to output via source.pipe(listener). Internal mutation is synchronous and single-threaded — there is no actual concurrency, only logical races detected through version vectors.
Version Vectors (DVVSet)
Every versioned unit carries a dots object { txnId: counter }. Dots are advanced on transactional writes only; non-txn writes do not increment them. A zero-allocation FlatDots variant (Uint32Array) is available for hot paths.
Conflict Modes
| Mode | Behavior | Stores |
|---|---|---|
| Persistent | Conflicting value stored alongside committed value. Multiple versions visible. | Fact Graph, Document Store, Tree Store |
| Abortive | Transaction aborts. Key appended to blocked set. | Text Store, Cluster Tree Store, Vector Index |
| Delegated | Conflict detection handled by individual stores wrapped by the federation layer. | Multi-State Store |
MVCC Snapshot Isolation
Fact Graph, Document Store, and Tree Store support lazy shadow chain MVCC. When any snapshot transaction is open, commits go to a shadow chain instead of mutating base state in-place. Snapshot transactions see state frozen at their start time; read-committed and non-txn queries see the full merged chain.
Stores (8)
1. Fact Graph
createFactGraph(options?: object) → { source, sink, getFact, getFactVersion, getMergedDots, compareAndSwap, checkConflicts, getConflicts, resolveConflict, searchStream, query, killSnapshot, getMvccState, describe, close }
A fact-based triple store (entity, relation, value) with temporal and spatial indexing, CocoEdge composite index sketch, and streaming lazy query.
Write frames: FACT_INSERT, FACT_UPDATE, FACT_DELETE
Conflict mode: persistent | MVCC: snapshot isolation
Parameters
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
options.now |
function |
No | Date.now |
Timestamp provider |
options.ttlSweepIntervalMs |
number |
No | 0 |
TTL sweep interval (0 = disabled) |
options.setTimeout |
function |
No | setTimeout |
setTimeout provider |
options.clearTimeout |
function |
No | clearTimeout |
clearTimeout provider |
options.defaultTimeout |
number |
No | 120000 |
Default transaction timeout in ms |
options.durability |
object |
No | {} |
Durability config: { enabled, wrapperId, walStore } |
options.indexHints |
object |
No | {} |
Index hints: { sketch } for composite index |
options.enableCompositeIndex |
boolean |
No | true |
Enable composite index |
options.enableQuerySketch |
boolean |
No | per enableCompositeIndex |
Enable query sketch estimation |
options.maxCursors |
number |
No | 100 |
Max streaming cursors |
Key Methods
| Method | Signature | Description |
|---|---|---|
getFact(factId) |
→ fact | null |
Read a fact by ID |
getFactVersion(factId, chainLimit?) |
→ dots |
Current version dots for a fact |
getMergedDots(factId, chainLimit?) |
→ dots |
Merged dots (base + shadow chain) |
compareAndSwap(factId, dots, value) |
→ { accepted, dots } |
CAS write |
checkConflicts(txnId) |
→ { conflicts, mode } |
Pre-flight conflict check |
getConflicts({ factId }) |
→ conflicts |
Get conflicts for a fact |
resolveConflict(factId, resolution) |
→ void |
Resolve a conflict |
searchStream(query, options) |
→ void |
Streaming query |
query(dsl) |
→ results |
DSL query via query executor |
killSnapshot(txnId) |
→ boolean |
Kill a snapshot transaction |
getMvccState() |
→ mvcc state |
MVCC state snapshot |
describe() |
→ description |
Lightweight state summary |
close() |
→ void |
Close the store |
2. Document Store
createDocumentStore(options?: object) → { source, sink, getSection, getSectionVersion, getMergedDots, compareAndSwap, checkConflicts, getConflicts, resolveConflict, querySectionsStream, getSnapshot, describe, close }
A section-based document store. Documents are composed of sections identified by sectionId.
Write frames: DOC_UPDATE
Conflict mode: persistent | MVCC: snapshot isolation
Parameters
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
options.now |
function |
No | Date.now |
Timestamp provider |
options.setTimeout |
function |
No | setTimeout |
setTimeout provider |
options.clearTimeout |
function |
No | clearTimeout |
clearTimeout provider |
options.defaultTimeout |
number |
No | 120000 |
Default transaction timeout in ms |
options.emitSnapshotOnCommit |
boolean |
No | false |
Emit snapshot frames on commit |
options.durability |
object |
No | {} |
Durability config: { enabled, wrapperId, walStore } |
options.lazyPointRead |
boolean |
No | true |
Enable lazy point reads |
options.maxCursors |
number |
No | 100 |
Max streaming cursors |
Key Methods
| Method | Signature | Description |
|---|---|---|
getSection(sectionId) |
→ section | null |
Read a section by ID |
getSectionVersion(sectionId, chainLimit?) |
→ dots |
Current version dots |
getMergedDots(sectionId, chainLimit?) |
→ dots |
Merged dots (base + shadow chain) |
compareAndSwap(sectionId, dots, value) |
→ { accepted, dots } |
CAS write |
checkConflicts(txnId) |
→ { conflicts, mode } |
Pre-flight conflict check |
getConflicts({ sectionId }) |
→ conflicts |
Get conflicts for a section |
resolveConflict(sectionId, resolution) |
→ void |
Resolve a conflict |
querySectionsStream(query, options) |
→ void |
Streaming query |
getSnapshot(options?) |
→ snapshot |
Get snapshot |
describe() |
→ description |
Lightweight state summary |
close() |
→ void |
Close the store |
3. Tree Store
createTreeStore(options?: object) → { source, sink, getCandidates, getPathVersion, getMergedDots, compareAndSwapPath, checkConflicts, getConflicts, resolveConflict, searchStream, describe, close }
A wormhole-trie-backed tree store. Paths are the versioned unit — rearranging the tree structure IS versioned data.
Write frames: TREE_UPSERT, TREE_LINK, TREE_UNLINK
Conflict mode: persistent | MVCC: snapshot isolation
Parameters
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
options.now |
function |
No | Date.now |
Timestamp provider |
options.setTimeout |
function |
No | setTimeout |
setTimeout provider |
options.clearTimeout |
function |
No | clearTimeout |
clearTimeout provider |
options.defaultTimeout |
number |
No | 120000 |
Default transaction timeout in ms |
options.durability |
object |
No | {} |
Durability config: { enabled, wrapperId, walStore } |
options.documentStore |
duplex |
No | null |
Document store for persistence |
options.persistToDocumentStore |
boolean |
No | Boolean(documentStore) |
Auto-persist to doc store |
options.snapshotMode |
'copy' |
No | 'copy' |
Snapshot mode |
options.trackNodePaths |
boolean |
No | true |
Track reverse node→path mapping |
options.trackPathMap |
boolean |
No | true |
Track path→nodeId mapping |
options.compactNodes |
boolean |
No | false |
Enable node compaction |
options.maxPathKeyCache |
number |
No | 2000 |
Path key cache size |
options.maxCursors |
number |
No | 100 |
Max streaming cursors |
Key Methods
| Method | Signature | Description |
|---|---|---|
getCandidates(query, options) |
→ candidates |
Query tree paths |
getPathVersion(treeId, path, chainLimit?) |
→ dots |
Current version dots for a path |
getMergedDots(treeId, path, chainLimit?) |
→ dots |
Merged dots (base + shadow chain) |
compareAndSwapPath(treeId, path, dots, node) |
→ { accepted, dots } |
CAS write to tree path |
checkConflicts(txnId) |
→ { conflicts, mode } |
Pre-flight conflict check |
getConflicts({ treeId, path }) |
→ conflicts |
Get path conflicts |
resolveConflict(treeId, path, resolution) |
→ void |
Resolve a conflict |
searchStream(query, options) |
→ void |
Streaming tree search |
describe() |
→ description |
Lightweight state summary |
close() |
→ void |
Close the store |
4. Text Store
createTextStore(options?: object) → { source, sink, index, remove, search, searchStream, searchByEmbedding, get, getDocumentVersion, checkConflicts, isBlocked, resolveConflict, compareAndSwapDocument, describe, stats, close }
Full-text search with BM25 scoring, Porter stemming, stopword filtering, LSH-based fuzzy matching, and optional embedding support. Two-phase commit prevents partial inverted index corruption.
Write frames: TEXT_INDEX, TEXT_REMOVE
Conflict mode: abortive | MVCC: no (two-phase commit, doc-level CAS)
Parameters
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
options.now |
function |
No | Date.now |
Timestamp provider |
options.setTimeout |
function |
No | setTimeout |
setTimeout provider |
options.clearTimeout |
function |
No | clearTimeout |
clearTimeout provider |
options.defaultTimeout |
number |
No | 120000 |
Default transaction timeout in ms |
options.durability |
object |
No | {} |
Durability config: { enabled, wrapperId, walStore } |
options.analyzer |
object |
No | DEFAULT_ANALYZER |
Tokenizer + normalizer |
options.fuzzyThreshold |
number |
No | 0.3 |
LSH fuzzy match similarity threshold |
options.maxFuzzyExpansion |
number |
No | 10 |
Max fuzzy-expanded terms per query |
options.storeText |
boolean |
No | true |
Store full text in memory |
options.storeAttributesInMemory |
boolean |
No | true |
Store attributes in memory |
options.compactAfterIndex |
boolean |
No | false |
Compact posting lists after index |
options.enableFuzzy |
boolean |
No | true |
Enable fuzzy matching |
options.bm25 |
object |
No | {} |
BM25 parameters: { k1?, b? } |
options.bmw |
object |
No | {} |
BMW parameters |
options.documentStore |
duplex |
No | null |
Document store for text offloading |
options.persistToDocumentStore |
boolean |
No | Boolean(documentStore) |
Auto-persist to doc store |
options.lazyLoadDocumentText |
boolean |
No | per storeText + documentStore |
Lazy load text from doc store |
Key Methods
| Method | Signature | Description |
|---|---|---|
index(docId, text, options?) |
→ void |
Index a document |
remove(docId) |
→ boolean |
Remove a document |
search(query, options?) |
→ results |
Full-text search |
searchStream(query, options) |
→ void |
Streaming search |
searchByEmbedding(embedding, limit) |
→ results |
Embedding-based search |
get(docId) |
→ doc | null |
Get document by ID |
getDocumentVersion(docId) |
→ dots |
Current version dots |
checkConflicts(txnId) |
→ { conflicts, mode } |
Conflict check |
isBlocked(docId) |
→ boolean |
Check if doc is blocked |
resolveConflict(docId, resolution) |
→ void |
Resolve a conflict |
compareAndSwapDocument(docId, dots, value) |
→ { accepted, dots } |
CAS write |
describe() |
→ description |
State summary |
stats() |
→ stats |
Store statistics |
close() |
→ void |
Close the store |
Throws:
TXN_BEGINwith missingtxnId: emits error on transaction timeout- Two-phase commit failure:
TXN_CONFLICTframe, doc blocked
5. Cluster Tree Store
createClusterTreeStore(options?: object) → { source, sink, upsert, remove, buildTree, searchStream, isBlocked, resolveConflict, getEmbeddingVersion, describe, close }
Quality-threshold (QT) clustering over vector embeddings. Maintains a cohesive cluster tree with auto-rebuild on drift. Optional vector store for candidate retrieval; operates in tree-only mode without one.
Write frames: CLUSTER_UPSERT
Conflict mode: abortive | MVCC: no (doc-level CAS)
Parameters
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
options.vectorStore |
object |
No | null |
Vector store with getCandidates() and getSignals() |
options.treeStore |
duplex |
No | null |
Tree store (auto-creates if null) |
options.treeId |
string |
No | 'cluster-tree:default' |
Tree identifier |
options.maxLeafDocs |
number |
No | 32 |
Max docs per leaf |
options.targetLeafCount |
number |
No | null |
Target leaf count |
options.staleRatioThreshold |
number |
No | 1.25 |
Staleness threshold for rebuild |
options.staleLeafEventThreshold |
number |
No | 0.2 |
Fraction of stale events |
options.clusterSignalWeight |
number |
No | 0.15 |
Signal weight in scoring |
options.scoringMethod |
string |
No | 'full-hybrid' |
Scoring method |
options.nnCandidateLimit |
number |
No | 12 |
NN candidate limit |
options.nnMaxCheck |
number |
No | 24 |
NN max check |
options.buildNearestExactThreshold |
number |
No | 1500 |
Threshold for exact nearest |
options.durability |
object |
No | {} |
Durability config |
options.emitFrames |
boolean |
No | true |
Emit frames on mutations |
options.emitFrameTypes |
string[] |
No | ['all'] |
Frame types to emit |
Key Methods
| Method | Signature | Description |
|---|---|---|
upsert(docId, embedding, metadata?) |
→ void |
Upsert a doc/embedding |
remove(docId) |
→ void |
Remove a doc |
buildTree() |
→ void |
Build/rebuild cluster tree |
searchStream(query, options) |
→ void |
Streaming search |
isBlocked(docId) |
→ boolean |
Check if doc is blocked |
resolveConflict(docId, resolution) |
→ void |
Resolve a conflict |
getEmbeddingVersion(docId) |
→ dots |
Get version dots for a doc |
describe() |
→ description |
State summary |
close() |
→ void |
Close the store |
Frame Protocol
| Input Frame | Description |
|---|---|
CLUSTER_UPSERT |
{ docId, embedding, metadata?, attributes?, txnId? } |
CLUSTER_REMOVE |
{ docId, txnId? } |
CLUSTER_BUILD_TREE |
{ } |
CLUSTER_SEARCH |
{ query, limit?, docMask?, ... } |
TXN_BEGIN / TXN_COMMIT / TXN_ABORT |
Standard transaction frames |
| Output Frame | Description |
|---|---|
CLUSTER_RESULT |
Search result |
CLUSTER_UPSERT_DOTS |
Upsert response with dots |
CLUSTER_BUILT |
Tree build complete |
TXN_CONFLICT |
Abortive conflict |
CLUSTER_CONFLICT |
Cluster-level conflict |
CLUSTER_KEY_BLOCKED / CLUSTER_KEY_UNBLOCKED |
Block/unblock notification |
DESCRIPTION |
Description response |
6. Text Embedding Vector Store
createTextEmbeddingVectorStore(textStore: duplex, options?: object) → { getCandidates, getSignals, stats, supportsDocMask }
A thin read-only adapter wrapping a Text Store to expose a vector-index-like API surface. No state or conflict detection of its own. Uses textStore.searchByEmbedding() for candidate retrieval.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
textStore |
duplex |
Yes | — | Text store with searchByEmbedding() and get() |
options.maskOverfetch |
number |
No | 4 |
Overfetch multiplier when a docMask is active |
Throws: Error('createTextEmbeddingVectorStore requires textStore.searchByEmbedding() and textStore.get()')
Returns:
getCandidates(query, searchOptions?)— Returns doc IDs based on embedding similaritygetSignals(query, docIds)— ReturnsMap<docId, cosineSimilarity>for given doc IDsstats()— Delegates totextStore.stats()supportsDocMask— Alwaystrue
7. Vector Index
createVectorIndex(options: object) → { source, sink, addBatch, deleteId, search, build, getRowVersion, isBlocked, resolveConflict, unblockAll, describe, close }
A low-level multi-algorithm vector index (JAG, RNG, KDT, BKT) with product quantization, paged sampling, and row-level CAS-based conflict detection.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
options.dataset |
DistanceMatrix |
Yes | — | Dataset backing the index |
options.graph |
JAG/RNG graph | Yes | — | Graph structure |
options.treeKdt |
KDT tree | No | null |
KD-tree for seed candidates |
options.treeBkt |
BKT tree | No | null |
BK-tree for seed candidates |
options.distance |
function |
Yes | — | Distance function |
options.quantizer |
PQ quantizer | No | null |
Product quantization |
options.quantizedDataset |
Uint8Matrix |
No | null |
Quantized dataset representation |
options.useQuantized |
boolean |
No | true |
Use quantized vectors for search |
options.labelset |
Labelset | No | null |
Deleted row marker |
options.workspacePool |
workspace pool | No | auto-created | Search workspace pool |
options.seedCandidates |
number |
No | 0 |
Seed candidate count |
options.seedMaxCheck |
number |
No | 4096 |
Max seed checks |
options.adaptiveSearch |
boolean |
No | true |
Enable adaptive search |
options.adaptiveMinConfidence |
number |
No | 0.08 |
Min confidence threshold |
options.adaptiveStabilityThreshold |
number |
No | 0.9 |
Stability threshold |
options.adaptiveMaxCheckMultiplier |
number |
No | 8 |
Max check multiplier |
options.adaptiveMaxStageCount |
number |
No | 4 |
Max adaptive stages |
options.adaptiveSeedGrowth |
number |
No | 2 |
Seed growth factor |
options.adaptiveMaxSeedCandidates |
number |
No | 4096 |
Max seed candidates |
options.maintenanceEnabled |
boolean |
No | true |
Auto-maintenance |
options.maintenanceDeleteRatio |
number |
No | 0.35 |
Delete threshold for rebuild |
options.maintenanceMinDeleted |
number |
No | 4096 |
Min deletes for rebuild |
options.maintenanceMinOps |
number |
No | 2048 |
Min ops for maintenance |
options.maintenanceMode |
'rebuild' |
No | 'rebuild' |
Maintenance mode |
Throws:
Error('VectorIndex requires dataset')ifdatasetis missingError('VectorIndex requires graph')ifgraphis missingError('VectorIndex requires distance function')ifdistanceis not a function
Key Methods
| Method | Signature | Description |
|---|---|---|
addBatch(rows) |
→ void |
Add rows to the index |
deleteId(id) |
→ void |
Mark a row as deleted |
search(query, k?) |
→ results |
Search with optional docMask |
build() |
→ void |
Force rebuild |
getRowVersion(id) |
→ dots |
Get row version |
isBlocked(id) |
→ boolean |
Check if blocked |
resolveConflict(id, resolution) |
→ void |
Resolve conflict |
unblockAll() |
→ void |
Unblock all blocked rows |
describe() |
→ description |
State summary |
close() |
→ void |
Close the store |
8. Multi-State Store
createMultiStateStore(options?: object) → { source, sink, search, query, queryStream, multihopQueryStream, getAttributes, getState, getCursorRegistry, getMvccState, getMultiTxnState, triggerRestore, insert, get, update, delete, defineProjection, searchEntities, describe, close }
A federation store wrapping text, tree, graph, and document stores for parallel retrieval. Provides unified lazy query with variable binding, multihop traversal, cross-store candidate aggregation, weighted scoring, filter resolution, and entity projections.
Write frames: All store frames + TXN_BEGIN, TXN_COMMIT, TXN_ABORT, KILL_SNAPSHOT, KILL_ALL_SNAPSHOTS, DESCRIBE
Conflict mode: Delegated (abortive-dominant across stores) | MVCC: Snapshot forwarded
Parameters
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
options.textStore |
duplex |
No | auto-created | Text store |
options.treeStore |
duplex |
No | auto-created | Tree store |
options.graphStore |
duplex |
No | auto-created | Fact graph |
options.docStore |
duplex |
No | auto-created | Document store |
options.stores |
object |
No | {} |
Additional custom stores |
options.nodeToDocIds |
function |
No | defaultNodeToDocIds |
Node→doc mapping |
options.predicates |
object |
No | {} |
Predicate definitions |
options.predicateStats |
object |
No | {} |
Predicate statistics |
options.candidateWeights |
object |
No | {} |
Per-store candidate weights |
options.signalWeights |
object |
No | {} |
Signal scoring weights |
options.defaultCandidateLimit |
number |
No | 200 |
Default candidate fetch limit |
options.defaultCandidateStores |
string[] |
No | ['text'] |
Default stores to search |
options.plannerEnabled |
boolean |
No | false |
Enable query planner |
options.ranker |
function |
No | null |
Custom cross-store ranker |
options.persistInsertedDocuments |
boolean |
No | true |
Persist inserted docs |
options.projections |
object |
No | null |
Entity projection config |
options.durability |
object |
No | {} |
Durability config |
Key Methods
| Method | Signature | Description |
|---|---|---|
search(query, options?) |
→ results |
Cross-store search |
query(query, options) |
→ results |
Structured query |
queryStream(query, options) |
→ void |
Streaming structured query |
multihopQueryStream(query, options) |
→ void |
Multihop streaming query |
getAttributes(docId) |
→ attributes |
Get stored attributes for a doc |
getState() |
→ state |
Get internal state snapshot |
getCursorRegistry() |
→ CursorRegistry |
Cursor registry |
getMvccState() |
→ mvcc state |
Aggregated MVCC state |
getMultiTxnState(txnId) |
→ txn state |
Per-txn state |
triggerRestore() |
→ void |
Trigger durability restore |
insert(kind, fields) |
→ entity |
Insert entity (projection CRUD) |
get(kind, docId, options?) |
→ entity |
Get entity with optional relation expansion |
update(kind, docId, fields) |
→ void |
Update entity fields |
delete(kind, docId) |
→ void |
Delete entity |
defineProjection(name, spec) |
→ void |
Register a projection |
searchEntities(kind, query, options?) |
→ results |
Search entities with optional expansion |
describe() |
→ description |
State summary |
close() |
→ void |
Close all stores |
Multi-Store Transactions
Atomic commit across all stores with a shared txnId. Abortive-dominant: a text store conflict aborts writes to all stores.
store.sink.write({ type: 'TXN_BEGIN', content: { txnId: 't1', isolation: 'snapshot' } });
store.sink.write({ type: 'FACT_INSERT', content: { ..., txnId: 't1', targetStore: 'graph' } });
store.sink.write({ type: 'TEXT_INDEX', content: { ..., txnId: 't1', targetStore: 'text' } });
store.sink.write({ type: 'TXN_COMMIT', content: { txnId: 't1' } });
Entity Projections
Entity projections let you define entity types as collections of fields and relations. The Multi-State Store derives which stores to create, which frames to emit, and how to read data back.
Field Kinds
| Kind | Stores | Description |
|---|---|---|
value |
graph | String attribute |
value:n |
graph | Number attribute |
value:b |
graph | Boolean attribute |
value:? |
graph | Nullable string |
content |
document+text | Document section + text index |
content:stored |
document | Document section only |
content:index |
text | Text index only |
location:/root |
tree | Tree path, auto-set on insert |
location:/root/link |
tree | Directional TREE_LINK |
embedding:768 |
vector+cluster | Vector (768d) + clustered |
embedding:768:tag |
vector+cluster | Per-tag vector |
embedding:768:tag:rng |
vector+cluster | With RNG algorithm |
ref:Entity |
graph | Single entity reference |
refs:Entity |
graph | Multi-entity references |
timestamp |
graph | Auto-set ISO timestamp on insert |
timestamp:update / ts:update |
graph | Auto-set on insert + update |
geometry:point |
graph | GeoJSON Point coordinates |
expiry:3600 |
graph | Auto-set TTL (seconds) |
CRUD Example
const ms = createMultiStateStore({ projections: {
Article: { primaryKey: 'docId', fields: { title: 'value', body: 'content' } }
}});
ms.insert('Article', { docId: 'art:1', title: 'ML', body: 'Intro...' });
const raw = ms.get('Article', 'art:1');
const expanded = ms.get('Article', 'art:1', { expand: ['authors'] });
ms.update('Article', 'art:1', { title: 'Deep Learning' });
ms.delete('Article', 'art:1');
createMultiStateStoreFromSpec(spec, registry?, options?)
Creates a multi-state store from a declarative spec with store references, candidate sets, and search profiles.
createMultiStateStoreFromSpec(spec, registry?, options?) → multiStateStore
Adds searchProfile(profileName, query, options) and getSpecInfo() to the returned store.
compileMultiStateSpec(spec, registry?, options?)
Compiles a spec into the options object passed to createMultiStateStore.
Transaction Contract
| Method | Signature | Purpose |
|---|---|---|
checkConflicts(txnId) |
→ { conflicts, mode } |
Non-destructive pre-flight conflict check |
getXxxVersion(key, chainLimit?) |
→ dots |
Current dots for a key |
getMergedDots(key, chainLimit?) |
→ dots |
Merged dots (base + shadow chain, MVCC stores) |
isBlocked(key) |
→ boolean |
Whether a key is locked (abortive stores) |
killSnapshot(txnId) |
→ boolean |
Kill a snapshot transaction (MVCC stores) |
describe() |
→ description |
Lightweight state summary |
Frame Protocol (all stores except Vector Index)
store.sink.write({ type: 'TXN_BEGIN', content: { txnId } });
store.sink.write({ type: 'TXN_COMMIT', content: { txnId } });
store.sink.write({ type: 'TXN_ABORT', content: { txnId } });
Snapshot transactions on MVCC stores:
store.sink.write({ type: 'TXN_BEGIN', content: { txnId, isolation: 'snapshot' } });
DESCRIBE Protocol
All stores handle DESCRIBE frames, returning DESCRIPTION with a lightweight state summary.
store.sink.write({ type: 'DESCRIBE', content: { streamId: 'req1' } });
// → DESCRIPTION { content: { storeType, ...counts, mvcc, conflicts, blocked } }
DVVSet Version Vectors (lib/dvv-set.js)
Object-based (zero-allocation)
| Export | Signature | Description |
|---|---|---|
dotsDominate(a, b) |
(dot, dot) → boolean |
a strictly dominates b |
dotsEqual(a, b) |
(dot, dot) → boolean |
a is equal to b |
dotsConflicting(a, b) |
(dot, dot) → boolean |
Neither dominates the other |
advanceDot(dots, id) |
(dot, string) → dot |
Increment counter for id |
dotsDescribe(dots) |
(dot) → object |
Copy dots into plain object |
mergeDots(a, b) |
(dot, dot) → dot |
Pointwise max merge |
dotsCount(dots) |
(dot) → number |
Count active replicas |
dotsIsEmpty(dots) |
(dot) → boolean |
True if no active replicas |
FlatDots (Uint32Array-based, zero-GC)
createFlatDotsRegistry() → {
register(replicaId), replicaCount(), create(),
dominate(a, b), equal(a, b), advance(dots, idx),
conflicting(a, b), merge(a, b), copy(dots),
isEmpty(dots), toObject(dots)
}
Maps replica IDs to sequential integer indices. Dots are dense Uint32Array — no hash lookups, cache-friendly.
Remote Stores (remote/remote-stores.js)
Local-first duplex wrappers with remote fallback. All follow the same pattern: try local first, if empty policy says "empty", fall back to remote.
| Export | Required Options | Throws if missing |
|---|---|---|
createTreeRemoteStoreDuplex({ local, remote, cache?, propagateWrites?, emptyPolicy? }) |
local |
Error('Tree remote store requires local duplex.') |
createDocumentRemoteStoreDuplex({ local, remote, cacheResponse?, propagateWrites?, emptyPolicy? }) |
local |
Error('Document remote store requires local duplex.') |
createFactRemoteStoreDuplex({ local, remote, cacheFacts?, propagateWrites?, emptyPolicy? }) |
local |
Error('Fact remote store requires local duplex.') |
createTextRemoteStoreDuplex({ local, remote, propagateWrites?, emptyPolicy? }) |
local |
Error('Text remote store requires local duplex.') |
Error output: Emits STATE_NOT_FOUND error frames when remote returns empty.
File Stores (remote/)
| Export | Description |
|---|---|
createDocumentFileStore(dirPath, options?) |
File-backed document store |
createTreeFileStore(dirPath, options?) |
File-backed tree store |
createTextFileStore(dirPath, options?) |
File-backed text store |
createFactFileStore(dirPath, options?) |
File-backed fact graph |
createFileLayout(dirs?, options?) |
File layout manager for multi-store |
Streaming Infrastructure (streaming.js)
Frame-based streaming protocol for lazy query results. Pure push-stream, no async/await.
| Export | Description |
|---|---|
FrameTypes |
Constants: { QUERY, RESPONSE, META, NEXT, CONTINUE, CLOSE, FINISH, ERROR } |
CursorRegistry |
Manages streaming query cursors; constructor accepts { maxCursors } |
createQueryFrame(query, options) |
Create a QUERY frame with { cursorId, limit, offset, filters } |
createResponseFrame(cursorId, results, metadata) |
Create a RESPONSE frame |
createNextFrame(cursorId) |
Create a NEXT frame to request more results |
createCloseFrame(cursorId) |
Create a CLOSE frame |
createMetaFrame(content) |
Create a META frame |
createStreamingErrorFrame(cursorId, message) |
Create an ERROR frame |
createStreamingSearch(store, options) |
High-level streaming search harness |
createStreamingFrameHandler(handlers) |
Frame handler factory |
collectStreamResults(source, timeoutMs) |
Collect streaming results synchronously |
StreamingPropertyTests |
Property test helper |
runStreamingPropertyTests() |
Run streaming property tests |
Additional Exports
| Export | Source | Description |
|---|---|---|
createEmptyAttributes() |
attribute-schema.js |
Creates an empty attributes bucket |
normalizeAttributes(raw, schema?) |
attribute-schema.js |
Normalizes raw attributes |
mergeNormalizedAttributes(target, source) |
attribute-schema.js |
Merges normalized attributes |
createMultihopQueryExecutor(options) |
multi-state/multihop-query-executor.js |
Multihop traversal executor |
createJAGGraph(options) |
vector-store/graph-jag.js |
JAG graph construction |
createCoordinatorReplayState(options) |
multi-state/durability-schema.js |
Coordinator replay state |
applyCoordinatorDelta(state, delta) |
multi-state/durability-schema.js |
Apply coordinator delta |
createDurableStoreOp(type, store, delta) |
multi-state/durability-schema.js |
Durable store operation |
serializeCoordinatorReplayState(state) |
multi-state/durability-schema.js |
Serialize replay state |
MultiStateDurabilityDeltaType |
multi-state/durability-schema.js |
Delta type enum |
resolveEmptyPolicy(policy) |
remote/empty-policy.js |
Resolve empty-check policy |
Design Decisions
- In-place commit is safe: JavaScript is single-threaded and synchronous at
sink.write(). - Kind-driven store selection: The caller declares what a field means (
'value','content','embedding:768'); the system chooses stores, frame types, and read strategies. - Relations are first-class: Named predicates with inverses and cardinality, stored as fact graph facts, expanded on read.
- Tree-store path-level dots: The versioned unit is
treeId||pathKey, because rearranging the tree IS versioned data. - Text-store two-phase commit: Phase 1 checks all ops against committed dots; Phase 2 applies atomically.
- MVCC shadow chain over clone: Zero-allocation shadow chain. No clone cost for reads when the chain is empty.
- Conflicts are per-key: Only keys that were read AND written trigger conflict checks.
- Dual API: Every store exposes both a direct API and a frame protocol. Both are first-class.
- Federated compatible: All stores are
{ source, sink }duplexes following the push-stream contract.
Tests
npm test
489 tests across 51 test files covering: DVVSet conflict detection (object and FlatDots), lazy MVCC snapshot isolation, transactional commit/rollback/abort, conflict resolution, kind-driven entity projections with CRUD + relation expansion, streaming cursors, store-level property tests (js-rigor), text store BM25 correctness (BEIR SciFact evaluation), cluster tree store, embedding vector store, vector index (JAG comprehensive, multi-attribute, NOT logic), multi-state attribute handling, multihop query execution, remote stores, WAL crash recovery, durability schemas, and performance analysis.
Requirements
- Node.js 22+
- ESM only
Dependencies: heapify, porter-stemmer
Peer dependencies: @polyweave/core, @polyweave/durability
Dependencies
Dependencies
| ID | Version |
|---|---|
| heapify | ^0.6.0 |
| porter-stemmer | ^0.9.1 |
Development Dependencies
| ID | Version |
|---|---|
| @rigor/core | * |
| fast-check | * |
| tape | * |
Peer Dependencies
| ID | Version |
|---|---|
| @polyweave/core | * |
| @polyweave/durability | * |
| @polyweave/errors | * |