Skip to content

All Capabilities

This page exists so you never have to grep the source to find out “can plasma do X?”. Every capability plasma exposes in v1.0 is listed once, categorised, and linked to the guide that explains it.

Capability How
Declare a table table("name", { id: id(), ... })Schema
Group tables into a schema defineSchema({ users, todos, ... })
Nullable column .nullable()
Column default value .default(value)
Unique constraint .unique()
Encrypted at rest .encrypted()Encryption
Foreign key ref(() => users.id, { onDelete }) — cascade / restrict / setNull / noAction
Auto UUID primary key id() — must be named id, one per table
String column text()
Integer column int(), bigint()
Boolean boolean()
Binary inline blob() (small bytes stored in the row)
JSON json()
Content-addressable file (R2) file({ maxSize?, mimeAllowList?, immutable?, upload? })Files
Grow-only counter crdtCounter()CRDT
Signed counter crdtPnCounter()
Last-writer-wins register crdtLwwRegister<T>()
Observed-remove set crdtOrSet<T>()
Row-level auth (read/write) TableOptions.auth: { read, write }Auth
Custom conflict merge TableOptions.resolveConflictConflict resolution
Local-only cache table TableOptions.changeLogSuppressed: trueOffline
Per-table storage adapter (schema-side declaration) TableOptions.blobs: storageRef("name")v1.0 note: the sync handler currently only honours default; non-default names throw at startup

The read side is drizzle-shaped. Every operator on the Schema guide table above is composable in db.select().from(...).where(...).orderBy(...).limit(...).offset(...).

Capability How
Comparison eq, ne, gt, gte, lt, lte
Boolean logic and, or, not
Set membership inArray
Null checks isNull, isNotNull
Pattern match like
Ordering asc, desc
Aggregates count, sum, avg, max, min
Grouping .groupBy(col)Query builder
Group filter .having(expr) — post-aggregate filter
Pagination — limit .limit(n)
Pagination — offset .offset(n)
Inner join .innerJoin(table, on)
Left join .leftJoin(table, on)
Subquery as FROM .fromSubquery(inner, "alias") + colRef("alias", "col")
Live subscription .live() returns LiveQuery<T>Live queries
React binding useLiveQuery(factory, deps)

Column projection (select({ x: todos.title })) is supported; row types flow through the projected shape.

Capability How
Define mutators defineMutators<S, Ctx>()({ name: async ({db, args, ctx}) => ... })Mutators
Args validation Standard Schema — Zod / Valibot / ArkType / Effect all work
Cross-table writes in one transaction Multi-table db.insert/update/delete inside one mutator
Access the mutation origin clientID, mutationID on the destructure
React binding useMutation<M, K>(name) returns { mutate, isPending, error, reset }
Server-only mutator run invokeMutator(mutators, name, {db, ctx, args}) — for scheduled jobs
Client-side one-shot mutate client.mutate("name", args) — instant IDB apply, enqueues outbox
Discard a queued mutation client.discardMutation(id)
Capability How
Start the sync loop client.start() — opens poll timer + WebSocket + upload worker
Stop the sync loop client.stop()
Wait for outbox drain await client.flush()
Manual push client.pushOnce()
Manual pull client.pullOnce()
Poll interval PlasmaClientOptions.pollIntervalMs
Retry policy PlasmaClientOptions.retry: { maxAttempts, initialDelayMs, maxDelayMs }
WebSocket subscription subscribe: createWebSocketSubscription({ url })
Offline mode PlasmaClientOptions.offline: trueOffline
Auth headers authHeaders: async () => ({ authorization: ... })
Custom fetch fetcher: async (input, init) => Response
Context provider getContext: async () => ({ ...ctx })
Schema mismatch handler onSchemaMismatch: async ({ phase }) => "reset" | "stay"
Error hook onError: (err: SyncClientError) => void
Reset local state client.resetLocalState() — wipes IDB + rotates clientID
Capability How
Upload from File / Blob Just pass it as the arg — desugared before outbox. Uint8Array / ArrayBuffer are not accepted (would clash with blob() column); wrap in a Blob first
Read from a FileRef client.readFile(ref) or the usePlasmaFile(ref) hook
Blob upload retry policy blobUploadRetry: { maxAttempts, initialDelayMs, maxDelayMs }
Retry a failed upload client.retryBlobUpload(hash)
GC orphaned blobs gcOrphanedBlobs({ executor, storage, minOrphanAgeMs, limit }) — run from scheduled
Rebuild _plasma_blob_refs after raw driver writes reconcileBlobRefs({ schema, executor, dialect })
Per-table storage adapter (schema-side declaration) TableOptions.blobs: storageRef("name")v1.0 note: the sync handler currently only honours default; non-default names throw at startup
Server-side blob adapter blobs: { default: r2Storage({ bucket }) } on SyncHandlerOptions
Blob read auth cap SyncHandlerOptions.readAuthMaxRefs (default 128)
Capability How
Subscribe to snapshot live.subscribe((rows) => ...)
Subscribe to diffs live.subscribeDelta?.((delta) => ...){ added, removed, changed }
Await initial delivery await live.whenReady?.()
IVM eligibility inspection classifyIvm(ast) returns "select" | "aggregate" | "none"
Server-side live serverLiveSelect({ executor, fetch, onChange, tables?, pollIntervalMs? })Server-side operations

