@sh1n4ps/plasma-server is the Cloudflare Workers runtime. It compiles the
same @sh1n4ps/plasma-core query AST into SQLite / Postgres dialects,
handles push / pull / blob endpoints, and provides the Durable
Objects the realtime path needs.
Complete SyncHandlerOptions field list with defaults:
| Option |
What |
schema |
The isomorphic schema from defineSchema({...}) |
mutators |
Registry from defineMutators<S, Ctx>()({...}) |
executor |
SqlExecutor — fromD1(env.DB) on Cloudflare, fromBetterSqlite3(sqlite) for tests |
schemaVersion |
Must match PlasmaClientOptions.schemaVersion. Mismatch → 409 handshake |
auth |
async (req) => { ok, clientGroupID, clientID, ctx } | { ok: false, reason } — runs on every push/pull/blob request. See Auth and permissions |
| Option |
Default |
What |
basePath |
"/sync" |
Path prefix. Override to serve at /api/plasma, etc. |
| Option |
Default |
What |
onPushed |
— |
({ clientGroupID, clientID, ctx }) => void. Called once per successful push (after every mutation batch that changed something). Canonical wiring: nudge SyncCoordinator via pokeCoordinator(env.COORDINATOR, ...). Optional — poll fallback keeps working without it. See Server-side operations |
onError |
— |
(err: SyncServerError) => void. Observability sink for every server-side failure — mutator throws, schema mismatches, onPushed failures, blob validation. Wire to your logs/Sentry |
onMetric |
— |
(event: PlasmaMetric) => void. Numeric telemetry: push / pull / blob-put / blob-get with latencyMs, ok, and per-kind counters. Wire to Prometheus / Analytics Engine |
waitUntil |
inline await |
Non-blocking Cache API put hook. Pass ctx.waitUntil.bind(ctx) from the fetch handler so blob GET cache-fills don’t stretch p95 |
| Option |
Default |
What |
maxMutationsPerPush |
200 |
Requests exceeding this are 413-rejected before any mutator runs. Defence against a compromised client trying to burn D1 write budget |
maxPayloadBytes |
1_048_576 (1 MiB) |
Push body cap. Checked via Content-Length; clients omitting the header are allowed through and the mutation count catches them |
maxPullPageSize |
1000 |
Upper bound on ?limit=N for pull responses. Clients that don’t pass ?limit are also capped at this. Guards against unbounded change-log downloads |
| Option |
Default |
What |
requireTableAuth |
false |
When true, createSyncHandler throws at construction if any table lacks a TableOptions.auth block. Enable in production to catch a forgotten auth predicate before it leaks cross-tenant |
| Option |
Default |
What |
envelopeValidation.maxCiphertextBytes |
— |
Cap on ct size in any incoming envelope (base64 length pre-decode). Absent → shape check only |
envelopeValidation.allowedKeyIds |
— |
Whitelist of allowed keyId values. Push with an unrecognised keyId is rejected |
envelopeValidation.maxKemCiphertextBytes |
4096 |
Cap on the kem.ct field of a PQ hybrid envelope. ML-KEM-768 is ~1088 bytes; default leaves headroom without letting a malicious client inflate up to maxCiphertextBytes |
| Option |
Default |
What |
blobs |
— |
{ default: r2Storage({ bucket }) }. Required when the schema declares any file() column. v1.0 only honours default; per-table override throws at startup |
readAuthMaxRefs |
128 |
Fan-out cap for blob GET auth. Only the newest N refs pointing at a hash are checked; if none belong to a row the caller can read, request is denied. Bump for viral content-addressed assets |
| Option |
Default |
What |
dialect |
sqliteDialect |
Override to postgresDialect for Hyperdrive-backed Postgres |
The union delivered to your onError hook:
| Kind |
Payload |
Meaning |
mutation-error |
{ clientGroupID, clientID, mutationID, mutationName, cause } |
A mutator threw. Transaction rolled back, last_mutation_id still advanced |
onPushed-failed |
{ clientGroupID, clientID, cause } |
Your onPushed hook threw. Push still succeeded on the wire |
protocol-mismatch |
{ expected, got } (numbers) |
Client sent an old / new protocolVersion |
schema-mismatch |
{ expected, got } (strings) |
Client / server schemaVersion differ. Push/pull rejected with 409 |
unauthorized |
— |
auth() returned { ok: false } |
invalid-request |
{ reason } |
Malformed body / bad params |
blob-invalid-request |
{ reason } |
Blob endpoint request malformed |
blob-not-found |
{ hash } |
GET for a hash the storage has no record of |
blob-read-auth-cap-hit |
{ hash, cap } |
readAuthMaxRefs reached before finding a readable owner — request denied even though an older ref might have granted access. Performance guard |
blob-hash-mismatch |
{ hash, reason } |
PUT’s bytes hash doesn’t match the URL hash |
blob-upload-failed-server |
{ hash, cause } |
Storage backend rejected the PUT (R2 down, quota, etc.) |
- Sync handler —
createSyncHandler, SyncHandlerOptions, SyncServerError
- DDL —
ensureSchema, runMigrations, MigrationRefused, SchemaDiff, assertSafeForV1
- Executors —
SqlExecutor interface, fromBetterSqlite3, fromD1
- Server-side db —
createServerDb, ServerDbOptions, AuthorizePolicy, PlasmaAuthorizationError
- Server-side live —
serverLiveSelect, ServerLiveOptions, ServerLiveHandle, schemaTableNames
- Server-side time-travel —
snapshotAsOf
- SQL dialects —
sqliteDialect, postgresDialect, SqlDialect, SqlWriteObserver
- Realtime —
SyncCoordinator DO. pokeCoordinator from /coordinator subpath
- Storage —
r2Storage, Storage interface, StorageObjectMeta, PresignedUpload, PresignedDownload, R2StorageConfig, StorageDownload, StorageDownloadParams, StorageRange, StorageUploadParams
- GC —
gcOrphanedBlobs, GcOrphanedBlobsOptions, GcResult, reconcileBlobRefs, ReconcileBlobRefsOptions, ReconcileResult
- Change log helpers —
CHANGES_TABLE, compactChangeLog, CompactionResult
- Multi-region —
SequencerDO, MemorySequencerStorage, SequencerStorage, SequencerState, ReserveResult, SequencerDoSimulator, advanceCookieFromChanges, pullPredicate, REGION_VERSIONS_TABLE, reserveRegionVersions, ensureRegionVersionsTable
- Migration hooks —
MigrationEvent, MigrationResult, RunMigrationsOptions
- SQL engine —
SqlEngineOptions (for advanced adapter authors)
Under reference/generated/plasma-server/src.