Skip to content

@sh1n4ps/plasma-server

@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.

createSyncHandler(options) — every option

Section titled “createSyncHandler(options) — every option”

Complete SyncHandlerOptions field list with defaults:

Option What
schema The isomorphic schema from defineSchema({...})
mutators Registry from defineMutators<S, Ctx>()({...})
executor SqlExecutorfromD1(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 handlercreateSyncHandler, SyncHandlerOptions, SyncServerError
  • DDLensureSchema, runMigrations, MigrationRefused, SchemaDiff, assertSafeForV1
  • ExecutorsSqlExecutor interface, fromBetterSqlite3, fromD1
  • Server-side dbcreateServerDb, ServerDbOptions, AuthorizePolicy, PlasmaAuthorizationError
  • Server-side liveserverLiveSelect, ServerLiveOptions, ServerLiveHandle, schemaTableNames
  • Server-side time-travelsnapshotAsOf
  • SQL dialectssqliteDialect, postgresDialect, SqlDialect, SqlWriteObserver
  • RealtimeSyncCoordinator DO. pokeCoordinator from /coordinator subpath
  • Storager2Storage, Storage interface, StorageObjectMeta, PresignedUpload, PresignedDownload, R2StorageConfig, StorageDownload, StorageDownloadParams, StorageRange, StorageUploadParams
  • GCgcOrphanedBlobs, GcOrphanedBlobsOptions, GcResult, reconcileBlobRefs, ReconcileBlobRefsOptions, ReconcileResult
  • Change log helpersCHANGES_TABLE, compactChangeLog, CompactionResult
  • Multi-regionSequencerDO, MemorySequencerStorage, SequencerStorage, SequencerState, ReserveResult, SequencerDoSimulator, advanceCookieFromChanges, pullPredicate, REGION_VERSIONS_TABLE, reserveRegionVersions, ensureRegionVersionsTable
  • Migration hooksMigrationEvent, MigrationResult, RunMigrationsOptions
  • SQL engineSqlEngineOptions (for advanced adapter authors)

Under reference/generated/plasma-server/src.