Read helpers (call on the row’s stored value):

Column type Read Merge
crdtCounter sumCrdtCounter(map) mergeCrdtCounter(a, b)
crdtPnCounter pnRead(map) mergePnCounter(a, b)
crdtLwwRegister<T> lwwRead(reg, fallback) mergeLwwRegister(a, b)
crdtOrSet<T> orSetValues(set) / orSetHas(set, v) mergeOrSet(a, b)

Write helpers (build the new stored value inside a mutator):

Column Helper
crdtCounter crdtIncrement(clientID, delta, current)
crdtPnCounter pnIncrement(clientID, delta, current), pnDecrement(clientID, delta, current)
crdtLwwRegister lwwSet(clientID, value, ts, current)
crdtOrSet orSetAdd(clientID, seq, value, current), orSetRemove(value, current)
Capability How
Column-level marker .encrypted() on a column
At-rest client-local wrapping PlasmaClientOptions.encryption: { dek, keyId }
Manual encryption inside a mutator (E2EE) encryptField(dek, aad, value) / decryptField(dek, aad, envelope)
Envelope wire format Envelope — AES-GCM-256, keyId-tagged, AAD from (table, rowId, column, keyId)
Envelope validation on incoming push validateEnvelope(env, { maxCiphertextBytes, allowedKeyIds })
PQ hybrid wrapping encryptFieldPq(provider, aad, value) / decryptFieldPq(provider, aad, env)
PQ envelope PqEnvelope, isPqEnvelope
Provider interface for KEM PqHybridProvider
Insecure staging provider insecurePlaceholderProvider({ acceptInsecure: true }) — throws without opt-in
Capability How
Mount the handler createSyncHandler({ schema, mutators, executor, schemaVersion, auth, ... })Deployment
Custom path prefix basePath: "/api/plasma" (default "/sync")
Request auth auth: async (req) => ({ ok, clientGroupID, clientID, ctx })
Post-push hook (poke fan-out) onPushed: async ({ clientGroupID, ... }) => pokeCoordinator(...)
Server-side error hook onError: (err: SyncServerError) => void — 11 kinds
Non-blocking Cache API put waitUntil: ctx.waitUntil.bind(ctx)
Cap mutations per push maxMutationsPerPush: 200 (default)
Cap payload size maxPayloadBytes: 1_048_576 (default 1 MiB)
Encryption envelope validation envelopeValidation: { maxCiphertextBytes, allowedKeyIds, maxKemCiphertextBytes }
Attach a blob storage blobs: { default: r2Storage({ bucket }) }
Cap blob read auth fan-out readAuthMaxRefs: 128
Server-side db access outside handler createServerDb({ schema, executor, ctx })
Authorization policy AuthorizePolicy — bind read / write from ctx to TableAuth predicates
Raise mutator authz errors throw new PlasmaAuthorizationError(...)
Capability How
First-run DDL (idempotent) ensureSchema({ schema, executor }) — call every request
Reconcile diff at deploy runMigrations({ schema, executor })Migrations
Refuse destructive migrations MigrationRefused — thrown on drops / renames / kind changes
Introspect the diff Returned SchemaDiff structure
Bump schema version SCHEMA_VERSION string in the shared schema file
Handshake behaviour 409 on mismatch; client onSchemaMismatch chooses "reset" or "stay"
Capability How
Durable Object for WebSocket fan-out Export SyncCoordinator from your Worker
Trigger a WebSocket poke pokeCoordinator(env.COORDINATOR, clientGroupID) from @sh1n4ps/plasma-server/coordinator
Client-side WebSocket transport createWebSocketSubscription({ url, protocols? })
Capability How
Broadcast client userInfo payload createWebSocketSubscription({ userInfo, onPresence })Presence
Receive presence changes onPresence: (entries) => ... — full snapshot every time
Presence entry shape { clientID, userInfo }userInfo is any JSON
Room scoping clientGroupID by default; override with ?room= on the WebSocket URL
Update your own userInfo Reconnect with new payload (or throttle for cursor-like data)
Capability How
Per-region monotonic id source SequencerDO
Reserve id range stub.reserve(count) returns { start, end } as strings (bigint safe)
Storage backend for the DO SequencerStorage interface, MemorySequencerStorage for tests
Test-time simulator SequencerDoSimulator
Server-side version reservation reserveRegionVersions({ executor, regionId, count })
DDL for _plasma_region_versions ensureRegionVersionsTable({ executor })
Causal cookie codec encodeCookie, decodeCookie, mergeCookie, cookieCovers
Advance a cookie from a change batch advanceCookieFromChanges(cookie, rows)
Pull predicate from cookie pullPredicate(cookie) — the SQL WHERE fragment
Pack / unpack region+version packRegionVersion, unpackRegionVersion
Constants REGION_VERSIONS_TABLE, CHANGES_TABLE
Capability How
Fold intermediate puts / del pairs compactChangeLog(executor, safeUpToVersion) — scheduled worker
Result stats CompactionResult
Capability How
Client engine against fake IDB createIdbEngine({ schema, dbName }) + fake-indexeddb/autoTesting
SQLite executor for tests fromBetterSqlite3(sqlite)
Worker-side tests @cloudflare/vitest-pool-workers + Miniflare
Await first live delivery await live.whenReady?.()
Sequencer stub for tests SequencerDoSimulator + MemorySequencerStorage
Server-side db for isolated writes createServerDb({ schema, executor, ctx })
Capability How
Wrap the tree <PlasmaProvider client={plasma}>
Read the current client usePlasma()
Live query useLiveQuery(factory, deps)
Mutation with state useMutation<M, K>(name){ mutate, isPending, error, reset }
File resolution + Blob URL usePlasmaFile(ref)FileHandle union (pending / local / uploading / ready / missing / error)
Capability How
In-page panel <PlasmaDevtools client={plasma} dbName="..." schema={schema} />
Snapshot hook useDevtoolsSnapshot(client, options)
postMessage bridge attachDevtoolsBridge(client, { dbName, schema, targetOrigin, allowedOrigins, ... })
Message discriminant PLASMA_DEVTOOLS_SOURCE = "plasma-devtools"
Type guard isDevtoolsMessage(evt.data)
Command kinds flush, pull, reset-local-state, ping
Capability How
Cloudflare D1 fromD1(env.DB)
better-sqlite3 (Node) fromBetterSqlite3(sqlite)
SQLite dialect sqliteDialect
Postgres dialect postgresDialect
SQL executor interface SqlExecutor — bring your own driver
SQL dialect interface SqlDialect — bring your own dialect
R2 storage adapter r2Storage({ bucket })
Storage interface Storage — bring your own backend
  • Nested-relational query sugar (db.query.x.findMany({ with })).
  • db.increment(col, delta) high-level CRDT counter API.
  • Args-boundary envelope walker (so .encrypted() E2EE stops requiring encryptField() inside the mutator body).
  • Runtime client.setOffline(bool) toggle.
  • Per-table blob storage adapter (declared today, refused at runtime because handler only wires default).
  • Full per-region pull predicate (v1.0 handler simplifies to hi = max(cursors); correct for single-region, has a drop-window for heterogeneous multi-region).

See the Roadmap for the tracking status.