-
Notifications
You must be signed in to change notification settings - Fork 0
Database Core
Beyond CRUD, PolyGraph has database-grade write-path
guarantees: transactions, revisions, conditional writes, resource limits,
secondary indexes, and adapter capability checks.
await graph.transaction(async (tx) => {
tx.addNode({ id: 'a', type: 't', data: {}, insertedAt: Date.now(), updatedAt: Date.now() })
tx.patchNode('b', { increment: { views: 1 } })
}, { operationId: 'req-123' })transaction(callback, options?) gives atomic read-your-own-writes:
mutations inside the callback are visible to later reads in the same
callback, but only committed as one unit if the callback resolves without
throwing — a thrown error rolls the whole transaction back. Nested
transactions are rejected. options may include operationId (a stable
logical identity for retries/audit), actor, baseRevision, and
metadata — retained in the durable mutation record when the adapter
supports a change feed (see Persistence). Requires an
adapter that declares both atomicBatches and transactions
capabilities, or it throws AdapterCapabilityError.
Every node carries a revision, starting at 0 and incrementing on each
write. Pass expectedRevision on a write to make it conditional (optimistic
concurrency):
try {
graph.updateNode('doc_1', { title: 'v2' }, undefined, undefined, { expectedRevision: 3 })
} catch (err) {
// ConflictError — someone else wrote doc_1 since revision 3
}A mismatch throws ConflictError(id, expectedRevision, actualRevision).
patchNode(id, patch, options?) applies structured, atomic operations
against a loaded node in one call:
graph.patchNode('doc_1', {
set: { 'data.status': 'published' },
unset: ['data.draftNote'],
increment: { 'data.viewCount': 1 },
compareAndSet: { 'data.status': { expected: 'draft', value: 'review' } },
})set/unset/increment target data.* (or unprefixed) paths, plus the
directly-patchable top-level paths updatedAt and the provenance fields
(memoryClass, confidence, source, observedAt, derivedFrom,
supersedes, contradicts — see Property graph).
compareAndSet throws ConflictError if the path's current value doesn't
deep-equal expected.
graph.setResourceLimits({ maxVectorDimensions?, maxNodePayloadBytes?, maxBatchSize? }) rejects writes that exceed configured bounds —
maxVectorDimensions on node vectors, maxNodePayloadBytes on serialized
node data, maxBatchSize on addNodes/transaction mutation counts.
graph.getResourceLimits() reads the current configuration back. Violations
throw ResourceLimitError.
graph.defineIndex({ name, nodeType?, fields, unique?, sparse? }) builds a
compound (multi-field) or unique index over node data, optionally scoped to
one node type. sparse skips nodes missing an indexed field rather than
indexing them under undefined. registerNodeType's convenience indexes
option calls this for you at registration time (see Property
graph). Persisted index metadata round-trips through
adapters that support it, including the Python bindings.
Persisted and hot-query plans expose index selection, estimated cost, and operational metrics, so you can see why a query chose (or skipped) an index — useful when a query isn't hitting the index you expect.
graph.adapterCapabilities reads what the active adapter declares
(atomicBatches, transactions, fsync, secondaryIndexes, snapshots,
changeFeed, concurrentWriters, vectorSearch).
graph.requireAdapterCapabilities({ name: expected, ... }) throws
AdapterCapabilityError unless every listed capability matches. Use this
to fail fast when a feature (transactions, a change feed, fsync durability)
needs a capability the configured adapter doesn't provide. See
Persistence for the adapters themselves.
Application-level record migrations are a related but separate concern — see Schema migrations.
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