-
Notifications
You must be signed in to change notification settings - Fork 0
Persistence
PolyGraph is backed by a pluggable PersistenceAdapter.
Two are bundled, and the contract is open for custom backends.
-
MemoryAdapter(maxNodes?)— stores serialized records in memory. WhenmaxNodesis set, the least-recently-put node (plus its edges and vector) is evicted once the cap is reached; writing a node again (viaputNode,bulkPutNodes, orapplyChanges) bumps it back to most-recently-put. -
BinaryStoreAdapter({ storeDir, compactThreshold?, fileIO?, syncWrites?, mutationLogRetention? })— persists as a MessagePack snapshot plus an append-only write-ahead log (WAL). Nodes, edges, and vectors are committed atomically per batch; the WAL is compacted into a snapshot once it passes an adaptive threshold and onclose().compactThresholdis the minimum WAL-entry count at which compaction is scheduled (default 10,000) — the effective threshold also grows with the store (max(threshold, records / 4)), so a 1M-node build doesn't rewrite the snapshot quadratically. Startup replays the WAL, then persists a snapshot before deleting it, so a crash between those steps loses nothing; a truncated WAL tail from a mid-append crash is also tolerated.-
syncWrites: truefsyncs WAL appends and snapshot writes (including the containing directory) for crash durability at a throughput cost. -
mutationLogRetention: { maxEntries?, maxAgeMs? }opts the durable mutation log (mutations.msgpack) into trimming on the same compaction cadence — unset by default, which keeps the full mutation history forever. When both bounds are set, a record must satisfy both to survive.trimMutationLog()also trims on demand, independent of WAL-driven compaction. Only enable this when nothing depends ongetMutationsSince/getMutationLogPagereaching back further than the retained window (e.g. replicas re-snapshot instead of tailing arbitrarily far behind). The Rust core and Python bindings expose the same policy — see Database core.
-
BinaryStoreAdapter lives behind platform subpaths so the core entry point
stays free of node: built-ins:
-
@0xx0lostcause0xx0/polypack/persistence/node—NodeFileIO(filesystem). -
@0xx0lostcause0xx0/polypack/persistence/opfs—OPFSFileIO(browser File System Access API). -
@0xx0lostcause0xx0/polypack/persistence—MemoryFileIOand theFileIOtype, for tests and custom storage. WhenfileIOis omitted, a platform default is created at first use.
-
FileIO— the storage contract forBinaryStoreAdapter:readFile,writeFile,appendFile,deleteFile,fileExists. Implement it to plug in any backing byte store. -
PersistenceAdapter— the contract for a fully custom adapter: node, edge, and vector single/bulk operations, plusclearAll()andclose(). -
PersistenceChanges— describes one logical node/edge/vector commit. Adapters may implementapplyChanges(changes)to commit it atomically;PolyGraphprefers this hook and restores the complete dirty batch when it rejects. - Adapter methods should reject on storage errors. Bulk methods should be
atomic where the backing store permits it.
MemoryAdapterapplies changes through copy-on-commit maps;BinaryStoreAdapterappends one WAL batch covering all three record kinds. Existing custom adapters withoutapplyChangesremain compatible but cannot guarantee cross-record atomicity through the fallback path.
Adapters that expose changeFeed provide the durable logical log through
graph.mutationLogSince(sequence), graph.mutationLogPage(sequence, limit), and graph.latestMutationSequence(). Sequences are exclusive
bigint cursors, so callers can resume replication or audit scans without
rereading acknowledged records. This is the mechanism the sync
layer and cross-language conformance fixtures build on.
Adapters may implement getSchemaDefinitions()/setSchemaDefinitions() to
persist structural node/edge schema metadata. The canonical shape is
nodeTypes[{ nodeType, ... }] and edgeTypes[{ edgeType, ... }], shared
with the Rust and Python bindings. Runtime validator callbacks are
intentionally not serialized; applications must re-register them after
opening a store.
Adapters declare AdapterCapabilities: atomicBatches, transactions,
fsync, secondaryIndexes, snapshots, changeFeed,
concurrentWriters, and vectorSearch ('none' | 'exact' | 'ann').
graph.adapterCapabilities reads them; graph.requireAdapterCapabilities({ name: expected, ... }) throws AdapterCapabilityError unless the attached
adapter declares every listed capability at the expected value. See
Database core for resource limits and transactions built
on top of this.
Back to Home.
polypack
By feature
- Property graph
- Query builder
- Vector search & embeddings
- Persistence
- Database core
- Schema migrations
- Adaptive memory
- Real-time sync
- React integration
Related projects
In the repo