This is the full developer documentation for pgxsinkit
# pgxsinkit
> Opinionated, Offline-first, RLS-aware sync between PostgreSQL/Supabase and Drizzle/PGlite at the edge.
## Two paths, one toolkit
[Section titled “Two paths, one toolkit”](#two-paths-one-toolkit)
pgxsinkit is the **`@pgxsinkit/*`** library set. It gives a local-first app a read path that streams Postgres rows to PGlite through ElectricSQL, and a write path that takes local edits back to Postgres through a typed write route — with per-row access control on both (Postgres row-level security on writes, a matching shape filter on reads).
Read path
`PostgreSQL -> ElectricSQL -> PGlite`. Shapes (including membership fan-out via subquery `where`) stream into local PGlite. Reads never hit Electric directly — they go through the **shape proxy**, which enforces ownership.
Write path
`client -> write route -> PostgreSQL`. Edits are staged locally, flushed as a batch, and applied in a single in-database PL/pgSQL function — one path, no per-table CRUD.
Tested against real infrastructure
A container-backed integration + performance harness runs every path against real PostgreSQL, ElectricSQL, and PGlite — not mocks — so the toolkit’s behaviour is proven, not asserted.
AI-ready
These docs publish `llms.txt`, and the `@pgxsinkit/*` packages ship [TanStack Intent](https://tanstack.com/intent) **Agent Skills** — version-pinned guidance your coding assistant can load to wire sync correctly. See [Use these docs with your AI assistant](/start/ai-assistants/).
# ClientDisposedError
Defined in: packages/client/src/index.ts:355
The client was stopped/destroyed (or its worker attachment detached) before a boot stage it exposed could resolve (ADR-0041 FIX 2). `stop()`/`destroy()` reject the still-pending `writeReady` and `ready` with this so a parked mutation or an `await client.ready` / `client.start()` fails FAST rather than hanging forever after teardown. `bootSettled` still RESOLVES (it means “teardown completion”, not “boot succeeded”).
## Extends
[Section titled “Extends”](#extends)
* `Error`
## Constructors
[Section titled “Constructors”](#constructors)
### Constructor
[Section titled “Constructor”](#constructor)
> **new ClientDisposedError**(): `ClientDisposedError`
Defined in: packages/client/src/index.ts:356
#### Returns
[Section titled “Returns”](#returns)
`ClientDisposedError`
#### Overrides
[Section titled “Overrides”](#overrides)
`Error.constructor`
## Properties
[Section titled “Properties”](#properties)
### cause?
[Section titled “cause?”](#cause)
> `optional` **cause?**: `unknown`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:24
The cause of the error.
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
`Error.cause`
***
### message
[Section titled “message”](#message)
> **message**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1075
#### Inherited from
[Section titled “Inherited from”](#inherited-from-1)
`Error.message`
***
### name
[Section titled “name”](#name)
> **name**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1074
#### Inherited from
[Section titled “Inherited from”](#inherited-from-2)
`Error.name`
***
### stack?
[Section titled “stack?”](#stack)
> `optional` **stack?**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1076
#### Inherited from
[Section titled “Inherited from”](#inherited-from-3)
`Error.stack`
***
### stackTraceLimit
[Section titled “stackTraceLimit”](#stacktracelimit)
> `static` **stackTraceLimit**: `number`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:67
The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`).
The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed.
If set to a non-number value, or set to a negative number, stack traces will not capture any frames.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-4)
`Error.stackTraceLimit`
## Methods
[Section titled “Methods”](#methods)
### captureStackTrace()
[Section titled “captureStackTrace()”](#capturestacktrace)
#### Call Signature
[Section titled “Call Signature”](#call-signature)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:51
Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called.
```js
const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack; // Similar to `new Error().stack`
```
The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`.
The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace.
The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance:
```js
function a() {
b();
}
function b() {
c();
}
function c() {
// Create an error without stack trace to avoid calculating the stack trace twice.
const { stackTraceLimit } = Error;
Error.stackTraceLimit = 0;
const error = new Error();
Error.stackTraceLimit = stackTraceLimit;
// Capture the stack trace above function b
Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
throw error;
}
a();
```
##### Parameters
[Section titled “Parameters”](#parameters)
###### targetObject
[Section titled “targetObject”](#targetobject)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt)
`Function`
##### Returns
[Section titled “Returns”](#returns-1)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-5)
`Error.captureStackTrace`
#### Call Signature
[Section titled “Call Signature”](#call-signature-1)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1042
Create .stack property on a target object
##### Parameters
[Section titled “Parameters”](#parameters-1)
###### targetObject
[Section titled “targetObject”](#targetobject-1)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt-1)
`Function`
##### Returns
[Section titled “Returns”](#returns-2)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-6)
`Error.captureStackTrace`
***
### isError()
[Section titled “isError()”](#iserror)
#### Call Signature
[Section titled “Call Signature”](#call-signature-2)
> `static` **isError**(`error`): `error is Error`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.esnext.error.d.ts:21
Indicates whether the argument provided is a built-in Error instance or not.
##### Parameters
[Section titled “Parameters”](#parameters-2)
###### error
[Section titled “error”](#error)
`unknown`
##### Returns
[Section titled “Returns”](#returns-3)
`error is Error`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-7)
`Error.isError`
#### Call Signature
[Section titled “Call Signature”](#call-signature-3)
> `static` **isError**(`value`): `value is Error`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1037
Check if a value is an instance of Error
##### Parameters
[Section titled “Parameters”](#parameters-3)
###### value
[Section titled “value”](#value)
`unknown`
The value to check
##### Returns
[Section titled “Returns”](#returns-4)
`value is Error`
True if the value is an instance of Error, false otherwise
##### Inherited from
[Section titled “Inherited from”](#inherited-from-8)
`Error.isError`
***
### prepareStackTrace()
[Section titled “prepareStackTrace()”](#preparestacktrace)
> `static` **prepareStackTrace**(`err`, `stackTraces`): `any`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:55
#### Parameters
[Section titled “Parameters”](#parameters-4)
##### err
[Section titled “err”](#err)
`Error`
##### stackTraces
[Section titled “stackTraces”](#stacktraces)
`CallSite`\[]
#### Returns
[Section titled “Returns”](#returns-5)
`any`
#### See
[Section titled “See”](#see)
#### Inherited from
[Section titled “Inherited from”](#inherited-from-9)
`Error.prepareStackTrace`
# CommittedStoreUnreachableError
Defined in: packages/client/src/store-boot.ts:185
Thrown when a boot whose engine home holds NO OPFS sync-access grant meets a store whose meta record says `opfs-committed` — the user’s real store lives in the OPFS backend, which this home cannot open. Left unguarded that boot opens `idb://` instead and MINTS AN EMPTY SIBLING at the same path: the app looks wiped, and any offline writes fork into a store no OPFS-capable boot ever opens. So the boot fails CLOSED, matching the ADR-0050 posture (never a silently different storage mode) and the [NonPersistentStoreError](/api/client/classes/nonpersistentstoreerror/) / `StorageDeclarationRefusedError` precedents. There is deliberately no override: a committed store is only reachable from a home that can hold handles.
A distinct type — not a bare `Error` — so a caller can `instanceof`-branch it from a genuine boot failure, and it survives the worker bridge AS THAT TYPE: the instance carries the clone-safe [CommittedStoreUnreachableWire](/api/client/interfaces/committedstoreunreachablewire/) `detail` the bridge forwards, and the tab side reconstructs it through [committedStoreUnreachableFromWire](/api/client/functions/committedstoreunreachablefromwire/) (the same tagged-detail pair the execution-limit and relocation errors use). `storePath` is on the instance too, because the remedy is path-addressed. The message names BOTH exits, because a consumer meeting this has to choose between them.
## Extends
[Section titled “Extends”](#extends)
* `Error`
## Constructors
[Section titled “Constructors”](#constructors)
### Constructor
[Section titled “Constructor”](#constructor)
> **new CommittedStoreUnreachableError**(`storePath`): `CommittedStoreUnreachableError`
Defined in: packages/client/src/store-boot.ts:190
#### Parameters
[Section titled “Parameters”](#parameters)
##### storePath
[Section titled “storePath”](#storepath)
`string`
#### Returns
[Section titled “Returns”](#returns)
`CommittedStoreUnreachableError`
#### Overrides
[Section titled “Overrides”](#overrides)
`Error.constructor`
## Properties
[Section titled “Properties”](#properties)
### cause?
[Section titled “cause?”](#cause)
> `optional` **cause?**: `unknown`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:24
The cause of the error.
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
`Error.cause`
***
### code
[Section titled “code”](#code)
> `readonly` **code**: `"committed-store-unreachable"` = `COMMITTED_STORE_UNREACHABLE_CODE`
Defined in: packages/client/src/store-boot.ts:186
***
### detail
[Section titled “detail”](#detail)
> `readonly` **detail**: [`CommittedStoreUnreachableWire`](/api/client/interfaces/committedstoreunreachablewire/)
Defined in: packages/client/src/store-boot.ts:188
***
### message
[Section titled “message”](#message)
> **message**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1075
#### Inherited from
[Section titled “Inherited from”](#inherited-from-1)
`Error.message`
***
### name
[Section titled “name”](#name)
> **name**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1074
#### Inherited from
[Section titled “Inherited from”](#inherited-from-2)
`Error.name`
***
### stack?
[Section titled “stack?”](#stack)
> `optional` **stack?**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1076
#### Inherited from
[Section titled “Inherited from”](#inherited-from-3)
`Error.stack`
***
### storePath
[Section titled “storePath”](#storepath-1)
> `readonly` **storePath**: `string`
Defined in: packages/client/src/store-boot.ts:187
***
### stackTraceLimit
[Section titled “stackTraceLimit”](#stacktracelimit)
> `static` **stackTraceLimit**: `number`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:67
The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`).
The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed.
If set to a non-number value, or set to a negative number, stack traces will not capture any frames.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-4)
`Error.stackTraceLimit`
## Methods
[Section titled “Methods”](#methods)
### captureStackTrace()
[Section titled “captureStackTrace()”](#capturestacktrace)
#### Call Signature
[Section titled “Call Signature”](#call-signature)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:51
Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called.
```js
const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack; // Similar to `new Error().stack`
```
The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`.
The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace.
The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance:
```js
function a() {
b();
}
function b() {
c();
}
function c() {
// Create an error without stack trace to avoid calculating the stack trace twice.
const { stackTraceLimit } = Error;
Error.stackTraceLimit = 0;
const error = new Error();
Error.stackTraceLimit = stackTraceLimit;
// Capture the stack trace above function b
Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
throw error;
}
a();
```
##### Parameters
[Section titled “Parameters”](#parameters-1)
###### targetObject
[Section titled “targetObject”](#targetobject)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt)
`Function`
##### Returns
[Section titled “Returns”](#returns-1)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-5)
`Error.captureStackTrace`
#### Call Signature
[Section titled “Call Signature”](#call-signature-1)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1042
Create .stack property on a target object
##### Parameters
[Section titled “Parameters”](#parameters-2)
###### targetObject
[Section titled “targetObject”](#targetobject-1)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt-1)
`Function`
##### Returns
[Section titled “Returns”](#returns-2)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-6)
`Error.captureStackTrace`
***
### isError()
[Section titled “isError()”](#iserror)
#### Call Signature
[Section titled “Call Signature”](#call-signature-2)
> `static` **isError**(`error`): `error is Error`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.esnext.error.d.ts:21
Indicates whether the argument provided is a built-in Error instance or not.
##### Parameters
[Section titled “Parameters”](#parameters-3)
###### error
[Section titled “error”](#error)
`unknown`
##### Returns
[Section titled “Returns”](#returns-3)
`error is Error`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-7)
`Error.isError`
#### Call Signature
[Section titled “Call Signature”](#call-signature-3)
> `static` **isError**(`value`): `value is Error`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1037
Check if a value is an instance of Error
##### Parameters
[Section titled “Parameters”](#parameters-4)
###### value
[Section titled “value”](#value)
`unknown`
The value to check
##### Returns
[Section titled “Returns”](#returns-4)
`value is Error`
True if the value is an instance of Error, false otherwise
##### Inherited from
[Section titled “Inherited from”](#inherited-from-8)
`Error.isError`
***
### prepareStackTrace()
[Section titled “prepareStackTrace()”](#preparestacktrace)
> `static` **prepareStackTrace**(`err`, `stackTraces`): `any`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:55
#### Parameters
[Section titled “Parameters”](#parameters-5)
##### err
[Section titled “err”](#err)
`Error`
##### stackTraces
[Section titled “stackTraces”](#stacktraces)
`CallSite`\[]
#### Returns
[Section titled “Returns”](#returns-5)
`any`
#### See
[Section titled “See”](#see)
#### Inherited from
[Section titled “Inherited from”](#inherited-from-9)
`Error.prepareStackTrace`
# DataExportDrainError
Defined in: packages/client/src/export-data.ts:87
Thrown when `exportData`’s strict drain cannot produce a drained journal (ADR-0035 decision 3). Carries the `MutationDiagnostics` snapshot at the moment of failure so the caller sees exactly what blocked the export, and a `reason` distinguishing the two failure modes:
* `"non-drainable-state"` — `failed`/`quarantined`/`conflicted` rows are present (either pre-existing, so the failure is immediate with no waiting, or a mid-drain `flush()` failure that moved rows to `failed`); these terminal states never drain on their own, so waiting is pointless.
* `"timeout"` — drainable rows (`pending`/`sending`/`acked`) did not reach fully-drained within the budget; most commonly `acked` writes whose synced echo never arrived (an offline device, a stalled read path).
A distinct type — not a bare `Error` — so a caller can `instanceof`-branch a drain failure from a genuine export failure, inspect the diagnostics, and choose the escape hatch (`drainJournal: false`) or the lossless store backup instead.
## Extends
[Section titled “Extends”](#extends)
* `Error`
## Constructors
[Section titled “Constructors”](#constructors)
### Constructor
[Section titled “Constructor”](#constructor)
> **new DataExportDrainError**(`reason`, `diagnostics`): `DataExportDrainError`
Defined in: packages/client/src/export-data.ts:93
#### Parameters
[Section titled “Parameters”](#parameters)
##### reason
[Section titled “reason”](#reason)
`"non-drainable-state"` | `"timeout"`
##### diagnostics
[Section titled “diagnostics”](#diagnostics)
[`MutationDiagnostics`](/api/client/interfaces/mutationdiagnostics/)
#### Returns
[Section titled “Returns”](#returns)
`DataExportDrainError`
#### Overrides
[Section titled “Overrides”](#overrides)
`Error.constructor`
## Properties
[Section titled “Properties”](#properties)
### cause?
[Section titled “cause?”](#cause)
> `optional` **cause?**: `unknown`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:24
The cause of the error.
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
`Error.cause`
***
### diagnostics
[Section titled “diagnostics”](#diagnostics-1)
> `readonly` **diagnostics**: [`MutationDiagnostics`](/api/client/interfaces/mutationdiagnostics/)
Defined in: packages/client/src/export-data.ts:91
The mutation diagnostics at the moment the drain gave up — the evidence of what was unflushed.
***
### message
[Section titled “message”](#message)
> **message**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1075
#### Inherited from
[Section titled “Inherited from”](#inherited-from-1)
`Error.message`
***
### name
[Section titled “name”](#name)
> **name**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1074
#### Inherited from
[Section titled “Inherited from”](#inherited-from-2)
`Error.name`
***
### reason
[Section titled “reason”](#reason-1)
> `readonly` **reason**: `"non-drainable-state"` | `"timeout"`
Defined in: packages/client/src/export-data.ts:89
Which drain failure occurred (see the class doc).
***
### stack?
[Section titled “stack?”](#stack)
> `optional` **stack?**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1076
#### Inherited from
[Section titled “Inherited from”](#inherited-from-3)
`Error.stack`
***
### stackTraceLimit
[Section titled “stackTraceLimit”](#stacktracelimit)
> `static` **stackTraceLimit**: `number`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:67
The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`).
The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed.
If set to a non-number value, or set to a negative number, stack traces will not capture any frames.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-4)
`Error.stackTraceLimit`
## Methods
[Section titled “Methods”](#methods)
### captureStackTrace()
[Section titled “captureStackTrace()”](#capturestacktrace)
#### Call Signature
[Section titled “Call Signature”](#call-signature)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:51
Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called.
```js
const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack; // Similar to `new Error().stack`
```
The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`.
The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace.
The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance:
```js
function a() {
b();
}
function b() {
c();
}
function c() {
// Create an error without stack trace to avoid calculating the stack trace twice.
const { stackTraceLimit } = Error;
Error.stackTraceLimit = 0;
const error = new Error();
Error.stackTraceLimit = stackTraceLimit;
// Capture the stack trace above function b
Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
throw error;
}
a();
```
##### Parameters
[Section titled “Parameters”](#parameters-1)
###### targetObject
[Section titled “targetObject”](#targetobject)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt)
`Function`
##### Returns
[Section titled “Returns”](#returns-1)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-5)
`Error.captureStackTrace`
#### Call Signature
[Section titled “Call Signature”](#call-signature-1)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1042
Create .stack property on a target object
##### Parameters
[Section titled “Parameters”](#parameters-2)
###### targetObject
[Section titled “targetObject”](#targetobject-1)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt-1)
`Function`
##### Returns
[Section titled “Returns”](#returns-2)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-6)
`Error.captureStackTrace`
***
### isError()
[Section titled “isError()”](#iserror)
#### Call Signature
[Section titled “Call Signature”](#call-signature-2)
> `static` **isError**(`error`): `error is Error`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.esnext.error.d.ts:21
Indicates whether the argument provided is a built-in Error instance or not.
##### Parameters
[Section titled “Parameters”](#parameters-3)
###### error
[Section titled “error”](#error)
`unknown`
##### Returns
[Section titled “Returns”](#returns-3)
`error is Error`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-7)
`Error.isError`
#### Call Signature
[Section titled “Call Signature”](#call-signature-3)
> `static` **isError**(`value`): `value is Error`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1037
Check if a value is an instance of Error
##### Parameters
[Section titled “Parameters”](#parameters-4)
###### value
[Section titled “value”](#value)
`unknown`
The value to check
##### Returns
[Section titled “Returns”](#returns-4)
`value is Error`
True if the value is an instance of Error, false otherwise
##### Inherited from
[Section titled “Inherited from”](#inherited-from-8)
`Error.isError`
***
### prepareStackTrace()
[Section titled “prepareStackTrace()”](#preparestacktrace)
> `static` **prepareStackTrace**(`err`, `stackTraces`): `any`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:55
#### Parameters
[Section titled “Parameters”](#parameters-5)
##### err
[Section titled “err”](#err)
`Error`
##### stackTraces
[Section titled “stackTraces”](#stacktraces)
`CallSite`\[]
#### Returns
[Section titled “Returns”](#returns-5)
`any`
#### See
[Section titled “See”](#see)
#### Inherited from
[Section titled “Inherited from”](#inherited-from-9)
`Error.prepareStackTrace`
# ElectedEngineUnconstructibleError
Defined in: packages/client/src/worker/attach-sync-client.ts:206
Attach WIRING failure (ADR-0049 D1/D5): the SharedWorker requires election (a router-only, handle-denied home) but the elected engine worker CANNOT be constructed — neither a `createEngineWorker` override was supplied NOR is the SharedWorker’s own script URL derivable (a plain scope that cannot report `self.location.href`, or a non-module entry). Capability absence FALLS BACK (idbfs); a wiring failure ERRORS — the capability was present and the configuration was wrong, and silently downgrading storage would hide the defect. `[pgxsinkit]`-prefixed like the repo’s other typed errors so a consumer can `instanceof`-branch it.
## Extends
[Section titled “Extends”](#extends)
* `Error`
## Constructors
[Section titled “Constructors”](#constructors)
### Constructor
[Section titled “Constructor”](#constructor)
> **new ElectedEngineUnconstructibleError**(`detail`): `ElectedEngineUnconstructibleError`
Defined in: packages/client/src/worker/attach-sync-client.ts:207
#### Parameters
[Section titled “Parameters”](#parameters)
##### detail
[Section titled “detail”](#detail)
`string`
#### Returns
[Section titled “Returns”](#returns)
`ElectedEngineUnconstructibleError`
#### Overrides
[Section titled “Overrides”](#overrides)
`Error.constructor`
## Properties
[Section titled “Properties”](#properties)
### cause?
[Section titled “cause?”](#cause)
> `optional` **cause?**: `unknown`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:24
The cause of the error.
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
`Error.cause`
***
### message
[Section titled “message”](#message)
> **message**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1075
#### Inherited from
[Section titled “Inherited from”](#inherited-from-1)
`Error.message`
***
### name
[Section titled “name”](#name)
> **name**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1074
#### Inherited from
[Section titled “Inherited from”](#inherited-from-2)
`Error.name`
***
### stack?
[Section titled “stack?”](#stack)
> `optional` **stack?**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1076
#### Inherited from
[Section titled “Inherited from”](#inherited-from-3)
`Error.stack`
***
### stackTraceLimit
[Section titled “stackTraceLimit”](#stacktracelimit)
> `static` **stackTraceLimit**: `number`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:67
The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`).
The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed.
If set to a non-number value, or set to a negative number, stack traces will not capture any frames.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-4)
`Error.stackTraceLimit`
## Methods
[Section titled “Methods”](#methods)
### captureStackTrace()
[Section titled “captureStackTrace()”](#capturestacktrace)
#### Call Signature
[Section titled “Call Signature”](#call-signature)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:51
Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called.
```js
const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack; // Similar to `new Error().stack`
```
The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`.
The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace.
The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance:
```js
function a() {
b();
}
function b() {
c();
}
function c() {
// Create an error without stack trace to avoid calculating the stack trace twice.
const { stackTraceLimit } = Error;
Error.stackTraceLimit = 0;
const error = new Error();
Error.stackTraceLimit = stackTraceLimit;
// Capture the stack trace above function b
Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
throw error;
}
a();
```
##### Parameters
[Section titled “Parameters”](#parameters-1)
###### targetObject
[Section titled “targetObject”](#targetobject)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt)
`Function`
##### Returns
[Section titled “Returns”](#returns-1)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-5)
`Error.captureStackTrace`
#### Call Signature
[Section titled “Call Signature”](#call-signature-1)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1042
Create .stack property on a target object
##### Parameters
[Section titled “Parameters”](#parameters-2)
###### targetObject
[Section titled “targetObject”](#targetobject-1)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt-1)
`Function`
##### Returns
[Section titled “Returns”](#returns-2)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-6)
`Error.captureStackTrace`
***
### isError()
[Section titled “isError()”](#iserror)
#### Call Signature
[Section titled “Call Signature”](#call-signature-2)
> `static` **isError**(`error`): `error is Error`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.esnext.error.d.ts:21
Indicates whether the argument provided is a built-in Error instance or not.
##### Parameters
[Section titled “Parameters”](#parameters-3)
###### error
[Section titled “error”](#error)
`unknown`
##### Returns
[Section titled “Returns”](#returns-3)
`error is Error`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-7)
`Error.isError`
#### Call Signature
[Section titled “Call Signature”](#call-signature-3)
> `static` **isError**(`value`): `value is Error`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1037
Check if a value is an instance of Error
##### Parameters
[Section titled “Parameters”](#parameters-4)
###### value
[Section titled “value”](#value)
`unknown`
The value to check
##### Returns
[Section titled “Returns”](#returns-4)
`value is Error`
True if the value is an instance of Error, false otherwise
##### Inherited from
[Section titled “Inherited from”](#inherited-from-8)
`Error.isError`
***
### prepareStackTrace()
[Section titled “prepareStackTrace()”](#preparestacktrace)
> `static` **prepareStackTrace**(`err`, `stackTraces`): `any`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:55
#### Parameters
[Section titled “Parameters”](#parameters-5)
##### err
[Section titled “err”](#err)
`Error`
##### stackTraces
[Section titled “stackTraces”](#stacktraces)
`CallSite`\[]
#### Returns
[Section titled “Returns”](#returns-5)
`any`
#### See
[Section titled “See”](#see)
#### Inherited from
[Section titled “Inherited from”](#inherited-from-9)
`Error.prepareStackTrace`
# EngineRelocatedError
Defined in: packages/client/src/worker/engine-control.ts:348
The exported, consumer-visible relocation error (ADR D10, invariant 5). Consumers branch on `code` + `outcome`, never on message prose — but the message NAMES the semantics so a developer hitting it in a log understands immediately. `[pgxsinkit]`-prefixed like the repo’s other errors.
## Extends
[Section titled “Extends”](#extends)
* `Error`
## Constructors
[Section titled “Constructors”](#constructors)
### Constructor
[Section titled “Constructor”](#constructor)
> **new EngineRelocatedError**(`outcome`): `EngineRelocatedError`
Defined in: packages/client/src/worker/engine-control.ts:352
#### Parameters
[Section titled “Parameters”](#parameters)
##### outcome
[Section titled “outcome”](#outcome)
[`EngineRelocatedOutcome`](/api/client/type-aliases/enginerelocatedoutcome/)
#### Returns
[Section titled “Returns”](#returns)
`EngineRelocatedError`
#### Overrides
[Section titled “Overrides”](#overrides)
`Error.constructor`
## Properties
[Section titled “Properties”](#properties)
### cause?
[Section titled “cause?”](#cause)
> `optional` **cause?**: `unknown`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:24
The cause of the error.
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
`Error.cause`
***
### code
[Section titled “code”](#code)
> `readonly` **code**: `"engine-relocated"` = `ENGINE_RELOCATED_CODE`
Defined in: packages/client/src/worker/engine-control.ts:349
***
### message
[Section titled “message”](#message)
> **message**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1075
#### Inherited from
[Section titled “Inherited from”](#inherited-from-1)
`Error.message`
***
### name
[Section titled “name”](#name)
> **name**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1074
#### Inherited from
[Section titled “Inherited from”](#inherited-from-2)
`Error.name`
***
### outcome
[Section titled “outcome”](#outcome-1)
> `readonly` **outcome**: [`EngineRelocatedOutcome`](/api/client/type-aliases/enginerelocatedoutcome/)
Defined in: packages/client/src/worker/engine-control.ts:350
***
### stack?
[Section titled “stack?”](#stack)
> `optional` **stack?**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1076
#### Inherited from
[Section titled “Inherited from”](#inherited-from-3)
`Error.stack`
***
### stackTraceLimit
[Section titled “stackTraceLimit”](#stacktracelimit)
> `static` **stackTraceLimit**: `number`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:67
The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`).
The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed.
If set to a non-number value, or set to a negative number, stack traces will not capture any frames.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-4)
`Error.stackTraceLimit`
## Methods
[Section titled “Methods”](#methods)
### captureStackTrace()
[Section titled “captureStackTrace()”](#capturestacktrace)
#### Call Signature
[Section titled “Call Signature”](#call-signature)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:51
Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called.
```js
const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack; // Similar to `new Error().stack`
```
The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`.
The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace.
The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance:
```js
function a() {
b();
}
function b() {
c();
}
function c() {
// Create an error without stack trace to avoid calculating the stack trace twice.
const { stackTraceLimit } = Error;
Error.stackTraceLimit = 0;
const error = new Error();
Error.stackTraceLimit = stackTraceLimit;
// Capture the stack trace above function b
Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
throw error;
}
a();
```
##### Parameters
[Section titled “Parameters”](#parameters-1)
###### targetObject
[Section titled “targetObject”](#targetobject)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt)
`Function`
##### Returns
[Section titled “Returns”](#returns-1)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-5)
`Error.captureStackTrace`
#### Call Signature
[Section titled “Call Signature”](#call-signature-1)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1042
Create .stack property on a target object
##### Parameters
[Section titled “Parameters”](#parameters-2)
###### targetObject
[Section titled “targetObject”](#targetobject-1)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt-1)
`Function`
##### Returns
[Section titled “Returns”](#returns-2)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-6)
`Error.captureStackTrace`
***
### isError()
[Section titled “isError()”](#iserror)
#### Call Signature
[Section titled “Call Signature”](#call-signature-2)
> `static` **isError**(`error`): `error is Error`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.esnext.error.d.ts:21
Indicates whether the argument provided is a built-in Error instance or not.
##### Parameters
[Section titled “Parameters”](#parameters-3)
###### error
[Section titled “error”](#error)
`unknown`
##### Returns
[Section titled “Returns”](#returns-3)
`error is Error`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-7)
`Error.isError`
#### Call Signature
[Section titled “Call Signature”](#call-signature-3)
> `static` **isError**(`value`): `value is Error`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1037
Check if a value is an instance of Error
##### Parameters
[Section titled “Parameters”](#parameters-4)
###### value
[Section titled “value”](#value)
`unknown`
The value to check
##### Returns
[Section titled “Returns”](#returns-4)
`value is Error`
True if the value is an instance of Error, false otherwise
##### Inherited from
[Section titled “Inherited from”](#inherited-from-8)
`Error.isError`
***
### prepareStackTrace()
[Section titled “prepareStackTrace()”](#preparestacktrace)
> `static` **prepareStackTrace**(`err`, `stackTraces`): `any`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:55
#### Parameters
[Section titled “Parameters”](#parameters-5)
##### err
[Section titled “err”](#err)
`Error`
##### stackTraces
[Section titled “stackTraces”](#stacktraces)
`CallSite`\[]
#### Returns
[Section titled “Returns”](#returns-5)
`any`
#### See
[Section titled “See”](#see)
#### Inherited from
[Section titled “Inherited from”](#inherited-from-9)
`Error.prepareStackTrace`
# EventPayloadInvalidError
Defined in: packages/client/src/event-lane.ts:228
The payload failed the Event stream’s registered zod schema. Validated at append so the Outbox stays well-formed.
## Extends
[Section titled “Extends”](#extends)
* `Error`
## Constructors
[Section titled “Constructors”](#constructors)
### Constructor
[Section titled “Constructor”](#constructor)
> **new EventPayloadInvalidError**(`stream`, `detail`): `EventPayloadInvalidError`
Defined in: packages/client/src/event-lane.ts:230
#### Parameters
[Section titled “Parameters”](#parameters)
##### stream
[Section titled “stream”](#stream)
`string`
##### detail
[Section titled “detail”](#detail)
`string`
#### Returns
[Section titled “Returns”](#returns)
`EventPayloadInvalidError`
#### Overrides
[Section titled “Overrides”](#overrides)
`Error.constructor`
## Properties
[Section titled “Properties”](#properties)
### cause?
[Section titled “cause?”](#cause)
> `optional` **cause?**: `unknown`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:24
The cause of the error.
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
`Error.cause`
***
### message
[Section titled “message”](#message)
> **message**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1075
#### Inherited from
[Section titled “Inherited from”](#inherited-from-1)
`Error.message`
***
### name
[Section titled “name”](#name)
> **name**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1074
#### Inherited from
[Section titled “Inherited from”](#inherited-from-2)
`Error.name`
***
### stack?
[Section titled “stack?”](#stack)
> `optional` **stack?**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1076
#### Inherited from
[Section titled “Inherited from”](#inherited-from-3)
`Error.stack`
***
### stream
[Section titled “stream”](#stream-1)
> `readonly` **stream**: `string`
Defined in: packages/client/src/event-lane.ts:229
***
### stackTraceLimit
[Section titled “stackTraceLimit”](#stacktracelimit)
> `static` **stackTraceLimit**: `number`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:67
The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`).
The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed.
If set to a non-number value, or set to a negative number, stack traces will not capture any frames.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-4)
`Error.stackTraceLimit`
## Methods
[Section titled “Methods”](#methods)
### captureStackTrace()
[Section titled “captureStackTrace()”](#capturestacktrace)
#### Call Signature
[Section titled “Call Signature”](#call-signature)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:51
Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called.
```js
const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack; // Similar to `new Error().stack`
```
The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`.
The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace.
The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance:
```js
function a() {
b();
}
function b() {
c();
}
function c() {
// Create an error without stack trace to avoid calculating the stack trace twice.
const { stackTraceLimit } = Error;
Error.stackTraceLimit = 0;
const error = new Error();
Error.stackTraceLimit = stackTraceLimit;
// Capture the stack trace above function b
Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
throw error;
}
a();
```
##### Parameters
[Section titled “Parameters”](#parameters-1)
###### targetObject
[Section titled “targetObject”](#targetobject)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt)
`Function`
##### Returns
[Section titled “Returns”](#returns-1)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-5)
`Error.captureStackTrace`
#### Call Signature
[Section titled “Call Signature”](#call-signature-1)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1042
Create .stack property on a target object
##### Parameters
[Section titled “Parameters”](#parameters-2)
###### targetObject
[Section titled “targetObject”](#targetobject-1)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt-1)
`Function`
##### Returns
[Section titled “Returns”](#returns-2)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-6)
`Error.captureStackTrace`
***
### isError()
[Section titled “isError()”](#iserror)
#### Call Signature
[Section titled “Call Signature”](#call-signature-2)
> `static` **isError**(`error`): `error is Error`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.esnext.error.d.ts:21
Indicates whether the argument provided is a built-in Error instance or not.
##### Parameters
[Section titled “Parameters”](#parameters-3)
###### error
[Section titled “error”](#error)
`unknown`
##### Returns
[Section titled “Returns”](#returns-3)
`error is Error`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-7)
`Error.isError`
#### Call Signature
[Section titled “Call Signature”](#call-signature-3)
> `static` **isError**(`value`): `value is Error`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1037
Check if a value is an instance of Error
##### Parameters
[Section titled “Parameters”](#parameters-4)
###### value
[Section titled “value”](#value)
`unknown`
The value to check
##### Returns
[Section titled “Returns”](#returns-4)
`value is Error`
True if the value is an instance of Error, false otherwise
##### Inherited from
[Section titled “Inherited from”](#inherited-from-8)
`Error.isError`
***
### prepareStackTrace()
[Section titled “prepareStackTrace()”](#preparestacktrace)
> `static` **prepareStackTrace**(`err`, `stackTraces`): `any`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:55
#### Parameters
[Section titled “Parameters”](#parameters-5)
##### err
[Section titled “err”](#err)
`Error`
##### stackTraces
[Section titled “stackTraces”](#stacktraces)
`CallSite`\[]
#### Returns
[Section titled “Returns”](#returns-5)
`any`
#### See
[Section titled “See”](#see)
#### Inherited from
[Section titled “Inherited from”](#inherited-from-9)
`Error.prepareStackTrace`
# EventPayloadTooLargeError
Defined in: packages/client/src/event-lane.ts:238
The serialized payload exceeded MAX\_EVENT\_PAYLOAD\_BYTES; the server would reject it per-event anyway.
## Extends
[Section titled “Extends”](#extends)
* `Error`
## Constructors
[Section titled “Constructors”](#constructors)
### Constructor
[Section titled “Constructor”](#constructor)
> **new EventPayloadTooLargeError**(`stream`, `byteLength`): `EventPayloadTooLargeError`
Defined in: packages/client/src/event-lane.ts:241
#### Parameters
[Section titled “Parameters”](#parameters)
##### stream
[Section titled “stream”](#stream)
`string`
##### byteLength
[Section titled “byteLength”](#bytelength)
`number`
#### Returns
[Section titled “Returns”](#returns)
`EventPayloadTooLargeError`
#### Overrides
[Section titled “Overrides”](#overrides)
`Error.constructor`
## Properties
[Section titled “Properties”](#properties)
### byteLength
[Section titled “byteLength”](#bytelength-1)
> `readonly` **byteLength**: `number`
Defined in: packages/client/src/event-lane.ts:240
***
### cause?
[Section titled “cause?”](#cause)
> `optional` **cause?**: `unknown`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:24
The cause of the error.
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
`Error.cause`
***
### message
[Section titled “message”](#message)
> **message**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1075
#### Inherited from
[Section titled “Inherited from”](#inherited-from-1)
`Error.message`
***
### name
[Section titled “name”](#name)
> **name**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1074
#### Inherited from
[Section titled “Inherited from”](#inherited-from-2)
`Error.name`
***
### stack?
[Section titled “stack?”](#stack)
> `optional` **stack?**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1076
#### Inherited from
[Section titled “Inherited from”](#inherited-from-3)
`Error.stack`
***
### stream
[Section titled “stream”](#stream-1)
> `readonly` **stream**: `string`
Defined in: packages/client/src/event-lane.ts:239
***
### stackTraceLimit
[Section titled “stackTraceLimit”](#stacktracelimit)
> `static` **stackTraceLimit**: `number`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:67
The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`).
The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed.
If set to a non-number value, or set to a negative number, stack traces will not capture any frames.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-4)
`Error.stackTraceLimit`
## Methods
[Section titled “Methods”](#methods)
### captureStackTrace()
[Section titled “captureStackTrace()”](#capturestacktrace)
#### Call Signature
[Section titled “Call Signature”](#call-signature)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:51
Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called.
```js
const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack; // Similar to `new Error().stack`
```
The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`.
The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace.
The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance:
```js
function a() {
b();
}
function b() {
c();
}
function c() {
// Create an error without stack trace to avoid calculating the stack trace twice.
const { stackTraceLimit } = Error;
Error.stackTraceLimit = 0;
const error = new Error();
Error.stackTraceLimit = stackTraceLimit;
// Capture the stack trace above function b
Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
throw error;
}
a();
```
##### Parameters
[Section titled “Parameters”](#parameters-1)
###### targetObject
[Section titled “targetObject”](#targetobject)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt)
`Function`
##### Returns
[Section titled “Returns”](#returns-1)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-5)
`Error.captureStackTrace`
#### Call Signature
[Section titled “Call Signature”](#call-signature-1)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1042
Create .stack property on a target object
##### Parameters
[Section titled “Parameters”](#parameters-2)
###### targetObject
[Section titled “targetObject”](#targetobject-1)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt-1)
`Function`
##### Returns
[Section titled “Returns”](#returns-2)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-6)
`Error.captureStackTrace`
***
### isError()
[Section titled “isError()”](#iserror)
#### Call Signature
[Section titled “Call Signature”](#call-signature-2)
> `static` **isError**(`error`): `error is Error`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.esnext.error.d.ts:21
Indicates whether the argument provided is a built-in Error instance or not.
##### Parameters
[Section titled “Parameters”](#parameters-3)
###### error
[Section titled “error”](#error)
`unknown`
##### Returns
[Section titled “Returns”](#returns-3)
`error is Error`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-7)
`Error.isError`
#### Call Signature
[Section titled “Call Signature”](#call-signature-3)
> `static` **isError**(`value`): `value is Error`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1037
Check if a value is an instance of Error
##### Parameters
[Section titled “Parameters”](#parameters-4)
###### value
[Section titled “value”](#value)
`unknown`
The value to check
##### Returns
[Section titled “Returns”](#returns-4)
`value is Error`
True if the value is an instance of Error, false otherwise
##### Inherited from
[Section titled “Inherited from”](#inherited-from-8)
`Error.isError`
***
### prepareStackTrace()
[Section titled “prepareStackTrace()”](#preparestacktrace)
> `static` **prepareStackTrace**(`err`, `stackTraces`): `any`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:55
#### Parameters
[Section titled “Parameters”](#parameters-5)
##### err
[Section titled “err”](#err)
`Error`
##### stackTraces
[Section titled “stackTraces”](#stacktraces)
`CallSite`\[]
#### Returns
[Section titled “Returns”](#returns-5)
`any`
#### See
[Section titled “See”](#see)
#### Inherited from
[Section titled “Inherited from”](#inherited-from-9)
`Error.prepareStackTrace`
# EventStreamsNotRegisteredError
Defined in: packages/client/src/event-lane.ts:204
The registry registered no Event stream at all, so there is nothing an append could target.
## Extends
[Section titled “Extends”](#extends)
* `Error`
## Constructors
[Section titled “Constructors”](#constructors)
### Constructor
[Section titled “Constructor”](#constructor)
> **new EventStreamsNotRegisteredError**(): `EventStreamsNotRegisteredError`
Defined in: packages/client/src/event-lane.ts:205
#### Returns
[Section titled “Returns”](#returns)
`EventStreamsNotRegisteredError`
#### Overrides
[Section titled “Overrides”](#overrides)
`Error.constructor`
## Properties
[Section titled “Properties”](#properties)
### cause?
[Section titled “cause?”](#cause)
> `optional` **cause?**: `unknown`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:24
The cause of the error.
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
`Error.cause`
***
### message
[Section titled “message”](#message)
> **message**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1075
#### Inherited from
[Section titled “Inherited from”](#inherited-from-1)
`Error.message`
***
### name
[Section titled “name”](#name)
> **name**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1074
#### Inherited from
[Section titled “Inherited from”](#inherited-from-2)
`Error.name`
***
### stack?
[Section titled “stack?”](#stack)
> `optional` **stack?**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1076
#### Inherited from
[Section titled “Inherited from”](#inherited-from-3)
`Error.stack`
***
### stackTraceLimit
[Section titled “stackTraceLimit”](#stacktracelimit)
> `static` **stackTraceLimit**: `number`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:67
The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`).
The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed.
If set to a non-number value, or set to a negative number, stack traces will not capture any frames.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-4)
`Error.stackTraceLimit`
## Methods
[Section titled “Methods”](#methods)
### captureStackTrace()
[Section titled “captureStackTrace()”](#capturestacktrace)
#### Call Signature
[Section titled “Call Signature”](#call-signature)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:51
Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called.
```js
const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack; // Similar to `new Error().stack`
```
The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`.
The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace.
The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance:
```js
function a() {
b();
}
function b() {
c();
}
function c() {
// Create an error without stack trace to avoid calculating the stack trace twice.
const { stackTraceLimit } = Error;
Error.stackTraceLimit = 0;
const error = new Error();
Error.stackTraceLimit = stackTraceLimit;
// Capture the stack trace above function b
Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
throw error;
}
a();
```
##### Parameters
[Section titled “Parameters”](#parameters)
###### targetObject
[Section titled “targetObject”](#targetobject)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt)
`Function`
##### Returns
[Section titled “Returns”](#returns-1)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-5)
`Error.captureStackTrace`
#### Call Signature
[Section titled “Call Signature”](#call-signature-1)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1042
Create .stack property on a target object
##### Parameters
[Section titled “Parameters”](#parameters-1)
###### targetObject
[Section titled “targetObject”](#targetobject-1)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt-1)
`Function`
##### Returns
[Section titled “Returns”](#returns-2)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-6)
`Error.captureStackTrace`
***
### isError()
[Section titled “isError()”](#iserror)
#### Call Signature
[Section titled “Call Signature”](#call-signature-2)
> `static` **isError**(`error`): `error is Error`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.esnext.error.d.ts:21
Indicates whether the argument provided is a built-in Error instance or not.
##### Parameters
[Section titled “Parameters”](#parameters-2)
###### error
[Section titled “error”](#error)
`unknown`
##### Returns
[Section titled “Returns”](#returns-3)
`error is Error`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-7)
`Error.isError`
#### Call Signature
[Section titled “Call Signature”](#call-signature-3)
> `static` **isError**(`value`): `value is Error`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1037
Check if a value is an instance of Error
##### Parameters
[Section titled “Parameters”](#parameters-3)
###### value
[Section titled “value”](#value)
`unknown`
The value to check
##### Returns
[Section titled “Returns”](#returns-4)
`value is Error`
True if the value is an instance of Error, false otherwise
##### Inherited from
[Section titled “Inherited from”](#inherited-from-8)
`Error.isError`
***
### prepareStackTrace()
[Section titled “prepareStackTrace()”](#preparestacktrace)
> `static` **prepareStackTrace**(`err`, `stackTraces`): `any`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:55
#### Parameters
[Section titled “Parameters”](#parameters-4)
##### err
[Section titled “err”](#err)
`Error`
##### stackTraces
[Section titled “stackTraces”](#stacktraces)
`CallSite`\[]
#### Returns
[Section titled “Returns”](#returns-5)
`any`
#### See
[Section titled “See”](#see)
#### Inherited from
[Section titled “Inherited from”](#inherited-from-9)
`Error.prepareStackTrace`
# ExecutionLimitMismatchError
Defined in: packages/client/src/worker/engine-control.ts:260
Thrown at attach when a tab’s execution-limit value disagrees with the engine’s construction value (ADR D5): every tab must carry the SAME config. Named + `[pgxsinkit]`-prefixed like the repo’s other protocol errors so a consumer can `instanceof`-branch it.
## Extends
[Section titled “Extends”](#extends)
* `Error`
## Constructors
[Section titled “Constructors”](#constructors)
### Constructor
[Section titled “Constructor”](#constructor)
> **new ExecutionLimitMismatchError**(`engineValue`, `attachValue`): `ExecutionLimitMismatchError`
Defined in: packages/client/src/worker/engine-control.ts:264
#### Parameters
[Section titled “Parameters”](#parameters)
##### engineValue
[Section titled “engineValue”](#enginevalue)
`number` | `undefined`
##### attachValue
[Section titled “attachValue”](#attachvalue)
`number` | `undefined`
#### Returns
[Section titled “Returns”](#returns)
`ExecutionLimitMismatchError`
#### Overrides
[Section titled “Overrides”](#overrides)
`Error.constructor`
## Properties
[Section titled “Properties”](#properties)
### attachValue
[Section titled “attachValue”](#attachvalue-1)
> `readonly` **attachValue**: `number` | `undefined`
Defined in: packages/client/src/worker/engine-control.ts:262
***
### cause?
[Section titled “cause?”](#cause)
> `optional` **cause?**: `unknown`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:24
The cause of the error.
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
`Error.cause`
***
### engineValue
[Section titled “engineValue”](#enginevalue-1)
> `readonly` **engineValue**: `number` | `undefined`
Defined in: packages/client/src/worker/engine-control.ts:261
***
### message
[Section titled “message”](#message)
> **message**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1075
#### Inherited from
[Section titled “Inherited from”](#inherited-from-1)
`Error.message`
***
### name
[Section titled “name”](#name)
> **name**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1074
#### Inherited from
[Section titled “Inherited from”](#inherited-from-2)
`Error.name`
***
### stack?
[Section titled “stack?”](#stack)
> `optional` **stack?**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1076
#### Inherited from
[Section titled “Inherited from”](#inherited-from-3)
`Error.stack`
***
### stackTraceLimit
[Section titled “stackTraceLimit”](#stacktracelimit)
> `static` **stackTraceLimit**: `number`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:67
The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`).
The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed.
If set to a non-number value, or set to a negative number, stack traces will not capture any frames.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-4)
`Error.stackTraceLimit`
## Methods
[Section titled “Methods”](#methods)
### captureStackTrace()
[Section titled “captureStackTrace()”](#capturestacktrace)
#### Call Signature
[Section titled “Call Signature”](#call-signature)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:51
Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called.
```js
const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack; // Similar to `new Error().stack`
```
The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`.
The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace.
The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance:
```js
function a() {
b();
}
function b() {
c();
}
function c() {
// Create an error without stack trace to avoid calculating the stack trace twice.
const { stackTraceLimit } = Error;
Error.stackTraceLimit = 0;
const error = new Error();
Error.stackTraceLimit = stackTraceLimit;
// Capture the stack trace above function b
Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
throw error;
}
a();
```
##### Parameters
[Section titled “Parameters”](#parameters-1)
###### targetObject
[Section titled “targetObject”](#targetobject)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt)
`Function`
##### Returns
[Section titled “Returns”](#returns-1)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-5)
`Error.captureStackTrace`
#### Call Signature
[Section titled “Call Signature”](#call-signature-1)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1042
Create .stack property on a target object
##### Parameters
[Section titled “Parameters”](#parameters-2)
###### targetObject
[Section titled “targetObject”](#targetobject-1)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt-1)
`Function`
##### Returns
[Section titled “Returns”](#returns-2)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-6)
`Error.captureStackTrace`
***
### isError()
[Section titled “isError()”](#iserror)
#### Call Signature
[Section titled “Call Signature”](#call-signature-2)
> `static` **isError**(`error`): `error is Error`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.esnext.error.d.ts:21
Indicates whether the argument provided is a built-in Error instance or not.
##### Parameters
[Section titled “Parameters”](#parameters-3)
###### error
[Section titled “error”](#error)
`unknown`
##### Returns
[Section titled “Returns”](#returns-3)
`error is Error`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-7)
`Error.isError`
#### Call Signature
[Section titled “Call Signature”](#call-signature-3)
> `static` **isError**(`value`): `value is Error`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1037
Check if a value is an instance of Error
##### Parameters
[Section titled “Parameters”](#parameters-4)
###### value
[Section titled “value”](#value)
`unknown`
The value to check
##### Returns
[Section titled “Returns”](#returns-4)
`value is Error`
True if the value is an instance of Error, false otherwise
##### Inherited from
[Section titled “Inherited from”](#inherited-from-8)
`Error.isError`
***
### prepareStackTrace()
[Section titled “prepareStackTrace()”](#preparestacktrace)
> `static` **prepareStackTrace**(`err`, `stackTraces`): `any`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:55
#### Parameters
[Section titled “Parameters”](#parameters-5)
##### err
[Section titled “err”](#err)
`Error`
##### stackTraces
[Section titled “stackTraces”](#stacktraces)
`CallSite`\[]
#### Returns
[Section titled “Returns”](#returns-5)
`any`
#### See
[Section titled “See”](#see)
#### Inherited from
[Section titled “Inherited from”](#inherited-from-9)
`Error.prepareStackTrace`
# InvalidStorePathError
Defined in: packages/client/src/store-path.ts:29
Thrown when a `storePath` is not a plain name (ADR-0036 decision 1): it carries a URL scheme (`://`) or is empty/whitespace-only. A distinct type — not a bare `Error` — so a caller can `instanceof`-branch the old dataDir-URL contract from a genuine store failure. The message names the plain-path contract and shows the corrected form, without echoing a resolvable URL a consumer might imitate.
## Extends
[Section titled “Extends”](#extends)
* `Error`
## Constructors
[Section titled “Constructors”](#constructors)
### Constructor
[Section titled “Constructor”](#constructor)
> **new InvalidStorePathError**(`message`): `InvalidStorePathError`
Defined in: packages/client/src/store-path.ts:30
#### Parameters
[Section titled “Parameters”](#parameters)
##### message
[Section titled “message”](#message)
`string`
#### Returns
[Section titled “Returns”](#returns)
`InvalidStorePathError`
#### Overrides
[Section titled “Overrides”](#overrides)
`Error.constructor`
## Properties
[Section titled “Properties”](#properties)
### cause?
[Section titled “cause?”](#cause)
> `optional` **cause?**: `unknown`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:24
The cause of the error.
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
`Error.cause`
***
### message
[Section titled “message”](#message-1)
> **message**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1075
#### Inherited from
[Section titled “Inherited from”](#inherited-from-1)
`Error.message`
***
### name
[Section titled “name”](#name)
> **name**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1074
#### Inherited from
[Section titled “Inherited from”](#inherited-from-2)
`Error.name`
***
### stack?
[Section titled “stack?”](#stack)
> `optional` **stack?**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1076
#### Inherited from
[Section titled “Inherited from”](#inherited-from-3)
`Error.stack`
***
### stackTraceLimit
[Section titled “stackTraceLimit”](#stacktracelimit)
> `static` **stackTraceLimit**: `number`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:67
The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`).
The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed.
If set to a non-number value, or set to a negative number, stack traces will not capture any frames.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-4)
`Error.stackTraceLimit`
## Methods
[Section titled “Methods”](#methods)
### captureStackTrace()
[Section titled “captureStackTrace()”](#capturestacktrace)
#### Call Signature
[Section titled “Call Signature”](#call-signature)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:51
Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called.
```js
const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack; // Similar to `new Error().stack`
```
The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`.
The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace.
The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance:
```js
function a() {
b();
}
function b() {
c();
}
function c() {
// Create an error without stack trace to avoid calculating the stack trace twice.
const { stackTraceLimit } = Error;
Error.stackTraceLimit = 0;
const error = new Error();
Error.stackTraceLimit = stackTraceLimit;
// Capture the stack trace above function b
Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
throw error;
}
a();
```
##### Parameters
[Section titled “Parameters”](#parameters-1)
###### targetObject
[Section titled “targetObject”](#targetobject)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt)
`Function`
##### Returns
[Section titled “Returns”](#returns-1)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-5)
`Error.captureStackTrace`
#### Call Signature
[Section titled “Call Signature”](#call-signature-1)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1042
Create .stack property on a target object
##### Parameters
[Section titled “Parameters”](#parameters-2)
###### targetObject
[Section titled “targetObject”](#targetobject-1)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt-1)
`Function`
##### Returns
[Section titled “Returns”](#returns-2)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-6)
`Error.captureStackTrace`
***
### isError()
[Section titled “isError()”](#iserror)
#### Call Signature
[Section titled “Call Signature”](#call-signature-2)
> `static` **isError**(`error`): `error is Error`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.esnext.error.d.ts:21
Indicates whether the argument provided is a built-in Error instance or not.
##### Parameters
[Section titled “Parameters”](#parameters-3)
###### error
[Section titled “error”](#error)
`unknown`
##### Returns
[Section titled “Returns”](#returns-3)
`error is Error`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-7)
`Error.isError`
#### Call Signature
[Section titled “Call Signature”](#call-signature-3)
> `static` **isError**(`value`): `value is Error`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1037
Check if a value is an instance of Error
##### Parameters
[Section titled “Parameters”](#parameters-4)
###### value
[Section titled “value”](#value)
`unknown`
The value to check
##### Returns
[Section titled “Returns”](#returns-4)
`value is Error`
True if the value is an instance of Error, false otherwise
##### Inherited from
[Section titled “Inherited from”](#inherited-from-8)
`Error.isError`
***
### prepareStackTrace()
[Section titled “prepareStackTrace()”](#preparestacktrace)
> `static` **prepareStackTrace**(`err`, `stackTraces`): `any`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:55
#### Parameters
[Section titled “Parameters”](#parameters-5)
##### err
[Section titled “err”](#err)
`Error`
##### stackTraces
[Section titled “stackTraces”](#stacktraces)
`CallSite`\[]
#### Returns
[Section titled “Returns”](#returns-5)
`any`
#### See
[Section titled “See”](#see)
#### Inherited from
[Section titled “Inherited from”](#inherited-from-9)
`Error.prepareStackTrace`
# LazyRelationNotActivatedError
Defined in: packages/client/src/lazy-guard.ts:162
Thrown by the backstop ([assertLazyRefsActivated](/api/client/functions/assertlazyrefsactivated/)) when a referenced lazy relation could not be activated.
## Extends
[Section titled “Extends”](#extends)
* `Error`
## Constructors
[Section titled “Constructors”](#constructors)
### Constructor
[Section titled “Constructor”](#constructor)
> **new LazyRelationNotActivatedError**(`relations`): `LazyRelationNotActivatedError`
Defined in: packages/client/src/lazy-guard.ts:166
#### Parameters
[Section titled “Parameters”](#parameters)
##### relations
[Section titled “relations”](#relations)
readonly `string`\[]
#### Returns
[Section titled “Returns”](#returns)
`LazyRelationNotActivatedError`
#### Overrides
[Section titled “Overrides”](#overrides)
`Error.constructor`
## Properties
[Section titled “Properties”](#properties)
### cause?
[Section titled “cause?”](#cause)
> `optional` **cause?**: `unknown`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:24
The cause of the error.
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
`Error.cause`
***
### message
[Section titled “message”](#message)
> **message**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1075
#### Inherited from
[Section titled “Inherited from”](#inherited-from-1)
`Error.message`
***
### name
[Section titled “name”](#name)
> **name**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1074
#### Inherited from
[Section titled “Inherited from”](#inherited-from-2)
`Error.name`
***
### relations
[Section titled “relations”](#relations-1)
> `readonly` **relations**: readonly `string`\[]
Defined in: packages/client/src/lazy-guard.ts:164
The lazy registry keys that were referenced but could not be activated.
***
### stack?
[Section titled “stack?”](#stack)
> `optional` **stack?**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1076
#### Inherited from
[Section titled “Inherited from”](#inherited-from-3)
`Error.stack`
***
### stackTraceLimit
[Section titled “stackTraceLimit”](#stacktracelimit)
> `static` **stackTraceLimit**: `number`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:67
The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`).
The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed.
If set to a non-number value, or set to a negative number, stack traces will not capture any frames.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-4)
`Error.stackTraceLimit`
## Methods
[Section titled “Methods”](#methods)
### captureStackTrace()
[Section titled “captureStackTrace()”](#capturestacktrace)
#### Call Signature
[Section titled “Call Signature”](#call-signature)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:51
Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called.
```js
const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack; // Similar to `new Error().stack`
```
The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`.
The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace.
The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance:
```js
function a() {
b();
}
function b() {
c();
}
function c() {
// Create an error without stack trace to avoid calculating the stack trace twice.
const { stackTraceLimit } = Error;
Error.stackTraceLimit = 0;
const error = new Error();
Error.stackTraceLimit = stackTraceLimit;
// Capture the stack trace above function b
Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
throw error;
}
a();
```
##### Parameters
[Section titled “Parameters”](#parameters-1)
###### targetObject
[Section titled “targetObject”](#targetobject)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt)
`Function`
##### Returns
[Section titled “Returns”](#returns-1)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-5)
`Error.captureStackTrace`
#### Call Signature
[Section titled “Call Signature”](#call-signature-1)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1042
Create .stack property on a target object
##### Parameters
[Section titled “Parameters”](#parameters-2)
###### targetObject
[Section titled “targetObject”](#targetobject-1)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt-1)
`Function`
##### Returns
[Section titled “Returns”](#returns-2)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-6)
`Error.captureStackTrace`
***
### isError()
[Section titled “isError()”](#iserror)
#### Call Signature
[Section titled “Call Signature”](#call-signature-2)
> `static` **isError**(`error`): `error is Error`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.esnext.error.d.ts:21
Indicates whether the argument provided is a built-in Error instance or not.
##### Parameters
[Section titled “Parameters”](#parameters-3)
###### error
[Section titled “error”](#error)
`unknown`
##### Returns
[Section titled “Returns”](#returns-3)
`error is Error`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-7)
`Error.isError`
#### Call Signature
[Section titled “Call Signature”](#call-signature-3)
> `static` **isError**(`value`): `value is Error`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1037
Check if a value is an instance of Error
##### Parameters
[Section titled “Parameters”](#parameters-4)
###### value
[Section titled “value”](#value)
`unknown`
The value to check
##### Returns
[Section titled “Returns”](#returns-4)
`value is Error`
True if the value is an instance of Error, false otherwise
##### Inherited from
[Section titled “Inherited from”](#inherited-from-8)
`Error.isError`
***
### prepareStackTrace()
[Section titled “prepareStackTrace()”](#preparestacktrace)
> `static` **prepareStackTrace**(`err`, `stackTraces`): `any`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:55
#### Parameters
[Section titled “Parameters”](#parameters-5)
##### err
[Section titled “err”](#err)
`Error`
##### stackTraces
[Section titled “stackTraces”](#stacktraces)
`CallSite`\[]
#### Returns
[Section titled “Returns”](#returns-5)
`any`
#### See
[Section titled “See”](#see)
#### Inherited from
[Section titled “Inherited from”](#inherited-from-9)
`Error.prepareStackTrace`
# LifecycleBusyError
Defined in: packages/client/src/lifecycle-slot.ts:15
Thrown when a lifecycle operation is attempted while another already holds the slot (ADR-0035). Carries the running operation’s `label` so a caller (or a retry policy) can report exactly what it collided with. A distinct error type — not a bare `Error` — so callers can `instanceof`-branch a busy collision from a genuine operation failure.
## Extends
[Section titled “Extends”](#extends)
* `Error`
## Constructors
[Section titled “Constructors”](#constructors)
### Constructor
[Section titled “Constructor”](#constructor)
> **new LifecycleBusyError**(`attemptedLabel`, `runningLabel`): `LifecycleBusyError`
Defined in: packages/client/src/lifecycle-slot.ts:21
#### Parameters
[Section titled “Parameters”](#parameters)
##### attemptedLabel
[Section titled “attemptedLabel”](#attemptedlabel)
`string`
##### runningLabel
[Section titled “runningLabel”](#runninglabel)
`string`
#### Returns
[Section titled “Returns”](#returns)
`LifecycleBusyError`
#### Overrides
[Section titled “Overrides”](#overrides)
`Error.constructor`
## Properties
[Section titled “Properties”](#properties)
### attemptedLabel
[Section titled “attemptedLabel”](#attemptedlabel-1)
> `readonly` **attemptedLabel**: `string`
Defined in: packages/client/src/lifecycle-slot.ts:19
The label of the operation that was refused.
***
### cause?
[Section titled “cause?”](#cause)
> `optional` **cause?**: `unknown`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:24
The cause of the error.
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
`Error.cause`
***
### message
[Section titled “message”](#message)
> **message**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1075
#### Inherited from
[Section titled “Inherited from”](#inherited-from-1)
`Error.message`
***
### name
[Section titled “name”](#name)
> **name**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1074
#### Inherited from
[Section titled “Inherited from”](#inherited-from-2)
`Error.name`
***
### runningLabel
[Section titled “runningLabel”](#runninglabel-1)
> `readonly` **runningLabel**: `string`
Defined in: packages/client/src/lifecycle-slot.ts:17
The label of the lifecycle operation currently holding the slot.
***
### stack?
[Section titled “stack?”](#stack)
> `optional` **stack?**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1076
#### Inherited from
[Section titled “Inherited from”](#inherited-from-3)
`Error.stack`
***
### stackTraceLimit
[Section titled “stackTraceLimit”](#stacktracelimit)
> `static` **stackTraceLimit**: `number`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:67
The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`).
The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed.
If set to a non-number value, or set to a negative number, stack traces will not capture any frames.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-4)
`Error.stackTraceLimit`
## Methods
[Section titled “Methods”](#methods)
### captureStackTrace()
[Section titled “captureStackTrace()”](#capturestacktrace)
#### Call Signature
[Section titled “Call Signature”](#call-signature)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:51
Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called.
```js
const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack; // Similar to `new Error().stack`
```
The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`.
The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace.
The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance:
```js
function a() {
b();
}
function b() {
c();
}
function c() {
// Create an error without stack trace to avoid calculating the stack trace twice.
const { stackTraceLimit } = Error;
Error.stackTraceLimit = 0;
const error = new Error();
Error.stackTraceLimit = stackTraceLimit;
// Capture the stack trace above function b
Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
throw error;
}
a();
```
##### Parameters
[Section titled “Parameters”](#parameters-1)
###### targetObject
[Section titled “targetObject”](#targetobject)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt)
`Function`
##### Returns
[Section titled “Returns”](#returns-1)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-5)
`Error.captureStackTrace`
#### Call Signature
[Section titled “Call Signature”](#call-signature-1)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1042
Create .stack property on a target object
##### Parameters
[Section titled “Parameters”](#parameters-2)
###### targetObject
[Section titled “targetObject”](#targetobject-1)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt-1)
`Function`
##### Returns
[Section titled “Returns”](#returns-2)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-6)
`Error.captureStackTrace`
***
### isError()
[Section titled “isError()”](#iserror)
#### Call Signature
[Section titled “Call Signature”](#call-signature-2)
> `static` **isError**(`error`): `error is Error`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.esnext.error.d.ts:21
Indicates whether the argument provided is a built-in Error instance or not.
##### Parameters
[Section titled “Parameters”](#parameters-3)
###### error
[Section titled “error”](#error)
`unknown`
##### Returns
[Section titled “Returns”](#returns-3)
`error is Error`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-7)
`Error.isError`
#### Call Signature
[Section titled “Call Signature”](#call-signature-3)
> `static` **isError**(`value`): `value is Error`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1037
Check if a value is an instance of Error
##### Parameters
[Section titled “Parameters”](#parameters-4)
###### value
[Section titled “value”](#value)
`unknown`
The value to check
##### Returns
[Section titled “Returns”](#returns-4)
`value is Error`
True if the value is an instance of Error, false otherwise
##### Inherited from
[Section titled “Inherited from”](#inherited-from-8)
`Error.isError`
***
### prepareStackTrace()
[Section titled “prepareStackTrace()”](#preparestacktrace)
> `static` **prepareStackTrace**(`err`, `stackTraces`): `any`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:55
#### Parameters
[Section titled “Parameters”](#parameters-5)
##### err
[Section titled “err”](#err)
`Error`
##### stackTraces
[Section titled “stackTraces”](#stacktraces)
`CallSite`\[]
#### Returns
[Section titled “Returns”](#returns-5)
`any`
#### See
[Section titled “See”](#see)
#### Inherited from
[Section titled “Inherited from”](#inherited-from-9)
`Error.prepareStackTrace`
# LiveRowsMaterializer
Defined in: packages/client/src/worker/live-diff.ts:104
The tab-side fold of live diffs into an ordered row array with STABLE object identity (§4). The initial snapshot seeds the cache; each diff rebuilds the array from `diff.order`, reusing the cached object for any key not in `added`/`changed` (so an unchanged row keeps `===` and a memoized React row skips its re-render), and installing a fresh object for added/changed keys. Order is exactly `diff.order` — the query’s ORDER BY as delivered by the worker.
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRow
[Section titled “TRow”](#trow)
`TRow` *extends* `Record`<`string`, `unknown`> = `Record`<`string`, `unknown`>
## Constructors
[Section titled “Constructors”](#constructors)
### Constructor
[Section titled “Constructor”](#constructor)
> **new LiveRowsMaterializer**<`TRow`>(`pkColumns`): `LiveRowsMaterializer`<`TRow`>
Defined in: packages/client/src/worker/live-diff.ts:109
#### Parameters
[Section titled “Parameters”](#parameters)
##### pkColumns
[Section titled “pkColumns”](#pkcolumns)
readonly `string`\[] | `undefined`
#### Returns
[Section titled “Returns”](#returns)
`LiveRowsMaterializer`<`TRow`>
## Methods
[Section titled “Methods”](#methods)
### apply()
[Section titled “apply()”](#apply)
> **apply**(`diff`): `TRow`\[]
Defined in: packages/client/src/worker/live-diff.ts:126
Apply a diff; returns the new ordered array (a fresh array, but unchanged rows keep their object identity).
#### Parameters
[Section titled “Parameters”](#parameters-1)
##### diff
[Section titled “diff”](#diff)
`Omit`<[`LiveDiffPayload`](/api/client/interfaces/livediffpayload/), `"queryId"`>
#### Returns
[Section titled “Returns”](#returns-1)
`TRow`\[]
***
### current()
[Section titled “current()”](#current)
> **current**(): `TRow`\[]
Defined in: packages/client/src/worker/live-diff.ts:137
The current ordered rows (identity-stable across `apply` calls for unchanged rows).
#### Returns
[Section titled “Returns”](#returns-2)
`TRow`\[]
***
### seed()
[Section titled “seed()”](#seed)
> **seed**(`initialRows`): `TRow`\[]
Defined in: packages/client/src/worker/live-diff.ts:114
Seed from the initial snapshot; returns the initial ordered rows (each a distinct cached object).
#### Parameters
[Section titled “Parameters”](#parameters-2)
##### initialRows
[Section titled “initialRows”](#initialrows)
readonly `TRow`\[]
#### Returns
[Section titled “Returns”](#returns-3)
`TRow`\[]
# NonPersistentStoreError
Defined in: packages/client/src/store-path.ts:43
Thrown when a caller-owned PGlite handed to [CreateSyncClientOptions.pgliteInstance](/api/client/interfaces/createsyncclientoptions/#pgliteinstance) / [CreateSyncClientOptions.precreatedPglite](/api/client/interfaces/createsyncclientoptions/#precreatedpglite) is PROVABLY non-persistent (ADR-0036 decision 4): its `dataDir` is `undefined` (PGlite’s in-memory default — the `new PGlite()` a copy-paste reaches) or begins `memory://`. Names both the why (durability semantics assume a persisted store) and the two exits, so a consumer is never left guessing which store to hand us instead.
## Extends
[Section titled “Extends”](#extends)
* `Error`
## Constructors
[Section titled “Constructors”](#constructors)
### Constructor
[Section titled “Constructor”](#constructor)
> **new NonPersistentStoreError**(`observed`): `NonPersistentStoreError`
Defined in: packages/client/src/store-path.ts:44
#### Parameters
[Section titled “Parameters”](#parameters)
##### observed
[Section titled “observed”](#observed)
`"in-memory-default"` | `"memory-scheme"`
#### Returns
[Section titled “Returns”](#returns)
`NonPersistentStoreError`
#### Overrides
[Section titled “Overrides”](#overrides)
`Error.constructor`
## Properties
[Section titled “Properties”](#properties)
### cause?
[Section titled “cause?”](#cause)
> `optional` **cause?**: `unknown`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:24
The cause of the error.
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
`Error.cause`
***
### message
[Section titled “message”](#message)
> **message**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1075
#### Inherited from
[Section titled “Inherited from”](#inherited-from-1)
`Error.message`
***
### name
[Section titled “name”](#name)
> **name**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1074
#### Inherited from
[Section titled “Inherited from”](#inherited-from-2)
`Error.name`
***
### stack?
[Section titled “stack?”](#stack)
> `optional` **stack?**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1076
#### Inherited from
[Section titled “Inherited from”](#inherited-from-3)
`Error.stack`
***
### stackTraceLimit
[Section titled “stackTraceLimit”](#stacktracelimit)
> `static` **stackTraceLimit**: `number`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:67
The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`).
The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed.
If set to a non-number value, or set to a negative number, stack traces will not capture any frames.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-4)
`Error.stackTraceLimit`
## Methods
[Section titled “Methods”](#methods)
### captureStackTrace()
[Section titled “captureStackTrace()”](#capturestacktrace)
#### Call Signature
[Section titled “Call Signature”](#call-signature)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:51
Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called.
```js
const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack; // Similar to `new Error().stack`
```
The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`.
The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace.
The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance:
```js
function a() {
b();
}
function b() {
c();
}
function c() {
// Create an error without stack trace to avoid calculating the stack trace twice.
const { stackTraceLimit } = Error;
Error.stackTraceLimit = 0;
const error = new Error();
Error.stackTraceLimit = stackTraceLimit;
// Capture the stack trace above function b
Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
throw error;
}
a();
```
##### Parameters
[Section titled “Parameters”](#parameters-1)
###### targetObject
[Section titled “targetObject”](#targetobject)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt)
`Function`
##### Returns
[Section titled “Returns”](#returns-1)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-5)
`Error.captureStackTrace`
#### Call Signature
[Section titled “Call Signature”](#call-signature-1)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1042
Create .stack property on a target object
##### Parameters
[Section titled “Parameters”](#parameters-2)
###### targetObject
[Section titled “targetObject”](#targetobject-1)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt-1)
`Function`
##### Returns
[Section titled “Returns”](#returns-2)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-6)
`Error.captureStackTrace`
***
### isError()
[Section titled “isError()”](#iserror)
#### Call Signature
[Section titled “Call Signature”](#call-signature-2)
> `static` **isError**(`error`): `error is Error`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.esnext.error.d.ts:21
Indicates whether the argument provided is a built-in Error instance or not.
##### Parameters
[Section titled “Parameters”](#parameters-3)
###### error
[Section titled “error”](#error)
`unknown`
##### Returns
[Section titled “Returns”](#returns-3)
`error is Error`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-7)
`Error.isError`
#### Call Signature
[Section titled “Call Signature”](#call-signature-3)
> `static` **isError**(`value`): `value is Error`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1037
Check if a value is an instance of Error
##### Parameters
[Section titled “Parameters”](#parameters-4)
###### value
[Section titled “value”](#value)
`unknown`
The value to check
##### Returns
[Section titled “Returns”](#returns-4)
`value is Error`
True if the value is an instance of Error, false otherwise
##### Inherited from
[Section titled “Inherited from”](#inherited-from-8)
`Error.isError`
***
### prepareStackTrace()
[Section titled “prepareStackTrace()”](#preparestacktrace)
> `static` **prepareStackTrace**(`err`, `stackTraces`): `any`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:55
#### Parameters
[Section titled “Parameters”](#parameters-5)
##### err
[Section titled “err”](#err)
`Error`
##### stackTraces
[Section titled “stackTraces”](#stacktraces)
`CallSite`\[]
#### Returns
[Section titled “Returns”](#returns-5)
`any`
#### See
[Section titled “See”](#see)
#### Inherited from
[Section titled “Inherited from”](#inherited-from-9)
`Error.prepareStackTrace`
# ProvisionExpiredError
Defined in: packages/client/src/worker/attach-sync-client.ts:243
A [provisionSyncWorker](/api/client/functions/provisionsyncworker/) that never settled inside its bounded window (`provisionExpiryMs`, default 60000). The provision’s three ordinary outcomes all depend on the SharedWorker connection being alive — the `provision-ack`, a storage-declaration refusal, an unconstructible elected engine — so a connection that DIES before any of them (the SharedWorker crashed, its port was never serviced, the elected engine never came up) would otherwise leave the caller pending FOREVER. The deadline settles it loudly instead.
The guarantee is narrow, deliberately: it bounds THIS CALLER’S promise. What becomes of the worker-side create attempt is decided by PLACEMENT, and the two homes differ:
* **SW-direct.** The attempt lives in the SharedWorker and is left running (an in-flight store open cannot be safely abandoned — a second open against the same store is an ownership conflict). A later [attachSyncClient](/api/client/functions/attachsyncclient/) ADOPTS it if it completed and WAITS on it if it is genuinely stuck; a retried provision re-acks against the SAME attempt, so a second open never starts. A stuck storage open blocks that store whether or not it was ever provisioned, and expiring this promise neither causes nor cures that.
* **Elected.** This deadline is ALSO the provision claim’s expiry, so the claim releases here too. When it is the LAST claim on the store’s coordinator, that release runs last-claim retirement (`engine-retiring` → `engine-teardown` → terminate), taking the elected engine and the attempt inside it — agent termination releases the VFS ownership — and the next attach elects a FRESH engine that opens the store again. An attach that adopted the coordinator BEFORE the expiry holds its own claim, which keeps the engine (and its attempt) alive: the SW-direct adopt-or-wait shape then applies here too.
`[pgxsinkit]`-prefixed like the repo’s other typed errors so a consumer can `instanceof`-branch it.
## Extends
[Section titled “Extends”](#extends)
* `Error`
## Constructors
[Section titled “Constructors”](#constructors)
### Constructor
[Section titled “Constructor”](#constructor)
> **new ProvisionExpiredError**(`storePath`, `expiryMs`): `ProvisionExpiredError`
Defined in: packages/client/src/worker/attach-sync-client.ts:248
#### Parameters
[Section titled “Parameters”](#parameters)
##### storePath
[Section titled “storePath”](#storepath)
`string`
##### expiryMs
[Section titled “expiryMs”](#expiryms)
`number`
#### Returns
[Section titled “Returns”](#returns)
`ProvisionExpiredError`
#### Overrides
[Section titled “Overrides”](#overrides)
`Error.constructor`
## Properties
[Section titled “Properties”](#properties)
### cause?
[Section titled “cause?”](#cause)
> `optional` **cause?**: `unknown`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:24
The cause of the error.
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
`Error.cause`
***
### expiryMs
[Section titled “expiryMs”](#expiryms-1)
> `readonly` **expiryMs**: `number`
Defined in: packages/client/src/worker/attach-sync-client.ts:247
The window that elapsed without a settlement, in ms (the effective `provisionExpiryMs`).
***
### message
[Section titled “message”](#message)
> **message**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1075
#### Inherited from
[Section titled “Inherited from”](#inherited-from-1)
`Error.message`
***
### name
[Section titled “name”](#name)
> **name**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1074
#### Inherited from
[Section titled “Inherited from”](#inherited-from-2)
`Error.name`
***
### stack?
[Section titled “stack?”](#stack)
> `optional` **stack?**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1076
#### Inherited from
[Section titled “Inherited from”](#inherited-from-3)
`Error.stack`
***
### storePath
[Section titled “storePath”](#storepath-1)
> `readonly` **storePath**: `string`
Defined in: packages/client/src/worker/attach-sync-client.ts:245
The store whose provision expired — the `storePath` the call was made for.
***
### stackTraceLimit
[Section titled “stackTraceLimit”](#stacktracelimit)
> `static` **stackTraceLimit**: `number`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:67
The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`).
The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed.
If set to a non-number value, or set to a negative number, stack traces will not capture any frames.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-4)
`Error.stackTraceLimit`
## Methods
[Section titled “Methods”](#methods)
### captureStackTrace()
[Section titled “captureStackTrace()”](#capturestacktrace)
#### Call Signature
[Section titled “Call Signature”](#call-signature)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:51
Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called.
```js
const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack; // Similar to `new Error().stack`
```
The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`.
The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace.
The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance:
```js
function a() {
b();
}
function b() {
c();
}
function c() {
// Create an error without stack trace to avoid calculating the stack trace twice.
const { stackTraceLimit } = Error;
Error.stackTraceLimit = 0;
const error = new Error();
Error.stackTraceLimit = stackTraceLimit;
// Capture the stack trace above function b
Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
throw error;
}
a();
```
##### Parameters
[Section titled “Parameters”](#parameters-1)
###### targetObject
[Section titled “targetObject”](#targetobject)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt)
`Function`
##### Returns
[Section titled “Returns”](#returns-1)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-5)
`Error.captureStackTrace`
#### Call Signature
[Section titled “Call Signature”](#call-signature-1)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1042
Create .stack property on a target object
##### Parameters
[Section titled “Parameters”](#parameters-2)
###### targetObject
[Section titled “targetObject”](#targetobject-1)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt-1)
`Function`
##### Returns
[Section titled “Returns”](#returns-2)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-6)
`Error.captureStackTrace`
***
### isError()
[Section titled “isError()”](#iserror)
#### Call Signature
[Section titled “Call Signature”](#call-signature-2)
> `static` **isError**(`error`): `error is Error`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.esnext.error.d.ts:21
Indicates whether the argument provided is a built-in Error instance or not.
##### Parameters
[Section titled “Parameters”](#parameters-3)
###### error
[Section titled “error”](#error)
`unknown`
##### Returns
[Section titled “Returns”](#returns-3)
`error is Error`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-7)
`Error.isError`
#### Call Signature
[Section titled “Call Signature”](#call-signature-3)
> `static` **isError**(`value`): `value is Error`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1037
Check if a value is an instance of Error
##### Parameters
[Section titled “Parameters”](#parameters-4)
###### value
[Section titled “value”](#value)
`unknown`
The value to check
##### Returns
[Section titled “Returns”](#returns-4)
`value is Error`
True if the value is an instance of Error, false otherwise
##### Inherited from
[Section titled “Inherited from”](#inherited-from-8)
`Error.isError`
***
### prepareStackTrace()
[Section titled “prepareStackTrace()”](#preparestacktrace)
> `static` **prepareStackTrace**(`err`, `stackTraces`): `any`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:55
#### Parameters
[Section titled “Parameters”](#parameters-5)
##### err
[Section titled “err”](#err)
`Error`
##### stackTraces
[Section titled “stackTraces”](#stacktraces)
`CallSite`\[]
#### Returns
[Section titled “Returns”](#returns-5)
`any`
#### See
[Section titled “See”](#see)
#### Inherited from
[Section titled “Inherited from”](#inherited-from-9)
`Error.prepareStackTrace`
# RestoreTargetExistsError
Defined in: packages/client/src/store-path.ts:68
Thrown when a restore (`restoreFrom`, ADR-0035 decision 6) targets a store path whose backend store ALREADY exists. Restore is a CREATION-path feature — it boots a brand-new store on `loadDataDir` — and never overlays a live store: silently merging a backup into an existing datadir would corrupt it. A distinct type (not a bare `Error`) so a caller can `instanceof`-branch “target already there” from a genuine boot failure. The remedy is a deliberate manual [SyncClient.destroy](/api/client/interfaces/syncclient/#destroy) of the existing store first (never automatic — dropping a user’s local store must be their explicit act).
## Extends
[Section titled “Extends”](#extends)
* `Error`
## Constructors
[Section titled “Constructors”](#constructors)
### Constructor
[Section titled “Constructor”](#constructor)
> **new RestoreTargetExistsError**(`storePath`): `RestoreTargetExistsError`
Defined in: packages/client/src/store-path.ts:69
#### Parameters
[Section titled “Parameters”](#parameters)
##### storePath
[Section titled “storePath”](#storepath)
`string`
#### Returns
[Section titled “Returns”](#returns)
`RestoreTargetExistsError`
#### Overrides
[Section titled “Overrides”](#overrides)
`Error.constructor`
## Properties
[Section titled “Properties”](#properties)
### cause?
[Section titled “cause?”](#cause)
> `optional` **cause?**: `unknown`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:24
The cause of the error.
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
`Error.cause`
***
### message
[Section titled “message”](#message)
> **message**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1075
#### Inherited from
[Section titled “Inherited from”](#inherited-from-1)
`Error.message`
***
### name
[Section titled “name”](#name)
> **name**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1074
#### Inherited from
[Section titled “Inherited from”](#inherited-from-2)
`Error.name`
***
### stack?
[Section titled “stack?”](#stack)
> `optional` **stack?**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1076
#### Inherited from
[Section titled “Inherited from”](#inherited-from-3)
`Error.stack`
***
### stackTraceLimit
[Section titled “stackTraceLimit”](#stacktracelimit)
> `static` **stackTraceLimit**: `number`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:67
The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`).
The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed.
If set to a non-number value, or set to a negative number, stack traces will not capture any frames.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-4)
`Error.stackTraceLimit`
## Methods
[Section titled “Methods”](#methods)
### captureStackTrace()
[Section titled “captureStackTrace()”](#capturestacktrace)
#### Call Signature
[Section titled “Call Signature”](#call-signature)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:51
Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called.
```js
const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack; // Similar to `new Error().stack`
```
The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`.
The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace.
The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance:
```js
function a() {
b();
}
function b() {
c();
}
function c() {
// Create an error without stack trace to avoid calculating the stack trace twice.
const { stackTraceLimit } = Error;
Error.stackTraceLimit = 0;
const error = new Error();
Error.stackTraceLimit = stackTraceLimit;
// Capture the stack trace above function b
Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
throw error;
}
a();
```
##### Parameters
[Section titled “Parameters”](#parameters-1)
###### targetObject
[Section titled “targetObject”](#targetobject)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt)
`Function`
##### Returns
[Section titled “Returns”](#returns-1)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-5)
`Error.captureStackTrace`
#### Call Signature
[Section titled “Call Signature”](#call-signature-1)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1042
Create .stack property on a target object
##### Parameters
[Section titled “Parameters”](#parameters-2)
###### targetObject
[Section titled “targetObject”](#targetobject-1)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt-1)
`Function`
##### Returns
[Section titled “Returns”](#returns-2)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-6)
`Error.captureStackTrace`
***
### isError()
[Section titled “isError()”](#iserror)
#### Call Signature
[Section titled “Call Signature”](#call-signature-2)
> `static` **isError**(`error`): `error is Error`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.esnext.error.d.ts:21
Indicates whether the argument provided is a built-in Error instance or not.
##### Parameters
[Section titled “Parameters”](#parameters-3)
###### error
[Section titled “error”](#error)
`unknown`
##### Returns
[Section titled “Returns”](#returns-3)
`error is Error`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-7)
`Error.isError`
#### Call Signature
[Section titled “Call Signature”](#call-signature-3)
> `static` **isError**(`value`): `value is Error`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1037
Check if a value is an instance of Error
##### Parameters
[Section titled “Parameters”](#parameters-4)
###### value
[Section titled “value”](#value)
`unknown`
The value to check
##### Returns
[Section titled “Returns”](#returns-4)
`value is Error`
True if the value is an instance of Error, false otherwise
##### Inherited from
[Section titled “Inherited from”](#inherited-from-8)
`Error.isError`
***
### prepareStackTrace()
[Section titled “prepareStackTrace()”](#preparestacktrace)
> `static` **prepareStackTrace**(`err`, `stackTraces`): `any`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:55
#### Parameters
[Section titled “Parameters”](#parameters-5)
##### err
[Section titled “err”](#err)
`Error`
##### stackTraces
[Section titled “stackTraces”](#stacktraces)
`CallSite`\[]
#### Returns
[Section titled “Returns”](#returns-5)
`any`
#### See
[Section titled “See”](#see)
#### Inherited from
[Section titled “Inherited from”](#inherited-from-9)
`Error.prepareStackTrace`
# StoreDestroyRefusedError
Defined in: packages/client/src/worker/attach-sync-client.ts:185
`destroy()` on an attached facade REFUSES when other tabs still hold the store (ADR-0049 D8, plan fault row “`destroy()` with peers attached → refused with a typed error — close peers first”). The SharedWorker — the only role that knows the attached-tab count — answers a peer-count query; more than one attached tab (this one plus at least one other) raises this. `peers` is the total attached-tab count, this tab included.
## Extends
[Section titled “Extends”](#extends)
* `Error`
## Constructors
[Section titled “Constructors”](#constructors)
### Constructor
[Section titled “Constructor”](#constructor)
> **new StoreDestroyRefusedError**(`peers`): `StoreDestroyRefusedError`
Defined in: packages/client/src/worker/attach-sync-client.ts:187
#### Parameters
[Section titled “Parameters”](#parameters)
##### peers
[Section titled “peers”](#peers)
`number`
#### Returns
[Section titled “Returns”](#returns)
`StoreDestroyRefusedError`
#### Overrides
[Section titled “Overrides”](#overrides)
`Error.constructor`
## Properties
[Section titled “Properties”](#properties)
### cause?
[Section titled “cause?”](#cause)
> `optional` **cause?**: `unknown`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:24
The cause of the error.
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
`Error.cause`
***
### message
[Section titled “message”](#message)
> **message**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1075
#### Inherited from
[Section titled “Inherited from”](#inherited-from-1)
`Error.message`
***
### name
[Section titled “name”](#name)
> **name**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1074
#### Inherited from
[Section titled “Inherited from”](#inherited-from-2)
`Error.name`
***
### peers
[Section titled “peers”](#peers-1)
> `readonly` **peers**: `number`
Defined in: packages/client/src/worker/attach-sync-client.ts:186
***
### stack?
[Section titled “stack?”](#stack)
> `optional` **stack?**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1076
#### Inherited from
[Section titled “Inherited from”](#inherited-from-3)
`Error.stack`
***
### stackTraceLimit
[Section titled “stackTraceLimit”](#stacktracelimit)
> `static` **stackTraceLimit**: `number`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:67
The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`).
The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed.
If set to a non-number value, or set to a negative number, stack traces will not capture any frames.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-4)
`Error.stackTraceLimit`
## Methods
[Section titled “Methods”](#methods)
### captureStackTrace()
[Section titled “captureStackTrace()”](#capturestacktrace)
#### Call Signature
[Section titled “Call Signature”](#call-signature)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:51
Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called.
```js
const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack; // Similar to `new Error().stack`
```
The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`.
The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace.
The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance:
```js
function a() {
b();
}
function b() {
c();
}
function c() {
// Create an error without stack trace to avoid calculating the stack trace twice.
const { stackTraceLimit } = Error;
Error.stackTraceLimit = 0;
const error = new Error();
Error.stackTraceLimit = stackTraceLimit;
// Capture the stack trace above function b
Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
throw error;
}
a();
```
##### Parameters
[Section titled “Parameters”](#parameters-1)
###### targetObject
[Section titled “targetObject”](#targetobject)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt)
`Function`
##### Returns
[Section titled “Returns”](#returns-1)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-5)
`Error.captureStackTrace`
#### Call Signature
[Section titled “Call Signature”](#call-signature-1)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1042
Create .stack property on a target object
##### Parameters
[Section titled “Parameters”](#parameters-2)
###### targetObject
[Section titled “targetObject”](#targetobject-1)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt-1)
`Function`
##### Returns
[Section titled “Returns”](#returns-2)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-6)
`Error.captureStackTrace`
***
### isError()
[Section titled “isError()”](#iserror)
#### Call Signature
[Section titled “Call Signature”](#call-signature-2)
> `static` **isError**(`error`): `error is Error`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.esnext.error.d.ts:21
Indicates whether the argument provided is a built-in Error instance or not.
##### Parameters
[Section titled “Parameters”](#parameters-3)
###### error
[Section titled “error”](#error)
`unknown`
##### Returns
[Section titled “Returns”](#returns-3)
`error is Error`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-7)
`Error.isError`
#### Call Signature
[Section titled “Call Signature”](#call-signature-3)
> `static` **isError**(`value`): `value is Error`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1037
Check if a value is an instance of Error
##### Parameters
[Section titled “Parameters”](#parameters-4)
###### value
[Section titled “value”](#value)
`unknown`
The value to check
##### Returns
[Section titled “Returns”](#returns-4)
`value is Error`
True if the value is an instance of Error, false otherwise
##### Inherited from
[Section titled “Inherited from”](#inherited-from-8)
`Error.isError`
***
### prepareStackTrace()
[Section titled “prepareStackTrace()”](#preparestacktrace)
> `static` **prepareStackTrace**(`err`, `stackTraces`): `any`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:55
#### Parameters
[Section titled “Parameters”](#parameters-5)
##### err
[Section titled “err”](#err)
`Error`
##### stackTraces
[Section titled “stackTraces”](#stacktraces)
`CallSite`\[]
#### Returns
[Section titled “Returns”](#returns-5)
`any`
#### See
[Section titled “See”](#see)
#### Inherited from
[Section titled “Inherited from”](#inherited-from-9)
`Error.prepareStackTrace`
# UnknownEventStreamError
Defined in: packages/client/src/event-lane.ts:215
`appendEvent` named an Event stream this registry does not register — a call-site bug, never a rollout.
## Extends
[Section titled “Extends”](#extends)
* `Error`
## Constructors
[Section titled “Constructors”](#constructors)
### Constructor
[Section titled “Constructor”](#constructor)
> **new UnknownEventStreamError**(`stream`, `known`): `UnknownEventStreamError`
Defined in: packages/client/src/event-lane.ts:217
#### Parameters
[Section titled “Parameters”](#parameters)
##### stream
[Section titled “stream”](#stream)
`string`
##### known
[Section titled “known”](#known)
readonly `string`\[]
#### Returns
[Section titled “Returns”](#returns)
`UnknownEventStreamError`
#### Overrides
[Section titled “Overrides”](#overrides)
`Error.constructor`
## Properties
[Section titled “Properties”](#properties)
### cause?
[Section titled “cause?”](#cause)
> `optional` **cause?**: `unknown`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:24
The cause of the error.
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
`Error.cause`
***
### message
[Section titled “message”](#message)
> **message**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1075
#### Inherited from
[Section titled “Inherited from”](#inherited-from-1)
`Error.message`
***
### name
[Section titled “name”](#name)
> **name**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1074
#### Inherited from
[Section titled “Inherited from”](#inherited-from-2)
`Error.name`
***
### stack?
[Section titled “stack?”](#stack)
> `optional` **stack?**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1076
#### Inherited from
[Section titled “Inherited from”](#inherited-from-3)
`Error.stack`
***
### stream
[Section titled “stream”](#stream-1)
> `readonly` **stream**: `string`
Defined in: packages/client/src/event-lane.ts:216
***
### stackTraceLimit
[Section titled “stackTraceLimit”](#stacktracelimit)
> `static` **stackTraceLimit**: `number`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:67
The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`).
The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed.
If set to a non-number value, or set to a negative number, stack traces will not capture any frames.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-4)
`Error.stackTraceLimit`
## Methods
[Section titled “Methods”](#methods)
### captureStackTrace()
[Section titled “captureStackTrace()”](#capturestacktrace)
#### Call Signature
[Section titled “Call Signature”](#call-signature)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:51
Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called.
```js
const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack; // Similar to `new Error().stack`
```
The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`.
The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace.
The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance:
```js
function a() {
b();
}
function b() {
c();
}
function c() {
// Create an error without stack trace to avoid calculating the stack trace twice.
const { stackTraceLimit } = Error;
Error.stackTraceLimit = 0;
const error = new Error();
Error.stackTraceLimit = stackTraceLimit;
// Capture the stack trace above function b
Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
throw error;
}
a();
```
##### Parameters
[Section titled “Parameters”](#parameters-1)
###### targetObject
[Section titled “targetObject”](#targetobject)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt)
`Function`
##### Returns
[Section titled “Returns”](#returns-1)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-5)
`Error.captureStackTrace`
#### Call Signature
[Section titled “Call Signature”](#call-signature-1)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1042
Create .stack property on a target object
##### Parameters
[Section titled “Parameters”](#parameters-2)
###### targetObject
[Section titled “targetObject”](#targetobject-1)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt-1)
`Function`
##### Returns
[Section titled “Returns”](#returns-2)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-6)
`Error.captureStackTrace`
***
### isError()
[Section titled “isError()”](#iserror)
#### Call Signature
[Section titled “Call Signature”](#call-signature-2)
> `static` **isError**(`error`): `error is Error`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.esnext.error.d.ts:21
Indicates whether the argument provided is a built-in Error instance or not.
##### Parameters
[Section titled “Parameters”](#parameters-3)
###### error
[Section titled “error”](#error)
`unknown`
##### Returns
[Section titled “Returns”](#returns-3)
`error is Error`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-7)
`Error.isError`
#### Call Signature
[Section titled “Call Signature”](#call-signature-3)
> `static` **isError**(`value`): `value is Error`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1037
Check if a value is an instance of Error
##### Parameters
[Section titled “Parameters”](#parameters-4)
###### value
[Section titled “value”](#value)
`unknown`
The value to check
##### Returns
[Section titled “Returns”](#returns-4)
`value is Error`
True if the value is an instance of Error, false otherwise
##### Inherited from
[Section titled “Inherited from”](#inherited-from-8)
`Error.isError`
***
### prepareStackTrace()
[Section titled “prepareStackTrace()”](#preparestacktrace)
> `static` **prepareStackTrace**(`err`, `stackTraces`): `any`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:55
#### Parameters
[Section titled “Parameters”](#parameters-5)
##### err
[Section titled “err”](#err)
`Error`
##### stackTraces
[Section titled “stackTraces”](#stacktraces)
`CallSite`\[]
#### Returns
[Section titled “Returns”](#returns-5)
`any`
#### See
[Section titled “See”](#see)
#### Inherited from
[Section titled “Inherited from”](#inherited-from-9)
`Error.prepareStackTrace`
# WriteNotReadyError
Defined in: packages/client/src/index.ts:339
A write-path method was invoked against a client whose `writeReady` stage has not resolved and the caller opted out of awaiting it (ADR-0041). pgxsinkit’s own write methods always `await writeReady` internally, so this is NEVER thrown on the stage-1 default path. It is **reserved for the stage-2 opt-out surface** — the ADR’s typed pre-`writeReady` rejection for a consumer who chooses not to await — so a pre-`writeReady` write fails loudly rather than silently no-oping. Exported now so that surface is a non-breaking addition later.
## Extends
[Section titled “Extends”](#extends)
* `Error`
## Constructors
[Section titled “Constructors”](#constructors)
### Constructor
[Section titled “Constructor”](#constructor)
> **new WriteNotReadyError**(): `WriteNotReadyError`
Defined in: packages/client/src/index.ts:340
#### Returns
[Section titled “Returns”](#returns)
`WriteNotReadyError`
#### Overrides
[Section titled “Overrides”](#overrides)
`Error.constructor`
## Properties
[Section titled “Properties”](#properties)
### cause?
[Section titled “cause?”](#cause)
> `optional` **cause?**: `unknown`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:24
The cause of the error.
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
`Error.cause`
***
### message
[Section titled “message”](#message)
> **message**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1075
#### Inherited from
[Section titled “Inherited from”](#inherited-from-1)
`Error.message`
***
### name
[Section titled “name”](#name)
> **name**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1074
#### Inherited from
[Section titled “Inherited from”](#inherited-from-2)
`Error.name`
***
### stack?
[Section titled “stack?”](#stack)
> `optional` **stack?**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1076
#### Inherited from
[Section titled “Inherited from”](#inherited-from-3)
`Error.stack`
***
### stackTraceLimit
[Section titled “stackTraceLimit”](#stacktracelimit)
> `static` **stackTraceLimit**: `number`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:67
The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`).
The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed.
If set to a non-number value, or set to a negative number, stack traces will not capture any frames.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-4)
`Error.stackTraceLimit`
## Methods
[Section titled “Methods”](#methods)
### captureStackTrace()
[Section titled “captureStackTrace()”](#capturestacktrace)
#### Call Signature
[Section titled “Call Signature”](#call-signature)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:51
Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called.
```js
const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack; // Similar to `new Error().stack`
```
The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`.
The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace.
The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance:
```js
function a() {
b();
}
function b() {
c();
}
function c() {
// Create an error without stack trace to avoid calculating the stack trace twice.
const { stackTraceLimit } = Error;
Error.stackTraceLimit = 0;
const error = new Error();
Error.stackTraceLimit = stackTraceLimit;
// Capture the stack trace above function b
Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
throw error;
}
a();
```
##### Parameters
[Section titled “Parameters”](#parameters)
###### targetObject
[Section titled “targetObject”](#targetobject)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt)
`Function`
##### Returns
[Section titled “Returns”](#returns-1)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-5)
`Error.captureStackTrace`
#### Call Signature
[Section titled “Call Signature”](#call-signature-1)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1042
Create .stack property on a target object
##### Parameters
[Section titled “Parameters”](#parameters-1)
###### targetObject
[Section titled “targetObject”](#targetobject-1)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt-1)
`Function`
##### Returns
[Section titled “Returns”](#returns-2)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-6)
`Error.captureStackTrace`
***
### isError()
[Section titled “isError()”](#iserror)
#### Call Signature
[Section titled “Call Signature”](#call-signature-2)
> `static` **isError**(`error`): `error is Error`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.esnext.error.d.ts:21
Indicates whether the argument provided is a built-in Error instance or not.
##### Parameters
[Section titled “Parameters”](#parameters-2)
###### error
[Section titled “error”](#error)
`unknown`
##### Returns
[Section titled “Returns”](#returns-3)
`error is Error`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-7)
`Error.isError`
#### Call Signature
[Section titled “Call Signature”](#call-signature-3)
> `static` **isError**(`value`): `value is Error`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1037
Check if a value is an instance of Error
##### Parameters
[Section titled “Parameters”](#parameters-3)
###### value
[Section titled “value”](#value)
`unknown`
The value to check
##### Returns
[Section titled “Returns”](#returns-4)
`value is Error`
True if the value is an instance of Error, false otherwise
##### Inherited from
[Section titled “Inherited from”](#inherited-from-8)
`Error.isError`
***
### prepareStackTrace()
[Section titled “prepareStackTrace()”](#preparestacktrace)
> `static` **prepareStackTrace**(`err`, `stackTraces`): `any`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:55
#### Parameters
[Section titled “Parameters”](#parameters-4)
##### err
[Section titled “err”](#err)
`Error`
##### stackTraces
[Section titled “stackTraces”](#stacktraces)
`CallSite`\[]
#### Returns
[Section titled “Returns”](#returns-5)
`any`
#### See
[Section titled “See”](#see)
#### Inherited from
[Section titled “Inherited from”](#inherited-from-9)
`Error.prepareStackTrace`
# assertLazyRefsActivated
> **assertLazyRefsActivated**(`args`): `void`
Defined in: packages/client/src/lazy-guard.ts:151
The activation backstop (ADR-0021): throw if a query references a lazy relation that is still not active. Called *after* the referenced lazy relations have been activated, so in the normal path every scanned relation is now active and this passes. It fires only when activation could not make a referenced relation active (a start that failed, or a lazy relation with no consistency group) — converting a would-be silent empty/stale read into a loud error.
## Parameters
[Section titled “Parameters”](#parameters)
### args
[Section titled “args”](#args)
#### index
[Section titled “index”](#index)
[`LazyGuardIndex`](/api/client/interfaces/lazyguardindex/)
#### isActive
[Section titled “isActive”](#isactive)
(`key`) => `boolean`
#### sql
[Section titled “sql”](#sql)
`string`
## Returns
[Section titled “Returns”](#returns)
`void`
# attachSyncClient
> **attachSyncClient**<`TRegistry`>(`options`): `Promise`<[`AttachedSyncClient`](/api/client/type-aliases/attachedsyncclient/)<`TRegistry`>>
Defined in: packages/client/src/worker/attach-sync-client.ts:988
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
## Parameters
[Section titled “Parameters”](#parameters)
### options
[Section titled “options”](#options)
[`AttachSyncClientOptions`](/api/client/interfaces/attachsyncclientoptions/)<`TRegistry`>
## Returns
[Section titled “Returns”](#returns)
`Promise`<[`AttachedSyncClient`](/api/client/type-aliases/attachedsyncclient/)<`TRegistry`>>
# buildLazyGuardIndex
> **buildLazyGuardIndex**(`registry`): [`LazyGuardIndex`](/api/client/interfaces/lazyguardindex/)
Defined in: packages/client/src/lazy-guard.ts:78
Build the [LazyGuardIndex](/api/client/interfaces/lazyguardindex/) for a registry. Cheap and pure — cache it per client.
## Parameters
[Section titled “Parameters”](#parameters)
### registry
[Section titled “registry”](#registry)
`SyncTableRegistry`
## Returns
[Section titled “Returns”](#returns)
[`LazyGuardIndex`](/api/client/interfaces/lazyguardindex/)
# buildRegistryReadHandles
> **buildRegistryReadHandles**<`TRegistry`>(`registry`, `client?`): `object`
Defined in: packages/client/src/index.ts:3666
The tab-side READ handles a worker-attached client exposes (ADR-0032 S2/decision 4): a Drizzle instance plus the registry views. `createSyncClient`’s SAME builders, so a query built on either client compiles identically. The `client` argument is the executor Drizzle runs against:
* Omit it (the default `{}` stub) for a BUILD-ONLY instance — Drizzle only compiles to SQL (`.toSQL()`) and the stub is never invoked; used where execution happens elsewhere (e.g. the live-rows bridge).
* Pass a `ClientPGlite`-shaped BRIDGE executor (its `query` routes to the worker’s `guardedQuery` RPC) so that awaiting a builder executes through the bridge and Drizzle’s own result mapping (relational/nested queries included) runs on the tab — the one-shot read path `attachSyncClient` wires (ADR-0032 decision 4).
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
## Parameters
[Section titled “Parameters”](#parameters)
### registry
[Section titled “registry”](#registry)
`TRegistry`
### client?
[Section titled “client?”](#client)
[`ClientPGlite`](/api/client/type-aliases/clientpglite/) = `...`
## Returns
[Section titled “Returns”](#returns)
### drizzle
[Section titled “drizzle”](#drizzle)
> **drizzle**: `PgliteDatabase`<`ExtractTablesWithRelations`<{ }, `RegistryTables`<`TRegistry`>>>
### drizzleFor
[Section titled “drizzleFor”](#drizzlefor)
> **drizzleFor**: (`client`) => `PgliteDatabase`<`ExtractTablesWithRelations`<{ }, `RegistryTables`<`TRegistry`>>>
A drizzle-database FACTORY over this registry’s schema (ADR-0032 decision 4): `(client) => db`, reusing the schema + relations computed ONCE here so a caller can cheaply build additional per-executor databases without recomputing them. `attachSyncClient` uses it to give each `queryRaw`/`queryRawRow` its OWN bridge executor carrying that call’s `use` — a scoped db, never a shared mutable stash (no read races).
#### Parameters
[Section titled “Parameters”](#parameters-1)
##### client
[Section titled “client”](#client-1)
[`ClientPGlite`](/api/client/type-aliases/clientpglite/)
#### Returns
[Section titled “Returns”](#returns-1)
`PgliteDatabase`<`ExtractTablesWithRelations`<{ }, `RegistryTables`<`TRegistry`>>>
### views
[Section titled “views”](#views)
> **views**: `RegistryViews`<`TRegistry`>
# classifyEventBatchFailure
> **classifyEventBatchFailure**(`httpStatus`): `"auth"` | `"retryable"`
Defined in: packages/client/src/event-lane.ts:343
The lane has EXACTLY TWO retry classes, and this is the whole rule. Unlike the mutation path there is no third, terminal class: a structural 4xx (400/404/413) is unreachable through the library (appends are validated, envelopes are library-built), and quarantining events on one would discard data the server never rejected — which at-least-once forbids. So everything that is not auth is retryable.
## Parameters
[Section titled “Parameters”](#parameters)
### httpStatus
[Section titled “httpStatus”](#httpstatus)
`number` | `null` | `undefined`
## Returns
[Section titled “Returns”](#returns)
`"auth"` | `"retryable"`
# committedStoreUnreachableFromWire
> **committedStoreUnreachableFromWire**(`detail`): [`CommittedStoreUnreachableError`](/api/client/classes/committedstoreunreachableerror/) | `undefined`
Defined in: packages/client/src/store-boot.ts:216
Reconstruct a typed [CommittedStoreUnreachableError](/api/client/classes/committedstoreunreachableerror/) from a bridge error’s `detail`. STRICT shape check: returns `undefined` for anything that is not exactly the `{ code: "committed-store-unreachable", storePath }` wire form (a foreign/absent detail, a wrong code, a missing/non-string path, a non-object), so a different failure is never misclassified as this refusal. The single source of truth for the decoding — every bridge seam calls THIS, never its own shape sniff.
## Parameters
[Section titled “Parameters”](#parameters)
### detail
[Section titled “detail”](#detail)
`unknown`
## Returns
[Section titled “Returns”](#returns)
[`CommittedStoreUnreachableError`](/api/client/classes/committedstoreunreachableerror/) | `undefined`
# computeEventBackoffMs
> **computeEventBackoffMs**(`attempt`, `options`, `random`): `number`
Defined in: packages/client/src/event-lane.ts:371
Jittered exponential backoff with a ceiling — equal jitter around the doubling ceiling, exactly the mutation path’s congestion policy (half the ceiling plus a random share of the other half), so a fleet recovering from an outage never stampedes in lockstep.
## Parameters
[Section titled “Parameters”](#parameters)
### attempt
[Section titled “attempt”](#attempt)
`number`
### options
[Section titled “options”](#options)
[`EventBackoffOptions`](/api/client/interfaces/eventbackoffoptions/)
### random
[Section titled “random”](#random)
() => `number`
## Returns
[Section titled “Returns”](#returns)
`number`
# computeLiveDiff
> **computeLiveDiff**(`state`, `nextRows`): `Omit`<[`LiveDiffPayload`](/api/client/interfaces/livediffpayload/), `"queryId"`>
Defined in: packages/client/src/worker/live-diff.ts:61
Diff a fresh ordered result set against the state’s previous set, MUTATING the state to the new set and returning the wire diff (§4). `order` is every key in the new ORDER BY order (keys only — cheap); `added`/ `changed` carry the delta row BODIES; `removed` carries dropped keys. Never emits the full result set: a single-row change yields one `changed` and the (key-only) `order`.
## Parameters
[Section titled “Parameters”](#parameters)
### state
[Section titled “state”](#state)
[`LiveDiffState`](/api/client/interfaces/livediffstate/)
### nextRows
[Section titled “nextRows”](#nextrows)
readonly `Record`<`string`, `unknown`>\[]
## Returns
[Section titled “Returns”](#returns)
`Omit`<[`LiveDiffPayload`](/api/client/interfaces/livediffpayload/), `"queryId"`>
# createBrowserConvergenceTrigger
> **createBrowserConvergenceTrigger**(`options?`): [`ConvergenceTrigger`](/api/client/interfaces/convergencetrigger/)
Defined in: packages/client/src/convergence.ts:157
Browser convergence trigger: fires on `online`, `visibilitychange`, and a fallback interval, and converges only while online and not backgrounded. The adapter most apps use.
## Parameters
[Section titled “Parameters”](#parameters)
### options?
[Section titled “options?”](#options)
#### intervalMs?
[Section titled “intervalMs?”](#intervalms)
`number`
## Returns
[Section titled “Returns”](#returns)
[`ConvergenceTrigger`](/api/client/interfaces/convergencetrigger/)
# createClientPGlite
> **createClientPGlite**(`store`, `options?`): `Promise`<[`ClientPGlite`](/api/client/type-aliases/clientpglite/)>
Defined in: packages/client/src/index.ts:528
Create the raw local PGlite store the sync client runs on — the SAME `PGlite.create` call [createSyncClient](/api/client/functions/createsyncclient/) makes internally (the `electric` + `live` extensions, pre-warmed boot-asset consumption, and the `boot pglite.create` rail stamp), extracted so exactly one implementation exists and a host can create the store EAGERLY on an earlier screen. Hand the returned (still-pending) instance to [CreateSyncClientOptions.precreatedPglite](/api/client/interfaces/createsyncclientoptions/#precreatedpglite): the client then owns schema exec, prepare hooks, journal recovery, and registry reconciliation, exactly as its own `storePath` path does.
Takes a plain store path (ADR-0036), never a storage URL — the backend is DERIVED from the engine home (capability-selected opfs-repacked with IndexedDB fallback in a browser, or the filesystem on Bun/Node); a scheme-bearing path throws [InvalidStorePathError](/api/client/classes/invalidstorepatherror/). Also accepts the testing helper’s output (`createClientPGlite(memoryStoreForTests("x"))`) so a test can mint a memory store without naming a backend.
The instance is deliberately **schemaless** — the registry-derived local schema is role/registry dependent, so it is applied post-create by `createSyncClient`. The eager create buys only the expensive initdb (+ persistent-store open), which is the dominant cold-boot cost once the WASM is pre-warmed.
`bootAssets` is the pre-warmed WASM/fs bundle (see [CreateSyncClientOptions.pgliteBootAssets](/api/client/interfaces/createsyncclientoptions/#pglitebootassets)); a rejected warm is caught to `undefined`, so PGlite falls back to loading its own assets — never a failure.
## Parameters
[Section titled “Parameters”](#parameters)
### store
[Section titled “store”](#store)
`StorePathInput`
### options?
[Section titled “options?”](#options)
[`CreateClientPGliteOptions`](/api/client/interfaces/createclientpgliteoptions/)
## Returns
[Section titled “Returns”](#returns)
`Promise`<[`ClientPGlite`](/api/client/type-aliases/clientpglite/)>
# createConvergenceDriver
> **createConvergenceDriver**(`options`): [`ConvergenceDriver`](/api/client/interfaces/convergencedriver/)
Defined in: packages/client/src/convergence.ts:70
Drive convergence from a [ConvergenceTrigger](/api/client/interfaces/convergencetrigger/). Each signal runs at most one pass at a time (`flush` → `reconcile`); a signal arriving mid-pass coalesces into a single follow-up pass, so a burst of triggers never stampedes the server (the per-mutation backoff in the runtime handles the rest).
## Parameters
[Section titled “Parameters”](#parameters)
### options
[Section titled “options”](#options)
[`ConvergenceDriverOptions`](/api/client/interfaces/convergencedriveroptions/)
## Returns
[Section titled “Returns”](#returns)
[`ConvergenceDriver`](/api/client/interfaces/convergencedriver/)
# createEventFlushDriver
> **createEventFlushDriver**(`options`): [`EventFlushDriver`](/api/client/interfaces/eventflushdriver/)
Defined in: packages/client/src/event-lane.ts:1119
Drive the Outbox flush: at most one pass in flight, a signal arriving mid-pass coalescing into a single follow-up, an interval as the FALLBACK trigger (retries + recovery), and `requestPass` as the event-driven path an append takes. `start()` runs a pass immediately, which is what drains events written offline after a reconnect or a boot — no new append required.
## Parameters
[Section titled “Parameters”](#parameters)
### options
[Section titled “options”](#options)
[`EventFlushDriverOptions`](/api/client/interfaces/eventflushdriveroptions/)
## Returns
[Section titled “Returns”](#returns)
[`EventFlushDriver`](/api/client/interfaces/eventflushdriver/)
# createEventLaneRuntime
> **createEventLaneRuntime**<`TRegistry`>(`options`): [`EventLaneRuntime`](/api/client/interfaces/eventlaneruntime/)
Defined in: packages/client/src/event-lane.ts:511
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
## Parameters
[Section titled “Parameters”](#parameters)
### options
[Section titled “options”](#options)
`CreateEventLaneRuntimeOptions`<`TRegistry`>
## Returns
[Section titled “Returns”](#returns)
[`EventLaneRuntime`](/api/client/interfaces/eventlaneruntime/)
# createIntervalConvergenceTrigger
> **createIntervalConvergenceTrigger**(`intervalMs`): [`ConvergenceTrigger`](/api/client/interfaces/convergencetrigger/)
Defined in: packages/client/src/convergence.ts:185
Interval convergence trigger: fires on a fixed cadence and always converges. The minimal non-browser adapter — usable directly in tests/servers and the base a React Native `AppState`/`NetInfo` adapter builds on — which is the second adapter that proves the seam.
## Parameters
[Section titled “Parameters”](#parameters)
### intervalMs
[Section titled “intervalMs”](#intervalms)
`number`
## Returns
[Section titled “Returns”](#returns)
[`ConvergenceTrigger`](/api/client/interfaces/convergencetrigger/)
# createLifecycleSlot
> **createLifecycleSlot**(): [`LifecycleSlot`](/api/client/interfaces/lifecycleslot/)
Defined in: packages/client/src/lifecycle-slot.ts:48
Create an empty lifecycle slot (ADR-0035 decision 4).
## Returns
[Section titled “Returns”](#returns)
[`LifecycleSlot`](/api/client/interfaces/lifecycleslot/)
# createMutationsApi
> **createMutationsApi**<`TRegistry`>(`deps`): [`MutationsApi`](/api/client/interfaces/mutationsapi/)<`TRegistry`>
Defined in: packages/client/src/mutations-api.ts:266
Build the `client.mutations` API over the shared seams. Implemented ONCE — both client modes pass their own `subscribeLiveRows` + one-shot `query`, so semantics are identical and the worker bridge is untouched. A registry with NO writable table emits no view, so the API short-circuits to empty shapes WITHOUT touching the (absent) view — derived from the registry, never a runtime probe.
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
## Parameters
[Section titled “Parameters”](#parameters)
### deps
[Section titled “deps”](#deps)
[`MutationsApiDeps`](/api/client/interfaces/mutationsapideps/)<`TRegistry`>
## Returns
[Section titled “Returns”](#returns)
[`MutationsApi`](/api/client/interfaces/mutationsapi/)<`TRegistry`>
# createOpfsEffects
> **createOpfsEffects**(`storePath`, `deps?`): [`OpfsEffects`](/api/client/interfaces/opfseffects/)
Defined in: packages/client/src/opfs-effects.ts:75
Construct the real OPFS effects for a store, all under `store-path.ts`’s disjoint namespaces. The returned object’s methods satisfy the idempotency contracts the `store-lifecycle.ts` machines require, so a boot that resumes an interrupted destruction/candidate re-runs them safely.
## Parameters
[Section titled “Parameters”](#parameters)
### storePath
[Section titled “storePath”](#storepath)
`string`
### deps?
[Section titled “deps?”](#deps)
[`OpfsEffectsDeps`](/api/client/interfaces/opfseffectsdeps/)
## Returns
[Section titled “Returns”](#returns)
[`OpfsEffects`](/api/client/interfaces/opfseffects/)
# createSyncClient
> **createSyncClient**<`TRegistry`>(`options`): `Promise`<[`SyncClient`](/api/client/interfaces/syncclient/)<`TRegistry`>>
Defined in: packages/client/src/index.ts:1789
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
## Parameters
[Section titled “Parameters”](#parameters)
### options
[Section titled “options”](#options)
[`CreateSyncClientOptions`](/api/client/interfaces/createsyncclientoptions/)<`TRegistry`>
## Returns
[Section titled “Returns”](#returns)
`Promise`<[`SyncClient`](/api/client/interfaces/syncclient/)<`TRegistry`>>
# createWorkerTokenCache
> **createWorkerTokenCache**(`options`): [`WorkerTokenCache`](/api/client/interfaces/workertokencache/)
Defined in: packages/client/src/worker/token-cache.ts:29
## Parameters
[Section titled “Parameters”](#parameters)
### options
[Section titled “options”](#options)
#### broadcastRequest
[Section titled “broadcastRequest”](#broadcastrequest)
(`requestId`) => `void`
#### marginMs
[Section titled “marginMs”](#marginms)
`number`
#### now?
[Section titled “now?”](#now)
() => `number`
## Returns
[Section titled “Returns”](#returns)
[`WorkerTokenCache`](/api/client/interfaces/workertokencache/)
# defineSyncWorker
> **defineSyncWorker**<`TRegistry`>(`options`): [`SyncWorkerHost`](/api/client/interfaces/syncworkerhost/)<`TRegistry`>
Defined in: packages/client/src/worker/define-sync-worker.ts:259
Build a worker host over the given engine config. In a real worker file the consumer calls this at module top level and it auto-binds the global scope; a test constructs it and drives [SyncWorkerHost.connect](/api/client/interfaces/syncworkerhost/#connect).
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
## Parameters
[Section titled “Parameters”](#parameters)
### options
[Section titled “options”](#options)
[`DefineSyncWorkerOptions`](/api/client/interfaces/definesyncworkeroptions/)<`TRegistry`>
## Returns
[Section titled “Returns”](#returns)
[`SyncWorkerHost`](/api/client/interfaces/syncworkerhost/)<`TRegistry`>
# deriveBatchEventUrl
> **deriveBatchEventUrl**(`batchWriteUrl`): `string` | `undefined`
Defined in: packages/client/src/event-lane.ts:310
The ingestion endpoint DERIVED from the mutation endpoint, for the ordinary deployment where one `createSyncServer` mounts both: `…/api/mutations` → `…/api/events`. Returns `undefined` when the write URL is not in the canonical shape, so the caller can stay silent rather than guess (the lane is then only usable with an explicit `batchEventUrl`).
## Parameters
[Section titled “Parameters”](#parameters)
### batchWriteUrl
[Section titled “batchWriteUrl”](#batchwriteurl)
`string`
## Returns
[Section titled “Returns”](#returns)
`string` | `undefined`
# deriveStoreId
> **deriveStoreId**(`storePath`): `string`
Defined in: packages/client/src/export-store.ts:190
Reduce a plain store PATH (ADR-0036) to a filesystem-safe token for the backup file name. Takes the path’s LAST segment (as PGlite’s own `dumpDataDir` does when it names the inner db), then keeps only `[A-Za-z0-9._-]`. No scheme stripping — the store path is a plain name, never a storage URL. Falls back to a fixed token for an empty path so the name is always well formed.
## Parameters
[Section titled “Parameters”](#parameters)
### storePath
[Section titled “storePath”](#storepath)
`string` | `undefined`
## Returns
[Section titled “Returns”](#returns)
`string`
# destroyStoreArtifacts
> **destroyStoreArtifacts**(`storePath`, `opts?`): `Promise`<`void`>
Defined in: packages/client/src/worker/attach-sync-client.ts:352
Destroy every local artifact of a store BY PATH (ADR-0050 stage 2; the public face of the ADR-0049 D8 destruction machinery): the OPFS store directory, the commitment sentinel, the meta record, AND the idb database — backend-agnostic (delete-if-present on both backends), with the bounded VFS-ownership-lag retry.
**Precondition: the store is NOT running.** This is the companion to [SyncClient.destroy](/api/client/interfaces/syncclient/#destroy) (the supervised destroy of an ATTACHED store — peer-checked, teardown-acknowledged): reach for `destroyStoreArtifacts` for a store nobody is attached to — an obsolete path a preference change left behind, a wipe of known store paths. Called on a path a live engine still holds, the backend delete throws an ownership error after the bounded retry — loud, and safely RE-RUNNABLE: the destruction sequence is idempotent and phase-recorded (a store whose meta record says `deleting` is refused for boot), so a re-run completes it. No liveness probe is attempted here — it could not be race-free, and the ownership error already fails hard; callers keep failed paths on their own retry list (e.g. the board’s Obsolete stores).
## Parameters
[Section titled “Parameters”](#parameters)
### storePath
[Section titled “storePath”](#storepath)
`string`
### opts?
[Section titled “opts?”](#opts)
[`StoreDestructionRetryOptions`](/api/client/interfaces/storedestructionretryoptions/)
## Returns
[Section titled “Returns”](#returns)
`Promise`<`void`>
# encodeEnvelope
> **encodeEnvelope**(`codec`, `type`, `payload`, `id?`): `object`
Defined in: packages/client/src/worker/protocol.ts:530
Build a wire envelope, running the payload through the codec. Returns the envelope + any transferables.
## Parameters
[Section titled “Parameters”](#parameters)
### codec
[Section titled “codec”](#codec)
[`BridgeCodec`](/api/client/interfaces/bridgecodec/)
### type
[Section titled “type”](#type)
[`BridgeMessageType`](/api/client/type-aliases/bridgemessagetype/)
### payload
[Section titled “payload”](#payload)
`unknown`
### id?
[Section titled “id?”](#id)
`string`
## Returns
[Section titled “Returns”](#returns)
`object`
### envelope
[Section titled “envelope”](#envelope)
> **envelope**: [`BridgeEnvelope`](/api/client/interfaces/bridgeenvelope/)
### transfer?
[Section titled “transfer?”](#transfer)
> `optional` **transfer?**: `unknown`\[]
# findReferencedLazyKeysInSql
> **findReferencedLazyKeysInSql**(`sql`, `index`): `Set`<`string`>
Defined in: packages/client/src/lazy-guard.ts:111
The compiled-SQL scan: the lazy registry keys whose quoted reference token appears in `sql`. Because the token is fully quoted it is self-delimiting (`"a"` cannot match inside `"ab"`), so a substring test is exact. A *bare* token sitting in alias position (`… as "name"`, which Drizzle emits for `.as("name")`) is excluded — the one realistic collision for a schema-less relation; a schema-qualified token cannot be aliased and so needs no such guard.
## Parameters
[Section titled “Parameters”](#parameters)
### sql
[Section titled “sql”](#sql)
`string`
### index
[Section titled “index”](#index)
[`LazyGuardIndex`](/api/client/interfaces/lazyguardindex/)
## Returns
[Section titled “Returns”](#returns)
`Set`<`string`>
# findReferencedSyncedKeysInSql
> **findReferencedSyncedKeysInSql**(`sql`, `index`): `Set`<`string`>
Defined in: packages/client/src/lazy-guard.ts:125
The compiled-SQL scan across EVERY synced relation (eager AND lazy): the registry keys whose quoted reference token appears in `sql`. Same exact-token matching as [findReferencedLazyKeysInSql](/api/client/functions/findreferencedlazykeysinsql/), just over [LazyGuardIndex.allTokens](/api/client/interfaces/lazyguardindex/#alltokens). Drives the hydration guarantee — the caller maps each key to its consistency group and gates a live subscription’s `hydrating` on every group still catching up.
## Parameters
[Section titled “Parameters”](#parameters)
### sql
[Section titled “sql”](#sql)
`string`
### index
[Section titled “index”](#index)
[`LazyGuardIndex`](/api/client/interfaces/lazyguardindex/)
## Returns
[Section titled “Returns”](#returns)
`Set`<`string`>
# generateLocalSchemaSql
> **generateLocalSchemaSql**<`TRegistry`>(`registry`): `string`
Defined in: packages/client/src/schema.ts:550
The complete local schema — durable statements then ephemeral statements — the single script the wipe/rebuild paths and every external caller keep using. For an all-persistent registry the durable stream is the registry-ordered stream, followed by the always-applied ephemeral portion — which for any registry with a writable table carries the `pgxsinkit_all_mutations` TEMP VIEW as its trailing statement. For a MIXED registry the script is idempotent-equivalent to (not a character-for-character match of) a single-pass interleave: the persistent clusters precede the ephemeral ones and the ephemeral portion re-emits idempotent enum guards — a reordering of independent `IF NOT EXISTS` DDL that executes to the same schema. Equals `durable + ephemeral` by construction (test-covered).
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
## Parameters
[Section titled “Parameters”](#parameters)
### registry
[Section titled “registry”](#registry)
`TRegistry`
## Returns
[Section titled “Returns”](#returns)
`string`
# getAllMutationsView
> **getAllMutationsView**(`_registry`): `PgViewWithSelection`
Defined in: packages/client/src/local-tables.ts:611
## Parameters
[Section titled “Parameters”](#parameters)
### \_registry
[Section titled “\_registry”](#_registry)
`SyncTableRegistry`
## Returns
[Section titled “Returns”](#returns)
`PgViewWithSelection`
# getJournalTable
> **getJournalTable**<`TRegistry`>(`registry`, `tableKey`): [`JournalTable`](/api/client/type-aliases/journaltable/)
Defined in: packages/client/src/local-tables.ts:403
The `_mutations` journal table as a runtime Drizzle object (writable tables only). The fixed runtime columns are typed; the PK/entity columns ride the index signature and are reached by DB column NAME (`journal["id"]`), because per-entry typing is not representable — see [JournalTable](/api/client/type-aliases/journaltable/).
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
## Parameters
[Section titled “Parameters”](#parameters)
### registry
[Section titled “registry”](#registry)
`TRegistry`
### tableKey
[Section titled “tableKey”](#tablekey)
`string` & keyof `TRegistry`
## Returns
[Section titled “Returns”](#returns)
[`JournalTable`](/api/client/type-aliases/journaltable/)
# getLocalMetaTable
> **getLocalMetaTable**(`registry`): `PgTableWithColumns`
Defined in: packages/client/src/local-tables.ts:636
The `pgxsinkit_local_meta` key/value table (ADR-0006) under the registry’s local schema.
## Parameters
[Section titled “Parameters”](#parameters)
### registry
[Section titled “registry”](#registry)
`SyncTableRegistry`
## Returns
[Section titled “Returns”](#returns)
`PgTableWithColumns`
# getOutboxTable
> **getOutboxTable**(`registry`): `PgTableWithColumns`
Defined in: packages/client/src/local-tables.ts:624
The **Outbox** (ADR-0053 decision 2) under the registry’s local schema, as a runtime Drizzle object — the one relation the Event lane stages into. Registry-WIDE (one table, a `stream` column) and memoized per local schema, exactly like [getLocalMetaTable](/api/client/functions/getlocalmetatable/); it takes the registry only to resolve that schema.
Public on purpose: the table’s shape is contract, so an app composing a best-guess view (pending events over down-synced aggregates) authors it as tier-① Drizzle instead of hand-written SQL — `client.query((c) => c.drizzle.select().from(getOutboxTable(registry)).where(eq(outbox.stream, "…")))`.
## Parameters
[Section titled “Parameters”](#parameters)
### registry
[Section titled “registry”](#registry)
`SyncTableRegistry`
## Returns
[Section titled “Returns”](#returns)
`PgTableWithColumns`
# getOverlayTable
> **getOverlayTable**<`TRegistry`, `TKey`>(`registry`, `tableKey`): [`OverlayTable`](/api/client/type-aliases/overlaytable/)<`TRegistry`\[`TKey`]>
Defined in: packages/client/src/local-tables.ts:374
The `_overlay` optimistic-intent table as a runtime Drizzle object (writable tables only — throws for a readonly entry, which has no overlay/journal projection). Its entity columns are typed exactly as [OverlayTable](/api/client/type-aliases/overlaytable/) describes — real per-column types under a concretely-typed registry, the open index-signature fallback under a bare `SyncTableRegistry` — plus the two fixed overlay columns.
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
### TKey
[Section titled “TKey”](#tkey)
`TKey` *extends* `string`
## Parameters
[Section titled “Parameters”](#parameters)
### registry
[Section titled “registry”](#registry)
`TRegistry`
### tableKey
[Section titled “tableKey”](#tablekey)
`TKey`
## Returns
[Section titled “Returns”](#returns)
[`OverlayTable`](/api/client/type-aliases/overlaytable/)<`TRegistry`\[`TKey`]>
# getReadModelView
> **getReadModelView**<`TRegistry`, `TKey`>(`registry`, `tableKey`): [`ReadModelView`](/api/client/type-aliases/readmodelview/)<`TRegistry`\[`TKey`]>
Defined in: packages/client/src/local-tables.ts:480
The `_read_model` overlay-merged read view (ADR-0004) as a runtime Drizzle object: every projected synced column under the entry’s own property key plus the two overlay columns (`overlay_kind`, `local_updated_at_us`), mirroring the generator’s `CREATE VIEW`. The entry’s own `entry.view` is schema-UNQUALIFIED (`defineSyncTable` builds it with a bare `pgView`), so a consumer whose local store lives in a non-public schema must author against this qualified object instead of `entry.view`.
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
### TKey
[Section titled “TKey”](#tkey)
`TKey` *extends* `string`
## Parameters
[Section titled “Parameters”](#parameters)
### registry
[Section titled “registry”](#registry)
`TRegistry`
### tableKey
[Section titled “tableKey”](#tablekey)
`TKey`
## Returns
[Section titled “Returns”](#returns)
[`ReadModelView`](/api/client/type-aliases/readmodelview/)<`TRegistry`\[`TKey`]>
# getSyncedLocalTable
> **getSyncedLocalTable**<`TRegistry`, `TKey`>(`registry`, `tableKey`): `EntryLocalTable`<`TRegistry`\[`TKey`]>
Defined in: packages/client/src/local-tables.ts:348
The projected SYNCED read-cache table as a runtime Drizzle object, under the resolved local name (`clientProjection.syncedTable` override honoured) and the registry’s local schema — the exact relation the generator’s `CREATE TABLE` provisions. Prefer `entry.localTable` where its name already matches; this object exists for the runtime/tests that must track the projection rename. Carries the entry’s real projected columns under a concretely-typed registry, `AnyPgTable` under a bare `SyncTableRegistry` (see SyncedLocalTable).
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
### TKey
[Section titled “TKey”](#tkey)
`TKey` *extends* `string`
## Parameters
[Section titled “Parameters”](#parameters)
### registry
[Section titled “registry”](#registry)
`TRegistry`
### tableKey
[Section titled “tableKey”](#tablekey)
`TKey`
## Returns
[Section titled “Returns”](#returns)
`EntryLocalTable`<`TRegistry`\[`TKey`]>
# getSyncStateView
> **getSyncStateView**<`TRegistry`>(`registry`, `tableKey`): [`SyncStateView`](/api/client/type-aliases/syncstateview/)
Defined in: packages/client/src/local-tables.ts:438
The `_sync_state` convergence view (ADR-0011) as a runtime Drizzle object (writable tables only) — PK columns under the entry’s own property keys plus the fixed state columns, mirroring `buildSyncStateView`’s projection. The fixed state columns are typed; the PK columns ride the index signature (not recoverable at the type level — see [SyncStateView](/api/client/type-aliases/syncstateview/)) and are reached by their drizzle PROPERTY key (`view["authorId"]`), unlike the journal, which keys its PK columns by DB column name.
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
## Parameters
[Section titled “Parameters”](#parameters)
### registry
[Section titled “registry”](#registry)
`TRegistry`
### tableKey
[Section titled “tableKey”](#tablekey)
`string` & keyof `TRegistry`
## Returns
[Section titled “Returns”](#returns)
[`SyncStateView`](/api/client/type-aliases/syncstateview/)
# instrumentShapeFetch
> **instrumentShapeFetch**(`baseFetch?`): *typeof* `fetch`
Defined in: packages/client/src/debug.ts:72
Wrap a `fetch` so every ShapeStream request (the Electric read-path long-poll/catch-up) emits a start line (shape/table, offset, live — derived from the request URL params) and a completion line (status, ms, and whether the response is up-to-date). Passthrough when instrumentation is off: the early return runs before any URL parsing, so the off-path pays nothing per request beyond this closure itself.
Inject it as a ShapeStream `fetchClient`; pass the stream’s existing `fetchClient` (if any) as the base so the instrumentation composes rather than replaces.
## Parameters
[Section titled “Parameters”](#parameters)
### baseFetch?
[Section titled “baseFetch?”](#basefetch)
*typeof* `fetch` = `fetch`
## Returns
[Section titled “Returns”](#returns)
*typeof* `fetch`
# isBridgeEnvelope
> **isBridgeEnvelope**(`data`): `data is BridgeEnvelope`
Defined in: packages/client/src/worker/protocol.ts:548
Type-guard a received message as a bridge envelope of the current protocol version (ignore foreign traffic).
## Parameters
[Section titled “Parameters”](#parameters)
### data
[Section titled “data”](#data)
`unknown`
## Returns
[Section titled “Returns”](#returns)
`data is BridgeEnvelope`
# parseRetryAfterMs
> **parseRetryAfterMs**(`headerValue`, `nowMs`): `number` | `null`
Defined in: packages/client/src/event-lane.ts:348
Parse a `Retry-After` header (delta-seconds or HTTP-date) into ms, or null when absent/unreadable.
## Parameters
[Section titled “Parameters”](#parameters)
### headerValue
[Section titled “headerValue”](#headervalue)
`string` | `null` | `undefined`
### nowMs
[Section titled “nowMs”](#nowms)
`number`
## Returns
[Section titled “Returns”](#returns)
`number` | `null`
# performDatadirDump
> **performDatadirDump**(`pglite`, `compression`, `startPerf`): `Promise`<{ `checkpointMs`: `number`; `checkpointStartedAtMs`: `number`; `dumped`: `File` | `Blob`; `dumpMs`: `number`; `dumpStartedAtMs`: `number`; }>
Defined in: packages/client/src/export-store.ts:216
The datadir dump both exports share (ADR-0035): a `CHECKPOINT` through the store’s normal query path, then `dumpDataDir`. The store backup keeps its bytes as the artefact; the diagnostic dump feeds them to a throwaway clone. Factored out so exactly one implementation of “flush + tar the live datadir” exists — `performStoreExport` and `performDiagnosticExport` cannot drift on the checkpoint ordering or the timing house style. Returns the raw `dumpDataDir` output plus the two phase walls, both offset from `startPerf` (the caller’s export-start monotonic anchor) so the timings compose into either report.
The `CHECKPOINT` is a utility statement — Drizzle has no builder for it, so a raw `exec` is the justified tier-③ form here. Running it via `pglite.exec` serialises it behind any in-flight engine work on PGlite’s single connection, flushing dirty buffers to the datadir the dump then reads — so the tarball reflects committed state, not a torn mid-write datadir.
## Parameters
[Section titled “Parameters”](#parameters)
### pglite
[Section titled “pglite”](#pglite)
`Pick`<[`ClientPGlite`](/api/client/type-aliases/clientpglite/), `"exec"` | `"dumpDataDir"`>
### compression
[Section titled “compression”](#compression)
`"none"` | `"gzip"` | `"auto"`
### startPerf
[Section titled “startPerf”](#startperf)
`number`
## Returns
[Section titled “Returns”](#returns)
`Promise`<{ `checkpointMs`: `number`; `checkpointStartedAtMs`: `number`; `dumped`: `File` | `Blob`; `dumpMs`: `number`; `dumpStartedAtMs`: `number`; }>
# performDataExport
> **performDataExport**(`deps`, `options?`): `Promise`<[`DataExportResult`](/api/client/interfaces/dataexportresult/)>
Defined in: packages/client/src/export-data.ts:236
Run a data export (ADR-0035): drain the journal (unless the escape hatch is set) → throwaway-clone `pg_dump -t` per synced table → concatenate the comment + enum DDL header → assemble the report. The caller (`createSyncClient`) awaits engine-ready and enters the lifecycle slot BEFORE calling this, so the whole drain+dump runs under the store’s single lifecycle slot (the drain must sit inside it — see the module header).
## Parameters
[Section titled “Parameters”](#parameters)
### deps
[Section titled “deps”](#deps)
[`DataExportDeps`](/api/client/interfaces/dataexportdeps/)
### options?
[Section titled “options?”](#options)
[`DataExportOptions`](/api/client/interfaces/dataexportoptions/) = `{}`
## Returns
[Section titled “Returns”](#returns)
`Promise`<[`DataExportResult`](/api/client/interfaces/dataexportresult/)>
# performDiagnosticExport
> **performDiagnosticExport**(`deps`, `options?`): `Promise`<[`DiagnosticExportResult`](/api/client/interfaces/diagnosticexportresult/)>
Defined in: packages/client/src/export-dump.ts:209
Run a diagnostic dump (ADR-0035): live datadir dump → memory-backed throwaway clone → `pg_dump` → discard the clone → assemble the report. The caller (`createSyncClient`) awaits engine-ready and enters the lifecycle slot BEFORE calling this, exactly as it does for the store backup — kept out of here so the helper stays a pure “do the dump” unit.
## Parameters
[Section titled “Parameters”](#parameters)
### deps
[Section titled “deps”](#deps)
[`DiagnosticExportDeps`](/api/client/interfaces/diagnosticexportdeps/)
### options?
[Section titled “options?”](#options)
[`DiagnosticExportOptions`](/api/client/interfaces/diagnosticexportoptions/) = `{}`
## Returns
[Section titled “Returns”](#returns)
`Promise`<[`DiagnosticExportResult`](/api/client/interfaces/diagnosticexportresult/)>
# performStoreExport
> **performStoreExport**(`deps`, `options?`): `Promise`<[`StoreExportResult`](/api/client/interfaces/storeexportresult/)>
Defined in: packages/client/src/export-store.ts:260
Run a live store backup (ADR-0035): checkpoint → dump → snapshot diagnostics → assemble the report. The caller (`createSyncClient`) is responsible for awaiting engine-ready and entering the lifecycle slot BEFORE calling this — kept out of here so the helper stays a pure “do the dump” unit. No engine suspension: the backup is live by design.
## Parameters
[Section titled “Parameters”](#parameters)
### deps
[Section titled “deps”](#deps)
[`StoreExportDeps`](/api/client/interfaces/storeexportdeps/)
### options?
[Section titled “options?”](#options)
[`StoreExportOptions`](/api/client/interfaces/storeexportoptions/) = `{}`
## Returns
[Section titled “Returns”](#returns)
`Promise`<[`StoreExportResult`](/api/client/interfaces/storeexportresult/)>
# postBridgeMessage
> **postBridgeMessage**(`port`, `codec`, `type`, `payload`, `id?`, `transfer?`): `void`
Defined in: packages/client/src/worker/protocol.ts:563
Send a typed message over a port: encode → post (with any transferables). The one write choke point. `transfer` lets a caller declare payload-specific transferables (e.g. a store-backup’s `ArrayBuffer`, ADR-0035) WITHOUT teaching the shared codec about that op — they are merged after any the codec itself produces, so both the codec’s future zero-copy path and per-message transfers coexist.
## Parameters
[Section titled “Parameters”](#parameters)
### port
[Section titled “port”](#port)
[`BridgePort`](/api/client/interfaces/bridgeport/)
### codec
[Section titled “codec”](#codec)
[`BridgeCodec`](/api/client/interfaces/bridgecodec/)
### type
[Section titled “type”](#type)
[`BridgeMessageType`](/api/client/type-aliases/bridgemessagetype/)
### payload
[Section titled “payload”](#payload)
`unknown`
### id?
[Section titled “id?”](#id)
`string`
### transfer?
[Section titled “transfer?”](#transfer)
`unknown`\[]
## Returns
[Section titled “Returns”](#returns)
`void`
# provisionSyncWorker
> **provisionSyncWorker**<`TRegistry`>(`options`): `Promise`<`void`>
Defined in: packages/client/src/worker/attach-sync-client.ts:2580
Pre-spawn a worker’s store WITHOUT attaching (ADR-0032 decision 5). Sent at the board’s login screen against a freshly-named spare `SharedWorker`: the worker runs PGlite `create`/initdb only and holds the raw store idle until the real [attachSyncClient](/api/client/functions/attachsyncclient/) claim adopts it. Resolves when the worker acks the provision (its initdb settled); rejects if the worker reports the create failed, if it refuses the storage declaration, if the elected engine is unconstructible, or — bounding the whole thing — with [ProvisionExpiredError](/api/client/classes/provisionexpirederror/) once `provisionExpiryMs` (default 60000) elapses without any of those. The caller treats every outcome as best-effort: a provision that fails or expires costs the accelerator, not the boot — the attach that follows meets whatever the worker actually opened (an expiry bounds THIS promise; the worker-side create attempt is kept or retired by the placement — see [ProvisionExpiredError](/api/client/classes/provisionexpirederror/)).
Routed through the SAME placement-query-first flow as [attachSyncClient](/api/client/functions/attachsyncclient/) (ADR-0049 step 8): a SW-direct (or declared-idbfs) SharedWorker takes the `provision` bridge envelope on the SW port; a router-only (`electionRequired`) SharedWorker DROPS bridge envelopes, so provision drives the election coordinator’s PROVISION CLAIM (expiry-bounded) and delivers the `provision` over the elected engine’s per-tab PIPE. The coordinator is REGISTERED per store so a later attach on the same tab ADOPTS the grant — no second lock, no second engine, no double initdb (invariant 2). Elected mode REQUIRES a createEngineWorker factory (mirroring attach); its absence is a clear rejection, never a hang.
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
## Parameters
[Section titled “Parameters”](#parameters)
### options
[Section titled “options”](#options)
`ProvisionSyncWorkerOptions`<`TRegistry`>
## Returns
[Section titled “Returns”](#returns)
`Promise`<`void`>
# quiesceStoreWorker
> **quiesceStoreWorker**(`worker`, `opts?`): `Promise`<[`StoreWorkerQuiesceOutcome`](/api/client/interfaces/storeworkerquiesceoutcome/)>
Defined in: packages/client/src/worker/attach-sync-client.ts:401
Quiesce the SharedWorker HOSTING a store, by path, so its backend connection is released BEFORE a [destroyStoreArtifacts](/api/client/functions/destroystoreartifacts/) on the same path (ADR-0050). The gap it closes: the board’s workers are `extendedLifetime`, so a store’s SharedWorker OUTLIVES the document that spawned it — and the idbfs backend holds its IndexedDB connection open for the engine’s whole life. So after a reload, an idbfs store’s worker is still alive and still holding the connection; `deleteDatabase` blocks and `destroyStoreArtifacts` fails, boot after boot, until the browser eventually reaps the worker. OPFS releases its handles when idle, so it never had this problem — this is the idbfs (SW-direct) fix.
`worker` is a factory (or instance), exactly like [attachSyncClient](/api/client/functions/attachsyncclient/)’s `worker` — the caller constructs `new SharedWorker(url, { name: storePath })`, so the library stays DOM-free. Connecting by that name reaches the LIVE worker (if any) or spawns a fresh one; either way the sequence is: post the declaration, query placement, and — for a `shared-worker` home — send `engine-teardown` and await its reserved ack, which the host posts only after `closeHost()` has stopped the engine and released the backend connection (it then closes its own scope). An `elected-worker` home resolves immediately with `toreDown: false` (see the field).
Idempotent and safe on an already-dead store: a fresh spawn boots no engine, and its teardown closes an empty host. Compose it as `await quiesceStoreWorker(f).catch(() => {}); await destroyStoreArtifacts(path)` — a quiesce failure (timeout) must NOT abort the destroy, whose own ownership-lag handling reports honestly and leaves the path re-runnable.
## Parameters
[Section titled “Parameters”](#parameters)
### worker
[Section titled “worker”](#worker)
`WorkerLike` | (() => `WorkerLike`)
### opts?
[Section titled “opts?”](#opts)
[`StoreWorkerQuiesceOptions`](/api/client/interfaces/storeworkerquiesceoptions/)
## Returns
[Section titled “Returns”](#returns)
`Promise`<[`StoreWorkerQuiesceOutcome`](/api/client/interfaces/storeworkerquiesceoutcome/)>
# replAdapter
> **replAdapter**(`client`): [`ReplInspectionSurface`](/api/client/interfaces/replinspectionsurface/)
Defined in: packages/client/src/index.ts:1778
Shape a [SyncClient](/api/client/interfaces/syncclient/)’s inspection surface (`rawQuery`/`rawExec`) as the `{ query, exec }` duck `@electric-sql/pglite-repl` needs. Identical on the in-process and worker-attached client — on the latter each statement routes through the worker bridge, so the REPL works even though `client.pglite` is unavailable. Cast the result at the `` prop (the REPL types the prop as a full `PGlite`). The surface is registry-independent, so it accepts any client’s `rawQuery`/`rawExec` pair.
## Parameters
[Section titled “Parameters”](#parameters)
### client
[Section titled “client”](#client)
`Pick`<[`SyncClient`](/api/client/interfaces/syncclient/)<`SyncTableRegistry`>, `"rawQuery"` | `"rawExec"`>
## Returns
[Section titled “Returns”](#returns)
[`ReplInspectionSurface`](/api/client/interfaces/replinspectionsurface/)
# resolveBatchEventUrl
> **resolveBatchEventUrl**(`batchEventUrl`): `string`
Defined in: packages/client/src/event-lane.ts:281
Resolve the ingestion endpoint under the same HARD-REQUIRED-PATH rule the mutation client applies to `batchWriteUrl`: a relative URL must BE the canonical path, an absolute one must end in it, and neither may carry a query or fragment. The endpoint is toolkit-owned (`createSyncServer` mounts it), so a URL that cannot be it is a configuration error worth failing on at boot rather than at the first append.
## Parameters
[Section titled “Parameters”](#parameters)
### batchEventUrl
[Section titled “batchEventUrl”](#batcheventurl)
`string`
## Returns
[Section titled “Returns”](#returns)
`string`
# resolveStoreBoot
> **resolveStoreBoot**(`storePath`, `opts`): `Promise`<[`StoreBootResolution`](/api/client/interfaces/storebootresolution/)>
Defined in: packages/client/src/store-boot.ts:313
Resolve where a store boots and finish any destructive/candidate work the verdict demands, then return the `dataDir` + `storageBackend` the mint seam opens at. The full plan boot classification 1–6, EXECUTED:
* **memory override** → `memory://` passthrough, no classification (the sanctioned test/ephemeral lane has no meta machinery).
* **non-browser** (no idb, no opfs handles) → `file://` passthrough, no classification (the filesystem backend has no meta machinery either).
* **browser** → read the meta record (readStoreMetaRecord; StoreMetaUnreadableError propagates = fail closed, invariant 12), map META\_STORE\_UNAVAILABLE to a provable absence (no idb ⇒ no record and no existing idb store), observe the commitment namespace and the recordless idb fact, classify, and execute:
* `resume-deletion` → complete the destructive lifecycle, then RE-CLASSIFY from the now-clean state (bounded by MAX\_DELETION\_RECLASSIFY).
* `delete-candidate-and-rebuild` → delete the stale sentinel AND the candidate directory (a barrier-gap crash’s sentinel must never survive) AND the record, then RE-CLASSIFY from the now-recordless state — so an idb store at this path is opened in place rather than shadowed by the rebuild.
* `repair-record-then-open-committed` → write `opfs-committed`, then open committed.
* `open-committed` → open the committed opfs store (open failures are HARD at mint time; the bounded retries for transient UnknownError-class failures live in the mint seam’s factory-call wrapper). A record already at `opfs-committed` takes the WARM FAST PATH: it is classified straight off the record, so neither the commitment-namespace observation nor the recordless-idb probe runs at all (both are irrelevant to classification 2).
* `boot-idb-authoritative` → write `idb-authoritative` FIRST when there is no record yet (recordless idb), then `idb://`. TERMINAL: a store’s backend is fixed at first mint, so an idb store stays idb whatever this boot’s capabilities are — the only route to another backend is a deliberate destroy + a fresh boot.
* `virgin-create` → with opfs access, beginFreshCandidate (record `opfs-candidate` BEFORE the directory) → `opfs://` UNCOMMITTED (barrier is step 10b/11); without opfs access, `idb-authoritative` → `idb://`.
## Parameters
[Section titled “Parameters”](#parameters)
### storePath
[Section titled “storePath”](#storepath)
`string`
### opts
[Section titled “opts”](#opts)
[`ResolveStoreBootOptions`](/api/client/interfaces/resolvestorebootoptions/)
## Returns
[Section titled “Returns”](#returns)
`Promise`<[`StoreBootResolution`](/api/client/interfaces/storebootresolution/)>
# resolveStoreDataDir
> **resolveStoreDataDir**(`storePath`, `backendOverride?`, `env?`): `string`
Defined in: packages/client/src/store-path.ts:199
Resolve a plain storePath to the PGlite dataDir URL the store opens at — the ONE derivation point (ADR-0036 decision 2, amended by ADR-0049). A browser worker with a proven OPFS sync-access grant resolves to `opfs://`; another browser context (`indexedDB` present) resolves to `idb://`; Bun/Node resolves to `file://` (relative paths use the working directory). The `backendOverride` is internal-only: `"memory"` selects a scheme-selected `memory://` test/ephemeral store.
Rejects a scheme-bearing or empty/whitespace-only path with [InvalidStorePathError](/api/client/classes/invalidstorepatherror/) — the storePath contract fails loudly at the boundary, never silently re-interpreted. The returned URL is internal plumbing; do not surface it to consumers as something to imitate.
CRITICAL (ADR-0036 decision 5, probed on PGlite 0.5.4): memory selection is ALWAYS the scheme-selected `memory://` form, NEVER PGlite’s explicit `fs: new MemoryFS()` option — `dumpDataDir` from an explicit-`fs` instance silently omits relation files created after initdb, so a restored clone raises “relation does not exist”. Callers that need a memory store must route through this function, never construct `MemoryFS`.
ADR-0049 (D1) adds the `opfs://` form: when the placement probe granted a sync-access handle in the executing scope (`env.hasOpfsSyncAccess`), the browser store lives on `opfs-repacked`. Precedence: memory override (test/ephemeral) → `opfs://` (probe granted) → `idb://` (browser, handle denied) → `file://` (Bun/Node). `opfs://` is TOOLKIT-INTERNAL plumbing — PGlite does NOT accept it as a `dataDir`; [createClientPGlite](/api/client/functions/createclientpglite/) (plan step 10) interprets it via the opfs-repacked factory + the OPFS namespace builders below. Like every URL resolved here, it never leaks to consumers.
## Parameters
[Section titled “Parameters”](#parameters)
### storePath
[Section titled “storePath”](#storepath)
`string`
### backendOverride?
[Section titled “backendOverride?”](#backendoverride)
`"memory"`
### env?
[Section titled “env?”](#env)
`StoreEnv` = `...`
## Returns
[Section titled “Returns”](#returns)
`string`
# rowKey
> **rowKey**(`row`, `pkColumns`): `string`
Defined in: packages/client/src/worker/live-diff.ts:20
The stable key for a result row (§4). With `pkColumns`, join their values (composite-safe) — this is the diff-keying identity, so a row whose PK is unchanged is the SAME logical row even if other columns moved. Without `pkColumns` (a keyless query) fall back to the whole-row JSON value: still diff-shaped (never a full resend), but an update surfaces as remove+add rather than a `changed` — the documented keyless fallback. Duplicate identical keyless rows collapse under this scheme (accepted for the fallback).
## Parameters
[Section titled “Parameters”](#parameters)
### row
[Section titled “row”](#row)
`Record`<`string`, `unknown`>
### pkColumns
[Section titled “pkColumns”](#pkcolumns)
readonly `string`\[] | `undefined`
## Returns
[Section titled “Returns”](#returns)
`string`
# runFreshCommitmentBarrier
> **runFreshCommitmentBarrier**(`storePath`, `strictSyncReturns`, `seams?`): `Promise`<`void`>
Defined in: packages/client/src/index.ts:814
## Parameters
[Section titled “Parameters”](#parameters)
### storePath
[Section titled “storePath”](#storepath)
`string`
### strictSyncReturns
[Section titled “strictSyncReturns”](#strictsyncreturns)
() => `Promise`<`void`>
### seams?
[Section titled “seams?”](#seams)
`FreshCommitmentSeams`
## Returns
[Section titled “Returns”](#returns)
`Promise`<`void`>
# runThrowawayCloneDump
> **runThrowawayCloneDump**(`pglite`, `startPerf`, `options?`): `Promise`<[`CloneDumpResult`](/api/client/interfaces/clonedumpresult/)>
Defined in: packages/client/src/export-dump.ts:132
## Parameters
[Section titled “Parameters”](#parameters)
### pglite
[Section titled “pglite”](#pglite)
`Pick`<[`ClientPGlite`](/api/client/type-aliases/clientpglite/), `"exec"` | `"dumpDataDir"`>
### startPerf
[Section titled “startPerf”](#startperf)
`number`
### options?
[Section titled “options?”](#options)
`CloneDumpOptions` = `{}`
## Returns
[Section titled “Returns”](#returns)
`Promise`<[`CloneDumpResult`](/api/client/interfaces/clonedumpresult/)>
# seedLiveDiffState
> **seedLiveDiffState**(`rows`, `pkColumns`): [`LiveDiffState`](/api/client/interfaces/livediffstate/)
Defined in: packages/client/src/worker/live-diff.ts:46
Seed the diff state from the initial snapshot (which the worker sends verbatim, not as a diff).
## Parameters
[Section titled “Parameters”](#parameters)
### rows
[Section titled “rows”](#rows)
readonly `Record`<`string`, `unknown`>\[]
### pkColumns
[Section titled “pkColumns”](#pkcolumns)
readonly `string`\[] | `undefined`
## Returns
[Section titled “Returns”](#returns)
[`LiveDiffState`](/api/client/interfaces/livediffstate/)
# setSyncDebugSink
> **setSyncDebugSink**(`sink`): `void`
Defined in: packages/client/src/debug.ts:29
Install (or clear, with `undefined`) the debug-rail sink. Idempotent; `defineSyncWorker` owns it.
## Parameters
[Section titled “Parameters”](#parameters)
### sink
[Section titled “sink”](#sink)
`SyncDebugSink` | `undefined`
## Returns
[Section titled “Returns”](#returns)
`void`
# storeIndexedDbDatabaseName
> **storeIndexedDbDatabaseName**(`storePath`): `string`
Defined in: packages/client/src/store-path.ts:312
The IndexedDB database name a browser store occupies (ADR-0036) — a browser-only OPERATIONAL helper for orphan GC / corrupt-store deletion (`indexedDB.deleteDatabase(...)`), NOT part of the create path. PGlite maps `idb://` to the IndexedDB database `/pglite/` (its `WASM_PREFIX` `/pglite` joined with the path after the scheme; verified against `@electric-sql/pglite` 0.5.4 dist). Exposed so a consumer that GCs its own stores routes that PGlite-internal naming knowledge through the library rather than re-deriving the `/pglite/` prefix itself. Rejects a scheme-bearing/empty path exactly as [resolveStoreDataDir](/api/client/functions/resolvestoredatadir/) does, so the two stay in lockstep.
## Parameters
[Section titled “Parameters”](#parameters)
### storePath
[Section titled “storePath”](#storepath)
`string`
## Returns
[Section titled “Returns”](#returns)
`string`
# storeTargetExists
> **storeTargetExists**(`storePath`, `backendOverride?`, `env?`): `Promise`<`boolean`>
Defined in: packages/client/src/store-path.ts:377
Does either backend a browser boot could own for a plain storePath ALREADY exist? The fresh-target gate for a restore (ADR-0035 decision 6) — restore refuses a target that is already there. Per backend:
* **memory** (`backendOverride === "memory"`) — a scheme-selected memory store is fresh by construction: it lives only for the instance about to be created, so there is nothing to collide with. Always `false` (the sanctioned test/ephemeral lane never blocks a restore).
* **`file://`** (Bun/Node) — filesystem existence of the datadir directory. `node:fs` is imported DYNAMICALLY inside this branch, never at module top level, so a browser bundle (which only ever hits the `idb://` branch below) never pulls `node:fs` in. Relative paths resolve against the working directory, exactly as PGlite’s filesystem backend and [resolveStoreDataDir](/api/client/functions/resolvestoredatadir/) do.
* **`opfs://`** (browser, placement probe granted) — existence of the store DIRECTORY at `pgxsinkit/stores/` (opfsStoreDirectoryPath), walked via `navigator.storage.getDirectory()` and a `getDirectoryHandle` chain with `{ create: false }`. This is ONLY the fresh-target gate; it is emphatically NOT commitment authority — a store directory without a commitment marker is an uncommitted CANDIDATE, and deciding that is the store meta record’s phase machine (plan step 2), never this function. `navigator.storage`/`getDirectory` absent → best-effort `false` (mirrors the idb `databases()` stance — never fabricate a positive). A `NotFoundError` anywhere in the chain → `false`; any OTHER error propagates (a genuine failure the restore caller must see). A missing OPFS directory does not finish a granted browser check: IDB is also probed, because a predecessor or fixed-placement store may still own the same public path.
* **`idb://`** (browser/worker) — `indexedDB.databases()` enumerated for the store’s database name ([storeIndexedDbDatabaseName](/api/client/functions/storeindexeddbdatabasename/)). BEST-EFFORT: `databases()` is unavailable on some engines (older Firefox, certain worker contexts); when absent we CANNOT prove existence, so we report `false` and let the restore proceed rather than fabricate a result — a real overlay collision would still surface as a PGlite-level boot failure. We never fake a positive.
## Parameters
[Section titled “Parameters”](#parameters)
### storePath
[Section titled “storePath”](#storepath)
`string`
### backendOverride?
[Section titled “backendOverride?”](#backendoverride)
`"memory"`
### env?
[Section titled “env?”](#env)
`StoreEnv` = `...`
## Returns
[Section titled “Returns”](#returns)
`Promise`<`boolean`>
# syncDebug
> **syncDebug**(`event`, `data?`): `void`
Defined in: packages/client/src/debug.ts:38
Log one timestamped event. Prints to the console only when `globalThis.__pgxsinkitDebug` is on, but ALSO feeds any installed [setSyncDebugSink](/api/client/functions/setsyncdebugsink/) sink (so the worker can forward the rail to tabs even when the worker’s own console gate is off). No sink AND not enabled → an early return, so the off-path pays nothing.
## Parameters
[Section titled “Parameters”](#parameters)
### event
[Section titled “event”](#event)
`string`
### data?
[Section titled “data?”](#data)
`Record`<`string`, `unknown`>
## Returns
[Section titled “Returns”](#returns)
`void`
# timeAsync
> **timeAsync**<`T`>(`event`, `fn`, `data?`): `Promise`<`T`>
Defined in: packages/client/src/debug.ts:142
Run `fn`, logging ` done` with its wall-clock duration (and any extra `data`). When instrumentation is off this is a thin pass-through with no logging and no timing overhead beyond the call itself. Returns whatever `fn` returns.
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### T
[Section titled “T”](#t)
`T`
## Parameters
[Section titled “Parameters”](#parameters)
### event
[Section titled “event”](#event)
`string`
### fn
[Section titled “fn”](#fn)
() => `Promise`<`T`>
### data?
[Section titled “data?”](#data)
`Record`<`string`, `unknown`>
## Returns
[Section titled “Returns”](#returns)
`Promise`<`T`>
# wrapEngineWorker
> **wrapEngineWorker**(`worker`): [`ElectedEngineWorker`](/api/client/interfaces/electedengineworker/)
Defined in: packages/client/src/worker/attach-sync-client.ts:162
## Parameters
[Section titled “Parameters”](#parameters)
### worker
[Section titled “worker”](#worker)
`WorkerLike`
## Returns
[Section titled “Returns”](#returns)
[`ElectedEngineWorker`](/api/client/interfaces/electedengineworker/)
# wrapLiveQueryForMaterialization
> **wrapLiveQueryForMaterialization**(`sql`, `fields`): `string`
Defined in: packages/client/src/live-rows-sql.ts:39
Make a live-read SQL statement SAFE TO MATERIALISE by giving every output column a UNIQUE explicit name.
`fields` is the ordered list of unique output aliases — one per output column, in the compiled SQL’s column order (Drizzle emits columns depth-first over the select’s field keys, which are unique by construction). The statement is wrapped in a derived table with a POSITIONAL column-alias-list:
```plaintext
SELECT * FROM () "__pgx_live" ("", "", …)
```
A plain `SELECT * FROM ()` does NOT dedupe — the inner duplicate names survive — so the column-alias-list is what renames every output column POSITIONALLY to a distinct name. The wrapped query materialises cleanly, and its rows come back keyed by the aliases (so same-named source columns keep DISTINCT values). `SELECT *` from the aliased derived table is safe precisely because the alias list has already made the names unique.
Returns the SQL UNCHANGED when `fields` is absent or empty — the default path for callers (raw SQL strings, or non-colliding queries that pass no `fields`): they keep name-keyed rows exactly as before. Only a caller that supplies `fields` opts into alias-keyed rows.
## Parameters
[Section titled “Parameters”](#parameters)
### sql
[Section titled “sql”](#sql)
`string`
### fields
[Section titled “fields”](#fields)
readonly `string`\[] | `undefined`
## Returns
[Section titled “Returns”](#returns)
`string`
# AttachAckPayload
Defined in: packages/client/src/worker/protocol.ts:229
worker → tab: attach acknowledged. `alreadyBooted` is false for the attach that booted the engine.
## Properties
[Section titled “Properties”](#properties)
### alreadyBooted
[Section titled “alreadyBooted”](#alreadybooted)
> **alreadyBooted**: `boolean`
Defined in: packages/client/src/worker/protocol.ts:230
***
### bootSettled?
[Section titled “bootSettled?”](#bootsettled)
> `optional` **bootSettled?**: `boolean`
Defined in: packages/client/src/worker/protocol.ts:245
***
### bootSettledError?
[Section titled “bootSettledError?”](#bootsettlederror)
> `optional` **bootSettledError?**: `BridgeErrorWire`
Defined in: packages/client/src/worker/protocol.ts:254
***
### engineReady?
[Section titled “engineReady?”](#engineready)
> `optional` **engineReady?**: `boolean`
Defined in: packages/client/src/worker/protocol.ts:237
True when the engine’s initial sync had ALREADY fired by the time this attach was acked (a late attach, ADR-0032 FIX 3). `ready` is monotonic in-process — once it resolves it stays resolved even if the phase later degrades — so a tab attaching after that moment must resolve `ready` immediately from the ack, rather than wait for a phase-“ready” status that may never come again.
***
### error?
[Section titled “error?”](#error)
> `optional` **error?**: `BridgeErrorWire`
Defined in: packages/client/src/worker/protocol.ts:261
Present when the engine boot REJECTED for this attach: the tab rejects `attachSyncClient` with this message instead of hanging forever on the ack (ADR-0032 FIX 1). A later attach retries the boot. This is a LOCAL-READ-CORE failure (the engine never reached `localReadReady`); a tail failure after `localReadReady` uses the stage-error fields above and does NOT reject the attach.
***
### writeReady?
[Section titled “writeReady?”](#writeready)
> `optional` **writeReady?**: `boolean`
Defined in: packages/client/src/worker/protocol.ts:244
Late-attach milestone fold (ADR-0041 stage 2): true when the engine had ALREADY crossed `writeReady` / `bootSettled` by the time this attach was acked, so a tab attaching after the stage fired resolves it straight from the ack rather than waiting for a `milestone` broadcast it missed. The ack fires AT `localReadReady`, so that stage is always implied by a non-error ack and carries no boolean.
***
### writeReadyError?
[Section titled “writeReadyError?”](#writereadyerror)
> `optional` **writeReadyError?**: `BridgeErrorWire`
Defined in: packages/client/src/worker/protocol.ts:253
Late-attach failure fold (ADR-0041 stage 2): present when the engine’s background write/sync tail had already REJECTED a downstream stage before this attach was acked. The tab rejects the matching stage promise (so a gated write / `bootSettled` awaiter fails loudly rather than hangs) — but the attach itself still RESOLVES (the engine reached `localReadReady`; only the tail failed). A live milestone failure after attach rides the `milestone-error` broadcast instead.
# AttachPayload
Defined in: packages/client/src/worker/protocol.ts:164
tab → worker: the attach handshake. The first attach boots the engine; later attaches join it.
## Properties
[Section titled “Properties”](#properties)
### config?
[Section titled “config?”](#config)
> `optional` **config?**: `object`
Defined in: packages/client/src/worker/protocol.ts:206
Serializable per-attach config overrides (kept minimal; the urls are baked into the worker). `role` selects which baked registry the worker boots (ADR-0032 S3): the spare is minted role-agnostic, so the role is only known at claim/attach — a single worker file bakes BOTH registries and picks here. `freshStore` is the fresh-store prefetch-overlap hint (ADR-0032 S4): the tab’s claim path knows a claimed spare is schemaless (fresh) while a mapped/returning store never is, and forwards that here so the worker’s `createSyncClient` can overlap the shape catch-up with the local boot phases.
#### freshStore?
[Section titled “freshStore?”](#freshstore)
> `optional` **freshStore?**: `boolean`
#### role?
[Section titled “role?”](#role)
> `optional` **role?**: `string`
#### syncEnabled?
[Section titled “syncEnabled?”](#syncenabled)
> `optional` **syncEnabled?**: `boolean`
***
### executionLimit?
[Section titled “executionLimit?”](#executionlimit)
> `optional` **executionLimit?**: `object`
Defined in: packages/client/src/worker/protocol.ts:179
ADR-0049 D5: the tab’s engine-construction limit, checked by the host before it acknowledges attach.
#### maxDispatchMs?
[Section titled “maxDispatchMs?”](#maxdispatchms)
> `optional` **maxDispatchMs?**: `number`
***
### restore?
[Section titled “restore?”](#restore)
> `optional` **restore?**: `RestoreArtefactWire`
Defined in: packages/client/src/worker/protocol.ts:188
Restore the store from a backup on THIS attach (ADR-0035 decision 6) — the worker-mode carrier of `createSyncClient`’s `restoreFrom`. Restore rides the FIRST attach (the boot attach): a `File`/`Blob` cannot cross `postMessage` as a transferable, so the tab decomposes it into a transferred `ArrayBuffer` plus its name/mime — the [ExportArtefactWire](/api/client/interfaces/exportartefactwire/) pattern in reverse — and the worker recomposes it into a `Blob` and hands it to `createSyncClient`. Absent on every ordinary attach. An attach that carries this AFTER the engine has already booted is REFUSED (you cannot restore into a running store).
***
### storage?
[Section titled “storage?”](#storage)
> `optional` **storage?**: `SyncStorageDeclaration`
Defined in: packages/client/src/worker/protocol.ts:195
The tab’s WIRE storage declaration (ADR-0050) — repeated from the pre-placement declaration message so the ENGINE binds it (durability included) wherever it runs, the elected dedicated engine included (whose scope never saw the SharedWorker’s declaration message). First payload binds; a later explicit disagreement with the bound resolution is refused typed (`StorageDeclarationRefusedError`).
***
### storeId?
[Section titled “storeId?”](#storeid)
> `optional` **storeId?**: `string`
Defined in: packages/client/src/worker/protocol.ts:166
The bound store id (SharedWorker naming resolves it tab-side before attach — ADR-0032 decision 5).
***
### storePath?
[Section titled “storePath?”](#storepath)
> `optional` **storePath?**: `string`
Defined in: packages/client/src/worker/protocol.ts:171
Explicit plain store PATH (ADR-0036) if the worker should create its own store — a name, not a storage URL; the worker derives the backend. When omitted, `storeId`/the worker default is used.
***
### token
[Section titled “token”](#token)
> **token**: [`AuthTokenSnapshot`](/api/client/interfaces/authtokensnapshot/) | `null`
Defined in: packages/client/src/worker/protocol.ts:197
The tab’s current auth token at attach time, or null when unauthenticated.
# AttachSyncClientOptions
Defined in: packages/client/src/worker/attach-sync-client.ts:590
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
## Properties
[Section titled “Properties”](#properties)
### awaitOwnershipRelease?
[Section titled “awaitOwnershipRelease?”](#awaitownershiprelease)
> `optional` **awaitOwnershipRelease?**: () => `Promise`<`void`>
Defined in: packages/client/src/worker/attach-sync-client.ts:731
Await the VFS ownership release after a deliberate engine termination (ADR-0049 step 8). Defaults to the documented no-op resolvedOwnershipRelease — the HONEST MINIMUM (step 11b follow-up 1): the OPFS-repacked VFS enforces ownership with EXCLUSIVE OPFS sync-access handles (`StoreOwnedError` / `STORE_OWNED`), NOT a Web Lock, so there is no lock name to `navigator.locks.request(..., { ifAvailable })` against here. The bounded wait therefore lives in the SUCCESSOR’S OPEN PATH: the respawned elected engine worker’s own `createOpfsRepacked` open throws the owned-store contention error and is retried with backoff (the `openWithBoundedRetries` wrapper in `createClientPGlite`) until the dead worker’s agent releases the handle — exactly the fault-matrix row “VFS ownership-lock release lag → successor open retries on contention until clear, bounded, then boot failure”. Supply a custom async wait only to inject a real probe (tests do).
#### Returns
[Section titled “Returns”](#returns)
`Promise`<`void`>
***
### bridgeSilenceMs?
[Section titled “bridgeSilenceMs?”](#bridgesilencems)
> `optional` **bridgeSilenceMs?**: `number`
Defined in: packages/client/src/worker/attach-sync-client.ts:676
The bridge-silence deadline (ms) for non-leader reconnection (ADR-0049 D5). DISABLED when undefined (the default); the election coordinator (step 8) sets it. When set: a pending op left with NO bridge traffic since it was posted, past this deadline, triggers ONE reconnect attempt via the [worker](/api/client/interfaces/attachsyncclientoptions/#worker) FACTORY (if the input is a factory) — construct a fresh SharedWorker, resolve its port, re-attach, flush the queue, re-subscribe. With a bare-instance input reconstruction is structurally unavailable, so no reconnect is armed. Scheduled on [timers](/api/client/interfaces/attachsyncclientoptions/#timers).
***
### codec?
[Section titled “codec?”](#codec)
> `optional` **codec?**: [`BridgeCodec`](/api/client/interfaces/bridgecodec/)
Defined in: packages/client/src/worker/attach-sync-client.ts:655
***
### createEngineWorker?
[Section titled “createEngineWorker?”](#createengineworker)
> `optional` **createEngineWorker?**: () => [`ElectedEngineWorker`](/api/client/interfaces/electedengineworker/)
Defined in: packages/client/src/worker/attach-sync-client.ts:700
The elected engine worker OVERRIDE (ADR-0049 D5). In `elected-worker` placement (a router-only SharedWorker) the tab’s election coordinator spawns the real engine as a dedicated `Worker`. NORMALLY NO WIRING IS NEEDED: the worker entry is dual-scope (one file serves both homes), the SharedWorker reports its own script URL in the placement reply, and the winning tab constructs the engine as `new Worker(swScriptUrl, { type: "module" })` itself. Supply this override ONLY for entries that cannot be reconstructed from their URL as a module worker (classic-script workers, `blob:`/`data:` URLs, CSP constraints); wrap the constructed worker with [wrapEngineWorker](/api/client/functions/wrapengineworker/). When election is required but NEITHER a derivable URL NOR this override is available, attach fails with the typed [ElectedEngineUnconstructibleError](/api/client/classes/electedengineunconstructibleerror/) — never a silent no-engine attach.
#### Returns
[Section titled “Returns”](#returns-1)
[`ElectedEngineWorker`](/api/client/interfaces/electedengineworker/)
***
### executionLimit?
[Section titled “executionLimit?”](#executionlimit)
> `optional` **executionLimit?**: [`ExecutionLimitConfig`](/api/client/interfaces/executionlimitconfig/)
Defined in: packages/client/src/worker/attach-sync-client.ts:711
The opt-in engine-construction EXECUTION LIMIT (ADR-0049 D5) as this tab carries it — every tab attaching to a store MUST carry the SAME value the worker was constructed with (`ExecutionLimitMismatchError` on a mismatch). DISABLED by default (`undefined` / absent `maxDispatchMs`) — no finite worst-case query duration exists, so enabling the limit (which converts slow to terminated by policy) is a deliberate consumer choice. When `maxDispatchMs` is set AND this tab is on an elected per-tab pipe, a dispatched RPC still outstanding past the limit is reported to the router as an `overdue-dispatch` (the router then probes the engine’s control channel; a WASM-blocked engine cannot answer → the leader retires + respawns it). ELECTED PLACEMENT ONLY — on SW-direct the option is rejected as unsupported during attach rather than silently ignored.
***
### freshStore?
[Section titled “freshStore?”](#freshstore)
> `optional` **freshStore?**: `boolean`
Defined in: packages/client/src/worker/attach-sync-client.ts:629
Fresh-store prefetch-overlap hint (ADR-0032 S4), forwarded in the attach `config.freshStore`. Set true ONLY when the tab knows the store is a claimed schemaless spare (never for a mapped/returning store); the worker’s `createSyncClient` then overlaps the shape catch-up with its local boot phases.
***
### getToken?
[Section titled “getToken?”](#gettoken)
> `optional` **getToken?**: () => `Promise`<[`AuthTokenSnapshot`](/api/client/interfaces/authtokensnapshot/) | `null`>
Defined in: packages/client/src/worker/attach-sync-client.ts:608
The tab’s token provider (ADR-0032 decision 3). Richer than `createSyncClient`’s string form: the worker needs the EXPIRY to apply its pull margin, so this yields `{accessToken, expiresAt}` (or null when unauthenticated). Pushed at attach and answered on every worker pull-request.
#### Returns
[Section titled “Returns”](#returns-2)
`Promise`<[`AuthTokenSnapshot`](/api/client/interfaces/authtokensnapshot/) | `null`>
***
### handoffQueue?
[Section titled “handoffQueue?”](#handoffqueue)
> `optional` **handoffQueue?**: `object`
Defined in: packages/client/src/worker/attach-sync-client.ts:684
The bounded handoff queue (ADR-0049 invariant 9). While the handoff window is open — after a relocation notice, before the replacement pipe’s handshake completes — new data-path ops are QUEUED, not posted. `cap` overflow or `deadlineMs` expiry fails queued ops with `EngineRelocatedError("not-dispatched")` (they never left the tab, so they are safe to retry). Defaults: `cap` 256, `deadlineMs` 15000. The deadline is scheduled on [timers](/api/client/interfaces/attachsyncclientoptions/#timers).
#### cap?
[Section titled “cap?”](#cap)
> `optional` **cap?**: `number`
#### deadlineMs?
[Section titled “deadlineMs?”](#deadlinems)
> `optional` **deadlineMs?**: `number`
***
### keepaliveIntervalMs?
[Section titled “keepaliveIntervalMs?”](#keepaliveintervalms)
> `optional` **keepaliveIntervalMs?**: `number`
Defined in: packages/client/src/worker/attach-sync-client.ts:717
The leader-keepalive ping cadence (ms) the election coordinator uses (ADR-0049 step 8). Default 20000. The keepalive is the ONE standing timer that detects SharedWorker death (unanswered pings) → reconstruct via the [worker](/api/client/interfaces/attachsyncclientoptions/#worker) factory + re-announce the still-live engine. Lower it to detect SW death faster.
***
### keepaliveMissThreshold?
[Section titled “keepaliveMissThreshold?”](#keepalivemissthreshold)
> `optional` **keepaliveMissThreshold?**: `number`
Defined in: packages/client/src/worker/attach-sync-client.ts:719
Consecutive unanswered keepalive pings before SharedWorker reconstruction (ADR-0049 step 8). Default 2.
***
### onBootReport?
[Section titled “onBootReport?”](#onbootreport)
> `optional` **onBootReport?**: (`report`) => `void`
Defined in: packages/client/src/worker/attach-sync-client.ts:667
Boot observability (ADR-0034): invoked once with the worker engine’s finalized [BootReport](/api/client/interfaces/bootreport/) if the engine’s boot finalizes WHILE this tab is attached (the one-shot `boot-report` broadcast). A tab that attaches AFTER the boot never receives the push — it reads the report via [SyncClient.bootReport](/api/client/interfaces/syncclient/#bootreport).
#### Parameters
[Section titled “Parameters”](#parameters)
##### report
[Section titled “report”](#report)
[`BootReport`](/api/client/interfaces/bootreport/)
#### Returns
[Section titled “Returns”](#returns-3)
`void`
***
### onConflict?
[Section titled “onConflict?”](#onconflict)
> `optional` **onConflict?**: (`details`) => `void`
Defined in: packages/client/src/worker/attach-sync-client.ts:657
#### Parameters
[Section titled “Parameters”](#parameters-1)
##### details
[Section titled “details”](#details)
[`MutationDetail`](/api/client/interfaces/mutationdetail/)\[]
#### Returns
[Section titled “Returns”](#returns-4)
`void`
***
### onQuarantine?
[Section titled “onQuarantine?”](#onquarantine)
> `optional` **onQuarantine?**: (`details`) => `void`
Defined in: packages/client/src/worker/attach-sync-client.ts:658
#### Parameters
[Section titled “Parameters”](#parameters-2)
##### details
[Section titled “details”](#details-1)
[`MutationDetail`](/api/client/interfaces/mutationdetail/)\[]
#### Returns
[Section titled “Returns”](#returns-5)
`void`
***
### onReject?
[Section titled “onReject?”](#onreject)
> `optional` **onReject?**: (`details`) => `void`
Defined in: packages/client/src/worker/attach-sync-client.ts:659
#### Parameters
[Section titled “Parameters”](#parameters-3)
##### details
[Section titled “details”](#details-2)
[`MutationDetail`](/api/client/interfaces/mutationdetail/)\[]
#### Returns
[Section titled “Returns”](#returns-6)
`void`
***
### onSchemaChange?
[Section titled “onSchemaChange?”](#onschemachange)
> `optional` **onSchemaChange?**: (`event`) => `void`
Defined in: packages/client/src/worker/attach-sync-client.ts:660
#### Parameters
[Section titled “Parameters”](#parameters-4)
##### event
[Section titled “event”](#event)
[`LocalStoreVersionEvent`](/api/client/interfaces/localstoreversionevent/)
#### Returns
[Section titled “Returns”](#returns-7)
`void`
***
### onStatusChange?
[Section titled “onStatusChange?”](#onstatuschange)
> `optional` **onStatusChange?**: (`status`) => `void`
Defined in: packages/client/src/worker/attach-sync-client.ts:656
#### Parameters
[Section titled “Parameters”](#parameters-5)
##### status
[Section titled “status”](#status)
`SyncRuntimeStatus`
#### Returns
[Section titled “Returns”](#returns-8)
`void`
***
### onSyncError?
[Section titled “onSyncError?”](#onsyncerror)
> `optional` **onSyncError?**: (`error`) => `void`
Defined in: packages/client/src/worker/attach-sync-client.ts:661
#### Parameters
[Section titled “Parameters”](#parameters-6)
##### error
[Section titled “error”](#error)
`Error`
#### Returns
[Section titled “Returns”](#returns-9)
`void`
***
### port?
[Section titled “port?”](#port)
> `optional` **port?**: [`BridgePort`](/api/client/interfaces/bridgeport/)
Defined in: packages/client/src/worker/attach-sync-client.ts:602
A raw transport port (a `MessageChannel` port in tests, a `SharedWorker.port` in a browser).
***
### registry
[Section titled “registry”](#registry)
> **registry**: `TRegistry`
Defined in: packages/client/src/worker/attach-sync-client.ts:591
***
### restoreFrom?
[Section titled “restoreFrom?”](#restorefrom)
> `optional` **restoreFrom?**: `File` | `Blob`
Defined in: packages/client/src/worker/attach-sync-client.ts:643
Restore the worker’s store from a backup on attach (ADR-0035 decision 6) — the worker-mode `restoreFrom`. A `File`/`Blob` as produced by [SyncClient.exportStore](/api/client/interfaces/syncclient/#exportstore); the facade decomposes it into a transferred `ArrayBuffer` + name/mime (RestoreArtefactWire) and the worker recomposes it for `createSyncClient`. Restore rides the one handshake that reaches the ENGINE HOME: a restore-bearing attach awaits the placement reply, then carries the artifact on the SW-port handshake when the in-scope host is the engine (SW-direct / declared-idbfs) or on the first per-tab PIPE handshake when the engine is elected — the router-only SharedWorker is payload-blind, so a restore posted there would be dropped and destroyed. Passing it when the engine has ALREADY booted rejects the attach with a typed error (`RestoreIntoRunningStoreError` by name — you cannot restore into a running store). The restored engine boots offline and its recovered journal is quarantined, exactly as in-process — see `restoreFrom` on `createSyncClient`.
***
### role?
[Section titled “role?”](#role)
> `optional` **role?**: `string`
Defined in: packages/client/src/worker/attach-sync-client.ts:623
Which baked registry the worker boots (ADR-0032 S3) — forwarded in the attach `config.role`. A worker file that bakes multiple role variants (the board’s admin/member) picks by this; single-registry workers ignore it.
***
### storage?
[Section titled “storage?”](#storage)
> `optional` **storage?**: `SyncStorageDeclaration`
Defined in: packages/client/src/worker/attach-sync-client.ts:654
The tab’s WIRE storage declaration for this store (ADR-0050) — posted as the declaration message on the worker port BEFORE the placement query (so `backend: "idbfs"` can skip the probe), and carried on the provision/attach payloads so the engine binds it (durability included). Omit (or `{}`) for “no opinion”: a registry-attached static declaration, else the capability defaults, decide. A registry-attached declaration is AUTHORITATIVE — an explicit field here that disagrees with it (or with the declaration another tab already bound) is a typed refusal (`StorageDeclarationRefusedError`), never silently overridden: a store’s declaration is immutable, and a preference change mints a fresh store under a fresh path instead.
***
### storeId?
[Section titled “storeId?”](#storeid)
> `optional` **storeId?**: `string`
Defined in: packages/client/src/worker/attach-sync-client.ts:610
The bound store id (resolved tab-side before attach — SharedWorker naming, ADR-0032 decision 5).
***
### storePath?
[Section titled “storePath?”](#storepath)
> `optional` **storePath?**: `string`
Defined in: packages/client/src/worker/attach-sync-client.ts:616
The plain store PATH (ADR-0036) the worker opens if it must create its own store — a name, not a storage URL. Spread `memoryStoreForTests(...)` from `@pgxsinkit/client/testing` here for a memory store in tests (it also carries the internal marker forwarded to the worker as the memory-backend override).
***
### syncEnabled?
[Section titled “syncEnabled?”](#syncenabled)
> `optional` **syncEnabled?**: `boolean`
Defined in: packages/client/src/worker/attach-sync-client.ts:617
***
### timers?
[Section titled “timers?”](#timers)
> `optional` **timers?**: `AttachClientTimers`
Defined in: packages/client/src/worker/attach-sync-client.ts:689
Injectable timers for the handoff-queue deadline and the bridge-silence reconnect (ADR-0049 step 7) — the same deterministic-test seam `engine-router.ts` exposes. Defaults to `globalThis.setTimeout`/`clearTimeout`.
***
### worker?
[Section titled “worker?”](#worker)
> `optional` **worker?**: `WorkerLike` | (() => `WorkerLike`)
Defined in: packages/client/src/worker/attach-sync-client.ts:600
The worker input — PRIMARILY a FACTORY `() => SharedWorker` (ADR-0049 D5). A `SharedWorker` object cannot be reconstructed from itself, so the factory is what makes SharedWorker-death recovery (the keepalive reconstruction, and the [bridgeSilenceMs](/api/client/interfaces/attachsyncclientoptions/#bridgesilencems) bridge-silence reconnect) a GUARANTEE rather than an option: both re-invoke it for a fresh SharedWorker. A bare instance (a native `Worker`/`SharedWorker`, or anything port-shaped) is ALSO accepted for tests and exotic hosts — reconstruction is then structurally unavailable (diagnostics say so). Provide this OR [port](/api/client/interfaces/attachsyncclientoptions/#port).
# AuthTokenSnapshot
Defined in: packages/client/src/worker/protocol.ts:101
A single auth token snapshot the tab owns and pushes to the worker (ADR-0032 decision 3).
## Properties
[Section titled “Properties”](#properties)
### accessToken
[Section titled “accessToken”](#accesstoken)
> **accessToken**: `string`
Defined in: packages/client/src/worker/protocol.ts:102
***
### expiresAt
[Section titled “expiresAt”](#expiresat)
> **expiresAt**: `number`
Defined in: packages/client/src/worker/protocol.ts:104
Absolute expiry in epoch milliseconds (`Date.now()`-comparable), so the worker can apply an expiry margin.
# BootReport
Defined in: packages/client/src/boot-report.ts:21
A structured, versioned record of one client boot (ADR-0034). `reportVersion` is a contract number: additive fields keep it, a breaking reshape bumps it. All durations are milliseconds; all `*AtMs` are offsets from boot start (the `startedAt` epoch anchor is the only wall-clock value).
## Properties
[Section titled “Properties”](#properties)
### engineHome?
[Section titled “engineHome?”](#enginehome)
> `optional` **engineHome?**: `"in-process"` | `"shared-worker"` | `"elected-worker"`
Defined in: packages/client/src/boot-report.ts:50
Where the engine ran for this boot (ADR-0049 decision 12): `"in-process"` for the main-thread/Bun `createSyncClient`; inside `defineSyncWorker`, the placement-probe result — `"shared-worker"` (the engine boots in the SharedWorker itself, WebKit today) or `"elected-worker"` (a tab-spawned dedicated worker holds the handles, Chromium/Firefox). Absent (omitted) when the boot cannot derive it (e.g. a dedicated elected-engine worker that never ran the SharedWorker placement decision). Additive field; `reportVersion` stays `1`.
***
### freshStore
[Section titled “freshStore”](#freshstore)
> **freshStore**: `boolean`
Defined in: packages/client/src/boot-report.ts:26
Whether the caller proved the store a schemaless spare (the ADR-0032 S4 fresh-store hint).
***
### groups
[Section titled “groups”](#groups)
> **groups**: `object`\[]
Defined in: packages/client/src/boot-report.ts:139
Per consistency GROUP, for the eager + promoted boot groups only (a lazily-activated-later group never appears — nor mutates a finalized report). Groups run concurrently, so `fetchMs`/`applyMs` are per-group wall SEGMENTS, not a partition of `totalMs`.
#### applyMs
[Section titled “applyMs”](#applyms)
> **applyMs**: `number`
Wall around this group’s batch commits into PGlite. Includes waiting behind another group’s transaction on the shared connection (single writer), so concurrent groups’ `applyMs` can overlap.
#### fetchMs
[Section titled “fetchMs”](#fetchms)
> **fetchMs**: `number`
Settle→next-delivery wall within this group’s chain. On the single-threaded WASM host this absorbs OTHER groups’ apply transactions and main-thread work between deliveries — read it as “time this group spent not applying”, an upper bound on its network wait, not pure network cost.
#### groupKey
[Section titled “groupKey”](#groupkey)
> **groupKey**: `string`
#### readyAtMs
[Section titled “readyAtMs”](#readyatms)
> **readyAtMs**: `number`
Offset from boot start when the group reached its initial sync.
#### requests
[Section titled “requests”](#requests)
> **requests**: `number`
Number of batch deliveries the group’s stream chain received during boot catch-up.
#### rows
[Section titled “rows”](#rows)
> **rows**: `number`
Number of change rows ingested during boot catch-up.
#### startedAtMs
[Section titled “startedAtMs”](#startedatms)
> **startedAtMs**: `number`
Offset from boot start when the group’s streams started.
#### tables
[Section titled “tables”](#tables)
> **tables**: `number`
Number of member tables (shapes) in the group.
***
### localReadReadyMs
[Section titled “localReadReadyMs”](#localreadreadyms)
> **localReadReadyMs**: `number` | `null`
Defined in: packages/client/src/boot-report.ts:73
Boot start → `localReadReady` resolved (ADR-0041): PGlite open, durable schema compatible, store-version reconcile complete, and the drizzle read facade built — cached reads are safe with ZERO network. `null` when the boot rejected before the stage. Additive field; `reportVersion` stays `1`.
***
### mode
[Section titled “mode”](#mode)
> **mode**: `"in-process"` | `"worker"`
Defined in: packages/client/src/boot-report.ts:24
How the engine booted: the in-process client (bun/Node/fallback) or inside `defineSyncWorker`.
***
### overlapPrefetch
[Section titled “overlapPrefetch”](#overlapprefetch)
> **overlapPrefetch**: `boolean`
Defined in: packages/client/src/boot-report.ts:61
Whether the ADR-0032 S4 fetch/schema overlap was active for this boot.
***
### phases
[Section titled “phases”](#phases)
> **phases**: `object`
Defined in: packages/client/src/boot-report.ts:91
#### catchupMs
[Section titled “catchupMs”](#catchupms)
> **catchupMs**: `number`
Sync-start done → last eager boot group ready.
#### journalRecoveryMs
[Section titled “journalRecoveryMs”](#journalrecoveryms)
> **journalRecoveryMs**: `number`
#### pgliteCreateMs
[Section titled “pgliteCreateMs”](#pglitecreatems)
> **pgliteCreateMs**: `number` | `null`
PGlite create cost, or `null` when the store was adopted from a spare (see [BootReport.provision](/api/client/interfaces/bootreport/#provision)).
#### prepareMs?
[Section titled “prepareMs?”](#preparems)
> `optional` **prepareMs?**: `number`
Cumulative time in configured prepare hooks (`prepareLocalDbBeforeSchema` + `prepareLocalDbAfterSchema`), present only when at least one hook is configured. Not part of the required v1 shape (ADR-0034).
#### schemaExecMs
[Section titled “schemaExecMs”](#schemaexecms)
> **schemaExecMs**: `number`
#### storeVersionReconcileMs
[Section titled “storeVersionReconcileMs”](#storeversionreconcilems)
> **storeVersionReconcileMs**: `number`
#### syncStartMs
[Section titled “syncStartMs”](#syncstartms)
> **syncStartMs**: `number`
`startConfiguredSync`: stream/group construction wall. On an overlap boot (ADR-0032 S4, [BootReport.overlapPrefetch](/api/client/interfaces/bootreport/#overlapprefetch)) the early-started segment runs concurrently with schema, journal recovery, and registry reconciliation, so this includes that shared wall. Structurally 0 when the boot is ready inside the sync-start call itself (zero eager groups / instant catch-up) — finalize runs before the phase closes.
***
### provision
[Section titled “provision”](#provision)
> **provision**: { `initdbMs`: `number`; `provisionedMsBeforeBoot`: `number`; } | `null`
Defined in: packages/client/src/boot-report.ts:85
Present only when the store was pre-provisioned (a spare’s initdb ran off-thread before this boot adopted it); `null` otherwise. When present, `phases.pgliteCreateMs` is `null` — the create cost is reported here instead.
#### Union Members
[Section titled “Union Members”](#union-members)
##### Type Literal
[Section titled “Type Literal”](#type-literal)
{ `initdbMs`: `number`; `provisionedMsBeforeBoot`: `number`; }
##### initdbMs
[Section titled “initdbMs”](#initdbms)
> **initdbMs**: `number`
The spare’s PGlite create (initdb) cost, paid at provision time.
##### provisionedMsBeforeBoot
[Section titled “provisionedMsBeforeBoot”](#provisionedmsbeforeboot)
> **provisionedMsBeforeBoot**: `number`
How long the provisioned store sat ready before this boot adopted it.
***
`null`
***
### registryFingerprint
[Section titled “registryFingerprint”](#registryfingerprint)
> **registryFingerprint**: `string`
Defined in: packages/client/src/boot-report.ts:63
The registry fingerprint the store is provisioned under — the same value store-version reconcile stamps.
***
### reportVersion
[Section titled “reportVersion”](#reportversion)
> **reportVersion**: `1`
Defined in: packages/client/src/boot-report.ts:22
***
### startedAt
[Section titled “startedAt”](#startedat)
> **startedAt**: `number`
Defined in: packages/client/src/boot-report.ts:65
Epoch anchor (`Date.now()`) at boot start; every other duration/offset is monotonic relative to it.
***
### storageBackend?
[Section titled “storageBackend?”](#storagebackend)
> `optional` **storageBackend?**: `"opfs-repacked"` | `"idbfs"` | `"filesystem"` | `"memory"`
Defined in: packages/client/src/boot-report.ts:42
The store backend this boot actually opened (ADR-0049 decision 12): `"opfs-repacked"` (the placement probe granted sync-access handles in the engine home), `"idbfs"` (browser/worker, handles denied — today’s default), `"filesystem"` (Bun/Node), or `"memory"` (the sanctioned test/ephemeral lane). Derived from the minted dataDir scheme at the single client-owned mint seam; absent (omitted) on a BYO instance whose backend is underivable. Additive field; `reportVersion` stays `1`. Distinct from [BootReport.storeKind](/api/client/interfaces/bootreport/#storekind), which is untouched.
***
### storageFallbackReason?
[Section titled “storageFallbackReason?”](#storagefallbackreason)
> `optional` **storageFallbackReason?**: `string`
Defined in: packages/client/src/boot-report.ts:59
The verbatim reason an opfs-CAPABLE boot (the probe granted sync-access handles) nonetheless opened `idbfs` (ADR-0049 decision 12). Set ONLY when such a fallback actually occurred — never on a plain idb boot (the probe denied from the start), and never on a granted opfs boot that stayed on opfs. Today’s set-sites are the granted-then-idb transitions the client owns: the recordless idb-store downgrade (invariant 14 — an existing idb store is opened in place, never overwritten by a fresh opfs mint) and the virgin-uncreatable session idbfs fallback (the verbatim opfs open failure). Additive field; `reportVersion` stays `1`.
***
### storeKind
[Section titled “storeKind”](#storekind)
> **storeKind**: `"fresh"` | `"warm"` | `"restored"`
Defined in: packages/client/src/boot-report.ts:34
How this store presented at boot: `"restored"` when the boot seeded a brand-new store from a backup (ADR-0035 `restoreFrom`); `"fresh"` when the caller proved it a schemaless spare (the SAME signal as [BootReport.freshStore](/api/client/interfaces/bootreport/#freshstore)); `"warm"` otherwise (an existing persisted store — the common case). Distinct from `freshStore`, which stays a bare boolean: `storeKind` additionally names the restore case, which a boolean cannot express.
***
### totalMs
[Section titled “totalMs”](#totalms)
> **totalMs**: `number`
Defined in: packages/client/src/boot-report.ts:67
Boot start → `onInitialSync` (all eager groups caught up).
***
### warmBoot
[Section titled “warmBoot”](#warmboot)
> **warmBoot**: `object`
Defined in: packages/client/src/boot-report.ts:117
Warm-store observability for the durable-schema and journal-recovery fast paths. Grouped like [BootReport.phases](/api/client/interfaces/bootreport/#phases) so the flags structured-clone across the worker bridge as one unit.
#### journalRecoveryRequired
[Section titled “journalRecoveryRequired”](#journalrecoveryrequired)
> **journalRecoveryRequired**: `boolean`
Whether the durable recovery marker required journal recovery this boot. A clean settle clears the marker, allowing the next boot to skip the recovery pass.
#### journalRecoverySkipped
[Section titled “journalRecoverySkipped”](#journalrecoveryskipped)
> **journalRecoverySkipped**: `boolean`
Whether the boot-time `recoverSending` journal pass was skipped this boot.
#### journalRowsRecovered
[Section titled “journalRowsRecovered”](#journalrowsrecovered)
> **journalRowsRecovered**: `number` | `null`
Rows lifted `sending → pending` by recovery; `null` when the selected recovery path cannot count them.
#### journalTablesVisited
[Section titled “journalTablesVisited”](#journaltablesvisited)
> **journalTablesVisited**: `number`
How many writable table journals the boot-time `recoverSending` pass visited (the registry’s writable-entry count).
#### schemaFingerprintMatch
[Section titled “schemaFingerprintMatch”](#schemafingerprintmatch)
> **schemaFingerprintMatch**: `boolean`
Whether the stored durable-schema fingerprint matched the generated schema.
#### schemaSkipped
[Section titled “schemaSkipped”](#schemaskipped)
> **schemaSkipped**: `boolean`
Whether durable-schema replay was skipped this boot because the stored fingerprint matched.
***
### writeReadyMs
[Section titled “writeReadyMs”](#writereadyms)
> **writeReadyMs**: `number` | `null`
Defined in: packages/client/src/boot-report.ts:79
Boot start → `writeReady` resolved (ADR-0041): the mutation runtime is constructed and boot recovery (plus restore quarantine on a restore boot) has completed — enqueue is safe. `null` when the boot rejected before the stage. Additive field; `reportVersion` stays `1`.
# BridgeCodec
Defined in: packages/client/src/worker/protocol.ts:509
The codec every bridge payload crosses through (owner-mandated seam, ADR-0032 S2 §1). v1 ships exactly ONE codec — [identityCodec](/api/client/variables/identitycodec/), which relies on the transport’s own structured clone. The seam exists so a columnar/transferable codec (e.g. one that packs live-diff rows into a shared `ArrayBuffer` and returns it as a transferable) can be swapped in later WITHOUT any protocol change: the router already treats `BridgeEnvelope.payload` as opaque, and `encode` may return `{ payload, transfer }` so the sender hands transferables to `postMessage`. Keep encode/decode inverse and side-effect free.
## Properties
[Section titled “Properties”](#properties)
### decode
[Section titled “decode”](#decode)
> **decode**: (`payload`) => `unknown`
Defined in: packages/client/src/worker/protocol.ts:513
Decode an envelope body back into the raw payload. Inverse of [encode](/api/client/interfaces/bridgecodec/#encode).
#### Parameters
[Section titled “Parameters”](#parameters)
##### payload
[Section titled “payload”](#payload)
`unknown`
#### Returns
[Section titled “Returns”](#returns)
`unknown`
***
### encode
[Section titled “encode”](#encode)
> **encode**: (`payload`) => `object`
Defined in: packages/client/src/worker/protocol.ts:511
Encode a raw payload into the envelope body. May declare transferables for a future zero-copy codec.
#### Parameters
[Section titled “Parameters”](#parameters-1)
##### payload
[Section titled “payload”](#payload-1)
`unknown`
#### Returns
[Section titled “Returns”](#returns-1)
`object`
##### payload
[Section titled “payload”](#payload-2)
> **payload**: `unknown`
##### transfer?
[Section titled “transfer?”](#transfer)
> `optional` **transfer?**: `unknown`\[]
# BridgeEnvelope
Defined in: packages/client/src/worker/protocol.ts:53
The wire envelope. `payload` is the codec-encoded body — opaque to the router, which only routes on `type` and correlates on `id`. Keeping the body opaque is what lets a future codec return a columnar buffer (a transferable) in `payload` without any change to routing.
## Properties
[Section titled “Properties”](#properties)
### ch
[Section titled “ch”](#ch)
> **ch**: `"pgxsinkit-bridge"`
Defined in: packages/client/src/worker/protocol.ts:54
***
### id?
[Section titled “id?”](#id)
> `optional` **id?**: `string`
Defined in: packages/client/src/worker/protocol.ts:58
Correlation id for request/response pairs (rpc, subscribe, token pull). Absent for fire-and-forget fanout.
***
### payload
[Section titled “payload”](#payload)
> **payload**: `unknown`
Defined in: packages/client/src/worker/protocol.ts:60
Codec-encoded body — opaque to the router; decode with the same [BridgeCodec](/api/client/interfaces/bridgecodec/).
***
### type
[Section titled “type”](#type)
> **type**: [`BridgeMessageType`](/api/client/type-aliases/bridgemessagetype/)
Defined in: packages/client/src/worker/protocol.ts:56
***
### v
[Section titled “v”](#v)
> **v**: `1`
Defined in: packages/client/src/worker/protocol.ts:55
# BridgePort
Defined in: packages/client/src/worker/protocol.ts:33
The minimal MessagePort-shaped transport the bridge needs — the intersection of a real `MessagePort`, a `SharedWorker` port, and a dedicated `Worker`/`self`. Injected everywhere so the protocol layer is exercised with a plain `MessageChannel` (no Worker) in tests. `start()` is optional (MessagePort needs it, `Worker`/`self` do not); `close()` is optional (a dedicated `self` cannot be closed by the tab).
## Properties
[Section titled “Properties”](#properties)
### addEventListener
[Section titled “addEventListener”](#addeventlistener)
> **addEventListener**: (`type`, `listener`) => `void`
Defined in: packages/client/src/worker/protocol.ts:42
#### Parameters
[Section titled “Parameters”](#parameters)
##### type
[Section titled “type”](#type)
`"message"`
##### listener
[Section titled “listener”](#listener)
(`event`) => `void`
#### Returns
[Section titled “Returns”](#returns)
`void`
***
### close?
[Section titled “close?”](#close)
> `optional` **close?**: () => `void`
Defined in: packages/client/src/worker/protocol.ts:45
#### Returns
[Section titled “Returns”](#returns-1)
`void`
***
### postMessage
[Section titled “postMessage”](#postmessage)
> **postMessage**: (`message`, `transfer?`) => `void`
Defined in: packages/client/src/worker/protocol.ts:41
#### Parameters
[Section titled “Parameters”](#parameters-1)
##### message
[Section titled “message”](#message)
`unknown`
##### transfer?
[Section titled “transfer?”](#transfer)
`any`
#### Returns
[Section titled “Returns”](#returns-2)
`void`
***
### removeEventListener
[Section titled “removeEventListener”](#removeeventlistener)
> **removeEventListener**: (`type`, `listener`) => `void`
Defined in: packages/client/src/worker/protocol.ts:43
#### Parameters
[Section titled “Parameters”](#parameters-2)
##### type
[Section titled “type”](#type-1)
`"message"`
##### listener
[Section titled “listener”](#listener-1)
(`event`) => `void`
#### Returns
[Section titled “Returns”](#returns-3)
`void`
***
### start?
[Section titled “start?”](#start)
> `optional` **start?**: () => `void`
Defined in: packages/client/src/worker/protocol.ts:44
#### Returns
[Section titled “Returns”](#returns-4)
`void`
# CloneDumpPhases
Defined in: packages/client/src/export-dump.ts:80
The four extra phase walls the throwaway-clone pipeline adds on top of the shared checkpoint/dump pair.
## Properties
[Section titled “Properties”](#properties)
### checkpointMs
[Section titled “checkpointMs”](#checkpointms)
> **checkpointMs**: `number`
Defined in: packages/client/src/export-dump.ts:88
`CHECKPOINT` wall — flushing dirty buffers before the internal datadir dump the clone consumes.
***
### checkpointStartedAtMs
[Section titled “checkpointStartedAtMs”](#checkpointstartedatms)
> **checkpointStartedAtMs**: `number`
Defined in: packages/client/src/export-dump.ts:86
Offset from `startPerf` when the `CHECKPOINT` began.
***
### cloneBootMs
[Section titled “cloneBootMs”](#clonebootms)
> **cloneBootMs**: `number`
Defined in: packages/client/src/export-dump.ts:92
Clone boot wall — booting the memory-backed throwaway from the internal dump.
***
### cloneBootStartedAtMs
[Section titled “cloneBootStartedAtMs”](#clonebootstartedatms)
> **cloneBootStartedAtMs**: `number`
Defined in: packages/client/src/export-dump.ts:90
Offset from `startPerf` when the throwaway clone’s `PGlite.create({ loadDataDir })` began.
***
### dumpMs
[Section titled “dumpMs”](#dumpms)
> **dumpMs**: `number`
Defined in: packages/client/src/export-dump.ts:84
`dumpDataDir` wall — the uncompressed internal tarball the throwaway clone boots from (`compression: "none"`).
***
### dumpStartedAtMs
[Section titled “dumpStartedAtMs”](#dumpstartedatms)
> **dumpStartedAtMs**: `number`
Defined in: packages/client/src/export-dump.ts:82
Offset from `startPerf` when the internal `dumpDataDir` (the clone’s source) began.
***
### pgDumpMs
[Section titled “pgDumpMs”](#pgdumpms)
> **pgDumpMs**: `number`
Defined in: packages/client/src/export-dump.ts:96
`pg_dump` wall — the WASM `pg_dump` reading the clone out to SQL.
***
### pgDumpStartedAtMs
[Section titled “pgDumpStartedAtMs”](#pgdumpstartedatms)
> **pgDumpStartedAtMs**: `number`
Defined in: packages/client/src/export-dump.ts:94
Offset from `startPerf` when `pg_dump` began running against the clone.
# CloneDumpResult
Defined in: packages/client/src/export-dump.ts:100
The raw SQL bytes plus the pipeline phase walls — [runThrowawayCloneDump](/api/client/functions/runthrowawayclonedump/)’s result.
## Properties
[Section titled “Properties”](#properties)
### phases
[Section titled “phases”](#phases)
> **phases**: [`CloneDumpPhases`](/api/client/interfaces/clonedumpphases/)
Defined in: packages/client/src/export-dump.ts:104
The pipeline phase timings, all offset from the caller’s `startPerf` anchor.
***
### sqlBytes
[Section titled “sqlBytes”](#sqlbytes)
> **sqlBytes**: `Uint8Array`<`ArrayBuffer`>
Defined in: packages/client/src/export-dump.ts:102
The `pg_dump` output bytes (unwrapped from pglite-tools’ `File` polyfill).
# CommittedStoreUnreachableWire
Defined in: packages/client/src/store-boot.ts:164
The clone-safe wire form of a [CommittedStoreUnreachableError](/api/client/classes/committedstoreunreachableerror/), carried in the EXISTING bridge error `detail` field (the `{ message, name, detail }` shape `serializeError` produces) — no new protocol field. The attach side calls [committedStoreUnreachableFromWire](/api/client/functions/committedstoreunreachablefromwire/) on every bridge error `detail`, so a refusal raised in an engine home reaches the tab as the CLASS, not a name-tagged plain `Error`.
## Properties
[Section titled “Properties”](#properties)
### code
[Section titled “code”](#code)
> **code**: `"committed-store-unreachable"`
Defined in: packages/client/src/store-boot.ts:165
***
### storePath
[Section titled “storePath”](#storepath)
> **storePath**: `string`
Defined in: packages/client/src/store-boot.ts:166
# ConvergenceClient
Defined in: packages/client/src/convergence.ts:22
The mechanism primitives a convergence pass schedules.
## Properties
[Section titled “Properties”](#properties)
### flush
[Section titled “flush”](#flush)
> **flush**: () => `Promise`<`void`>
Defined in: packages/client/src/convergence.ts:23
#### Returns
[Section titled “Returns”](#returns)
`Promise`<`void`>
***
### reconcile
[Section titled “reconcile”](#reconcile)
> **reconcile**: () => `Promise`<`void`>
Defined in: packages/client/src/convergence.ts:24
#### Returns
[Section titled “Returns”](#returns-1)
`Promise`<`void`>
# ConvergenceDriver
Defined in: packages/client/src/convergence.ts:46
## Properties
[Section titled “Properties”](#properties)
### requestPass
[Section titled “requestPass”](#requestpass)
> **requestPass**: () => `void`
Defined in: packages/client/src/convergence.ts:61
Request a convergence pass now — the event-driven path. The client calls this the moment a mutation is enqueued, so a local write flushes immediately instead of waiting for the trigger’s next interval tick. Coalesced exactly like a trigger signal (one pass at a time; a request mid-pass queues a single follow-up) and still gated by `shouldConverge()`, so a write made while offline stages without flushing. With this, the trigger’s interval is only a fallback (retries/recovery), which lets it run far less often — the interval, not real convergence, is what costs idle CPU.
#### Returns
[Section titled “Returns”](#returns)
`void`
***
### start
[Section titled “start”](#start)
> **start**: () => `void`
Defined in: packages/client/src/convergence.ts:47
#### Returns
[Section titled “Returns”](#returns-1)
`void`
***
### stop
[Section titled “stop”](#stop)
> **stop**: () => `Promise`<`void`>
Defined in: packages/client/src/convergence.ts:52
Stop scheduling and **await any in-flight pass** before resolving, so a caller can then safely close or wipe the underlying database without a pass racing against it. Idempotent.
#### Returns
[Section titled “Returns”](#returns-2)
`Promise`<`void`>
# ConvergenceDriverOptions
Defined in: packages/client/src/convergence.ts:39
## Properties
[Section titled “Properties”](#properties)
### client
[Section titled “client”](#client)
> **client**: [`ConvergenceClient`](/api/client/interfaces/convergenceclient/)
Defined in: packages/client/src/convergence.ts:40
***
### onPass?
[Section titled “onPass?”](#onpass)
> `optional` **onPass?**: (`error`) => `void`
Defined in: packages/client/src/convergence.ts:43
Invoked after each convergence pass with the error it raised, or `null` on success.
#### Parameters
[Section titled “Parameters”](#parameters)
##### error
[Section titled “error”](#error)
`unknown`
#### Returns
[Section titled “Returns”](#returns)
`void`
***
### trigger
[Section titled “trigger”](#trigger)
> **trigger**: [`ConvergenceTrigger`](/api/client/interfaces/convergencetrigger/)
Defined in: packages/client/src/convergence.ts:41
# ConvergenceTrigger
Defined in: packages/client/src/convergence.ts:32
The scheduling-policy seam. Browser (`online` / `visibilitychange`) and React Native (`AppState` / `NetInfo`) are two genuine adapters — which is what earns this its place as a seam rather than being inlined.
## Properties
[Section titled “Properties”](#properties)
### shouldConverge
[Section titled “shouldConverge”](#shouldconverge)
> **shouldConverge**: () => `boolean`
Defined in: packages/client/src/convergence.ts:36
Whether a convergence pass should run right now (e.g. online and foregrounded).
#### Returns
[Section titled “Returns”](#returns)
`boolean`
***
### subscribe
[Section titled “subscribe”](#subscribe)
> **subscribe**: (`onSignal`) => () => `void`
Defined in: packages/client/src/convergence.ts:34
Register a callback fired whenever the app wants a convergence attempt; returns an unsubscribe.
#### Parameters
[Section titled “Parameters”](#parameters)
##### onSignal
[Section titled “onSignal”](#onsignal)
() => `void`
#### Returns
[Section titled “Returns”](#returns-1)
() => `void`
# CreateClientPGliteOptions
Defined in: packages/client/src/index.ts:381
Options for [createClientPGlite](/api/client/functions/createclientpglite/).
## Properties
[Section titled “Properties”](#properties)
### bootAssets?
[Section titled “bootAssets?”](#bootassets)
> `optional` **bootAssets?**: `Promise`<[`PgliteBootAssets`](/api/client/interfaces/pglitebootassets/)>
Defined in: packages/client/src/index.ts:387
Pre-warmed PGlite boot assets (see [CreateSyncClientOptions.pgliteBootAssets](/api/client/interfaces/createsyncclientoptions/#pglitebootassets)). Awaited and passed into `PGlite.create`; a rejected warm is caught to `undefined` (falls back to PGlite’s own asset load), so it never fails the create.
***
### restoreFrom?
[Section titled “restoreFrom?”](#restorefrom)
> `optional` **restoreFrom?**: `File` | `Blob`
Defined in: packages/client/src/index.ts:414
A store-backup tarball to seed the new store from (ADR-0035 decision 6, restore) — a `File`/`Blob` as produced by [SyncClient.exportStore](/api/client/interfaces/syncclient/#exportstore). Passed straight to PGlite’s `loadDataDir`, so the created store boots ON the backup’s datadir. Restore is a CREATION-path feature: the caller (`createSyncClient`) has already proven the target does not yet exist ([storeTargetExists](/api/client/functions/storetargetexists/)); this option carries no freshness check of its own. A corrupt/foreign tarball surfaces as a PGlite boot failure here.
# CreateSyncClientOptions
Defined in: packages/client/src/index.ts:922
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
## Properties
[Section titled “Properties”](#properties)
### autoSync?
[Section titled “autoSync?”](#autosync)
> `optional` **autoSync?**: [`ConvergenceTrigger`](/api/client/interfaces/convergencetrigger/)
Defined in: packages/client/src/index.ts:1114
Opt-in convergence driver (ADR-0005). Supply a [ConvergenceTrigger](/api/client/interfaces/convergencetrigger/) (e.g. `createBrowserConvergenceTrigger()`) and the client drives `flush`/`reconcile`/`retryFailed` on the trigger’s schedule, started once sync is ready and stopped on `stop()`/`destroy()`. Omit it for fully-manual convergence (the mechanism primitives stay public either way).
***
### batchEventUrl?
[Section titled “batchEventUrl?”](#batcheventurl)
> `optional` **batchEventUrl?**: `string`
Defined in: packages/client/src/index.ts:1124
The **Event lane**’s ingestion endpoint (ADR-0053 decision 3) — `"/api/events"`, or an absolute deployment URL ending in it, under the same hard-required-path rule as [batchWriteUrl](/api/client/interfaces/createsyncclientoptions/#batchwriteurl). Omit it and the client DERIVES it from `batchWriteUrl` (`…/api/mutations` → `…/api/events`), which is correct for the ordinary deployment where one `createSyncServer` mounts both; set it explicitly when the two endpoints are not siblings.
***
### batchWriteUrl
[Section titled “batchWriteUrl”](#batchwriteurl)
> **batchWriteUrl**: `string`
Defined in: packages/client/src/index.ts:925
***
### electricUrl
[Section titled “electricUrl”](#electricurl)
> **electricUrl**: `string`
Defined in: packages/client/src/index.ts:924
***
### events?
[Section titled “events?”](#events)
> `optional` **events?**: [`EventLaneOptions`](/api/client/interfaces/eventlaneoptions/)
Defined in: packages/client/src/index.ts:1131
The Event lane’s client-level flush policy (ADR-0053): batch caps, the fallback interval, backoff tuning, and per-Event-stream overrides. NEVER on the registry — the registry is the contract, cadence is deployment tuning, and a batch-size tweak must not surface as a registry diff. Client batching is additionally clamped by the contracts-level request-shape limits the server enforces independently.
***
### freshStore?
[Section titled “freshStore?”](#freshstore)
> `optional` **freshStore?**: `boolean`
Defined in: packages/client/src/index.ts:1078
PROVABLY-fresh store hint (ADR-0032 S4 / backlog-0003): the caller guarantees this store is brand-new and schemaless — no prior schema, no synced rows, no persisted subscription state. When set (and sync is enabled, and the client owns schema exec — i.e. not the [pgliteInstance](/api/client/interfaces/createsyncclientoptions/#pgliteinstance) path), the shape catch-up is started BEFORE the local boot phases (schema exec, journal recovery, and registry reconciliation) and buffered in memory, with commits gated until those phases finish — so the network catch-up overlaps them instead of running strictly after. On a far-from-database caller this collapses boot from `local-phases + catch-up` toward `max(local-phases, catch-up)`.
MUST be set only when freshness is proven, never derived by probing — a claimed schemaless spare (the board’s claim path knows: a claimed spare is always fresh; a mapped/returning store never is). A wrong `true` on a warm store would start the streams from offset 0 and skip the subscription-state read, re-snapshotting instead of resuming. Absent/false → the exact sequential path (the default, correct for every warm store).
***
### getAuthToken?
[Section titled “getAuthToken?”](#getauthtoken)
> `optional` **getAuthToken?**: () => `Promise`<`string` | `undefined`>
Defined in: packages/client/src/index.ts:926
#### Returns
[Section titled “Returns”](#returns)
`Promise`<`string` | `undefined`>
***
### hasOpfsSyncAccess?
[Section titled “hasOpfsSyncAccess?”](#hasopfssyncaccess)
> `optional` **hasOpfsSyncAccess?**: `boolean`
Defined in: packages/client/src/index.ts:981
ADR-0049 D1: the placement probe’s OPFS-sync-access grant, threaded from the SharedWorker’s engine home (`defineSyncWorker`’s SW-direct bootstrap) into this boot so the client-owned create opens the OPFS-repacked backend. Absent/false is the honest IDBFS home — the declared `backend: "idbfs"` mode or a capability-absence fallback (a main thread can never hold handles either). Forwarded verbatim to [createClientPGlite](/api/client/functions/createclientpglite/), which resolves the actual dataDir from it.
***
### liveQueries?
[Section titled “liveQueries?”](#livequeries)
> `optional` **liveQueries?**: `object`
Defined in: packages/client/src/index.ts:1181
Bounded zero-subscriber keep-alive for the live-query manager (ADR-0040 decision 4) — same block as [DefineSyncWorkerOptions.liveQueries](/api/client/interfaces/definesyncworkeroptions/#livequeries). Takes effect in BOTH client forms: the in-process client now owns its own manager (decision 6), so this policy governs its live-query dedup and retention just as it does the worker’s. Defaults: `defaultKeepAliveMs` 0 (tear a query down the instant its last consumer leaves), `maxRetainedQueries` 16, `maxRetainedRows` 50\_000.
#### defaultKeepAliveMs?
[Section titled “defaultKeepAliveMs?”](#defaultkeepalivems)
> `optional` **defaultKeepAliveMs?**: `number`
#### maxRetainedQueries?
[Section titled “maxRetainedQueries?”](#maxretainedqueries)
> `optional` **maxRetainedQueries?**: `number`
#### maxRetainedRows?
[Section titled “maxRetainedRows?”](#maxretainedrows)
> `optional` **maxRetainedRows?**: `number`
***
### maxMutationAttempts?
[Section titled “maxMutationAttempts?”](#maxmutationattempts)
> `optional` **maxMutationAttempts?**: `number`
Defined in: packages/client/src/index.ts:1083
Hard cap on send attempts before a still-failing mutation is quarantined (ADR-0005 congestion policy). Defaults to the library’s built-in cap.
***
### onBootReport?
[Section titled “onBootReport?”](#onbootreport)
> `optional` **onBootReport?**: (`report`) => `void`
Defined in: packages/client/src/index.ts:1144
Boot observability (ADR-0034): invoked exactly once, at boot completion, with the finalized [BootReport](/api/client/interfaces/bootreport/). The push counterpart of [SyncClient.bootReport](/api/client/interfaces/syncclient/#bootreport) (the pull) for consumers that want the numbers without polling — dashboards, CI budget gates. Never fired before initial sync; a `stop()`/`destroy()` before then means it never fires.
#### Parameters
[Section titled “Parameters”](#parameters)
##### report
[Section titled “report”](#report)
[`BootReport`](/api/client/interfaces/bootreport/)
#### Returns
[Section titled “Returns”](#returns-1)
`void`
***
### onConflict?
[Section titled “onConflict?”](#onconflict)
> `optional` **onConflict?**: (`conflicted`) => `void` | `Promise`<`void`>
Defined in: packages/client/src/index.ts:1096
Invoked when mutations are `conflicted` — a stale write the server declined under the `reject-if-stale` Conflict policy (ADR-0015). The optimistic Overlay is kept, so the app shows a resolution/diff UI and resolves each as a new write (`mutate.update`) or `discardConflict`s it.
#### Parameters
[Section titled “Parameters”](#parameters-1)
##### conflicted
[Section titled “conflicted”](#conflicted)
[`MutationDetail`](/api/client/interfaces/mutationdetail/)\[]
#### Returns
[Section titled “Returns”](#returns-2)
`void` | `Promise`<`void`>
***
### onConvergencePass?
[Section titled “onConvergencePass?”](#onconvergencepass)
> `optional` **onConvergencePass?**: (`error`) => `void`
Defined in: packages/client/src/index.ts:1116
Invoked after each automatic convergence pass with its error, or `null` on success (only when `autoSync` is set).
#### Parameters
[Section titled “Parameters”](#parameters-2)
##### error
[Section titled “error”](#error)
`unknown`
#### Returns
[Section titled “Returns”](#returns-3)
`void`
***
### onQuarantine?
[Section titled “onQuarantine?”](#onquarantine)
> `optional` **onQuarantine?**: (`quarantined`) => `void` | `Promise`<`void`>
Defined in: packages/client/src/index.ts:1090
Invoked when mutations are quarantined (permanently rejected by the server, terminal). The library surfaces them here rather than silently dropping or retry-looping (ADR-0006). Surface, then either re-author + resubmit or roll back via [SyncClient.discardQuarantined](/api/client/interfaces/syncclient/#discardquarantined) — which clears the kept overlay + quarantined journal rows so the entity accepts new mutations again.
#### Parameters
[Section titled “Parameters”](#parameters-3)
##### quarantined
[Section titled “quarantined”](#quarantined)
[`MutationDetail`](/api/client/interfaces/mutationdetail/)\[]
#### Returns
[Section titled “Returns”](#returns-4)
`void` | `Promise`<`void`>
***
### onReject?
[Section titled “onReject?”](#onreject)
> `optional` **onReject?**: (`rejected`) => `void` | `Promise`<`void`>
Defined in: packages/client/src/index.ts:1102
Invoked when a pessimistic write-unit is `rejected` (ADR-0022) — a business decline from the authoritative endpoint (capacity/quota/uniqueness). The inverse of `onConflict`: the optimistic Overlay was auto-discarded for the whole unit, so the app surfaces the typed reason rather than a resolve UI.
#### Parameters
[Section titled “Parameters”](#parameters-4)
##### rejected
[Section titled “rejected”](#rejected)
[`MutationDetail`](/api/client/interfaces/mutationdetail/)\[]
#### Returns
[Section titled “Returns”](#returns-5)
`void` | `Promise`<`void`>
***
### onSchemaChange?
[Section titled “onSchemaChange?”](#onschemachange)
> `optional` **onSchemaChange?**: (`event`) => `void` | `Promise`<`void`>
Defined in: packages/client/src/index.ts:1107
Invoked when a supported store’s registry fingerprint changes. `rebuilt` means the clean read cache was rebuilt at the new shape; `deferred` means local mutations are still owed and must drain first.
#### Parameters
[Section titled “Parameters”](#parameters-5)
##### event
[Section titled “event”](#event)
[`LocalStoreVersionEvent`](/api/client/interfaces/localstoreversionevent/)
#### Returns
[Section titled “Returns”](#returns-6)
`void` | `Promise`<`void`>
***
### onStatusChange?
[Section titled “onStatusChange?”](#onstatuschange)
> `optional` **onStatusChange?**: (`status`) => `void`
Defined in: packages/client/src/index.ts:996
#### Parameters
[Section titled “Parameters”](#parameters-6)
##### status
[Section titled “status”](#status)
`SyncRuntimeStatus`
#### Returns
[Section titled “Returns”](#returns-7)
`void`
***
### onSyncError?
[Section titled “onSyncError?”](#onsyncerror)
> `optional` **onSyncError?**: (`error`) => `void`
Defined in: packages/client/src/index.ts:1137
Invoked when a read-path sync commit fails after exhausting its retries (ADR-0009 decision 5). The runtime enters the `degraded` phase and holds the read cache at the last applied commit instead of silently diverging from the server; recovery is a later commit or a restart/refetch.
#### Parameters
[Section titled “Parameters”](#parameters-7)
##### error
[Section titled “error”](#error-1)
`Error`
#### Returns
[Section titled “Returns”](#returns-8)
`void`
***
### onTableInitialSync?
[Section titled “onTableInitialSync?”](#ontableinitialsync)
> `optional` **onTableInitialSync?**: (`tableKey`) => `void`
Defined in: packages/client/src/index.ts:997
#### Parameters
[Section titled “Parameters”](#parameters-8)
##### tableKey
[Section titled “tableKey”](#tablekey)
`string`
#### Returns
[Section titled “Returns”](#returns-9)
`void`
***
### pgliteBootAssets?
[Section titled “pgliteBootAssets?”](#pglitebootassets)
> `optional` **pgliteBootAssets?**: `Promise`<[`PgliteBootAssets`](/api/client/interfaces/pglitebootassets/)>
Defined in: packages/client/src/index.ts:992
Pre-warmed PGlite boot assets (the WASM modules + filesystem bundle), awaited and passed straight into `PGlite.create`. The intent is to hide PGlite’s \~2.5s cold `boot pglite.create` cost — dominated by the WASM fetch+compile — behind user think-time: the host starts fetching/compiling these on an earlier screen (e.g. the login/identity picker) and hands the still-pending promise here, so by the time a store is opened the assets are already resolved and `PGlite.create` skips its own lazy asset load. Ignored when [pgliteInstance](/api/client/interfaces/createsyncclientoptions/#pgliteinstance) is supplied (the caller owns that instance’s boot). A rejected/failed warm is caught to `undefined` and never fails the boot — PGlite falls back to loading its own assets, so this is a pure best-effort accelerator.
***
### pgliteInstance?
[Section titled “pgliteInstance?”](#pgliteinstance)
> `optional` **pgliteInstance?**: [`ClientPGlite`](/api/client/type-aliases/clientpglite/)
Defined in: packages/client/src/index.ts:1016
A fully-provisioned PGlite instance the CALLER owns end-to-end. The client runs NONE of its post-create boot steps against it — no schema exec, prepare hooks, or registry reconciliation (journal recovery still runs, as it does on every path). Use it only when the caller has already applied the registry schema itself. Contrast the three PGlite-provenance seams:
* [storePath](/api/client/interfaces/createsyncclientoptions/#storepath) (default) — the client creates the store AND runs every post-create step.
* [precreatedPglite](/api/client/interfaces/createsyncclientoptions/#precreatedpglite) — the caller creates the raw store (via [createClientPGlite](/api/client/functions/createclientpglite/)), but the client still runs every post-create step (schema, prepare hooks, and reconciliation), exactly as `storePath` does.
* `pgliteInstance` — the caller creates AND provisions the store; the client runs none of them.
A caller-owned instance is REFUSED with [NonPersistentStoreError](/api/client/classes/nonpersistentstoreerror/) if it is provably non-persistent (a `new PGlite()` default, or an in-memory store) — pgxsinkit’s durability semantics assume a persisted store (ADR-0036). Acknowledge a deliberate test store by spreading `testStoreAcknowledgment()` from `@pgxsinkit/client/testing`.
Mutually exclusive with [precreatedPglite](/api/client/interfaces/createsyncclientoptions/#precreatedpglite) (supplying both throws).
***
### precreatedPglite?
[Section titled “precreatedPglite?”](#precreatedpglite)
> `optional` **precreatedPglite?**: `Promise`<[`ClientPGlite`](/api/client/type-aliases/clientpglite/)>
Defined in: packages/client/src/index.ts:1036
A raw PGlite instance the caller created EAGERLY (via [createClientPGlite](/api/client/functions/createclientpglite/)) — typically on an earlier screen, to hide the \~1.9s cold `initdb`/IDBFS open behind user think-time — but for which the client still owns EVERYTHING else: schema exec, prepare hooks, journal recovery, and registry reconciliation all run exactly as on the [storePath](/api/client/interfaces/createsyncclientoptions/#storepath) path. This is the difference from [pgliteInstance](/api/client/interfaces/createsyncclientoptions/#pgliteinstance) (which skips schema, prepare hooks, and reconciliation because the caller owns them); see that option’s JSDoc for the three-way distinction.
The promise form lets the still-pending eager create be handed straight in. Precedence/validation:
* Supplying both this and [pgliteInstance](/api/client/interfaces/createsyncclientoptions/#pgliteinstance) throws — they claim different ownership.
* [storePath](/api/client/interfaces/createsyncclientoptions/#storepath) is used ONLY as the fallback store name if this promise REJECTS: a failed eager create is caught, logged on the boot rail, and the normal `storePath` create path runs instead (also consuming [pgliteBootAssets](/api/client/interfaces/createsyncclientoptions/#pglitebootassets) if provided). The pattern is a pure accelerator, never a boot dependency.
* A successfully-adopted instance is subject to the same [NonPersistentStoreError](/api/client/classes/nonpersistentstoreerror/) refusal as [pgliteInstance](/api/client/interfaces/createsyncclientoptions/#pgliteinstance) (checked after resolution, so the refusal propagates rather than being swallowed by the reject-fallback).
***
### prepareLocalDbAfterSchema?
[Section titled “prepareLocalDbAfterSchema?”](#preparelocaldbafterschema)
> `optional` **prepareLocalDbAfterSchema?**: (`pglite`) => `Promise`<`void`>
Defined in: packages/client/src/index.ts:995
#### Parameters
[Section titled “Parameters”](#parameters-9)
##### pglite
[Section titled “pglite”](#pglite)
[`ClientPGlite`](/api/client/type-aliases/clientpglite/)
#### Returns
[Section titled “Returns”](#returns-10)
`Promise`<`void`>
***
### prepareLocalDbBeforeSchema?
[Section titled “prepareLocalDbBeforeSchema?”](#preparelocaldbbeforeschema)
> `optional` **prepareLocalDbBeforeSchema?**: (`pglite`) => `Promise`<`void`>
Defined in: packages/client/src/index.ts:994
#### Parameters
[Section titled “Parameters”](#parameters-10)
##### pglite
[Section titled “pglite”](#pglite-1)
[`ClientPGlite`](/api/client/type-aliases/clientpglite/)
#### Returns
[Section titled “Returns”](#returns-11)
`Promise`<`void`>
***
### readSilenceMs?
[Section titled “readSilenceMs?”](#readsilencems)
> `optional` **readSilenceMs?**: `number`
Defined in: packages/client/src/index.ts:954
The read-silence window (ms) after which a runtime claiming `ready` drops to `degraded` (reason “stream”). A pulled cable HANGS the live long-poll — nothing fails (the stall probe hears only settled attempts), nothing delivers — so without this a session that once reached `ready` would report “up to date” for as long as the outage lasts. A healthy stream is never silent (the long-poll cycles \~every 20-25s), so the default of 45s spans two full cycles. Self-recovering: the next delivered batch returns `ready`. Status honesty only — nothing is retired or torn down.
***
### registry
[Section titled “registry”](#registry)
> **registry**: `TRegistry`
Defined in: packages/client/src/index.ts:923
***
### requestHeaders?
[Section titled “requestHeaders?”](#requestheaders)
> `optional` **requestHeaders?**: `Record`<`string`, `string`>
Defined in: packages/client/src/index.ts:933
Static headers added to **every** read-shape and write request, alongside the per-request `Authorization` (which always wins). The toolkit is agnostic about deployment-gateway credentials, so this is the seam for them — e.g. a Supabase Cloud `apikey` header the platform function gateway expects. Sent even when no `getAuthToken` is supplied.
***
### resetSubscriptionKeys?
[Section titled “resetSubscriptionKeys?”](#resetsubscriptionkeys)
> `optional` **resetSubscriptionKeys?**: `string`\[]
Defined in: packages/client/src/index.ts:993
***
### restoreFrom?
[Section titled “restoreFrom?”](#restorefrom)
> `optional` **restoreFrom?**: `File` | `Blob`
Defined in: packages/client/src/index.ts:1062
Restore the store from a **store backup** (ADR-0035 decision 6) — a `File`/`Blob` tarball as produced by [SyncClient.exportStore](/api/client/interfaces/syncclient/#exportstore). The client creates its store with the backup handed to PGlite’s `loadDataDir`, so it boots ON the backup’s datadir (synced cache + Overlay + Mutation journal, all the bytes that travelled inside it). Three restore-only rules apply, none of them optional:
* **Fresh target only.** Refused with [RestoreTargetExistsError](/api/client/classes/restoretargetexistserror/) if a store already exists at the resolved [storePath](/api/client/interfaces/createsyncclientoptions/#storepath) — restore never overlays a live store (that would corrupt the datadir); the remedy is a deliberate [SyncClient.destroy](/api/client/interfaces/syncclient/#destroy) of the existing store first.
* **Comes online iff the recovered journal is clean (ADR-0046).** If journal recovery found NOTHING to quarantine — an empty recovered journal, the guaranteed-clean server-built bootstrap-artifact case — the restore boots ONLINE, honouring [syncEnabled](/api/client/interfaces/createsyncclientoptions/#syncenabled)/[autoSync](/api/client/interfaces/createsyncclientoptions/#autosync) exactly as a normal boot (streams, flush, convergence). If recovered mutations were quarantined, the boot stays OFFLINE (no shape streams, no read fetch, no flush): the app inspects [SyncClient.diagnostics](/api/client/interfaces/syncclient/#diagnostics), releases/discards the quarantined rows, then a subsequent NORMAL boot of the (now-persisted) store brings sync online. `loadDataDir` happens exactly once, on this restore boot. An explicit `syncEnabled: false` keeps it offline.
* **Journal quarantined.** Every non-terminal recovered row (`pending`/`sending`/`failed`) is moved to `quarantined` — nothing recovered from a backup auto-flushes (the write path has no `mutationId` dedupe ledger, so replay is unsafe on last-write-wins tables). Release (`retryFailed`) or discard (`discardQuarantined`) them explicitly. When this pass quarantines nothing, the restore comes online (above).
Mutually exclusive with [pgliteInstance](/api/client/interfaces/createsyncclientoptions/#pgliteinstance) AND [precreatedPglite](/api/client/interfaces/createsyncclientoptions/#precreatedpglite) — restore owns the store’s creation (`loadDataDir` is a create-time seed), so a caller-supplied instance conflicts (supplying either with `restoreFrom` throws).
***
### storePath?
[Section titled “storePath?”](#storepath)
> `optional` **storePath?**: `string`
Defined in: packages/client/src/index.ts:964
The local store’s name (ADR-0036) — a PLAIN path/name, never a PGlite storage URL. The storage backend is DERIVED from the engine home (capability-selected opfs-repacked with IndexedDB fallback in a browser, or the filesystem on Bun/Node); a scheme-bearing string (anything containing `://`) is rejected with [InvalidStorePathError](/api/client/classes/invalidstorepatherror/) at boot. Defaults to a built-in overlay store name when omitted. A memory-backed store is not a product configuration — for the test/ephemeral lane, spread `memoryStoreForTests(...)` from `@pgxsinkit/client/testing` instead of naming one here.
***
### syncEnabled?
[Section titled “syncEnabled?”](#syncenabled)
> `optional` **syncEnabled?**: `boolean`
Defined in: packages/client/src/index.ts:945
***
### writeRequestHeaders?
[Section titled “writeRequestHeaders?”](#writerequestheaders)
> `optional` **writeRequestHeaders?**: `Record`<`string`, `string`>
Defined in: packages/client/src/index.ts:944
Extra static headers sent on the **write** path only (the mutation-flush POST), merged over [requestHeaders](/api/client/interfaces/createsyncclientoptions/#requestheaders) (`{...requestHeaders, ...writeRequestHeaders}`). Read/shape requests never see these. The seam exists because the two ingress points have opposite geometry: the write function is DB-bound, so pinning it to the database’s region (e.g. an `x-region` header) keeps its chatty function→DB protocol on a \~1ms loop; the read proxy’s upstream is a globally-distributed CDN (Electric Cloud), so pinning reads away from the caller pays intercontinental round trips per catch-up hop. Put region/DB-affinity headers here; keep gateway credentials the reads also need (e.g. `apikey`) in the shared [requestHeaders](/api/client/interfaces/createsyncclientoptions/#requestheaders).
# DataExportDeps
Defined in: packages/client/src/export-data.ts:114
The dependencies [performDataExport](/api/client/functions/performdataexport/) needs from the owning client — narrow, so it is unit-testable.
## Properties
[Section titled “Properties”](#properties)
### cloneCleanupSql
[Section titled “cloneCleanupSql”](#clonecleanupsql)
> **cloneCleanupSql**: `string`
Defined in: packages/client/src/export-data.ts:136
The clone-cleanup SQL (`buildDataExportCloneCleanupSql`) run on the throwaway clone before `pg_dump -t`, dropping the reconcile triggers `-t` would otherwise pull into the artefact (referencing pgxsinkit functions the export excludes). `""` when the registry has no writable owning table.
***
### enumHeaderSql
[Section titled “enumHeaderSql”](#enumheadersql)
> **enumHeaderSql**: `string`
Defined in: packages/client/src/export-data.ts:130
The generated enum DDL header (`buildDataExportEnumHeaderSql`) — the `CREATE TYPE` statements for the enums the exported tables reference, which `pg_dump -t` omits. `""` when no exported table uses an enum.
***
### flush
[Section titled “flush”](#flush)
> **flush**: () => `Promise`<`void`>
Defined in: packages/client/src/export-data.ts:120
The optimistic flush (`client.flush()`), driven during the drain to send drainable rows.
#### Returns
[Section titled “Returns”](#returns)
`Promise`<`void`>
***
### pglite
[Section titled “pglite”](#pglite)
> **pglite**: `Pick`<[`ClientPGlite`](/api/client/type-aliases/clientpglite/), `"exec"` | `"dumpDataDir"`>
Defined in: packages/client/src/export-data.ts:116
The live store to checkpoint and dump (the clone source; the live engine is never suspended).
***
### readMutationStats
[Section titled “readMutationStats”](#readmutationstats)
> **readMutationStats**: () => `Promise`<[`MutationDiagnostics`](/api/client/interfaces/mutationdiagnostics/)>
Defined in: packages/client/src/export-data.ts:118
The Mutation diagnostics seam (`client.diagnostics().mutation` / `readMutationStats`).
#### Returns
[Section titled “Returns”](#returns-1)
`Promise`<[`MutationDiagnostics`](/api/client/interfaces/mutationdiagnostics/)>
***
### storePath?
[Section titled “storePath?”](#storepath)
> `optional` **storePath?**: `string`
Defined in: packages/client/src/export-data.ts:142
The store’s configured plain store PATH (ADR-0036) — reduced to the `storeId` in the default artefact file name. The resolved PGlite dataDir URL is deliberately NOT used: internal plumbing, never an artefact-name seed.
***
### syncedTableNames
[Section titled “syncedTableNames”](#syncedtablenames)
> **syncedTableNames**: `string`\[]
Defined in: packages/client/src/export-data.ts:125
The `-t` allowlist: the schema-qualified physical synced table names, resolved from the registry by the SAME projection the DDL generator uses (`collectDataExportSyncedTableNames`) — never re-derived here.
# DataExportOptions
Defined in: packages/client/src/export-data.ts:49
Options for [SyncClient.exportData](/api/client/interfaces/syncclient/#exportdata). Plain JSON — structured-clone-safe across the worker bridge.
## Properties
[Section titled “Properties”](#properties)
### drainJournal?
[Section titled “drainJournal?”](#drainjournal)
> `optional` **drainJournal?**: [`DrainJournalOption`](/api/client/type-aliases/drainjournaloption/)
Defined in: packages/client/src/export-data.ts:60
The drain guard (ADR-0035 decision 3). Default `{ timeoutMs: 15_000 }`: fail fast on non-drainable states, else flush + await convergence up to the budget. `false` is the escape hatch — export synced state as-is, skipping the drain entirely.
***
### fileName?
[Section titled “fileName?”](#filename)
> `optional` **fileName?**: `string`
Defined in: packages/client/src/export-data.ts:54
Override the generated artefact file name. When omitted, the name is `--data.sql`, where `storeId` is a filesystem-safe derivation of the store path (see `deriveStoreId`).
# DataExportReport
Defined in: packages/client/src/export-store.ts:129
A **data export** (ADR-0035 decision 1, via the throwaway clone of the addendum): the PORTABLE artefact — the synced tables and the enum types they depend on, schema + data, nothing of pgxsinkit’s machinery, loadable into a vanilla Postgres. It is a generated enum DDL header concatenated ahead of a `pg_dump -t` (per synced table) `--no-owner` run against the same memory-backed throwaway clone the diagnostic dump uses. Unlike the other two exports it GUARDS: it requires a drained Mutation journal (or the explicit `drainJournal: false` escape hatch), so its phases add the drain wall ahead of the clone pipeline, and it records its provenance (`tables` = the `-t` allowlist actually applied, `escapeHatch` = whether the drain was skipped).
## Extends
[Section titled “Extends”](#extends)
* [`ExportReportCommon`](/api/client/interfaces/exportreportcommon/)
## Properties
[Section titled “Properties”](#properties)
### byteLength
[Section titled “byteLength”](#bytelength)
> **byteLength**: `number`
Defined in: packages/client/src/export-store.ts:54
The artefact’s byte length — the size the caller downloads / persists.
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
[`ExportReportCommon`](/api/client/interfaces/exportreportcommon/).[`byteLength`](/api/client/interfaces/exportreportcommon/#bytelength)
***
### diagnostics
[Section titled “diagnostics”](#diagnostics)
> **diagnostics**: [`MutationDiagnostics`](/api/client/interfaces/mutationdiagnostics/)
Defined in: packages/client/src/export-store.ts:60
The [MutationDiagnostics](/api/client/interfaces/mutationdiagnostics/) snapshot at export time — the journal state captured alongside the artefact (for the store backup, the very journal that travels INSIDE the tarball; for the diagnostic dump, the live store’s journal at dump time, whose rows the SQL also carries).
#### Inherited from
[Section titled “Inherited from”](#inherited-from-1)
[`ExportReportCommon`](/api/client/interfaces/exportreportcommon/).[`diagnostics`](/api/client/interfaces/exportreportcommon/#diagnostics)
***
### escapeHatch
[Section titled “escapeHatch”](#escapehatch)
> **escapeHatch**: `boolean`
Defined in: packages/client/src/export-store.ts:143
`true` when `drainJournal: false` skipped the drain and the artefact reflects the SYNCED state as-is (unflushed local writes silently absent). `false` on the strict path (a drained or empty journal).
***
### kind
[Section titled “kind”](#kind)
> **kind**: `"data-export"`
Defined in: packages/client/src/export-store.ts:130
***
### phases
[Section titled “phases”](#phases)
> **phases**: `object`
Defined in: packages/client/src/export-store.ts:144
#### checkpointMs
[Section titled “checkpointMs”](#checkpointms)
> **checkpointMs**: `number`
`CHECKPOINT` wall — flushing dirty buffers before the internal datadir dump the clone consumes.
#### checkpointStartedAtMs
[Section titled “checkpointStartedAtMs”](#checkpointstartedatms)
> **checkpointStartedAtMs**: `number`
Offset from export start when the `CHECKPOINT` began.
#### cloneBootMs
[Section titled “cloneBootMs”](#clonebootms)
> **cloneBootMs**: `number`
Clone boot wall — booting the memory-backed throwaway from the internal dump.
#### cloneBootStartedAtMs
[Section titled “cloneBootStartedAtMs”](#clonebootstartedatms)
> **cloneBootStartedAtMs**: `number`
Offset from export start when the throwaway clone’s `PGlite.create({ loadDataDir })` began.
#### drainMs
[Section titled “drainMs”](#drainms)
> **drainMs**: `number`
Drain wall — flushing + awaiting the journal reach fully-drained (`0` under the escape hatch).
#### drainStartedAtMs
[Section titled “drainStartedAtMs”](#drainstartedatms)
> **drainStartedAtMs**: `number`
Offset from export start when the drain guard began (`0` when the escape hatch skipped it).
#### dumpMs
[Section titled “dumpMs”](#dumpms)
> **dumpMs**: `number`
`dumpDataDir` wall — the uncompressed internal tarball the throwaway clone boots from (`compression: "none"`).
#### dumpStartedAtMs
[Section titled “dumpStartedAtMs”](#dumpstartedatms)
> **dumpStartedAtMs**: `number`
Offset from export start when the internal `dumpDataDir` began.
#### pgDumpMs
[Section titled “pgDumpMs”](#pgdumpms)
> **pgDumpMs**: `number`
`pg_dump -t` wall — the WASM `pg_dump` reading the allowlisted tables out to SQL.
#### pgDumpStartedAtMs
[Section titled “pgDumpStartedAtMs”](#pgdumpstartedatms)
> **pgDumpStartedAtMs**: `number`
Offset from export start when `pg_dump -t` began running against the clone.
***
### reportVersion
[Section titled “reportVersion”](#reportversion)
> **reportVersion**: `1`
Defined in: packages/client/src/export-store.ts:48
#### Inherited from
[Section titled “Inherited from”](#inherited-from-2)
[`ExportReportCommon`](/api/client/interfaces/exportreportcommon/).[`reportVersion`](/api/client/interfaces/exportreportcommon/#reportversion)
***
### scope
[Section titled “scope”](#scope)
> **scope**: `"synced-tables"`
Defined in: packages/client/src/export-store.ts:132
A data export covers the synced tables + their enum types only — never pgxsinkit’s overlay/journal/metadata.
***
### startedAt
[Section titled “startedAt”](#startedat)
> **startedAt**: `number`
Defined in: packages/client/src/export-store.ts:50
Epoch anchor (`Date.now()`) at export start; every other duration/offset is monotonic relative to it.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-3)
[`ExportReportCommon`](/api/client/interfaces/exportreportcommon/).[`startedAt`](/api/client/interfaces/exportreportcommon/#startedat)
***
### tables
[Section titled “tables”](#tables)
> **tables**: `string`\[]
Defined in: packages/client/src/export-store.ts:138
The schema-qualified physical synced tables the `-t` allowlist targeted, in registry order — the self-describing provenance of exactly what the artefact carries (ephemeral/read-projection entries excluded by construction). May be empty when the registry declares no owning persistent table.
***
### totalMs
[Section titled “totalMs”](#totalms)
> **totalMs**: `number`
Defined in: packages/client/src/export-store.ts:52
Export start → artefact ready.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-4)
[`ExportReportCommon`](/api/client/interfaces/exportreportcommon/).[`totalMs`](/api/client/interfaces/exportreportcommon/#totalms)
# DataExportResult
Defined in: packages/client/src/export-data.ts:64
The SQL artefact + its report — the resolved value of [SyncClient.exportData](/api/client/interfaces/syncclient/#exportdata).
## Properties
[Section titled “Properties”](#properties)
### file
[Section titled “file”](#file)
> **file**: `File`
Defined in: packages/client/src/export-data.ts:66
The portable SQL as a named `File` (`application/sql`), loadable into a vanilla Postgres.
***
### report
[Section titled “report”](#report)
> **report**: [`DataExportReport`](/api/client/interfaces/dataexportreport/)
Defined in: packages/client/src/export-data.ts:68
The structured record of the export (ADR-0035).
# DefineSyncWorkerOptions
Defined in: packages/client/src/worker/define-sync-worker.ts:115
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
## Properties
[Section titled “Properties”](#properties)
### batchEventUrl?
[Section titled “batchEventUrl?”](#batcheventurl)
> `optional` **batchEventUrl?**: `string`
Defined in: packages/client/src/worker/define-sync-worker.ts:151
The **Event lane**’s ingestion endpoint (ADR-0053 decision 3). Omit it and the worker derives it from [batchWriteUrl](/api/client/interfaces/definesyncworkeroptions/#batchwriteurl) (`…/api/mutations` → `…/api/events`). See `createSyncClient`’s `batchEventUrl`.
***
### batchWriteUrl
[Section titled “batchWriteUrl”](#batchwriteurl)
> **batchWriteUrl**: `string`
Defined in: packages/client/src/worker/define-sync-worker.ts:138
***
### codec?
[Section titled “codec?”](#codec)
> `optional` **codec?**: [`BridgeCodec`](/api/client/interfaces/bridgecodec/)
Defined in: packages/client/src/worker/define-sync-worker.ts:182
Injected codec (ADR-0032 S2 §1). Defaults to the v1 identity codec.
***
### convergenceIntervalMs?
[Section titled “convergenceIntervalMs?”](#convergenceintervalms)
> `optional` **convergenceIntervalMs?**: `number`
Defined in: packages/client/src/worker/define-sync-worker.ts:169
The worker’s own convergence cadence (ms) — the interval trigger FALLBACK only. Local writes flush immediately via the event-driven `requestPass` seam and tab `wake` signals, so this interval exists purely for retry/recovery sweeps; each idle pass still costs real worker CPU (`flush` + `reconcile` queries). Default 15000.
***
### createPglite?
[Section titled “createPglite?”](#createpglite)
> `optional` **createPglite?**: (`storePath`, `backendOverride?`) => `Promise`<[`ClientPGlite`](/api/client/type-aliases/clientpglite/)>
Defined in: packages/client/src/worker/define-sync-worker.ts:136
How the worker creates its raw PGlite store (provision + fresh-attach paths). Defaults to [createClientPGlite](/api/client/functions/createclientpglite/), which loads PGlite’s own boot assets — in a browser worker those hit the same-origin HTTP cache the tab’s login-screen warm already primed (ADR-0032 S3). Injected in tests. Takes a plain store PATH (ADR-0036); the internal `backendOverride` is the test lane’s memory selection.
#### Parameters
[Section titled “Parameters”](#parameters)
##### storePath
[Section titled “storePath”](#storepath)
`string`
##### backendOverride?
[Section titled “backendOverride?”](#backendoverride)
`"memory"`
#### Returns
[Section titled “Returns”](#returns)
`Promise`<[`ClientPGlite`](/api/client/type-aliases/clientpglite/)>
***
### electricUrl
[Section titled “electricUrl”](#electricurl)
> **electricUrl**: `string`
Defined in: packages/client/src/worker/define-sync-worker.ts:137
***
### events?
[Section titled “events?”](#events)
> `optional` **events?**: [`EventLaneOptions`](/api/client/interfaces/eventlaneoptions/)
Defined in: packages/client/src/worker/define-sync-worker.ts:157
The Event lane’s client-level flush policy (ADR-0053): batch caps, the fallback interval, backoff, and per-Event-stream overrides. A worker-ENTRY option, never an attach option — flush cadence is one engine-wide policy, and the Outbox it drains is shared by every attached tab.
***
### executionLimit?
[Section titled “executionLimit?”](#executionlimit)
> `optional` **executionLimit?**: [`ExecutionLimitConfig`](/api/client/interfaces/executionlimitconfig/)
Defined in: packages/client/src/worker/define-sync-worker.ts:178
The opt-in engine-construction EXECUTION LIMIT (ADR-0049 D5), threaded into the ROUTER when this SharedWorker lands in `elected-worker` placement (router-only mode). DISABLED by default (`undefined` / absent `maxDispatchMs`) — no finite worst-case query duration exists, so an absent limit means the router forwards no probe and queries run unbounded; enabling it is a deliberate consumer choice. Elected placement ONLY: on SW-direct (`shared-worker`) the engine home is in-scope and there is no control channel to probe, so an enabled value is rejected as unsupported before engine boot.
***
### installGlobal?
[Section titled “installGlobal?”](#installglobal)
> `optional` **installGlobal?**: `boolean`
Defined in: packages/client/src/worker/define-sync-worker.ts:180
Injected transport binder. Defaults to auto-detecting the SharedWorker/dedicated-worker global scope.
***
### liveQueries?
[Section titled “liveQueries?”](#livequeries)
> `optional` **liveQueries?**: `object`
Defined in: packages/client/src/worker/define-sync-worker.ts:228
Bounded zero-subscriber keep-alive for the live-query manager (ADR-0040 decision 4). When an entry’s last subscriber leaves, a nonzero effective keep-alive retains its PGlite registration + diff state for a grace period so a matching resubscribe (e.g. a re-mounted route across tabs) reuses it verbatim — no \~400 ms re-materialization. Bounded by explicit budgets; DEFAULTS OFF (`defaultKeepAliveMs: 0` → tear a query down the instant its last consumer leaves). The 0 default is justified: a retained entry STILL pays a full SQL rerun + diff on every dependent write — PGlite live queries cannot be paused — so retention only pays off for a genuinely hot, re-mounted query, and the default keeps worker memory bounded with no surprise standing SQL reruns.
#### defaultKeepAliveMs?
[Section titled “defaultKeepAliveMs?”](#defaultkeepalivems)
> `optional` **defaultKeepAliveMs?**: `number`
Baseline retention (ms) for a zero-subscriber entry; floored by each subscriber’s own hint. Default 0.
#### maxRetainedQueries?
[Section titled “maxRetainedQueries?”](#maxretainedqueries)
> `optional` **maxRetainedQueries?**: `number`
Max simultaneously-retained (zero-subscriber) entries — LRU-evicted past this. Default 16.
#### maxRetainedRows?
[Section titled “maxRetainedRows?”](#maxretainedrows)
> `optional` **maxRetainedRows?**: `number`
Max total rows across all retained entries — LRU-evicted past this. Default 50\_000.
***
### maxMutationAttempts?
[Section titled “maxMutationAttempts?”](#maxmutationattempts)
> `optional` **maxMutationAttempts?**: `number`
Defined in: packages/client/src/worker/define-sync-worker.ts:145
***
### pgliteInstance?
[Section titled “pgliteInstance?”](#pgliteinstance)
> `optional` **pgliteInstance?**: [`ClientPGlite`](/api/client/type-aliases/clientpglite/)
Defined in: packages/client/src/worker/define-sync-worker.ts:191
A fully-provisioned PGlite (forwarded to [CreateSyncClientOptions.pgliteInstance](/api/client/interfaces/createsyncclientoptions/#pgliteinstance); caller owns schema).
***
### precreatedPglite?
[Section titled “precreatedPglite?”](#precreatedpglite)
> `optional` **precreatedPglite?**: `Promise`<[`ClientPGlite`](/api/client/type-aliases/clientpglite/)>
Defined in: packages/client/src/worker/define-sync-worker.ts:189
A raw PGlite the worker uses instead of creating its own (forwarded to `createSyncClient`’s [CreateSyncClientOptions.precreatedPglite](/api/client/interfaces/createsyncclientoptions/#precreatedpglite)) — the client still applies schema/reconcile. In a browser worker the store is minted internally (`storePath`); this is the seam for a prepopulated store in tests, and the future spare-worker claim (ADR-0032 decision 5).
***
### prepareLocalDbAfterSchema?
[Section titled “prepareLocalDbAfterSchema?”](#preparelocaldbafterschema)
> `optional` **prepareLocalDbAfterSchema?**: (`pglite`) => `Promise`<`void`>
Defined in: packages/client/src/worker/define-sync-worker.ts:217
App-level schema prep run IN THE WORKER, against the engine’s own local store, AFTER the registry schema exec — forwarded verbatim to [CreateSyncClientOptions.prepareLocalDbAfterSchema](/api/client/interfaces/createsyncclientoptions/#preparelocaldbafterschema), same timing as the in-process client. Like [prepareLocalDbBeforeSchema](/api/client/interfaces/definesyncworkeroptions/#preparelocaldbbeforeschema) this is a worker-ENTRY option, never an attach option (the tab never sees it — functions cannot cross the bridge). Use it for app-level indexes, views, or migrations that depend on the registry’s local tables, which DO exist by the time this runs.
Runs on the storePath, [precreatedPglite](/api/client/interfaces/definesyncworkeroptions/#precreatedpglite), and restore boots; SKIPPED entirely on the [pgliteInstance](/api/client/interfaces/definesyncworkeroptions/#pgliteinstance) path (the caller owns schema/prepare/reconcile there).
#### Parameters
[Section titled “Parameters”](#parameters-1)
##### pglite
[Section titled “pglite”](#pglite)
[`ClientPGlite`](/api/client/type-aliases/clientpglite/)
#### Returns
[Section titled “Returns”](#returns-1)
`Promise`<`void`>
***
### prepareLocalDbBeforeSchema?
[Section titled “prepareLocalDbBeforeSchema?”](#preparelocaldbbeforeschema)
> `optional` **prepareLocalDbBeforeSchema?**: (`pglite`) => `Promise`<`void`>
Defined in: packages/client/src/worker/define-sync-worker.ts:206
App-level schema prep run IN THE WORKER, against the engine’s own local store, BEFORE the registry schema exec — forwarded verbatim to [CreateSyncClientOptions.prepareLocalDbBeforeSchema](/api/client/interfaces/createsyncclientoptions/#preparelocaldbbeforeschema), same timing as the in-process client. This is a worker-ENTRY option (baked into the worker file as code), NOT an attach option: the hook is a function and functions cannot cross the bridge, so a tab can never supply it — the worker owns it. Use it for DDL that must precede the registry’s local tables (extensions, a bespoke schema search\_path). On a fresh store the registry-derived local tables do NOT yet exist when this runs (that ordering is what distinguishes it from [prepareLocalDbAfterSchema](/api/client/interfaces/definesyncworkeroptions/#preparelocaldbafterschema)).
Runs on the storePath, [precreatedPglite](/api/client/interfaces/definesyncworkeroptions/#precreatedpglite), and restore boots; SKIPPED entirely on the [pgliteInstance](/api/client/interfaces/definesyncworkeroptions/#pgliteinstance) path (the caller owns schema/prepare/reconcile there). On a restore boot it still runs, but the store already carries the registry tables from the backup’s datadir, so the “tables absent” invariant does not hold there.
#### Parameters
[Section titled “Parameters”](#parameters-2)
##### pglite
[Section titled “pglite”](#pglite-1)
[`ClientPGlite`](/api/client/type-aliases/clientpglite/)
#### Returns
[Section titled “Returns”](#returns-2)
`Promise`<`void`>
***
### registry
[Section titled “registry”](#registry)
> **registry**: `TRegistry`
Defined in: packages/client/src/worker/define-sync-worker.ts:121
The sync registry — imported as CODE by the worker file, never cloned into it (ADR-0032 decision 4). When [resolveRegistry](/api/client/interfaces/definesyncworkeroptions/#resolveregistry) is also given this is the DEFAULT (used when the attach carries no role or an unknown one).
***
### requestHeaders?
[Section titled “requestHeaders?”](#requestheaders)
> `optional` **requestHeaders?**: `Record`<`string`, `string`>
Defined in: packages/client/src/worker/define-sync-worker.ts:140
Static headers on every read + write request (e.g. a gateway `apikey`). See `createSyncClient`.
***
### resolveRegistry?
[Section titled “resolveRegistry?”](#resolveregistry)
> `optional` **resolveRegistry?**: (`role`) => `TRegistry` | `undefined`
Defined in: packages/client/src/worker/define-sync-worker.ts:129
Resolve the registry to boot from the attach’s `config.role` (ADR-0032 S3). A single worker file can bake BOTH role variants (e.g. the board’s admin/member registries — same TS shape, different write capability) and pick per-attach, which the spare flow needs: the spare is provisioned role-agnostic (before the user is known) and the role is only settled at claim/attach. Returns `undefined` to fall back to [registry](/api/client/interfaces/definesyncworkeroptions/#registry).
#### Parameters
[Section titled “Parameters”](#parameters-3)
##### role
[Section titled “role”](#role)
`string` | `undefined`
#### Returns
[Section titled “Returns”](#returns-3)
`TRegistry` | `undefined`
***
### storePath?
[Section titled “storePath?”](#storepath-1)
> `optional` **storePath?**: `string`
Defined in: packages/client/src/worker/define-sync-worker.ts:144
Default plain store PATH (ADR-0036) if the first attach carries none — a name, not a storage URL.
***
### syncEnabled?
[Section titled “syncEnabled?”](#syncenabled)
> `optional` **syncEnabled?**: `boolean`
Defined in: packages/client/src/worker/define-sync-worker.ts:146
***
### tokenExpiryMarginMs?
[Section titled “tokenExpiryMarginMs?”](#tokenexpirymarginms)
> `optional` **tokenExpiryMarginMs?**: `number`
Defined in: packages/client/src/worker/define-sync-worker.ts:162
How close to expiry (ms) a cached token may be before a read/write that needs it triggers a pull broadcast (ADR-0032 decision 3). Default 30s — comfortably ahead of a long-poll cycle.
***
### writeRequestHeaders?
[Section titled “writeRequestHeaders?”](#writerequestheaders)
> `optional` **writeRequestHeaders?**: `Record`<`string`, `string`>
Defined in: packages/client/src/worker/define-sync-worker.ts:142
Write-only headers merged over [requestHeaders](/api/client/interfaces/definesyncworkeroptions/#requestheaders) (e.g. region/DB-affinity). See `createSyncClient`.
# DiagnosticDumpReport
Defined in: packages/client/src/export-store.ts:95
A **diagnostic dump** (ADR-0035, via the throwaway clone of the addendum): human-readable SQL of EVERYTHING the store holds — synced tables, the `_overlay`/`_mutations` journal, the `pgxsinkit` metadata schema, the read-model views, and the reconcile functions/triggers. It is a live datadir dump (checkpoint
* `dumpDataDir`) fed into a memory-backed throwaway PGlite via `loadDataDir`, against which `pg_dump` runs — so the live engine is never touched (the addendum’s whole point). Its phases add the clone boot and the `pg_dump` walls to the shared checkpoint/dump pair.
## Extends
[Section titled “Extends”](#extends)
* [`ExportReportCommon`](/api/client/interfaces/exportreportcommon/)
## Properties
[Section titled “Properties”](#properties)
### byteLength
[Section titled “byteLength”](#bytelength)
> **byteLength**: `number`
Defined in: packages/client/src/export-store.ts:54
The artefact’s byte length — the size the caller downloads / persists.
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
[`ExportReportCommon`](/api/client/interfaces/exportreportcommon/).[`byteLength`](/api/client/interfaces/exportreportcommon/#bytelength)
***
### diagnostics
[Section titled “diagnostics”](#diagnostics)
> **diagnostics**: [`MutationDiagnostics`](/api/client/interfaces/mutationdiagnostics/)
Defined in: packages/client/src/export-store.ts:60
The [MutationDiagnostics](/api/client/interfaces/mutationdiagnostics/) snapshot at export time — the journal state captured alongside the artefact (for the store backup, the very journal that travels INSIDE the tarball; for the diagnostic dump, the live store’s journal at dump time, whose rows the SQL also carries).
#### Inherited from
[Section titled “Inherited from”](#inherited-from-1)
[`ExportReportCommon`](/api/client/interfaces/exportreportcommon/).[`diagnostics`](/api/client/interfaces/exportreportcommon/#diagnostics)
***
### kind
[Section titled “kind”](#kind)
> **kind**: `"diagnostic-dump"`
Defined in: packages/client/src/export-store.ts:96
***
### phases
[Section titled “phases”](#phases)
> **phases**: `object`
Defined in: packages/client/src/export-store.ts:99
#### checkpointMs
[Section titled “checkpointMs”](#checkpointms)
> **checkpointMs**: `number`
`CHECKPOINT` wall — flushing dirty buffers before the internal datadir dump the clone consumes.
#### checkpointStartedAtMs
[Section titled “checkpointStartedAtMs”](#checkpointstartedatms)
> **checkpointStartedAtMs**: `number`
Offset from export start when the `CHECKPOINT` began.
#### cloneBootMs
[Section titled “cloneBootMs”](#clonebootms)
> **cloneBootMs**: `number`
Clone boot wall — booting the memory-backed throwaway from the internal dump.
#### cloneBootStartedAtMs
[Section titled “cloneBootStartedAtMs”](#clonebootstartedatms)
> **cloneBootStartedAtMs**: `number`
Offset from export start when the throwaway clone’s `PGlite.create({ loadDataDir })` began.
#### dumpMs
[Section titled “dumpMs”](#dumpms)
> **dumpMs**: `number`
`dumpDataDir` wall — the uncompressed internal tarball the throwaway clone boots from (`compression: "none"`).
#### dumpStartedAtMs
[Section titled “dumpStartedAtMs”](#dumpstartedatms)
> **dumpStartedAtMs**: `number`
Offset from export start when the internal `dumpDataDir` began.
#### pgDumpMs
[Section titled “pgDumpMs”](#pgdumpms)
> **pgDumpMs**: `number`
`pg_dump` wall — the WASM `pg_dump` reading the clone out to SQL.
#### pgDumpStartedAtMs
[Section titled “pgDumpStartedAtMs”](#pgdumpstartedatms)
> **pgDumpStartedAtMs**: `number`
Offset from export start when `pg_dump` began running against the clone.
***
### reportVersion
[Section titled “reportVersion”](#reportversion)
> **reportVersion**: `1`
Defined in: packages/client/src/export-store.ts:48
#### Inherited from
[Section titled “Inherited from”](#inherited-from-2)
[`ExportReportCommon`](/api/client/interfaces/exportreportcommon/).[`reportVersion`](/api/client/interfaces/exportreportcommon/#reportversion)
***
### scope
[Section titled “scope”](#scope)
> **scope**: `"everything"`
Defined in: packages/client/src/export-store.ts:98
A diagnostic dump covers everything the store holds — synced data, journal, metadata, views, functions.
***
### startedAt
[Section titled “startedAt”](#startedat)
> **startedAt**: `number`
Defined in: packages/client/src/export-store.ts:50
Epoch anchor (`Date.now()`) at export start; every other duration/offset is monotonic relative to it.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-3)
[`ExportReportCommon`](/api/client/interfaces/exportreportcommon/).[`startedAt`](/api/client/interfaces/exportreportcommon/#startedat)
***
### totalMs
[Section titled “totalMs”](#totalms)
> **totalMs**: `number`
Defined in: packages/client/src/export-store.ts:52
Export start → artefact ready.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-4)
[`ExportReportCommon`](/api/client/interfaces/exportreportcommon/).[`totalMs`](/api/client/interfaces/exportreportcommon/#totalms)
# DiagnosticExportDeps
Defined in: packages/client/src/export-dump.ts:46
The dependencies [performDiagnosticExport](/api/client/functions/performdiagnosticexport/) needs from the owning client — narrow, so it is unit-testable.
## Properties
[Section titled “Properties”](#properties)
### pglite
[Section titled “pglite”](#pglite)
> **pglite**: `Pick`<[`ClientPGlite`](/api/client/type-aliases/clientpglite/), `"exec"` | `"dumpDataDir"`>
Defined in: packages/client/src/export-dump.ts:48
The live store to checkpoint and dump (the clone source; the live engine is never suspended).
***
### readMutationStats
[Section titled “readMutationStats”](#readmutationstats)
> **readMutationStats**: () => `Promise`<[`MutationDiagnostics`](/api/client/interfaces/mutationdiagnostics/)>
Defined in: packages/client/src/export-dump.ts:50
The Mutation diagnostics seam (`client.diagnostics().mutation` / `readMutationStats`).
#### Returns
[Section titled “Returns”](#returns)
`Promise`<[`MutationDiagnostics`](/api/client/interfaces/mutationdiagnostics/)>
***
### storePath?
[Section titled “storePath?”](#storepath)
> `optional` **storePath?**: `string`
Defined in: packages/client/src/export-dump.ts:56
The store’s configured plain store PATH (ADR-0036) — reduced to the `storeId` in the default artefact file name. The resolved PGlite dataDir URL is deliberately NOT used: it is internal plumbing and must not leak into an artefact name as something to imitate.
# DiagnosticExportOptions
Defined in: packages/client/src/export-dump.ts:28
Options for [SyncClient.exportDiagnostics](/api/client/interfaces/syncclient/#exportdiagnostics).
## Properties
[Section titled “Properties”](#properties)
### fileName?
[Section titled “fileName?”](#filename)
> `optional` **fileName?**: `string`
Defined in: packages/client/src/export-dump.ts:34
Override the generated artefact file name. When omitted, the name is `--diagnostics.sql`, where `storeId` is a filesystem-safe derivation of the store path (see `deriveStoreId`).
# DiagnosticExportResult
Defined in: packages/client/src/export-dump.ts:38
The SQL artefact + its report — the resolved value of [SyncClient.exportDiagnostics](/api/client/interfaces/syncclient/#exportdiagnostics).
## Properties
[Section titled “Properties”](#properties)
### file
[Section titled “file”](#file)
> **file**: `File`
Defined in: packages/client/src/export-dump.ts:40
The `pg_dump` output as a named `File` (`application/sql`), loadable into a vanilla Postgres.
***
### report
[Section titled “report”](#report)
> **report**: [`DiagnosticDumpReport`](/api/client/interfaces/diagnosticdumpreport/)
Defined in: packages/client/src/export-dump.ts:42
The structured record of the export (ADR-0035).
# DrainJournalOptions
Defined in: packages/client/src/export-data.ts:36
The drain guard’s tuning: the wait budget for reaching a fully-drained journal.
## Properties
[Section titled “Properties”](#properties)
### timeoutMs
[Section titled “timeoutMs”](#timeoutms)
> **timeoutMs**: `number`
Defined in: packages/client/src/export-data.ts:38
Milliseconds to flush + await convergence before throwing [DataExportDrainError](/api/client/classes/dataexportdrainerror/).
# DrizzleQueryBuilder
Defined in: packages/client/src/index.ts:1243
Minimal shape of a Drizzle select builder: inspectable via `.toSQL()` and awaitable for its rows.
## Extends
[Section titled “Extends”](#extends)
* `PromiseLike`<`TRows`>
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRows
[Section titled “TRows”](#trows)
`TRows` *extends* readonly `unknown`\[]
## Methods
[Section titled “Methods”](#methods)
### then()
[Section titled “then()”](#then)
> **then**<`TResult1`, `TResult2`>(`onfulfilled?`, `onrejected?`): `PromiseLike`<`TResult1` | `TResult2`>
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1542
Attaches callbacks for the resolution and/or rejection of the Promise.
#### Type Parameters
[Section titled “Type Parameters”](#type-parameters-1)
##### TResult1
[Section titled “TResult1”](#tresult1)
`TResult1` = `TRows`
##### TResult2
[Section titled “TResult2”](#tresult2)
`TResult2` = `never`
#### Parameters
[Section titled “Parameters”](#parameters)
##### onfulfilled?
[Section titled “onfulfilled?”](#onfulfilled)
((`value`) => `TResult1` | `PromiseLike`<`TResult1`>) | `null`
The callback to execute when the Promise is resolved.
##### onrejected?
[Section titled “onrejected?”](#onrejected)
((`reason`) => `TResult2` | `PromiseLike`<`TResult2`>) | `null`
The callback to execute when the Promise is rejected.
#### Returns
[Section titled “Returns”](#returns)
`PromiseLike`<`TResult1` | `TResult2`>
A Promise for the completion of which ever callback is executed.
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
`PromiseLike.then`
***
### toSQL()
[Section titled “toSQL()”](#tosql)
> **toSQL**(): `object`
Defined in: packages/client/src/index.ts:1244
#### Returns
[Section titled “Returns”](#returns-1)
`object`
##### params
[Section titled “params”](#params)
> **params**: `unknown`\[]
##### sql
[Section titled “sql”](#sql)
> **sql**: `string`
# ElectedEngineWorker
Defined in: packages/client/src/worker/attach-sync-client.ts:136
The elected engine worker handle the election coordinator drives (ADR-0049 D5, step 8): `terminate()` (a deliberate teardown / respawn), `onError` (a reported worker death → immediate respawn), and `deliverControlPort` (post the engine end of the announce control channel — `{ [CONTROL_PORT_DELIVERY_KEY]: true }` with the port transferred — so the elected worker’s step-9 control plane starts on it). The consumer supplies a factory that constructs their own `defineSyncWorker` entry as a dedicated `Worker`; wrap it with [wrapEngineWorker](/api/client/functions/wrapengineworker/).
## Methods
[Section titled “Methods”](#methods)
### deliverControlPort()
[Section titled “deliverControlPort()”](#delivercontrolport)
> **deliverControlPort**(`port`): `void`
Defined in: packages/client/src/worker/attach-sync-client.ts:139
#### Parameters
[Section titled “Parameters”](#parameters)
##### port
[Section titled “port”](#port)
`unknown`
#### Returns
[Section titled “Returns”](#returns)
`void`
***
### onError()
[Section titled “onError()”](#onerror)
> **onError**(`listener`): `void`
Defined in: packages/client/src/worker/attach-sync-client.ts:138
#### Parameters
[Section titled “Parameters”](#parameters-1)
##### listener
[Section titled “listener”](#listener)
(`message`) => `void`
#### Returns
[Section titled “Returns”](#returns-1)
`void`
***
### terminate()
[Section titled “terminate()”](#terminate)
> **terminate**(): `void`
Defined in: packages/client/src/worker/attach-sync-client.ts:137
#### Returns
[Section titled “Returns”](#returns-2)
`void`
# EventAppendResult
Defined in: packages/client/src/event-lane.ts:112
What `appendEvent` resolves with once the event is durably staged: the library’s stamps + the local ordinal.
## Properties
[Section titled “Properties”](#properties)
### eventId
[Section titled “eventId”](#eventid)
> **eventId**: `string`
Defined in: packages/client/src/event-lane.ts:114
The library-stamped uuid — the wire identity, and the key every [EventLaneVerdict](/api/client/interfaces/eventlaneverdict/) reports under.
***
### occurredAtUs
[Section titled “occurredAtUs”](#occurredatus)
> **occurredAtUs**: `string`
Defined in: packages/client/src/event-lane.ts:116
The library-stamped append time in microseconds (decimal string).
***
### seq
[Section titled “seq”](#seq)
> **seq**: `string`
Defined in: packages/client/src/event-lane.ts:118
The local append ordinal (decimal string). Local machinery: never transmitted.
# EventBackoffOptions
Defined in: packages/client/src/event-lane.ts:70
Jittered exponential backoff tuning, shared by the per-row deferred backoff and the batch-level one.
## Properties
[Section titled “Properties”](#properties)
### baseMs?
[Section titled “baseMs?”](#basems)
> `optional` **baseMs?**: `number`
Defined in: packages/client/src/event-lane.ts:72
The first delay, doubled per attempt. Defaults to [DEFAULT\_EVENT\_BACKOFF\_BASE\_MS](/api/client/variables/default_event_backoff_base_ms/).
***
### ceilingMs?
[Section titled “ceilingMs?”](#ceilingms)
> `optional` **ceilingMs?**: `number`
Defined in: packages/client/src/event-lane.ts:74
The delay ceiling. Defaults to [DEFAULT\_EVENT\_BACKOFF\_CEILING\_MS](/api/client/variables/default_event_backoff_ceiling_ms/).
# EventFlushDriver
Defined in: packages/client/src/event-lane.ts:1105
## Properties
[Section titled “Properties”](#properties)
### requestPass
[Section titled “requestPass”](#requestpass)
> **requestPass**: () => `void`
Defined in: packages/client/src/event-lane.ts:1110
Request a pass now — the nudge an append fires, coalesced exactly like an interval tick.
#### Returns
[Section titled “Returns”](#returns)
`void`
***
### start
[Section titled “start”](#start)
> **start**: () => `void`
Defined in: packages/client/src/event-lane.ts:1106
#### Returns
[Section titled “Returns”](#returns-1)
`void`
***
### stop
[Section titled “stop”](#stop)
> **stop**: () => `Promise`<`void`>
Defined in: packages/client/src/event-lane.ts:1108
Stop scheduling and await any in-flight pass, so a caller can safely close the store afterwards.
#### Returns
[Section titled “Returns”](#returns-2)
`Promise`<`void`>
# EventFlushDriverOptions
Defined in: packages/client/src/event-lane.ts:1095
## Properties
[Section titled “Properties”](#properties)
### flush
[Section titled “flush”](#flush)
> **flush**: () => `Promise`<`void`>
Defined in: packages/client/src/event-lane.ts:1096
#### Returns
[Section titled “Returns”](#returns)
`Promise`<`void`>
***
### gate?
[Section titled “gate?”](#gate)
> `optional` **gate?**: [`EventFlushGate`](/api/client/interfaces/eventflushgate/)
Defined in: packages/client/src/event-lane.ts:1098
The app’s online/foreground gate, when one is installed. Absent → always flush.
***
### intervalMs?
[Section titled “intervalMs?”](#intervalms)
> `optional` **intervalMs?**: `number`
Defined in: packages/client/src/event-lane.ts:1100
The fallback interval. Defaults to [DEFAULT\_EVENT\_FLUSH\_INTERVAL\_MS](/api/client/variables/default_event_flush_interval_ms/).
***
### onPass?
[Section titled “onPass?”](#onpass)
> `optional` **onPass?**: (`error`) => `void`
Defined in: packages/client/src/event-lane.ts:1102
Invoked after each pass with the error it raised, or `null`.
#### Parameters
[Section titled “Parameters”](#parameters)
##### error
[Section titled “error”](#error)
`unknown`
#### Returns
[Section titled “Returns”](#returns-1)
`void`
# EventFlushGate
Defined in: packages/client/src/event-lane.ts:1089
The scheduling gate the event-lane driver shares with the convergence driver (`autoSync`’s trigger).
## Properties
[Section titled “Properties”](#properties)
### shouldFlush
[Section titled “shouldFlush”](#shouldflush)
> **shouldFlush**: () => `boolean`
Defined in: packages/client/src/event-lane.ts:1092
Whether a flush should run right now — the offline pause (ADR-0053 decision 4).
#### Returns
[Section titled “Returns”](#returns)
`boolean`
***
### subscribe
[Section titled “subscribe”](#subscribe)
> **subscribe**: (`onSignal`) => () => `void`
Defined in: packages/client/src/event-lane.ts:1090
#### Parameters
[Section titled “Parameters”](#parameters)
##### onSignal
[Section titled “onSignal”](#onsignal)
() => `void`
#### Returns
[Section titled “Returns”](#returns-1)
() => `void`
# EventLaneBackoffTransition
Defined in: packages/client/src/event-lane.ts:140
A batch-level backoff transition (ADR-0053 decision 4) — the lane entering or leaving its retry backoff.
## Properties
[Section titled “Properties”](#properties)
### attempt?
[Section titled “attempt?”](#attempt)
> `optional` **attempt?**: `number`
Defined in: packages/client/src/event-lane.ts:145
Consecutive failed batch attempts behind the current backoff. Present when `entered`.
***
### httpStatus?
[Section titled “httpStatus?”](#httpstatus)
> `optional` **httpStatus?**: `number`
Defined in: packages/client/src/event-lane.ts:147
The HTTP status that caused it, when there was one (absent for a transport failure).
***
### reason?
[Section titled “reason?”](#reason)
> `optional` **reason?**: `string`
Defined in: packages/client/src/event-lane.ts:149
The failure message. Present when `entered`.
***
### retryInMs?
[Section titled “retryInMs?”](#retryinms)
> `optional` **retryInMs?**: `number`
Defined in: packages/client/src/event-lane.ts:143
How long the lane will hold off, in ms. Present when `entered`.
***
### state
[Section titled “state”](#state)
> **state**: `"entered"` | `"cleared"`
Defined in: packages/client/src/event-lane.ts:141
# EventLaneDb
Defined in: packages/client/src/event-lane.ts:46
The raw local-store seam the Event lane executes through (structurally PGlite; mirrors `MutationDb`).
## Properties
[Section titled “Properties”](#properties)
### query
[Section titled “query”](#query)
> **query**: <`TRow`>(`sql`, `params?`) => `Promise`<{ `rows`: `TRow`\[]; }>
Defined in: packages/client/src/event-lane.ts:47
#### Type Parameters
[Section titled “Type Parameters”](#type-parameters)
##### TRow
[Section titled “TRow”](#trow)
`TRow` *extends* `Record`<`string`, `unknown`> = `Record`<`string`, `unknown`>
#### Parameters
[Section titled “Parameters”](#parameters)
##### sql
[Section titled “sql”](#sql)
`string`
##### params?
[Section titled “params?”](#params)
`unknown`\[]
#### Returns
[Section titled “Returns”](#returns)
`Promise`<{ `rows`: `TRow`\[]; }>
# EventLaneOptions
Defined in: packages/client/src/event-lane.ts:100
The Event lane’s client-level flush policy (ADR-0053 consequences). It lives on `createSyncClient`/`defineSyncWorker`, NEVER on the registry: the registry is the contract, cadence is deployment tuning, and a batch-size tweak must not surface as a registry diff. Client batching is additionally clamped by the contracts-level request-shape limits, which the server enforces independently.
## Properties
[Section titled “Properties”](#properties)
### backoff?
[Section titled “backoff?”](#backoff)
> `optional` **backoff?**: [`EventBackoffOptions`](/api/client/interfaces/eventbackoffoptions/)
Defined in: packages/client/src/event-lane.ts:106
Backoff tuning for deferred rows and for batch-level faults.
***
### batchSize?
[Section titled “batchSize?”](#batchsize)
> `optional` **batchSize?**: `number`
Defined in: packages/client/src/event-lane.ts:102
Events per flush batch. Defaults to [DEFAULT\_EVENT\_BATCH\_SIZE](/api/client/variables/default_event_batch_size/); clamped to MAX\_EVENTS\_PER\_BATCH.
***
### intervalMs?
[Section titled “intervalMs?”](#intervalms)
> `optional` **intervalMs?**: `number`
Defined in: packages/client/src/event-lane.ts:104
The flush driver’s fallback interval. Defaults to [DEFAULT\_EVENT\_FLUSH\_INTERVAL\_MS](/api/client/variables/default_event_flush_interval_ms/).
***
### streams?
[Section titled “streams?”](#streams)
> `optional` **streams?**: `Record`<`string`, [`EventStreamFlushOptions`](/api/client/interfaces/eventstreamflushoptions/)>
Defined in: packages/client/src/event-lane.ts:108
Per-Event-stream overrides, keyed by Event-stream name.
# EventLaneReport
Defined in: packages/client/src/event-lane.ts:160
One flush pass’s report (ADR-0053 decision 2) — the ephemeral surface `onEventLaneReport` delivers.
It carries what the Outbox can no longer answer: `acked` is NOT reported (a successful append-only lane would drown the app in its own volume), but `refused` and `rejected` are — those rows are deleted, so once they are gone the Outbox cannot say what happened to them. `deferred` rows stay, but are reported so a rollout skew is visible rather than mysterious. The batch-level backoff transitions ride here too.
## Properties
[Section titled “Properties”](#properties)
### backoff?
[Section titled “backoff?”](#backoff)
> `optional` **backoff?**: [`EventLaneBackoffTransition`](/api/client/interfaces/eventlanebackofftransition/)
Defined in: packages/client/src/event-lane.ts:166
Present only on the pass that changed the lane’s batch-level backoff state.
***
### deferred
[Section titled “deferred”](#deferred)
> **deferred**: [`EventLaneVerdict`](/api/client/interfaces/eventlaneverdict/)\[]
Defined in: packages/client/src/event-lane.ts:164
`deferred` verdicts: the row STAYS in the Outbox and retries with backoff.
***
### terminal
[Section titled “terminal”](#terminal)
> **terminal**: [`EventLaneVerdict`](/api/client/interfaces/eventlaneverdict/)\[]
Defined in: packages/client/src/event-lane.ts:162
Terminal, non-`acked` verdicts: the row was DELETED on the server’s say-so.
# EventLaneRuntime
Defined in: packages/client/src/event-lane.ts:185
## Properties
[Section titled “Properties”](#properties)
### abortInFlight
[Section titled “abortInFlight”](#abortinflight)
> **abortInFlight**: () => `void`
Defined in: packages/client/src/event-lane.ts:198
Abort any in-flight flush request (the teardown seam, mirroring the mutation runtime’s).
#### Returns
[Section titled “Returns”](#returns)
`void`
***
### appendEvent
[Section titled “appendEvent”](#appendevent)
> **appendEvent**: (`stream`, `payload`) => `Promise`<[`EventAppendResult`](/api/client/interfaces/eventappendresult/)>
Defined in: packages/client/src/event-lane.ts:188
#### Parameters
[Section titled “Parameters”](#parameters)
##### stream
[Section titled “stream”](#stream)
`string`
##### payload
[Section titled “payload”](#payload)
`unknown`
#### Returns
[Section titled “Returns”](#returns-1)
`Promise`<[`EventAppendResult`](/api/client/interfaces/eventappendresult/)>
***
### flush
[Section titled “flush”](#flush)
> **flush**: () => `Promise`<`void`>
Defined in: packages/client/src/event-lane.ts:190
Drain the Outbox: assemble, POST and settle batches until nothing more is eligible this pass.
#### Returns
[Section titled “Returns”](#returns-2)
`Promise`<`void`>
***
### hasStreams
[Section titled “hasStreams”](#hasstreams)
> `readonly` **hasStreams**: `boolean`
Defined in: packages/client/src/event-lane.ts:187
Whether the registry registered any Event stream. False → `appendEvent` throws and no driver is stood up.
***
### onEventLaneReport
[Section titled “onEventLaneReport”](#oneventlanereport)
> **onEventLaneReport**: (`listener`) => () => `void`
Defined in: packages/client/src/event-lane.ts:196
Subscribe to per-flush reports. Ephemeral (nothing is retained); returns an unsubscribe.
#### Parameters
[Section titled “Parameters”](#parameters-1)
##### listener
[Section titled “listener”](#listener)
(`report`) => `void`
#### Returns
[Section titled “Returns”](#returns-3)
() => `void`
***
### onOutboxStatus
[Section titled “onOutboxStatus”](#onoutboxstatus)
> **onOutboxStatus**: (`listener`) => () => `void`
Defined in: packages/client/src/event-lane.ts:194
Subscribe to the drain signal; the CURRENT state is delivered on subscribe. Returns an unsubscribe.
#### Parameters
[Section titled “Parameters”](#parameters-2)
##### listener
[Section titled “listener”](#listener-1)
(`status`) => `void`
#### Returns
[Section titled “Returns”](#returns-4)
() => `void`
***
### outboxStatus
[Section titled “outboxStatus”](#outboxstatus)
> **outboxStatus**: () => `Promise`<[`OutboxStatus`](/api/client/interfaces/outboxstatus/)>
Defined in: packages/client/src/event-lane.ts:192
The current drain signal, read from the store.
#### Returns
[Section titled “Returns”](#returns-5)
`Promise`<[`OutboxStatus`](/api/client/interfaces/outboxstatus/)>
# EventLaneVerdict
Defined in: packages/client/src/event-lane.ts:131
One server-issued per-event verdict, as surfaced on the report.
## Properties
[Section titled “Properties”](#properties)
### eventId
[Section titled “eventId”](#eventid)
> **eventId**: `string`
Defined in: packages/client/src/event-lane.ts:132
***
### reason?
[Section titled “reason?”](#reason)
> `optional` **reason?**: `string`
Defined in: packages/client/src/event-lane.ts:136
***
### status
[Section titled “status”](#status)
> **status**: `"acked"` | `"refused"` | `"rejected"` | `"deferred"`
Defined in: packages/client/src/event-lane.ts:135
***
### stream
[Section titled “stream”](#stream)
> **stream**: `string`
Defined in: packages/client/src/event-lane.ts:134
The Event-stream name the event was appended under.
# EventStreamFlushOptions
Defined in: packages/client/src/event-lane.ts:78
Per-Event-stream flush overrides (ADR-0053 consequences: cadence is client config, never registry).
## Properties
[Section titled “Properties”](#properties)
### backoff?
[Section titled “backoff?”](#backoff)
> `optional` **backoff?**: [`EventBackoffOptions`](/api/client/interfaces/eventbackoffoptions/)
Defined in: packages/client/src/event-lane.ts:91
Backoff tuning for THIS stream’s `deferred` rows (e.g. a stream mid-rollout wanting a shorter retry).
***
### batchSize?
[Section titled “batchSize?”](#batchsize)
> `optional` **batchSize?**: `number`
Defined in: packages/client/src/event-lane.ts:89
Cap on how many events of THIS Event stream ride one batch. The lane assembles one mixed batch ordered by `seq` across every stream, so this is the knob that stops one chatty stream from filling every batch ahead of a quieter one. Absent → only the batch-wide [EventLaneOptions.batchSize](/api/client/interfaces/eventlaneoptions/#batchsize) applies.
The cap is applied IN the selection query (each stream’s eligible rows are ranked by `seq` and the over-cap ones dropped BEFORE the global order/limit), so it genuinely yields the batch slots it frees to other streams rather than just shrinking the request. Must be an integer >= 1 — a `0`/negative/`NaN` cap would hold every row of the stream back forever, so it is refused at construction.
# ExecutionLimitConfig
Defined in: packages/client/src/worker/engine-control.ts:250
The opt-in execution limit (ADR D5). ONE engine-construction value; DISABLED BY DEFAULT — no finite worst-case query duration exists, and the limit CONVERTS slow to terminated by policy, so enabling it must be a deliberate consumer choice (the public contract). When enabled, the limit CONVERTS slow to terminated after the control-probe threshold — it is NEVER claimed as death evidence. This feature is ELECTED-PLACEMENT ONLY: on SharedWorker-direct placement (WebKit) the option is rejected as unsupported — that rejection is wired in a later step; this module only defines the config + the cross-tab mismatch rule.
## Properties
[Section titled “Properties”](#properties)
### maxDispatchMs?
[Section titled “maxDispatchMs?”](#maxdispatchms)
> `optional` **maxDispatchMs?**: `number`
Defined in: packages/client/src/worker/engine-control.ts:252
ms; `undefined` = DISABLED (the default — preserves unbounded queries).
# ExportArtefactWire
Defined in: packages/client/src/worker/protocol.ts:364
worker → tab: the wire form of ANY local-store export (ADR-0035) — the store backup’s tarball OR the diagnostic dump’s SQL — carried as the `value` of an [RpcResultPayload](/api/client/interfaces/rpcresultpayload/). The artefact crosses as a transferred `ArrayBuffer` (zero-copy — the sender lists `buffer` in `postMessage`’s transfer list) plus the metadata to rebuild it; the tab reconstructs a `File` from `buffer`/`fileName`/`mimeType`. A `File` cannot itself be transferred, so it is decomposed here and reassembled tab-side, exactly the pattern the codec’s transferable seam anticipates for a zero-copy live-diff payload. `report` is the discriminated [ExportReport](/api/client/type-aliases/exportreport/) union, so the tab knows which export it round-tripped.
## Properties
[Section titled “Properties”](#properties)
### buffer
[Section titled “buffer”](#buffer)
> **buffer**: `ArrayBuffer`
Defined in: packages/client/src/worker/protocol.ts:366
The artefact bytes (tarball or SQL) — transferred, not copied.
***
### fileName
[Section titled “fileName”](#filename)
> **fileName**: `string`
Defined in: packages/client/src/worker/protocol.ts:368
The generated (or caller-supplied) artefact file name.
***
### mimeType
[Section titled “mimeType”](#mimetype)
> **mimeType**: `string`
Defined in: packages/client/src/worker/protocol.ts:370
The artefact MIME type (`application/x-gzip` / `application/x-tar` for a backup, `application/sql` for a dump).
***
### report
[Section titled “report”](#report)
> **report**: [`ExportReport`](/api/client/type-aliases/exportreport/)
Defined in: packages/client/src/worker/protocol.ts:372
The export report (ADR-0035), structured-cloned as a plain object — the `kind`-discriminated union.
# ExportReportCommon
Defined in: packages/client/src/export-store.ts:47
The fields EVERY export report carries, whatever its kind — built in the BootReport house style (ADR-0034): `reportVersion` is a contract number (additive fields keep it, a breaking reshape bumps it); all `*Ms` are milliseconds; `startedAt` is the only wall-clock value and every `*AtMs` is a monotonic offset from it. The per-kind interfaces below add their `kind`/`scope`/`phases` discriminant on top.
## Extended by
[Section titled “Extended by”](#extended-by)
* [`DataExportReport`](/api/client/interfaces/dataexportreport/)
* [`DiagnosticDumpReport`](/api/client/interfaces/diagnosticdumpreport/)
* [`StoreBackupReport`](/api/client/interfaces/storebackupreport/)
## Properties
[Section titled “Properties”](#properties)
### byteLength
[Section titled “byteLength”](#bytelength)
> **byteLength**: `number`
Defined in: packages/client/src/export-store.ts:54
The artefact’s byte length — the size the caller downloads / persists.
***
### diagnostics
[Section titled “diagnostics”](#diagnostics)
> **diagnostics**: [`MutationDiagnostics`](/api/client/interfaces/mutationdiagnostics/)
Defined in: packages/client/src/export-store.ts:60
The [MutationDiagnostics](/api/client/interfaces/mutationdiagnostics/) snapshot at export time — the journal state captured alongside the artefact (for the store backup, the very journal that travels INSIDE the tarball; for the diagnostic dump, the live store’s journal at dump time, whose rows the SQL also carries).
***
### reportVersion
[Section titled “reportVersion”](#reportversion)
> **reportVersion**: `1`
Defined in: packages/client/src/export-store.ts:48
***
### startedAt
[Section titled “startedAt”](#startedat)
> **startedAt**: `number`
Defined in: packages/client/src/export-store.ts:50
Epoch anchor (`Date.now()`) at export start; every other duration/offset is monotonic relative to it.
***
### totalMs
[Section titled “totalMs”](#totalms)
> **totalMs**: `number`
Defined in: packages/client/src/export-store.ts:52
Export start → artefact ready.
# FreshBootResolution
Defined in: packages/client/src/index.ts:685
The PRE-MINT outcome of resolveFreshBoot: the resolved backend and whether the milestone owes a barrier.
## Properties
[Section titled “Properties”](#properties)
### bootHasOpfs
[Section titled “bootHasOpfs”](#boothasopfs)
> **bootHasOpfs**: `boolean`
Defined in: packages/client/src/index.ts:687
Whether the client-owned mint should open the OPFS backend (a classification landing on idb flips this false).
***
### needsCommitmentBarrier
[Section titled “needsCommitmentBarrier”](#needscommitmentbarrier)
> **needsCommitmentBarrier**: `boolean`
Defined in: packages/client/src/index.ts:698
Whether an UNCOMMITTED opfs candidate was stood up that the shared commitment barrier must promote at the local-init milestone ([runFreshCommitmentBarrier](/api/client/functions/runfreshcommitmentbarrier/)) BEFORE exposure. True only for the two fresh candidate verdicts (`virgin-create` / `delete-candidate-and-rebuild`) landing on `opfs-repacked`; a committed store (`open-committed` / `repair-record-then-open-committed`) is already committed (no re-run).
***
### storageBackend
[Section titled “storageBackend”](#storagebackend)
> **storageBackend**: [`ResolvedStorageBackend`](/api/client/type-aliases/resolvedstoragebackend/)
Defined in: packages/client/src/index.ts:689
The resolved backend, for diagnostics (ADR-0049).
***
### verdict?
[Section titled “verdict?”](#verdict)
> `optional` **verdict?**: `StoreBootVerdict`
Defined in: packages/client/src/index.ts:691
The executed boot verdict (absent on the short-circuited non-opfs path).
# GuardedRawQuerySpec
Defined in: packages/client/src/index.ts:1261
A guarded query whose builder MAY embed a raw `sql` template fragment (ADR-0021). A raw fragment can name a lazy relation as a bare/unquoted identifier the compiled-SQL scan cannot see, so declare those relations in `use` — they are activated and awaited before the query runs. Pure-Drizzle reads need no `use` (the scan detects every relation): pass the builder callback directly to `client.query`.
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
### TRows
[Section titled “TRows”](#trows)
`TRows` *extends* readonly `unknown`\[]
## Properties
[Section titled “Properties”](#properties)
### build
[Section titled “build”](#build)
> **build**: [`GuardedQueryFn`](/api/client/type-aliases/guardedqueryfn/)<`TRegistry`, `TRows`>
Defined in: packages/client/src/index.ts:1263
***
### use?
[Section titled “use?”](#use)
> `optional` **use?**: readonly `SyncTableName`<`TRegistry`>\[]
Defined in: packages/client/src/index.ts:1262
# LazyGuardIndex
Defined in: packages/client/src/lazy-guard.ts:23
Static index of a registry’s synced relations and the exact quoted tokens their reads compile to.
## Properties
[Section titled “Properties”](#properties)
### allKeys
[Section titled “allKeys”](#allkeys)
> `readonly` **allKeys**: `ReadonlySet`<`string`>
Defined in: packages/client/src/lazy-guard.ts:39
EVERY synced relation key (eager AND lazy). [lazyKeys](/api/client/interfaces/lazyguardindex/#lazykeys) is a subset. Drives the hydration guarantee (ADR-0021/0032): a live subscription must gate `hydrating` on every consistency group it reads, not only the lazy ones — an eager group can still be catching up on a cold boot.
***
### allTokens
[Section titled “allTokens”](#alltokens)
> `readonly` **allTokens**: `ReadonlyMap`<`string`, readonly `string`\[]>
Defined in: packages/client/src/lazy-guard.ts:45
Per synced key (eager AND lazy), the exact quoted reference token(s) its reads compile to — the same token rules as [lazyTokens](/api/client/interfaces/lazyguardindex/#lazytokens), just spanning every subscription timing. [lazyTokens](/api/client/interfaces/lazyguardindex/#lazytokens) is a subset restricted to the lazy keys (activation stays lazy-only; hydration spans all).
***
### lazyKeys
[Section titled “lazyKeys”](#lazykeys)
> `readonly` **lazyKeys**: `ReadonlySet`<`string`>
Defined in: packages/client/src/lazy-guard.ts:25
Registry keys whose subscription timing is `lazy`.
***
### lazyTokens
[Section titled “lazyTokens”](#lazytokens)
> `readonly` **lazyTokens**: `ReadonlyMap`<`string`, readonly `string`\[]>
Defined in: packages/client/src/lazy-guard.ts:33
Per lazy key, the exact quoted reference token(s) the compiled SQL emits when reading it: the synced table (and, for readwrite, its read-model view) as `"name"` or `"schema"."name"` — or, for a read PROJECTION (ADR-0025), its DISTINCT local identity (`localTable`, the `as` name) rather than the owner’s physical table. Schema-qualified when the Drizzle object carries a schema — which makes the token collision-proof against bare aliases/CTEs.
# LifecycleSlot
Defined in: packages/client/src/lifecycle-slot.ts:32
A single-occupancy lifecycle slot. One owner per store (`createSyncClient`); one operation at a time.
## Properties
[Section titled “Properties”](#properties)
### isBusy
[Section titled “isBusy”](#isbusy)
> **isBusy**: () => `boolean`
Defined in: packages/client/src/lifecycle-slot.ts:42
Whether an operation currently holds the slot.
#### Returns
[Section titled “Returns”](#returns)
`boolean`
***
### run
[Section titled “run”](#run)
> **run**: <`T`>(`label`, `fn`) => `Promise`<`T`>
Defined in: packages/client/src/lifecycle-slot.ts:40
Run `fn` under the slot’s exclusion. Rejects with a [LifecycleBusyError](/api/client/classes/lifecyclebusyerror/) — without invoking `fn` — if the slot is already occupied; otherwise holds the slot for `fn`’s lifetime and releases it in a `finally`, so a throwing operation never leaves the slot stuck. The occupancy check + claim run synchronously on entry (before the first `await`), so two calls issued in the same tick resolve deterministically: the first claims, the second is refused.
#### Type Parameters
[Section titled “Type Parameters”](#type-parameters)
##### T
[Section titled “T”](#t)
`T`
#### Parameters
[Section titled “Parameters”](#parameters)
##### label
[Section titled “label”](#label)
`string`
##### fn
[Section titled “fn”](#fn)
() => `Promise`<`T`>
#### Returns
[Section titled “Returns”](#returns-1)
`Promise`<`T`>
***
### runningLabel
[Section titled “runningLabel”](#runninglabel)
> **runningLabel**: () => `string` | `null`
Defined in: packages/client/src/lifecycle-slot.ts:44
The label of the operation currently holding the slot, or `null` when free.
#### Returns
[Section titled “Returns”](#returns-2)
`string` | `null`
# LiveDiffPayload
Defined in: packages/client/src/worker/protocol.ts:453
worker → tab: a DIFF update for a subscription (§4). Never a full result-set resend after the initial snapshot. `order` is the full ordered list of row keys (cheap — keys only, no row bodies); `added`/ `changed` carry only the delta row bodies; `removed` carries only the dropped keys. The tab materializer rebuilds the ordered array from `order`, reusing cached row objects for keys not in `added`/`changed` so an unchanged row keeps its object identity (React memo bails on `===`).
## Properties
[Section titled “Properties”](#properties)
### added
[Section titled “added”](#added)
> **added**: `object`\[]
Defined in: packages/client/src/worker/protocol.ts:457
#### key
[Section titled “key”](#key)
> **key**: `string`
#### row
[Section titled “row”](#row)
> **row**: `Record`<`string`, `unknown`>
***
### changed
[Section titled “changed”](#changed)
> **changed**: `object`\[]
Defined in: packages/client/src/worker/protocol.ts:458
#### key
[Section titled “key”](#key-1)
> **key**: `string`
#### row
[Section titled “row”](#row-1)
> **row**: `Record`<`string`, `unknown`>
***
### order
[Section titled “order”](#order)
> **order**: `string`\[]
Defined in: packages/client/src/worker/protocol.ts:456
Every result key in ORDER BY order (the query’s delivered order).
***
### queryId
[Section titled “queryId”](#queryid)
> **queryId**: `string`
Defined in: packages/client/src/worker/protocol.ts:454
***
### removed
[Section titled “removed”](#removed)
> **removed**: `string`\[]
Defined in: packages/client/src/worker/protocol.ts:459
# LiveDiffState
Defined in: packages/client/src/worker/live-diff.ts:39
The worker’s per-subscription memory: the last delivered rows keyed by [rowKey](/api/client/functions/rowkey/), in delivered order.
## Properties
[Section titled “Properties”](#properties)
### pkColumns?
[Section titled “pkColumns?”](#pkcolumns)
> `optional` **pkColumns?**: readonly `string`\[]
Defined in: packages/client/src/worker/live-diff.ts:40
***
### previous
[Section titled “previous”](#previous)
> **previous**: `Map`<`string`, `Record`<`string`, `unknown`>>
Defined in: packages/client/src/worker/live-diff.ts:42
key → the exact row body last sent, so the next diff can detect `changed` and skip unchanged rows.
# LiveInitialPayload
Defined in: packages/client/src/worker/protocol.ts:416
worker → tab: the initial ordered snapshot for a subscription. `id` correlates the `subscribe` request.
## Properties
[Section titled “Properties”](#properties)
### hydratingTables?
[Section titled “hydratingTables?”](#hydratingtables)
> `optional` **hydratingTables?**: `string`\[]
Defined in: packages/client/src/worker/protocol.ts:431
The referenced consistency groups’ member tables (eager OR lazy) that were NOT YET caught up when this snapshot was taken (ADR-0021 / ADR-0032). Non-empty → the tab builds a `hydrated` promise the worker settles via `live-hydrated` (posted after the catch-up rows on this same port). Absent → every referenced group was already ready (steady state) or sync is disabled: nothing to hydrate.
***
### lazyTables?
[Section titled “lazyTables?”](#lazytables)
> `optional` **lazyTables?**: `string`\[]
Defined in: packages/client/src/worker/protocol.ts:424
The `lazy` relations the worker’s guard activated for this query (ADR-0021) — the relations held out of the eager boot set. Observability only: [hydratingTables](/api/client/interfaces/liveinitialpayload/#hydratingtables), not this, drives the tab’s `hydrated` promise.
***
### queryId
[Section titled “queryId”](#queryid)
> **queryId**: `string`
Defined in: packages/client/src/worker/protocol.ts:417
***
### rows
[Section titled “rows”](#rows)
> **rows**: `Record`<`string`, `unknown`>\[]
Defined in: packages/client/src/worker/protocol.ts:418
# LiveQueryDiagnostics
Defined in: packages/client/src/worker/live-query-manager.ts:118
A structured-clone-safe, per-entry diagnostics record (ADR-0040 decision 5). It carries ONLY opaque fingerprint digests and counts/timings — NEVER SQL text, bound param values, or result-row values (those live only in the fingerprint’s private `key`, which is deliberately absent here). Safe to cross the bridge and surface in support tooling.
## Properties
[Section titled “Properties”](#properties)
### createdAt
[Section titled “createdAt”](#createdat)
> **createdAt**: `number`
Defined in: packages/client/src/worker/live-query-manager.ts:136
Monotonic stamp (ms) when the entry was created.
***
### dedupHits
[Section titled “dedupHits”](#deduphits)
> **dedupHits**: `number`
Defined in: packages/client/src/worker/live-query-manager.ts:134
How many subscribes JOINED this entry rather than creating it (active-joins + retained rejoins).
***
### digest
[Section titled “digest”](#digest)
> **digest**: `string`
Defined in: packages/client/src/worker/live-query-manager.ts:120
The opaque fingerprint digest (a short hash — see live-query-fingerprint.ts). NEVER the full key.
***
### lastUsedAt
[Section titled “lastUsedAt”](#lastusedat)
> **lastUsedAt**: `number` | `null`
Defined in: packages/client/src/worker/live-query-manager.ts:142
Monotonic stamp (ms) of the last transition to zero subscribers, or `null` if never retained.
***
### refresh
[Section titled “refresh”](#refresh)
> **refresh**: `object`
Defined in: packages/client/src/worker/live-query-manager.ts:140
Refresh timings for the shared registration (the hydration-chain / force refreshes).
#### count
[Section titled “count”](#count)
> **count**: `number`
#### lastMs
[Section titled “lastMs”](#lastms)
> **lastMs**: `number` | `null`
#### maxMs
[Section titled “maxMs”](#maxms)
> **maxMs**: `number`
#### totalMs
[Section titled “totalMs”](#totalms)
> **totalMs**: `number`
***
### retained
[Section titled “retained”](#retained)
> **retained**: `boolean`
Defined in: packages/client/src/worker/live-query-manager.ts:132
Whether this is a zero-subscriber entry currently kept alive (ADR-0040 decision 4).
***
### retainedSinceMs
[Section titled “retainedSinceMs”](#retainedsincems)
> **retainedSinceMs**: `number` | `null`
Defined in: packages/client/src/worker/live-query-manager.ts:144
How long (ms) the entry has been retained (now − lastUsedAt), or `null` when not retained.
***
### rowCount
[Section titled “rowCount”](#rowcount)
> **rowCount**: `number`
Defined in: packages/client/src/worker/live-query-manager.ts:130
Rows currently held in the shared diff state.
***
### scopeCount
[Section titled “scopeCount”](#scopecount)
> **scopeCount**: `number`
Defined in: packages/client/src/worker/live-query-manager.ts:128
Distinct subscription scopes on this entry. The worker passes one scope per bridge port, so in WORKER mode this is the number of distinct tabs on the query; the in-process client passes none, so it stays 1 while any subscriber is attached (0 while retained).
***
### setupMs
[Section titled “setupMs”](#setupms)
> **setupMs**: `number` | `null`
Defined in: packages/client/src/worker/live-query-manager.ts:138
Time (ms) the registration setup took, or `null` until setup completes.
***
### subscriberCount
[Section titled “subscriberCount”](#subscribercount)
> **subscriberCount**: `number`
Defined in: packages/client/src/worker/live-query-manager.ts:122
Current subscriber count (0 while retained).
***
### teardownPending
[Section titled “teardownPending”](#teardownpending)
> **teardownPending**: `boolean`
Defined in: packages/client/src/worker/live-query-manager.ts:146
A teardown is in flight (the entry is unsubscribing from PGlite).
# LiveRowsSubscription
Defined in: packages/client/src/index.ts:1321
A handle to a live-rows subscription: the initial ordered snapshot plus an idempotent unsubscribe.
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRow
[Section titled “TRow”](#trow)
`TRow`
## Properties
[Section titled “Properties”](#properties)
### hydrated?
[Section titled “hydrated?”](#hydrated)
> `optional` **hydrated?**: `Promise`<`void`>
Defined in: packages/client/src/index.ts:1340
Present when the query reads ANY consistency group (eager OR lazy) that was NOT YET caught up at subscribe time: resolves once every such group has completed its initial catch-up AND this subscription has already delivered rows reflecting it (the seam refreshes the live query after catch-up, then resolves — rows-before-signal is guaranteed, so flipping a UI out of its loading state on this promise can never flash a false “empty”). Absent when every referenced group is already ready at subscribe time (the steady-state fast path — no extra refresh) or sync is disabled. Offline note: stays pending until the catch-up truly completes — gate empty-state COPY on it, not data access (rows flow regardless).
***
### initialRows
[Section titled “initialRows”](#initialrows)
> **initialRows**: `TRow`\[]
Defined in: packages/client/src/index.ts:1322
***
### lazyTables?
[Section titled “lazyTables?”](#lazytables)
> `optional` **lazyTables?**: readonly `string`\[]
Defined in: packages/client/src/index.ts:1329
The `lazy` relations the query reads (the guard’s scan) — the relations held out of the eager boot set. Activation (stream start) completed before the subscription registered. Informational only: [hydrated](/api/client/interfaces/liverowssubscription/#hydrated) — not this — reflects catch-up completion, and it now spans eager groups too.
***
### unsubscribe
[Section titled “unsubscribe”](#unsubscribe)
> **unsubscribe**: () => `void`
Defined in: packages/client/src/index.ts:1323
#### Returns
[Section titled “Returns”](#returns)
`void`
# LocalStoreVersionEvent
Defined in: packages/client/src/local-store.ts:24
A boot-time registry-version reconciliation outcome (ADR-0006 drain-then-drop).
## Properties
[Section titled “Properties”](#properties)
### nextFingerprint
[Section titled “nextFingerprint”](#nextfingerprint)
> **nextFingerprint**: `string`
Defined in: packages/client/src/local-store.ts:27
***
### owedMutations
[Section titled “owedMutations”](#owedmutations)
> **owedMutations**: `number`
Defined in: packages/client/src/local-store.ts:29
Mutations still owed to the server (pending + sending + failed + quarantined).
***
### previousFingerprint
[Section titled “previousFingerprint”](#previousfingerprint)
> **previousFingerprint**: `string`
Defined in: packages/client/src/local-store.ts:26
***
### status
[Section titled “status”](#status)
> **status**: `"deferred"` | `"rebuilt"`
Defined in: packages/client/src/local-store.ts:25
# MutationDetail
Defined in: packages/client/src/mutation.ts:156
## Properties
[Section titled “Properties”](#properties)
### attemptCount
[Section titled “attemptCount”](#attemptcount)
> **attemptCount**: `number`
Defined in: packages/client/src/mutation.ts:163
***
### conflictReason
[Section titled “conflictReason”](#conflictreason)
> **conflictReason**: `string` | `null`
Defined in: packages/client/src/mutation.ts:166
***
### entityKey
[Section titled “entityKey”](#entitykey)
> **entityKey**: `Record`<`string`, `string`>
Defined in: packages/client/src/mutation.ts:158
***
### lastError
[Section titled “lastError”](#lasterror)
> **lastError**: `string` | `null`
Defined in: packages/client/src/mutation.ts:165
***
### lastHttpStatus
[Section titled “lastHttpStatus”](#lasthttpstatus)
> **lastHttpStatus**: `number` | `null`
Defined in: packages/client/src/mutation.ts:164
***
### mutationId
[Section titled “mutationId”](#mutationid)
> **mutationId**: `string`
Defined in: packages/client/src/mutation.ts:159
***
### mutationKind
[Section titled “mutationKind”](#mutationkind)
> **mutationKind**: [`MutationKind`](/api/client/type-aliases/mutationkind/)
Defined in: packages/client/src/mutation.ts:161
***
### mutationSeq
[Section titled “mutationSeq”](#mutationseq)
> **mutationSeq**: `number`
Defined in: packages/client/src/mutation.ts:160
***
### nextRetryAtUs
[Section titled “nextRetryAtUs”](#nextretryatus)
> **nextRetryAtUs**: `string` | `null`
Defined in: packages/client/src/mutation.ts:167
***
### registryVersion
[Section titled “registryVersion”](#registryversion)
> **registryVersion**: `string`
Defined in: packages/client/src/mutation.ts:171
Registry fingerprint under which the mutation was authored.
***
### serverUpdatedAtUs
[Section titled “serverUpdatedAtUs”](#serverupdatedatus)
> **serverUpdatedAtUs**: `string` | `null`
Defined in: packages/client/src/mutation.ts:168
***
### status
[Section titled “status”](#status)
> **status**: `MutationStatus`
Defined in: packages/client/src/mutation.ts:162
***
### tableName
[Section titled “tableName”](#tablename)
> **tableName**: `string`
Defined in: packages/client/src/mutation.ts:157
***
### updatedAtUs
[Section titled “updatedAtUs”](#updatedatus)
> **updatedAtUs**: `string`
Defined in: packages/client/src/mutation.ts:169
# MutationDiagnostics
Defined in: packages/contracts/src/runtime.ts:28
## Properties
[Section titled “Properties”](#properties)
### ackedCount
[Section titled “ackedCount”](#ackedcount)
> **ackedCount**: `number`
Defined in: packages/contracts/src/runtime.ts:31
***
### conflictedCount
[Section titled “conflictedCount”](#conflictedcount)
> **conflictedCount**: `number`
Defined in: packages/contracts/src/runtime.ts:39
Stale writes the server declined under the `reject-if-stale` Conflict policy (terminal, ADR-0015). The optimistic Overlay is kept; the user resolves each as a new write or discards it.
***
### failedCount
[Section titled “failedCount”](#failedcount)
> **failedCount**: `number`
Defined in: packages/contracts/src/runtime.ts:32
***
### lastAckAtUs?
[Section titled “lastAckAtUs?”](#lastackatus)
> `optional` **lastAckAtUs?**: `string`
Defined in: packages/contracts/src/runtime.ts:47
***
### lastFlushAtUs?
[Section titled “lastFlushAtUs?”](#lastflushatus)
> `optional` **lastFlushAtUs?**: `string`
Defined in: packages/contracts/src/runtime.ts:46
***
### pendingCount
[Section titled “pendingCount”](#pendingcount)
> **pendingCount**: `number`
Defined in: packages/contracts/src/runtime.ts:29
***
### quarantinedCount
[Section titled “quarantinedCount”](#quarantinedcount)
> **quarantinedCount**: `number`
Defined in: packages/contracts/src/runtime.ts:34
Mutations the server permanently rejected (terminal); surfaced, never retried (ADR-0006).
***
### rejectedCount
[Section titled “rejectedCount”](#rejectedcount)
> **rejectedCount**: `number`
Defined in: packages/contracts/src/runtime.ts:45
Whole write-**units** the authoritative endpoint declined for a business reason (terminal, ADR-0022): the optimistic Overlay is auto-discarded for every member and the typed reason is surfaced via `onReject`. Never retried (the server’s answer is authoritative).
***
### sendingCount
[Section titled “sendingCount”](#sendingcount)
> **sendingCount**: `number`
Defined in: packages/contracts/src/runtime.ts:30
# MutationListOptions
Defined in: packages/client/src/mutations-api.ts:37
Optional filters for [MutationsApi.list](/api/client/interfaces/mutationsapi/#list) / [MutationsApi.subscribe](/api/client/interfaces/mutationsapi/#subscribe).
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
## Properties
[Section titled “Properties”](#properties)
### entityKey?
[Section titled “entityKey?”](#entitykey)
> `optional` **entityKey?**: `Record`<`string`, `string`>
Defined in: packages/client/src/mutations-api.ts:47
Restrict to one entity — matched on the serialized `entity_key_json` (`JSON.stringify(entityKey)`), byte-for-byte against the enqueue-time serialization. For a COMPOSITE key, pass the properties in the same order pgxsinkit serialized them (a key taken from a returned [MutationSummaryDetail](/api/client/type-aliases/mutationsummarydetail/) or the write API round-trips correctly; a hand-built object with a different property order silently matches nothing).
***
### limit?
[Section titled “limit?”](#limit)
> `optional` **limit?**: `number`
Defined in: packages/client/src/mutations-api.ts:51
Cap the returned rows (applied after the `enqueued_at_us` ordering).
***
### statuses?
[Section titled “statuses?”](#statuses)
> `optional` **statuses?**: readonly `MutationStatus`\[]
Defined in: packages/client/src/mutations-api.ts:49
Restrict to a set of journal statuses.
***
### table?
[Section titled “table?”](#table)
> `optional` **table?**: `SyncTableName`<`TRegistry`>
Defined in: packages/client/src/mutations-api.ts:39
Restrict to one registry table key (the `table_key` column).
# MutationListSubscription
Defined in: packages/client/src/mutations-api.ts:61
A handle to a live detail subscription: the initial ordered rows plus an idempotent unsubscribe.
## Properties
[Section titled “Properties”](#properties)
### initial
[Section titled “initial”](#initial)
> **initial**: [`MutationDetail`](/api/client/interfaces/mutationdetail/)\[]
Defined in: packages/client/src/mutations-api.ts:62
***
### unsubscribe
[Section titled “unsubscribe”](#unsubscribe)
> **unsubscribe**: () => `void`
Defined in: packages/client/src/mutations-api.ts:63
#### Returns
[Section titled “Returns”](#returns)
`void`
# MutationsApi
Defined in: packages/client/src/mutations-api.ts:75
The registry-wide reactive mutation-status surface (`client.mutations`), identical on the in-process and worker-attached client. Consumers NEVER touch the generated journal relation names — everything routes through the `pgxsinkit_all_mutations` view.
The `summary` is cheap enough to mount PERMANENTLY (one fingerprinted registration regardless of subscriber count — ADR-0040 dedup gives one shared rerun per journal write, not N). Full `list`/`subscribe` detail subscriptions should stay route- or feature-scoped.
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
## Methods
[Section titled “Methods”](#methods)
### list()
[Section titled “list()”](#list)
> **list**(`options?`): `Promise`<[`MutationDetail`](/api/client/interfaces/mutationdetail/)\[]>
Defined in: packages/client/src/mutations-api.ts:85
One-shot normalized detail rows, filtered by [MutationListOptions](/api/client/interfaces/mutationlistoptions/), ordered newest-first by `enqueued_at_us`.
#### Parameters
[Section titled “Parameters”](#parameters)
##### options?
[Section titled “options?”](#options)
[`MutationListOptions`](/api/client/interfaces/mutationlistoptions/)<`TRegistry`>
#### Returns
[Section titled “Returns”](#returns)
`Promise`<[`MutationDetail`](/api/client/interfaces/mutationdetail/)\[]>
***
### subscribe()
[Section titled “subscribe()”](#subscribe)
> **subscribe**(`options`, `listener`): `Promise`<[`MutationListSubscription`](/api/client/interfaces/mutationlistsubscription/)>
Defined in: packages/client/src/mutations-api.ts:91
Live normalized detail rows (same filters/ordering as [list](/api/client/interfaces/mutationsapi/#list)). Route/feature-scoped: a detail subscription reruns the union SELECT on each relevant write — cheap for a scoped view, but prefer the summary for anything mounted app-wide.
#### Parameters
[Section titled “Parameters”](#parameters-1)
##### options
[Section titled “options”](#options-1)
[`MutationListOptions`](/api/client/interfaces/mutationlistoptions/)<`TRegistry`>
##### listener
[Section titled “listener”](#listener)
(`rows`) => `void`
#### Returns
[Section titled “Returns”](#returns-1)
`Promise`<[`MutationListSubscription`](/api/client/interfaces/mutationlistsubscription/)>
***
### subscribeSummary()
[Section titled “subscribeSummary()”](#subscribesummary)
> **subscribeSummary**(`listener`): `Promise`<[`MutationSummarySubscription`](/api/client/interfaces/mutationsummarysubscription/)>
Defined in: packages/client/src/mutations-api.ts:83
Live per-status counts: `listener` fires on every change, the initial summary is on the returned handle (matching `subscribeLiveRows`’ initial-via-return / changes-via-callback split). One registration regardless of subscriber count. Cheap to mount permanently.
#### Parameters
[Section titled “Parameters”](#parameters-2)
##### listener
[Section titled “listener”](#listener-1)
(`summary`) => `void`
#### Returns
[Section titled “Returns”](#returns-2)
`Promise`<[`MutationSummarySubscription`](/api/client/interfaces/mutationsummarysubscription/)>
***
### summary()
[Section titled “summary()”](#summary)
> **summary**(): `Promise`<[`MutationSummary`](/api/client/interfaces/mutationsummary/)>
Defined in: packages/client/src/mutations-api.ts:77
One-shot per-status counts across every writable journal (absent statuses = 0).
#### Returns
[Section titled “Returns”](#returns-3)
`Promise`<[`MutationSummary`](/api/client/interfaces/mutationsummary/)>
# MutationsApiDeps
Defined in: packages/client/src/mutations-api.ts:122
The seams the factory needs — shared verbatim by both client modes (zero worker-protocol change).
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
## Properties
[Section titled “Properties”](#properties)
### query
[Section titled “query”](#query)
> **query**: (`sql`, `params?`) => `Promise`<{ `rows`: `Record`<`string`, `unknown`>\[]; }>
Defined in: packages/client/src/mutations-api.ts:129
One-shot query seam (in-process `pglite.query`; worker-attached the `rawQuery` RPC). Rows are UNMAPPED.
#### Parameters
[Section titled “Parameters”](#parameters)
##### sql
[Section titled “sql”](#sql)
`string`
##### params?
[Section titled “params?”](#params)
`unknown`\[]
#### Returns
[Section titled “Returns”](#returns)
`Promise`<{ `rows`: `Record`<`string`, `unknown`>\[]; }>
***
### registry
[Section titled “registry”](#registry)
> **registry**: `TRegistry`
Defined in: packages/client/src/mutations-api.ts:123
***
### subscribeLiveRows
[Section titled “subscribeLiveRows”](#subscribeliverows)
> **subscribeLiveRows**: <`TRow`>(`input`, `onRows`) => `Promise`<{ `initialRows`: `TRow`\[]; `unsubscribe`: () => `void`; }>
Defined in: packages/client/src/mutations-api.ts:124
#### Type Parameters
[Section titled “Type Parameters”](#type-parameters-1)
##### TRow
[Section titled “TRow”](#trow)
`TRow` *extends* `Record`<`string`, `unknown`>
#### Parameters
[Section titled “Parameters”](#parameters-1)
##### input
[Section titled “input”](#input)
###### params
[Section titled “params”](#params-1)
readonly `unknown`\[]
###### sql
[Section titled “sql”](#sql-1)
`string`
##### onRows
[Section titled “onRows”](#onrows)
(`rows`) => `void`
#### Returns
[Section titled “Returns”](#returns-1)
`Promise`<{ `initialRows`: `TRow`\[]; `unsubscribe`: () => `void`; }>
# MutationSummary
Defined in: packages/contracts/src/runtime.ts:73
A registry-wide mutation-journal summary for warm-store observability: the per-status counts across EVERY writable table’s journal, folded from one aggregate query/subscription over the `pgxsinkit_all_mutations` view — so a consumer renders a global sync indicator with ONE subscription instead of one live query per writable journal. Cheap enough to mount permanently.
`unsettledCount` and `settledCount` PARTITION the total — the user-facing “is any local edit still owed?” split, NOT the automatic state machine’s terminal/non-terminal split:
* `unsettledCount` = `pending` + `sending` + `failed` + `conflicted` + `quarantined` — every write still needing work or user action. `conflicted` and `quarantined` are journal-TERMINAL in the state machine (no auto-transition — see `MUTATION_TRANSITIONS`) yet BOTH count as unsettled: their optimistic Overlay is KEPT, later writes for the entity stay blocked, `destroy()` refuses them without `force`, and local-store reconciliation counts them owed. The user must act (`discardConflict` / `discardQuarantined`, then re-author) — so from the consumer’s data-safety standpoint they are NOT done. This is exactly the restore case, where pgxsinkit deliberately quarantines recovered writes for the user to resolve, so a global “unsynced changes” indicator MUST include them.
* `settledCount` = `acked` + `rejected` — the writes that are truly done from the user’s standpoint (acked awaits only its synced echo to be reconciled away; rejected’s Overlay was auto-discarded, nothing owed).
The field is `settledCount` (not `terminalCount`): “terminal” is the state-machine word, and quarantine is legitimately terminal there while being unsettled here — the old name invited exactly that confusion.
## Properties
[Section titled “Properties”](#properties)
### ackedCount
[Section titled “ackedCount”](#ackedcount)
> **ackedCount**: `number`
Defined in: packages/contracts/src/runtime.ts:76
***
### conflictedCount
[Section titled “conflictedCount”](#conflictedcount)
> **conflictedCount**: `number`
Defined in: packages/contracts/src/runtime.ts:79
***
### failedCount
[Section titled “failedCount”](#failedcount)
> **failedCount**: `number`
Defined in: packages/contracts/src/runtime.ts:77
***
### pendingCount
[Section titled “pendingCount”](#pendingcount)
> **pendingCount**: `number`
Defined in: packages/contracts/src/runtime.ts:74
***
### quarantinedCount
[Section titled “quarantinedCount”](#quarantinedcount)
> **quarantinedCount**: `number`
Defined in: packages/contracts/src/runtime.ts:80
***
### rejectedCount
[Section titled “rejectedCount”](#rejectedcount)
> **rejectedCount**: `number`
Defined in: packages/contracts/src/runtime.ts:78
***
### sendingCount
[Section titled “sendingCount”](#sendingcount)
> **sendingCount**: `number`
Defined in: packages/contracts/src/runtime.ts:75
***
### settledCount
[Section titled “settledCount”](#settledcount)
> **settledCount**: `number`
Defined in: packages/contracts/src/runtime.ts:87
`acked` + `rejected` — settled writes; the complement of [unsettledCount](/api/client/interfaces/mutationsummary/#unsettledcount).
***
### unsettledCount
[Section titled “unsettledCount”](#unsettledcount)
> **unsettledCount**: `number`
Defined in: packages/contracts/src/runtime.ts:85
`pending` + `sending` + `failed` + `conflicted` + `quarantined` — every write still needing work or user action (see the interface JSDoc; quarantined + conflicted are owed local edits, not settled).
# MutationSummarySubscription
Defined in: packages/client/src/mutations-api.ts:55
A handle to a live summary subscription: the initial summary plus an idempotent unsubscribe.
## Properties
[Section titled “Properties”](#properties)
### initial
[Section titled “initial”](#initial)
> **initial**: [`MutationSummary`](/api/client/interfaces/mutationsummary/)
Defined in: packages/client/src/mutations-api.ts:56
***
### unsubscribe
[Section titled “unsubscribe”](#unsubscribe)
> **unsubscribe**: () => `void`
Defined in: packages/client/src/mutations-api.ts:57
#### Returns
[Section titled “Returns”](#returns)
`void`
# OpfsEffects
Defined in: packages/client/src/opfs-effects.ts:40
The effects surface the store-boot wiring drives (create-if-absent / delete-if-present / never-creating observe).
## Methods
[Section titled “Methods”](#methods)
### deleteSentinel()
[Section titled “deleteSentinel()”](#deletesentinel)
> **deleteSentinel**(): `Promise`<`void`>
Defined in: packages/client/src/opfs-effects.ts:44
Delete-if-present the commitment sentinel (`NotFoundError` swallowed).
#### Returns
[Section titled “Returns”](#returns)
`Promise`<`void`>
***
### deleteStoreDirectory()
[Section titled “deleteStoreDirectory()”](#deletestoredirectory)
> **deleteStoreDirectory**(): `Promise`<`void`>
Defined in: packages/client/src/opfs-effects.ts:46
Recursively delete-if-present the store directory `pgxsinkit/stores/` (`NotFoundError` swallowed).
#### Returns
[Section titled “Returns”](#returns-1)
`Promise`<`void`>
***
### getStoreDirectoryHandle()
[Section titled “getStoreDirectoryHandle()”](#getstoredirectoryhandle)
> **getStoreDirectoryHandle**(): `Promise`<`unknown`>
Defined in: packages/client/src/opfs-effects.ts:53
Create-if-absent chain to the store directory, returning its handle for the opfs-repacked factory.
#### Returns
[Section titled “Returns”](#returns-2)
`Promise`<`unknown`>
***
### observeCommitmentNamespace()
[Section titled “observeCommitmentNamespace()”](#observecommitmentnamespace)
> **observeCommitmentNamespace**(): `Promise`<{ `sentinelPresent`: `boolean`; `storeDirectoryPresent`: `boolean`; } | `"unobservable"`>
Defined in: packages/client/src/opfs-effects.ts:51
Never-creating walk of the commitment namespace. A root/API failure (or the API being absent) reads as `"unobservable"` — this method NEVER throws.
#### Returns
[Section titled “Returns”](#returns-3)
`Promise`<{ `sentinelPresent`: `boolean`; `storeDirectoryPresent`: `boolean`; } | `"unobservable"`>
***
### publishSentinel()
[Section titled “publishSentinel()”](#publishsentinel)
> **publishSentinel**(): `Promise`<`void`>
Defined in: packages/client/src/opfs-effects.ts:42
Create-if-absent the commitment sentinel file at `pgxsinkit/commitments/`.
#### Returns
[Section titled “Returns”](#returns-4)
`Promise`<`void`>
# OpfsEffectsDeps
Defined in: packages/client/src/opfs-effects.ts:30
The injectable seam so Bun unit tests fake the OPFS root (there is no real OPFS there).
## Properties
[Section titled “Properties”](#properties)
### getRoot?
[Section titled “getRoot?”](#getroot)
> `optional` **getRoot?**: () => `Promise`<`DirLike`>
Defined in: packages/client/src/opfs-effects.ts:36
The OPFS root getter. Omit in production: the default reads `navigator.storage.getDirectory` off `globalThis` (structural, no DOM lib). Absent API → the delete effects are no-ops, `observe` is `"unobservable"`, and the create effects throw (they are only reached in the opfs engine home).
#### Returns
[Section titled “Returns”](#returns)
`Promise`<`DirLike`>
# OutboxStatus
Defined in: packages/client/src/event-lane.ts:126
The **drain signal** (ADR-0053 decision 2): whether the Outbox is empty. Deliberately no count — a count that updates only on transitions is stale by construction, and a count updated per append is a worse live query. Richer detail is a query against the Outbox table.
## Properties
[Section titled “Properties”](#properties)
### empty
[Section titled “empty”](#empty)
> **empty**: `boolean`
Defined in: packages/client/src/event-lane.ts:127
# PgliteBootAssets
Defined in: packages/client/src/index.ts:374
The pre-warmed PGlite boot assets (the WASM modules + filesystem bundle), consumed by [createClientPGlite](/api/client/functions/createclientpglite/) / [CreateSyncClientOptions.pgliteBootAssets](/api/client/interfaces/createsyncclientoptions/#pglitebootassets). The host fetches + compiles these on an earlier screen and hands the promise in, so `PGlite.create` skips its own lazy asset load — see the field’s JSDoc for the accelerator geometry.
## Properties
[Section titled “Properties”](#properties)
### fsBundle?
[Section titled “fsBundle?”](#fsbundle)
> `optional` **fsBundle?**: `Blob`
Defined in: packages/client/src/index.ts:377
***
### initdbWasmModule?
[Section titled “initdbWasmModule?”](#initdbwasmmodule)
> `optional` **initdbWasmModule?**: `Module`
Defined in: packages/client/src/index.ts:376
***
### pgliteWasmModule?
[Section titled “pgliteWasmModule?”](#pglitewasmmodule)
> `optional` **pgliteWasmModule?**: `Module`
Defined in: packages/client/src/index.ts:375
# PreparedQueryResult
Defined in: packages/client/src/index.ts:1354
Result of [SyncClient.prepareQuery](/api/client/interfaces/syncclient/#preparequery): the `lazy` relations the guard scanned out of the SQL (∪ the explicit `use`) and activated. Activation means the group’s STREAM IS STARTED — reads are safe from the tripwire — not that its initial catch-up has completed; await [SyncClient.groupReady](/api/client/interfaces/syncclient/#groupready) per key for that (see ADR-0021 / ADR-0032 decision 6).
Parameterized by the TABLE-NAME UNION, not the registry: `keyof TRegistry` in an output position would make the registry parameter contravariant, and `SyncClient` would stop being assignable to bare-`SyncTableRegistry` supertypes — the erasure pattern consumer seams rely on. A name union in output position keeps the whole client covariant in its registry.
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TTable
[Section titled “TTable”](#ttable)
`TTable` *extends* `string` = `string`
## Properties
[Section titled “Properties”](#properties)
### lazyTables
[Section titled “lazyTables”](#lazytables)
> **lazyTables**: readonly `TTable`\[]
Defined in: packages/client/src/index.ts:1355
# PrepareQueryInput
Defined in: packages/client/src/index.ts:1271
Inputs to the read-path safety seam [SyncClient.prepareQuery](/api/client/interfaces/syncclient/#preparequery). The lazy relations a query reads are detected by scanning the compiled `sql` (union with the optional explicit `use`), then activated. Shared by the live React hooks and the non-live facade.
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
## Properties
[Section titled “Properties”](#properties)
### sql
[Section titled “sql”](#sql)
> **sql**: `string`
Defined in: packages/client/src/index.ts:1273
The compiled (parameterised) Drizzle SQL the query will run — the scan’s ground-truth target.
***
### use?
[Section titled “use?”](#use)
> `optional` **use?**: readonly `SyncTableName`<`TRegistry`>\[]
Defined in: packages/client/src/index.ts:1275
Lazy relations to also activate, beyond those scanned from `sql` — a pre-activation hint, not required.
# ProvisionAckPayload
Defined in: packages/client/src/worker/protocol.ts:140
worker → tab: the provision completed (or failed). Lets the login screen confirm the spare is warm.
## Properties
[Section titled “Properties”](#properties)
### error?
[Section titled “error?”](#error)
> `optional` **error?**: `object`
Defined in: packages/client/src/worker/protocol.ts:144
Present when `!ok` — the initdb/create failed; the worker falls back to a fresh create at attach.
#### message
[Section titled “message”](#message)
> **message**: `string`
***
### ok
[Section titled “ok”](#ok)
> **ok**: `boolean`
Defined in: packages/client/src/worker/protocol.ts:141
***
### storeId?
[Section titled “storeId?”](#storeid)
> `optional` **storeId?**: `string`
Defined in: packages/client/src/worker/protocol.ts:142
# ProvisionPayload
Defined in: packages/client/src/worker/protocol.ts:115
tab → worker: pre-spawn the store WITHOUT booting the engine (ADR-0032 decision 5). Sent at the login screen against the freshly-named spare worker: the worker only runs PGlite `create`/initdb (off every thread that matters) and holds the raw store idle — no schema, no shape streams, no token — until the real [AttachPayload](/api/client/interfaces/attachpayload/) arrives (with config + token) and adopts the provisioned store. Role-agnostic on purpose: the spare is minted before the user (and role) is known, so provisioning carries no registry — the schema is applied at attach with the role-resolved registry.
## Properties
[Section titled “Properties”](#properties)
### storage?
[Section titled “storage?”](#storage)
> `optional` **storage?**: `SyncStorageDeclaration`
Defined in: packages/client/src/worker/protocol.ts:136
The tab’s WIRE storage declaration (ADR-0050) — the same declaration the pre-placement declaration message carried, repeated here so the ENGINE binds it (the mint’s durability) wherever it runs. The first provision/attach binds the store’s declaration; a later payload whose explicit field disagrees with the bound resolution is refused typed (`StorageDeclarationRefusedError`).
***
### storeId?
[Section titled “storeId?”](#storeid)
> `optional` **storeId?**: `string`
Defined in: packages/client/src/worker/protocol.ts:117
The bound store id (informational; naming is done tab-side via the SharedWorker name).
***
### storePath?
[Section titled “storePath?”](#storepath)
> `optional` **storePath?**: `string`
Defined in: packages/client/src/worker/protocol.ts:122
The plain store PATH (ADR-0036) to `create` ahead of attach — a name, not a storage URL; the worker derives the backend (IndexedDB in a browser worker). When omitted, `storeId`/the worker default is used.
# RawQueryOptions
Defined in: packages/client/src/index.ts:1761
The structured-clone-safe subset of PGlite’s `QueryOptions` the inspection surface carries. Kept narrow ON PURPOSE: on a worker-attached client the options object crosses the bridge via `postMessage`, so function-valued options (`parsers`, `serializers`, `onNotice`) can never be part of this contract. `rowMode: "array"` is what `@electric-sql/pglite-repl` asks for on every exec.
## Properties
[Section titled “Properties”](#properties)
### rowMode?
[Section titled “rowMode?”](#rowmode)
> `optional` **rowMode?**: `"object"` | `"array"`
Defined in: packages/client/src/index.ts:1762
# ReplInspectionSurface
Defined in: packages/client/src/index.ts:1766
The `{ query, exec }` duck `@electric-sql/pglite-repl` drives, backed by a client’s inspection surface.
## Properties
[Section titled “Properties”](#properties)
### exec
[Section titled “exec”](#exec)
> **exec**: (`sql`, `options?`) => `Promise`<`Results`\[]>
Defined in: packages/client/src/index.ts:1768
#### Parameters
[Section titled “Parameters”](#parameters)
##### sql
[Section titled “sql”](#sql)
`string`
##### options?
[Section titled “options?”](#options)
[`RawQueryOptions`](/api/client/interfaces/rawqueryoptions/)
#### Returns
[Section titled “Returns”](#returns)
`Promise`<`Results`\[]>
***
### query
[Section titled “query”](#query)
> **query**: (`sql`, `params?`, `options?`) => `Promise`<`Results`>
Defined in: packages/client/src/index.ts:1767
#### Parameters
[Section titled “Parameters”](#parameters-1)
##### sql
[Section titled “sql”](#sql-1)
`string`
##### params?
[Section titled “params?”](#params)
`unknown`\[]
##### options?
[Section titled “options?”](#options-1)
[`RawQueryOptions`](/api/client/interfaces/rawqueryoptions/)
#### Returns
[Section titled “Returns”](#returns-1)
`Promise`<`Results`>
# ResolveStoreBootOptions
Defined in: packages/client/src/store-boot.ts:69
Options for [resolveStoreBoot](/api/client/functions/resolvestoreboot/).
## Properties
[Section titled “Properties”](#properties)
### backendOverride?
[Section titled “backendOverride?”](#backendoverride)
> `optional` **backendOverride?**: `"memory"`
Defined in: packages/client/src/store-boot.ts:73
The internal test-only memory backend override (ADR-0036), carried through from the mint seam.
***
### deps?
[Section titled “deps?”](#deps)
> `optional` **deps?**: `object`
Defined in: packages/client/src/store-boot.ts:75
Injectable IO seams so Bun unit tests fake the whole browser surface.
#### idbExists?
[Section titled “idbExists?”](#idbexists)
> `optional` **idbExists?**: (`storePath`) => `Promise`<`boolean`>
The recordless idb existence check (defaults to store-meta’s non-creating idbStoreExists).
##### Parameters
[Section titled “Parameters”](#parameters)
###### storePath
[Section titled “storePath”](#storepath)
`string`
##### Returns
[Section titled “Returns”](#returns)
`Promise`<`boolean`>
#### meta?
[Section titled “meta?”](#meta)
> `optional` **meta?**: `StoreMetaDeps`
The store-meta IndexedDB seam (defaults to `globalThis.indexedDB` inside store-meta).
#### opfs?
[Section titled “opfs?”](#opfs)
> `optional` **opfs?**: [`OpfsEffectsDeps`](/api/client/interfaces/opfseffectsdeps/)
The OPFS root seam (defaults to `navigator.storage.getDirectory` inside opfs-effects).
***
### hasOpfsSyncAccess
[Section titled “hasOpfsSyncAccess”](#hasopfssyncaccess)
> **hasOpfsSyncAccess**: `boolean`
Defined in: packages/client/src/store-boot.ts:71
The placement probe’s result, injected by the caller (invariant 8 — probe per boot, never cached here).
# RpcPayload
Defined in: packages/client/src/worker/protocol.ts:350
tab → worker: a write/read-of-mutation-state RPC. `id` correlates the matching [RpcResultPayload](/api/client/interfaces/rpcresultpayload/).
## Properties
[Section titled “Properties”](#properties)
### args
[Section titled “args”](#args)
> **args**: `unknown`\[]
Defined in: packages/client/src/worker/protocol.ts:352
***
### op
[Section titled “op”](#op)
> **op**: [`RpcOp`](/api/client/type-aliases/rpcop/)
Defined in: packages/client/src/worker/protocol.ts:351
# RpcResultPayload
Defined in: packages/client/src/worker/protocol.ts:376
worker → tab: an RPC outcome, mirroring today’s in-process resolve/reject semantics.
## Properties
[Section titled “Properties”](#properties)
### error?
[Section titled “error?”](#error)
> `optional` **error?**: `BridgeErrorWire`
Defined in: packages/client/src/worker/protocol.ts:381
The rejection message + optional structured detail (present when `!ok`).
***
### ok
[Section titled “ok”](#ok)
> **ok**: `boolean`
Defined in: packages/client/src/worker/protocol.ts:377
***
### value?
[Section titled “value?”](#value)
> `optional` **value?**: `unknown`
Defined in: packages/client/src/worker/protocol.ts:379
The resolved value (present when `ok`).
# SetOnlinePayload
Defined in: packages/client/src/worker/protocol.ts:215
tab → worker: gate the worker’s outbound convergence (the board’s Offline toggle, ADR-0032 S3). In in-process mode the toggle gates the local `autoSync` trigger; the worker owns convergence instead, so the tab forwards the flag and the worker suppresses/resumes its flush passes. Going back online fires one immediate pass so queued writes flush without waiting for the next interval tick.
## Properties
[Section titled “Properties”](#properties)
### online
[Section titled “online”](#online)
> **online**: `boolean`
Defined in: packages/client/src/worker/protocol.ts:216
# StoreBackupReport
Defined in: packages/client/src/export-store.ts:68
A live **store backup** (ADR-0035): the whole datadir as a PGlite-restorable tarball, journal and overlay included. `checkpoint`/`dump` are the only phases — a store backup is a `CHECKPOINT` + `dumpDataDir`, no clone. Slice-1 shape, kept unchanged (the union is additive).
## Extends
[Section titled “Extends”](#extends)
* [`ExportReportCommon`](/api/client/interfaces/exportreportcommon/)
## Properties
[Section titled “Properties”](#properties)
### byteLength
[Section titled “byteLength”](#bytelength)
> **byteLength**: `number`
Defined in: packages/client/src/export-store.ts:54
The artefact’s byte length — the size the caller downloads / persists.
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
[`ExportReportCommon`](/api/client/interfaces/exportreportcommon/).[`byteLength`](/api/client/interfaces/exportreportcommon/#bytelength)
***
### compression
[Section titled “compression”](#compression)
> **compression**: `"none"` | `"gzip"`
Defined in: packages/client/src/export-store.ts:74
The compression actually applied to the tarball (resolved from `"auto"` at dump time).
***
### diagnostics
[Section titled “diagnostics”](#diagnostics)
> **diagnostics**: [`MutationDiagnostics`](/api/client/interfaces/mutationdiagnostics/)
Defined in: packages/client/src/export-store.ts:60
The [MutationDiagnostics](/api/client/interfaces/mutationdiagnostics/) snapshot at export time — the journal state captured alongside the artefact (for the store backup, the very journal that travels INSIDE the tarball; for the diagnostic dump, the live store’s journal at dump time, whose rows the SQL also carries).
#### Inherited from
[Section titled “Inherited from”](#inherited-from-1)
[`ExportReportCommon`](/api/client/interfaces/exportreportcommon/).[`diagnostics`](/api/client/interfaces/exportreportcommon/#diagnostics)
***
### kind
[Section titled “kind”](#kind)
> **kind**: `"store-backup"`
Defined in: packages/client/src/export-store.ts:70
Which export produced this artefact.
***
### phases
[Section titled “phases”](#phases)
> **phases**: `object`
Defined in: packages/client/src/export-store.ts:75
#### checkpointMs
[Section titled “checkpointMs”](#checkpointms)
> **checkpointMs**: `number`
`CHECKPOINT` wall — flushing dirty buffers to the datadir before it is tarred, serialised behind engine work.
#### checkpointStartedAtMs
[Section titled “checkpointStartedAtMs”](#checkpointstartedatms)
> **checkpointStartedAtMs**: `number`
Offset from export start when the `CHECKPOINT` began.
#### dumpMs
[Section titled “dumpMs”](#dumpms)
> **dumpMs**: `number`
`dumpDataDir` wall — reading the datadir out and assembling (optionally compressing) the tarball.
#### dumpStartedAtMs
[Section titled “dumpStartedAtMs”](#dumpstartedatms)
> **dumpStartedAtMs**: `number`
Offset from export start when `dumpDataDir` began.
***
### reportVersion
[Section titled “reportVersion”](#reportversion)
> **reportVersion**: `1`
Defined in: packages/client/src/export-store.ts:48
#### Inherited from
[Section titled “Inherited from”](#inherited-from-2)
[`ExportReportCommon`](/api/client/interfaces/exportreportcommon/).[`reportVersion`](/api/client/interfaces/exportreportcommon/#reportversion)
***
### scope
[Section titled “scope”](#scope)
> **scope**: `"whole-store"`
Defined in: packages/client/src/export-store.ts:72
What the artefact covers. A store backup is always the whole store, journal and overlay included.
***
### startedAt
[Section titled “startedAt”](#startedat)
> **startedAt**: `number`
Defined in: packages/client/src/export-store.ts:50
Epoch anchor (`Date.now()`) at export start; every other duration/offset is monotonic relative to it.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-3)
[`ExportReportCommon`](/api/client/interfaces/exportreportcommon/).[`startedAt`](/api/client/interfaces/exportreportcommon/#startedat)
***
### totalMs
[Section titled “totalMs”](#totalms)
> **totalMs**: `number`
Defined in: packages/client/src/export-store.ts:52
Export start → artefact ready.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-4)
[`ExportReportCommon`](/api/client/interfaces/exportreportcommon/).[`totalMs`](/api/client/interfaces/exportreportcommon/#totalms)
# StoreBootResolution
Defined in: packages/client/src/store-boot.ts:53
The outcome of [resolveStoreBoot](/api/client/functions/resolvestoreboot/): the URL to open the store at, its backend, and the boot verdict.
## Properties
[Section titled “Properties”](#properties)
### dataDir
[Section titled “dataDir”](#datadir)
> **dataDir**: `string`
Defined in: packages/client/src/store-boot.ts:55
The PGlite dataDir URL, always assembled by [resolveStoreDataDir](/api/client/functions/resolvestoredatadir/) (the one URL assembler).
***
### storageBackend
[Section titled “storageBackend”](#storagebackend)
> **storageBackend**: [`ResolvedStorageBackend`](/api/client/type-aliases/resolvedstoragebackend/)
Defined in: packages/client/src/store-boot.ts:57
The resolved backend, for diagnostics.
***
### verdict?
[Section titled “verdict?”](#verdict)
> `optional` **verdict?**: `StoreBootVerdict`
Defined in: packages/client/src/store-boot.ts:65
The executed boot verdict — always a TERMINAL one (the record-clearing verdicts re-classify rather than return). Absent on the passthrough backends (`memory` / `filesystem`), which have NO meta machinery. Present on every browser classification — in particular it is the signal that an opfs CANDIDATE was stood up UNCOMMITTED (`virgin-create`): the mint seam must run the commitment barrier before exposing that store to writes (plan step 10b/11).
# StoreDestructionRetryOptions
Defined in: packages/client/src/worker/attach-sync-client.ts:467
The bounded ownership-retry options for runStoreDestruction (the VFS-lock-lag guard, D8/fault row).
## Properties
[Section titled “Properties”](#properties)
### delay?
[Section titled “delay?”](#delay)
> `optional` **delay?**: (`ms`) => `Promise`<`void`>
Defined in: packages/client/src/worker/attach-sync-client.ts:473
Between-retries delay; defaults to a real timer. Injected in tests.
#### Parameters
[Section titled “Parameters”](#parameters)
##### ms
[Section titled “ms”](#ms)
`number`
#### Returns
[Section titled “Returns”](#returns)
`Promise`<`void`>
***
### isOwnershipError?
[Section titled “isOwnershipError?”](#isownershiperror)
> `optional` **isOwnershipError?**: (`error`) => `boolean`
Defined in: packages/client/src/worker/attach-sync-client.ts:471
Classify an error as VFS-ownership-lock lag (retryable). Default: OPFS `NoModificationAllowedError`-class.
#### Parameters
[Section titled “Parameters”](#parameters-1)
##### error
[Section titled “error”](#error)
`unknown`
#### Returns
[Section titled “Returns”](#returns-1)
`boolean`
***
### maxOwnershipRetries?
[Section titled “maxOwnershipRetries?”](#maxownershipretries)
> `optional` **maxOwnershipRetries?**: `number`
Defined in: packages/client/src/worker/attach-sync-client.ts:469
Max retries of `deleteBackendStore` on an ownership-lock-lag error before giving up (default 5).
***
### retryDelayMs?
[Section titled “retryDelayMs?”](#retrydelayms)
> `optional` **retryDelayMs?**: `number`
Defined in: packages/client/src/worker/attach-sync-client.ts:475
The between-retries delay (ms); default 50.
# StoreExportDeps
Defined in: packages/client/src/export-store.ts:241
The dependencies [performStoreExport](/api/client/functions/performstoreexport/) needs from the owning client — narrow, so it is unit-testable.
## Properties
[Section titled “Properties”](#properties)
### pglite
[Section titled “pglite”](#pglite)
> **pglite**: `Pick`<[`ClientPGlite`](/api/client/type-aliases/clientpglite/), `"exec"` | `"dumpDataDir"`>
Defined in: packages/client/src/export-store.ts:243
The live store to checkpoint and dump.
***
### readMutationStats
[Section titled “readMutationStats”](#readmutationstats)
> **readMutationStats**: () => `Promise`<[`MutationDiagnostics`](/api/client/interfaces/mutationdiagnostics/)>
Defined in: packages/client/src/export-store.ts:245
The Mutation diagnostics seam (`client.diagnostics().mutation` / `readMutationStats`).
#### Returns
[Section titled “Returns”](#returns)
`Promise`<[`MutationDiagnostics`](/api/client/interfaces/mutationdiagnostics/)>
***
### storePath?
[Section titled “storePath?”](#storepath)
> `optional` **storePath?**: `string`
Defined in: packages/client/src/export-store.ts:251
The store’s configured plain store PATH (ADR-0036) — reduced to the `storeId` in the default backup file name (`deriveStoreId`). The resolved PGlite dataDir URL is deliberately NOT used here: it is internal plumbing and must not leak into an artefact name as something to imitate.
# StoreExportOptions
Defined in: packages/client/src/export-store.ts:25
Options for [SyncClient.exportStore](/api/client/interfaces/syncclient/#exportstore).
## Properties
[Section titled “Properties”](#properties)
### compression?
[Section titled “compression?”](#compression)
> `optional` **compression?**: `"none"` | `"gzip"` | `"auto"`
Defined in: packages/client/src/export-store.ts:32
How to compress the tarball, forwarded to PGlite’s `dumpDataDir`. `"auto"` (the default) gzips when a `CompressionStream` is available and falls back to an uncompressed tar otherwise; `"gzip"` forces compression; `"none"` skips it. The report’s `compression` records the compression that was actually applied (which, under `"auto"`, is resolved at runtime).
***
### fileName?
[Section titled “fileName?”](#filename)
> `optional` **fileName?**: `string`
Defined in: packages/client/src/export-store.ts:38
Override the generated artefact file name. When omitted, the name is `-.pgdata.tar[.gz]`, where `storeId` is a filesystem-safe derivation of the store path and the extension reflects the applied compression.
# StoreExportResult
Defined in: packages/client/src/export-store.ts:177
The artefact + its report — the resolved value of [SyncClient.exportStore](/api/client/interfaces/syncclient/#exportstore).
## Properties
[Section titled “Properties”](#properties)
### file
[Section titled “file”](#file)
> **file**: `File`
Defined in: packages/client/src/export-store.ts:179
The store-backup tarball as a named `File`, restorable by PGlite via `loadDataDir`.
***
### report
[Section titled “report”](#report)
> **report**: [`StoreBackupReport`](/api/client/interfaces/storebackupreport/)
Defined in: packages/client/src/export-store.ts:181
The structured record of the export (ADR-0035).
# StoreWorkerQuiesceOptions
Defined in: packages/client/src/worker/attach-sync-client.ts:357
Options for [quiesceStoreWorker](/api/client/functions/quiescestoreworker/) (ADR-0050).
## Properties
[Section titled “Properties”](#properties)
### storage?
[Section titled “storage?”](#storage)
> `optional` **storage?**: `SyncStorageDeclaration`
Defined in: packages/client/src/worker/attach-sync-client.ts:361
The ADR-0050 storage declaration to post on the port. Omit (or `{}`) to state NO opinion — always compatible with whatever declaration the live worker already bound, so it can never be refused. Passing a concrete declaration risks a `StorageDeclarationRefusedError` if it disagrees with the bound one.
***
### timeoutMs?
[Section titled “timeoutMs?”](#timeoutms)
> `optional` **timeoutMs?**: `number`
Defined in: packages/client/src/worker/attach-sync-client.ts:364
Bound on the whole handshake (declaration → placement reply → teardown ack). Default 6000ms. On timeout the returned promise REJECTS — the caller keeps the path on its retry list; it is NOT proof of teardown.
***
### timers?
[Section titled “timers?”](#timers)
> `optional` **timers?**: `AttachClientTimers`
Defined in: packages/client/src/worker/attach-sync-client.ts:366
Injectable timers (tests); defaults to real `setTimeout`/`clearTimeout`.
# StoreWorkerQuiesceOutcome
Defined in: packages/client/src/worker/attach-sync-client.ts:370
The outcome of [quiesceStoreWorker](/api/client/functions/quiescestoreworker/).
## Properties
[Section titled “Properties”](#properties)
### engineHome
[Section titled “engineHome”](#enginehome)
> **engineHome**: `"shared-worker"` | `"elected-worker"`
Defined in: packages/client/src/worker/attach-sync-client.ts:372
The store’s engine home as reported by its placement reply.
***
### toreDown
[Section titled “toreDown”](#toredown)
> **toreDown**: `boolean`
Defined in: packages/client/src/worker/attach-sync-client.ts:377
True iff an SW-direct engine was torn down here (its host closed, releasing the backend connection). `false` for an `elected-worker` home — there is no in-SharedWorker engine to close from this router-only connection; the elected dedicated engine dies with its owning tab (the browser terminates it on document teardown), releasing its store, so no active teardown is needed before a path-addressed destroy.
# SubscribeLiveRowsInput
Defined in: packages/client/src/index.ts:1285
The narrow live-rows seam (ADR-0032 S2 §4) both client modes implement so the `@pgxsinkit/react` hooks work against either. Input for a reactive subscription: the compiled SQL + params (from a Drizzle builder’s `.toSQL()` or a raw string) and the result’s `pkColumns` (drives worker-side diff keying; omit for a keyless query). The in-process client runs it directly over `pglite.live`; the worker-attached client runs the live query in the worker and streams DIFFs across the bridge.
## Properties
[Section titled “Properties”](#properties)
### fields?
[Section titled “fields?”](#fields)
> `optional` **fields?**: readonly `string`\[]
Defined in: packages/client/src/index.ts:1298
The unique output aliases to render the query’s columns under so it is SAFE TO MATERIALISE, in the compiled SQL’s column order (one per output column). Drizzle emits no output aliases, so a JOIN whose tables share a column name compiles to duplicate output names — which PGlite’s `live` extension refuses to materialise (`column "title" specified more than once`) and which silently collapse same-named columns even in a plain query. When supplied, the seam wraps the query so every output column gets its alias and rows come back KEYED BY THESE ALIASES; the consumer’s row-mapper must read by alias (the `@pgxsinkit/react` hooks do). Omit for a raw or non-colliding query — the seam then leaves the SQL untouched and rows stay keyed by the underlying column names.
***
### keepAliveMs?
[Section titled “keepAliveMs?”](#keepalivems)
> `optional` **keepAliveMs?**: `number`
Defined in: packages/client/src/index.ts:1317
Per-subscription keep-alive hint (ms) for the live-query manager (ADR-0040 decision 4): retain this query’s shared registration for the grace period after its last consumer leaves, so a re-mount reuses it verbatim (no re-materialization). Honoured in BOTH modes — the worker manager and the in-process manager (decision 6). Bounded by the `liveQueries` policy budgets. Absent → no hint.
***
### params
[Section titled “params”](#params)
> **params**: readonly `unknown`\[]
Defined in: packages/client/src/index.ts:1287
***
### pkColumns?
[Section titled “pkColumns?”](#pkcolumns)
> `optional` **pkColumns?**: readonly `string`\[]
Defined in: packages/client/src/index.ts:1304
The result’s PK columns — the diff-keying identity across the bridge (§4). Omit for a keyless query. When [fields](/api/client/interfaces/subscribeliverowsinput/#fields) is supplied the result columns are the aliases, so a PK column here must be named by its ALIAS (the aliased result column), not the underlying source column.
***
### sql
[Section titled “sql”](#sql)
> **sql**: `string`
Defined in: packages/client/src/index.ts:1286
***
### use?
[Section titled “use?”](#use)
> `optional` **use?**: readonly `string`\[]
Defined in: packages/client/src/index.ts:1310
Lazy relations (ADR-0021) to activate before the query runs — forwarded to the worker so it can `prepareQuery` before registering the live query (the tab’s own `prepareQuery` is a no-op against the worker bridge). Ignored by the in-process client, whose `prepareQuery` already ran on the tab.
# SubscribePayload
Defined in: packages/client/src/worker/protocol.ts:385
tab → worker: register a live query. `id` correlates the initial snapshot; `queryId` keys subsequent diffs.
## Properties
[Section titled “Properties”](#properties)
### fields?
[Section titled “fields?”](#fields)
> `optional` **fields?**: `string`\[]
Defined in: packages/client/src/worker/protocol.ts:397
The unique output aliases the worker wraps the query’s columns under so it is safe to MATERIALISE (one per output column, in the compiled SQL’s column order). When omitted, the worker leaves the SQL untouched and returns name-keyed rows; supply it for a JOIN with same-named columns, which `live.query`/`live.incrementalQuery` otherwise refuse to materialise (`column "title" specified more than once`). When present, result columns are the aliases, so [pkColumns](/api/client/interfaces/subscribepayload/#pkcolumns) must name a PK by its alias.
***
### keepAliveMs?
[Section titled “keepAliveMs?”](#keepalivems)
> `optional` **keepAliveMs?**: `number`
Defined in: packages/client/src/worker/protocol.ts:412
Per-subscription keep-alive hint (ms) for the worker’s live-query manager (ADR-0040 decision 4): how long the shared registration should be retained after this subscription’s last consumer leaves, so a matching resubscribe reuses it verbatim. When omitted, the worker falls back to `liveQueries.defaultKeepAliveMs`; absent means “no hint”.
***
### params
[Section titled “params”](#params)
> **params**: `unknown`\[]
Defined in: packages/client/src/worker/protocol.ts:388
***
### pkColumns?
[Section titled “pkColumns?”](#pkcolumns)
> `optional` **pkColumns?**: `string`\[]
Defined in: packages/client/src/worker/protocol.ts:403
The result’s primary-key columns, driving diff keying (§4). One column → the incremental path (`live.incrementalQuery`); many → worker-side diff by composite PK; empty/absent → value-identity fallback for a keyless query. When [fields](/api/client/interfaces/subscribepayload/#fields) is supplied these name the ALIASED result columns.
***
### queryId
[Section titled “queryId”](#queryid)
> **queryId**: `string`
Defined in: packages/client/src/worker/protocol.ts:386
***
### sql
[Section titled “sql”](#sql)
> **sql**: `string`
Defined in: packages/client/src/worker/protocol.ts:387
***
### use?
[Section titled “use?”](#use)
> `optional` **use?**: `string`\[]
Defined in: packages/client/src/worker/protocol.ts:405
Lazy relations to activate before the query runs (ADR-0021), forwarded to the worker’s `prepareQuery`.
# SyncClient
Defined in: packages/client/src/index.ts:1358
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
## Properties
[Section titled “Properties”](#properties)
### appendEvent
[Section titled “appendEvent”](#appendevent)
> **appendEvent**: (`stream`, `payload`) => `Promise`<[`EventAppendResult`](/api/client/interfaces/eventappendresult/)>
Defined in: packages/client/src/index.ts:1444
Append one client event to the **Outbox** (ADR-0053 decision 2) — the Event lane’s entry point, and the only way an app produces an event. Fire-and-forget by design: nothing echoes back, nothing is overlaid, nothing converges. The promise resolves on DURABLE local enqueue under the store’s declared durability, NOT on delivery; the flush loop drains the Outbox in the background (and after reconnect/boot), and the server’s per-event verdicts surface on [onEventLaneReport](/api/client/interfaces/syncclient/#oneventlanereport).
Four refusals, all synchronous call-site failures rather than runtime conditions — the invariant “everything in the Outbox is well-formed” is what the flush loop and any best-guess view lean on:
* no Event stream registered at all → [EventStreamsNotRegisteredError](/api/client/classes/eventstreamsnotregisterederror/);
* an unregistered Event-stream name → [UnknownEventStreamError](/api/client/classes/unknowneventstreamerror/);
* a payload failing the stream’s registered zod schema → [EventPayloadInvalidError](/api/client/classes/eventpayloadinvaliderror/);
* a serialized payload over the contracts-level per-event cap → [EventPayloadTooLargeError](/api/client/classes/eventpayloadtoolargeerror/).
The library stamps `eventId` (uuid — the server’s dedupe key, which is what makes at-least-once delivery idempotent end-to-end) and `occurredAtUs`, and returns both plus the local append ordinal.
`stream`/`payload` are typed as `string`/`unknown`: a registry’s Event streams ride a symbol and are type-erased by `defineSyncRegistry`’s return type, so the REGISTERED ZOD SCHEMA is the enforced contract (at append, and again at ingest for non-library callers).
#### Parameters
[Section titled “Parameters”](#parameters)
##### stream
[Section titled “stream”](#stream)
`string`
##### payload
[Section titled “payload”](#payload)
`unknown`
#### Returns
[Section titled “Returns”](#returns)
`Promise`<[`EventAppendResult`](/api/client/interfaces/eventappendresult/)>
***
### bootReport
[Section titled “bootReport”](#bootreport)
> **bootReport**: () => `Promise`<[`BootReport`](/api/client/interfaces/bootreport/) | `null`>
Defined in: packages/client/src/index.ts:1697
Boot observability (ADR-0034): the engine’s most recent COMPLETED boot report, or `null` before the first boot finalizes (and after a `stop()`/`destroy()` that preceded initial sync). Pull — so a late-attaching tab can read a boot that predates it; in worker mode it round-trips to the worker’s stored report. The push counterpart is the `onBootReport` client option.
#### Returns
[Section titled “Returns”](#returns-1)
`Promise`<[`BootReport`](/api/client/interfaces/bootreport/) | `null`>
***
### destroy
[Section titled “destroy”](#destroy)
> **destroy**: (`options?`) => `Promise`<`void`>
Defined in: packages/client/src/index.ts:1411
Wipe the entire local store (synced cache + overlay + journal + Outbox) and close the handle (ADR-0005). Refuses if mutations are still owed to the server — OR if the Outbox still holds staged events (ADR-0053 decision 8: the same “never discard without a server verdict” rule the owed-mutations refusal upholds) — unless `force` is set, so it never silently drops un-flushed writes or un-delivered events. The refusal names which of the two blocked it. Distinct from `stop()`, which only halts sync. Runs under the single lifecycle slot (ADR-0035 decision 4): a concurrent export — or another `destroy`/`discardEphemeral`/`dropReadCache` — rejects with a [LifecycleBusyError](/api/client/classes/lifecyclebusyerror/) rather than interleaving the wipe with a running export.
#### Parameters
[Section titled “Parameters”](#parameters-1)
##### options?
[Section titled “options?”](#options)
###### force?
[Section titled “force?”](#force)
`boolean`
#### Returns
[Section titled “Returns”](#returns-2)
`Promise`<`void`>
***
### desync
[Section titled “desync”](#desync)
> **desync**: (`key`) => `Promise`<`void`>
Defined in: packages/client/src/index.ts:1615
Revert a `lazy` relation to dormant (ADR-0021 §2) — the inverse of on-demand activation: stop its consistency group’s stream, clear any persisted `lazy + persistent` activation (so the next boot holds it dormant again), and clean-truncate its local read cache. A later reference re-activates it from scratch. Refuses when the relation is `eager` (always-on, would immediately re-sync) or owes the server unsettled writes (the truncate would drop them — flush or discard first). The reclaim primitive a host wires to navigation/idle for a rarely-opened lazy view; for an `ephemeral` relation, idle-eviction is otherwise automatic at session end.
#### Parameters
[Section titled “Parameters”](#parameters-2)
##### key
[Section titled “key”](#key)
`SyncTableName`<`TRegistry`>
#### Returns
[Section titled “Returns”](#returns-3)
`Promise`<`void`>
***
### diagnostics
[Section titled “diagnostics”](#diagnostics)
> **diagnostics**: (`table?`) => `Promise`<{ `mutation`: [`MutationDiagnostics`](/api/client/interfaces/mutationdiagnostics/); `outbox?`: [`OutboxStatus`](/api/client/interfaces/outboxstatus/); }>
Defined in: packages/client/src/index.ts:1527
The store’s owed-state diagnostics: the mutation journal’s per-status counts, plus — when this client has an Event lane — the Outbox’s drain signal (ADR-0053 decision 8: the Event lane’s durable state takes a position on every lifecycle surface). `outbox` is the boolean signal rather than a count, for the reason [onOutboxStatus](/api/client/interfaces/syncclient/#onoutboxstatus) carries none; it is absent on a client with no lane.
#### Parameters
[Section titled “Parameters”](#parameters-3)
##### table?
[Section titled “table?”](#table)
`SyncTableName`<`TRegistry`>
#### Returns
[Section titled “Returns”](#returns-4)
`Promise`<{ `mutation`: [`MutationDiagnostics`](/api/client/interfaces/mutationdiagnostics/); `outbox?`: [`OutboxStatus`](/api/client/interfaces/outboxstatus/); }>
***
### discardConflict
[Section titled “discardConflict”](#discardconflict)
> **discardConflict**: <`TKey`>(`table`, `entityKey`) => `Promise`<`void`>
Defined in: packages/client/src/index.ts:1505
Discard a `conflicted` entity (ADR-0015): clear its conflicted journal entry and kept optimistic Overlay, so the Read model falls back to the synced (server) value. Use when the user abandons a stale edit instead of resolving it as a new write.
#### Type Parameters
[Section titled “Type Parameters”](#type-parameters-1)
##### TKey
[Section titled “TKey”](#tkey)
`TKey` *extends* `string`
#### Parameters
[Section titled “Parameters”](#parameters-4)
##### table
[Section titled “table”](#table-1)
`TKey`
##### entityKey
[Section titled “entityKey”](#entitykey)
`Record`<`string`, `string`>
#### Returns
[Section titled “Returns”](#returns-5)
`Promise`<`void`>
***
### discardEphemeral
[Section titled “discardEphemeral”](#discardephemeral)
> **discardEphemeral**: (`key`) => `Promise`<`void`>
Defined in: packages/client/src/index.ts:1633
Drop an **ephemeral** relation’s local rows and revert it to dormant (ADR-0021) — the narrow, scoped twin of [desync](/api/client/interfaces/syncclient/#desync). It runs the identical group-teardown machinery (stop the group stream, clear the persisted lazy activation, reset the persisted subscription, clean-truncate every member), but under a STRICTER gate: EVERY member of the relation’s consistency group must be `retention: "ephemeral"` — it refuses (naming the offender) if any member is persistent. Where a `desync` from one tab reverts the shared group for every attached tab (the SharedWorker footgun), `discardEphemeral` is safe under multi-tab because an ephemeral window is per-delivery-session and inherently single-consumer — nothing durable, and no other tab, depends on it. Refuses an `eager` relation (always-on, would immediately re-sync) and a group that owes the server unsettled writes, exactly as `desync` does. The finalize primitive for a secure-delivery window: drop the local rows at session end without touching a durable relation. Note the drop is local-lifecycle only: a later re-subscription re-activates the lazy group and re-streams whatever the SERVER still serves — post-finalize non-redelivery is the server gate’s guarantee (e.g. a consumed server-owned cursor), not this method’s. Runs under the single lifecycle slot (ADR-0035 decision 4): a concurrent export (or `destroy`/`dropReadCache`) rejects with a [LifecycleBusyError](/api/client/classes/lifecyclebusyerror/) rather than interleaving the cache truncate with a running export.
#### Parameters
[Section titled “Parameters”](#parameters-5)
##### key
[Section titled “key”](#key-1)
`SyncTableName`<`TRegistry`>
#### Returns
[Section titled “Returns”](#returns-6)
`Promise`<`void`>
***
### discardQuarantined
[Section titled “discardQuarantined”](#discardquarantined)
> **discardQuarantined**: <`TKey`>(`table`, `entityKey`) => `Promise`<`void`>
Defined in: packages/client/src/index.ts:1517
Discard a `quarantined` entity (ADR-0006): clear its quarantined journal entry and kept optimistic Overlay, so the Read model falls back to the synced (server) value. The rollback path for a write the server permanently rejected — e.g. an RLS policy denial (42501) routed to quarantine. After the discard the phantom optimistic row is gone and the entity accepts new mutations again (no longer blocked behind the quarantined head). Symmetric to [SyncClient.discardConflict](/api/client/interfaces/syncclient/#discardconflict); the overlay is kept when another still-owed write depends on it. No-op for an entity with no quarantined entry.
#### Type Parameters
[Section titled “Type Parameters”](#type-parameters-2)
##### TKey
[Section titled “TKey”](#tkey-1)
`TKey` *extends* `string`
#### Parameters
[Section titled “Parameters”](#parameters-6)
##### table
[Section titled “table”](#table-2)
`TKey`
##### entityKey
[Section titled “entityKey”](#entitykey-1)
`Record`<`string`, `string`>
#### Returns
[Section titled “Returns”](#returns-7)
`Promise`<`void`>
***
### drizzle
[Section titled “drizzle”](#drizzle)
> **drizzle**: `PgliteDatabase`<`ExtractTablesWithRelations`<{ }, `RegistryTables`<`TRegistry`>>>
Defined in: packages/client/src/index.ts:1359
***
### dropReadCache
[Section titled “dropReadCache”](#dropreadcache)
> **dropReadCache**: () => `Promise`<`void`>
Defined in: packages/client/src/index.ts:1421
Drop and rebuild the reconstructible synced read cache, preserving the overlay, the mutation journal, and the Outbox (ADR-0006; ADR-0053 decision 8 — the Outbox is not read cache, so this never touches it). The next sync refills it. Use to recover from a corrupt or stale read cache without losing un-flushed writes. Runs under the single lifecycle slot (ADR-0035 decision 4) — it drops and rebuilds the synced tables, so an export must not capture a half-rebuilt cache; a concurrent export (or `destroy`/`discardEphemeral`) rejects with a [LifecycleBusyError](/api/client/classes/lifecyclebusyerror/).
#### Returns
[Section titled “Returns”](#returns-8)
`Promise`<`void`>
***
### ensureSynced
[Section titled “ensureSynced”](#ensuresynced)
> **ensureSynced**: (`keys`) => `Promise`<`void`>
Defined in: packages/client/src/index.ts:1600
Activate one or more lazy relations (ADR-0021): open their consistency-group subscription if held out of the eager boot, resolving once each group’s STREAM IS STARTED — reads are then tripwire-safe, but the initial catch-up may still be in flight (local rows can be legitimately empty/stale until it lands; await [groupReady](/api/client/interfaces/syncclient/#groupready) per relation for catch-up completion — deliberately separate so an offline client still reads its persisted local rows instead of hanging on the network). Idempotent — eager or already-started relations resolve immediately. Use to pre-activate before a raw/`client.drizzle` read, or as the manual escape hatch the tripwire points to.
#### Parameters
[Section titled “Parameters”](#parameters-7)
##### keys
[Section titled “keys”](#keys)
readonly `SyncTableName`<`TRegistry`>\[]
#### Returns
[Section titled “Returns”](#returns-9)
`Promise`<`void`>
***
### exportData
[Section titled “exportData”](#exportdata)
> **exportData**: (`options?`) => `Promise`<[`DataExportResult`](/api/client/interfaces/dataexportresult/)>
Defined in: packages/client/src/index.ts:1750
Take a **data export** (ADR-0035): the PORTABLE artefact — the synced tables and the enum types they depend on, schema + data, nothing of pgxsinkit’s machinery — as SQL loadable into a vanilla Postgres. It is a generated enum DDL header concatenated ahead of `pg_dump -t ... --no-owner` (one `-t` per physical synced table; ephemeral/read-projection entries excluded by construction), run against the same memory-backed THROWAWAY clone the diagnostic dump uses — so the live engine is never touched.
Unlike the other two exports it GUARDS the journal (decision 3): a strict export requires a DRAINED journal — all mutation counts zero, including `acked` writes whose synced echo has not landed (they live only in the Overlay). The drain flushes drainable rows and awaits convergence up to `drainJournal.timeoutMs` (default `15_000`); non-drainable states (`failed`/`quarantined`/`conflicted`) fail FAST with a [DataExportDrainError](/api/client/classes/dataexportdrainerror/) carrying the diagnostics. `drainJournal: false` is the escape hatch — export synced state as-is (unflushed writes absent; `report.escapeHatch` records it). An offline device with a clean journal exports strictly and instantly; one with a dirty journal cannot produce a strict export (its lossless option is `exportStore`).
Awaits engine-ready rather than rejecting during boot, then runs the whole drain+dump under the single lifecycle slot — a concurrent export (or `destroy`/`discardEphemeral`) rejects with a [LifecycleBusyError](/api/client/classes/lifecyclebusyerror/). Resolves to the SQL `File` (`application/sql`) plus a [DataExportReport](/api/client/interfaces/dataexportreport/) (drain + clone-pipeline phase timings, the applied `-t` table list, and the escape-hatch flag).
#### Parameters
[Section titled “Parameters”](#parameters-8)
##### options?
[Section titled “options?”](#options-1)
[`DataExportOptions`](/api/client/interfaces/dataexportoptions/)
#### Returns
[Section titled “Returns”](#returns-10)
`Promise`<[`DataExportResult`](/api/client/interfaces/dataexportresult/)>
***
### exportDiagnostics
[Section titled “exportDiagnostics”](#exportdiagnostics)
> **exportDiagnostics**: (`options?`) => `Promise`<[`DiagnosticExportResult`](/api/client/interfaces/diagnosticexportresult/)>
Defined in: packages/client/src/index.ts:1728
Take a **diagnostic dump** (ADR-0035): human-readable SQL of EVERYTHING the store holds — synced tables, the `_overlay`/`_mutations` journal (unflushed writes included — the evidence a diagnostic exists for), the `pgxsinkit` metadata schema, the read-model views, and the reconcile functions/triggers — via `pg_dump`. To keep `pg_dump`’s `DEALLOCATE ALL` away from the live engine, the dump runs against a memory-backed THROWAWAY clone booted from a live datadir dump (ADR-0035 addendum): the running store is never suspended and no tab is disrupted. Awaits engine-ready rather than rejecting during boot, then runs under the single lifecycle slot — a concurrent export (or `destroy`/`discardEphemeral`) rejects immediately with a [LifecycleBusyError](/api/client/classes/lifecyclebusyerror/). Resolves to the SQL `File` (`application/sql`) plus a [DiagnosticDumpReport](/api/client/interfaces/diagnosticdumpreport/) (phase timings + a diagnostics snapshot). Active ephemeral (`pg_temp`) clusters are absent by construction (ADR-0035 decision 5 — `pg_dump` ignores temp objects).
#### Parameters
[Section titled “Parameters”](#parameters-9)
##### options?
[Section titled “options?”](#options-2)
[`DiagnosticExportOptions`](/api/client/interfaces/diagnosticexportoptions/)
#### Returns
[Section titled “Returns”](#returns-11)
`Promise`<[`DiagnosticExportResult`](/api/client/interfaces/diagnosticexportresult/)>
***
### exportStore
[Section titled “exportStore”](#exportstore)
> **exportStore**: (`options?`) => `Promise`<[`StoreExportResult`](/api/client/interfaces/storeexportresult/)>
Defined in: packages/client/src/index.ts:1715
Take a **store backup** (ADR-0035): a full-fidelity, PGlite-restorable tarball of the whole local store — synced cache, Overlay, and Mutation journal (unflushed writes included) — via PGlite’s `dumpDataDir`. Taken LIVE (a `CHECKPOINT` serialised behind engine work, then the dump; no engine suspension, no tab disruption), so it never blocks and is the only lossless export an offline device with unflushed writes can take. Awaits engine-ready rather than rejecting during boot, then runs under the single lifecycle slot: a second export (or, later, `destroy`/`discardEphemeral`) attempted while one is in flight rejects immediately with a [LifecycleBusyError](/api/client/classes/lifecyclebusyerror/) — retry once it settles. Resolves to the artefact `File` plus an [ExportReport](/api/client/type-aliases/exportreport/) (phase timings + a diagnostics snapshot).
#### Parameters
[Section titled “Parameters”](#parameters-10)
##### options?
[Section titled “options?”](#options-3)
[`StoreExportOptions`](/api/client/interfaces/storeexportoptions/)
#### Returns
[Section titled “Returns”](#returns-12)
`Promise`<[`StoreExportResult`](/api/client/interfaces/storeexportresult/)>
***
### flush
[Section titled “flush”](#flush)
> **flush**: (`table?`) => `Promise`<`void`>
Defined in: packages/client/src/index.ts:1482
#### Parameters
[Section titled “Parameters”](#parameters-11)
##### table?
[Section titled “table?”](#table-3)
`SyncTableName`<`TRegistry`>
#### Returns
[Section titled “Returns”](#returns-13)
`Promise`<`void`>
***
### flushEvents
[Section titled “flushEvents”](#flushevents)
> **flushEvents**: () => `Promise`<`void`>
Defined in: packages/client/src/index.ts:1453
Drain the Outbox now — the Event lane’s manual primitive, the twin of [flush](/api/client/interfaces/syncclient/#flush). Assembles batches in append (`seq`) order across every Event stream, POSTs them, and applies the per-event verdicts, until nothing more is eligible this pass. A no-op while the lane is in batch-level backoff, and a no-op for a registry with no Event streams. With `autoSync` installed the lane drives itself (appends nudge a pass, an interval is the fallback, and boot/reconnect drain what was written offline); this is the escape hatch for a fully-manual host.
#### Returns
[Section titled “Returns”](#returns-14)
`Promise`<`void`>
***
### groupReady
[Section titled “groupReady”](#groupready)
> **groupReady**: (`table`) => `Promise`<`void`>
Defined in: packages/client/src/index.ts:1669
Per-group readiness (ADR-0032 decision 6): a promise resolving the moment the given table’s consistency group is up-to-date. Resolves immediately for an already-ready (or sync-disabled) group; stays pending for a still-dormant `lazy` relation until it is activated and caught up. The opt-in progressive-paint signal beside the all-eager-groups `ready` gate. `status.groups` carries the same readiness as a synchronous snapshot.
#### Parameters
[Section titled “Parameters”](#parameters-12)
##### table
[Section titled “table”](#table-4)
`SyncTableName`<`TRegistry`>
#### Returns
[Section titled “Returns”](#returns-15)
`Promise`<`void`>
***
### haltActivity
[Section titled “haltActivity”](#haltactivity)
> **haltActivity**: () => `void`
Defined in: packages/client/src/index.ts:1399
Synchronously abort all sync/write activity (in-flight write fetches, shape-stream long-polls, convergence scheduling) without awaiting. Idempotent. The first action of every teardown path — stop()/destroy() call it, and the SharedWorker host calls it before draining subscribes / disposing live queries so no teardown step races a still-live engine. Not a substitute for stop()/destroy(): the awaited teardown (unsubscribe, dispose, pglite.close) still follows. Callers rarely invoke it directly.
#### Returns
[Section titled “Returns”](#returns-16)
`void`
***
### hydratingTablesFor
[Section titled “hydratingTablesFor”](#hydratingtablesfor)
> **hydratingTablesFor**: (`query`) => readonly `string`\[]
Defined in: packages/client/src/index.ts:1679
The referenced consistency groups’ member tables that are NOT YET caught up at call time (ADR-0021 / ADR-0032 decision 6): scan the compiled SQL for every synced relation the query reads (∪ `use`), map each to its consistency group, and return those whose group has not completed its initial catch-up. Empty when sync is disabled OR every referenced group is already ready — the steady-state fast path in which the live-rows seam builds no `hydrated` promise. The seam (and the worker bridge) use this to gate `hydrating` uniformly across eager AND lazy groups; ACTIVATION is separate and stays lazy-only. Returns plain table-name strings (not `keyof TRegistry`) to keep the client covariant in its registry.
#### Parameters
[Section titled “Parameters”](#parameters-13)
##### query
[Section titled “query”](#query)
###### sql
[Section titled “sql”](#sql)
`string`
###### use?
[Section titled “use?”](#use)
readonly `string`\[]
#### Returns
[Section titled “Returns”](#returns-17)
readonly `string`\[]
***
### isSynced
[Section titled “isSynced”](#issynced)
> **isSynced**: (`key`) => `boolean`
Defined in: packages/client/src/index.ts:1605
Whether a relation’s group has started and hydrated (ADR-0021). False for a still-dormant `lazy` relation; true for eager relations once boot completes (and always when sync is disabled).
#### Parameters
[Section titled “Parameters”](#parameters-14)
##### key
[Section titled “key”](#key-2)
`SyncTableName`<`TRegistry`>
#### Returns
[Section titled “Returns”](#returns-18)
`boolean`
***
### liveQueryDiagnostics
[Section titled “liveQueryDiagnostics”](#livequerydiagnostics)
> **liveQueryDiagnostics**: () => `Promise`<[`LiveQueryDiagnostics`](/api/client/interfaces/livequerydiagnostics/)\[]>
Defined in: packages/client/src/index.ts:1704
Live-query diagnostics (ADR-0040 decision 5): a point-in-time snapshot of the live-query manager’s entries — opaque fingerprint digests plus counts/timings only, NEVER SQL, params, or row values. Both client forms return the real snapshot: in WORKER mode this pulls the worker manager’s snapshot over the bridge; the IN-PROCESS client returns its own manager’s snapshot directly.
#### Returns
[Section titled “Returns”](#returns-19)
`Promise`<[`LiveQueryDiagnostics`](/api/client/interfaces/livequerydiagnostics/)\[]>
***
### localReadReady
[Section titled “localReadReady”](#localreadready)
> **localReadReady**: `Promise`<`void`>
Defined in: packages/client/src/index.ts:1373
Local-read readiness (ADR-0041): resolves once PGlite is open, the durable schema is ready, registry reconciliation has completed, and the drizzle read facade is built — so cached rows are queryable. Resolving this stage requires NO write runtime, NO sync start, and NO network I/O, so an offline boot resolves it promptly. Under the ADR-0041 Option B contract `createSyncClient()` (and, in stage 2, `attachSyncClient()`) resolves at exactly this stage; the write and sync tail continues in the background. Monotonic and idempotent.
***
### mutate
[Section titled “mutate”](#mutate)
> **mutate**: `object`
Defined in: packages/client/src/index.ts:1487
#### batch
[Section titled “batch”](#batch)
> **batch**: (`items`) => `Promise`<`void`>
##### Parameters
[Section titled “Parameters”](#parameters-15)
###### items
[Section titled “items”](#items)
readonly [`MutationBatchItem`](/api/client/type-aliases/mutationbatchitem/)<`TRegistry`>\[]
##### Returns
[Section titled “Returns”](#returns-20)
`Promise`<`void`>
#### create
[Section titled “create”](#create)
> **create**: <`TKey`>(`table`, `input`) => `Promise`<`void`>
##### Type Parameters
[Section titled “Type Parameters”](#type-parameters-3)
###### TKey
[Section titled “TKey”](#tkey-2)
`TKey` *extends* `string`
##### Parameters
[Section titled “Parameters”](#parameters-16)
###### table
[Section titled “table”](#table-5)
`TKey`
###### input
[Section titled “input”](#input)
`SyncTableCreateInput`<`TRegistry`, `TKey`>
##### Returns
[Section titled “Returns”](#returns-21)
`Promise`<`void`>
#### delete
[Section titled “delete”](#delete)
> **delete**: <`TKey`>(`table`, `entityKey`) => `Promise`<`void`>
##### Type Parameters
[Section titled “Type Parameters”](#type-parameters-4)
###### TKey
[Section titled “TKey”](#tkey-3)
`TKey` *extends* `string`
##### Parameters
[Section titled “Parameters”](#parameters-17)
###### table
[Section titled “table”](#table-6)
`TKey`
###### entityKey
[Section titled “entityKey”](#entitykey-2)
`Record`<`string`, `string`>
##### Returns
[Section titled “Returns”](#returns-22)
`Promise`<`void`>
#### update
[Section titled “update”](#update)
> **update**: <`TKey`>(`table`, `entityKey`, `patch`) => `Promise`<`void`>
##### Type Parameters
[Section titled “Type Parameters”](#type-parameters-5)
###### TKey
[Section titled “TKey”](#tkey-4)
`TKey` *extends* `string`
##### Parameters
[Section titled “Parameters”](#parameters-18)
###### table
[Section titled “table”](#table-7)
`TKey`
###### entityKey
[Section titled “entityKey”](#entitykey-3)
`Record`<`string`, `string`>
###### patch
[Section titled “patch”](#patch)
`SyncTableUpdateInput`<`TRegistry`, `TKey`>
##### Returns
[Section titled “Returns”](#returns-23)
`Promise`<`void`>
***
### mutations
[Section titled “mutations”](#mutations)
> **mutations**: [`MutationsApi`](/api/client/interfaces/mutationsapi/)<`TRegistry`>
Defined in: packages/client/src/index.ts:1538
The registry-wide reactive mutation-status surface: a global per-status summary and a filtered normalized detail list over EVERY writable journal, each available one-shot or as a live subscription. Identical on the in-process and worker-attached client. Render a global sync indicator with ONE `subscribeSummary` instead of one journal live query per writable table; consumers never touch the generated journal relation names. See [MutationsApi](/api/client/interfaces/mutationsapi/).
***
### onEventLaneReport
[Section titled “onEventLaneReport”](#oneventlanereport)
> **onEventLaneReport**: (`listener`) => () => `void`
Defined in: packages/client/src/index.ts:1481
The Event lane’s verdict/report surface (ADR-0053 decision 2): per flush pass, the TERMINAL non-`acked` verdicts (`refused` — the server’s gating hook said no; `rejected` — a schema-invalid or oversized payload for a KNOWN stream), the `deferred` ones (an Event stream the server does not yet know — ordinary rollout skew; the rows stay and retry), and the batch-level backoff transitions. `acked` is not reported: a high-volume append-only lane would drown the app in its own success.
An EPHEMERAL subscription, deliberately — a durable client-side verdict table would be retention-bearing state for a debugging need. With nothing subscribed the library logs each report at warn level rather than dropping it, because once a terminal row is deleted the Outbox cannot answer what happened to it.
#### Parameters
[Section titled “Parameters”](#parameters-19)
##### listener
[Section titled “listener”](#listener)
(`report`) => `void`
#### Returns
[Section titled “Returns”](#returns-24)
() => `void`
***
### onOutboxStatus
[Section titled “onOutboxStatus”](#onoutboxstatus)
> **onOutboxStatus**: (`listener`) => () => `void`
Defined in: packages/client/src/index.ts:1463
The **drain signal** (ADR-0053 decision 2): subscribe to the Outbox’s empty ↔ non-empty transitions, with the CURRENT state delivered on subscribe. Returns an unsubscribe.
The invalidation hook for a best-guess view that composes pending events with down-synced aggregates: when the Outbox drains, the aggregate is authoritative again. Deliberately carries no count — a count that updates only on transitions is stale by construction, and one that updates per append is a worse live query; for richer detail, query the Outbox table (`getOutboxTable(registry)`).
#### Parameters
[Section titled “Parameters”](#parameters-20)
##### listener
[Section titled “listener”](#listener-1)
(`status`) => `void`
#### Returns
[Section titled “Returns”](#returns-25)
() => `void`
***
### outboxStatus
[Section titled “outboxStatus”](#outboxstatus)
> **outboxStatus**: () => `Promise`<[`OutboxStatus`](/api/client/interfaces/outboxstatus/)>
Defined in: packages/client/src/index.ts:1469
The drain signal as a one-shot READ — the pull twin of [onOutboxStatus](/api/client/interfaces/syncclient/#onoutboxstatus)’s push (the [bootReport](/api/client/interfaces/syncclient/#bootreport) pattern). Reads the store, so it is always current: a tab attaching long after the engine’s last transition folds its initial state from here rather than waiting for the next one.
#### Returns
[Section titled “Returns”](#returns-26)
`Promise`<[`OutboxStatus`](/api/client/interfaces/outboxstatus/)>
***
### pglite
[Section titled “pglite”](#pglite)
> **pglite**: [`ClientPGlite`](/api/client/type-aliases/clientpglite/)
Defined in: packages/client/src/index.ts:1360
***
### prepareQuery
[Section titled “prepareQuery”](#preparequery)
> **prepareQuery**: (`input`) => `Promise`<[`PreparedQueryResult`](/api/client/interfaces/preparedqueryresult/)<`SyncTableName`<`TRegistry`>>>
Defined in: packages/client/src/index.ts:1645
The read-path safety seam (ADR-0021): scan the compiled `sql` for the lazy relations it reads (∪ the optional `use`) and activate them — so a lazy relation auto-activates on *any* reference (FROM, JOIN, subquery, WHERE). Resolves once it is safe to run the query (streams started, tripwire satisfied) and returns the activated keys ([PreparedQueryResult](/api/client/interfaces/preparedqueryresult/)) so a consumer can further await [groupReady](/api/client/interfaces/syncclient/#groupready) per key for catch-up completion — the React hooks drive `hydrating` off exactly that. A backstop throws [LazyRelationNotActivatedError](/api/client/classes/lazyrelationnotactivatederror/) only if a referenced relation could not be activated. Exposed for the React live hooks (which own their query build); `query`/`queryRow` are the higher-level non-live wrappers. Raw, non-Drizzle SQL is out of scope — pass `use`, or `ensureSynced` first.
#### Parameters
[Section titled “Parameters”](#parameters-21)
##### input
[Section titled “input”](#input-1)
[`PrepareQueryInput`](/api/client/interfaces/preparequeryinput/)<`TRegistry`>
#### Returns
[Section titled “Returns”](#returns-27)
`Promise`<[`PreparedQueryResult`](/api/client/interfaces/preparedqueryresult/)<`SyncTableName`<`TRegistry`>>>
***
### query
[Section titled “query”](#query-1)
> **query**: <`TRows`>(`build`) => `Promise`<`TRows`>
Defined in: packages/client/src/index.ts:1575
Run a one-shot (non-live) typed **pure-Drizzle** query with the lazy-relation safety net (ADR-0021). Pass the builder callback directly — pgxsinkit scans the compiled SQL and activates + awaits every lazy relation it reads (FROM, JOIN, subquery, WHERE) before it runs, and the tripwire rejects any lazy relation the SQL still references but that is not active, so the result is never silently empty/stale. The guaranteed-safe alternative to a bare `client.drizzle` read. If the builder embeds a raw `sql` template fragment, use [queryRaw](/api/client/interfaces/syncclient/#queryraw) and declare the lazy relations in `use`.
#### Type Parameters
[Section titled “Type Parameters”](#type-parameters-6)
##### TRows
[Section titled “TRows”](#trows)
`TRows` *extends* readonly `unknown`\[]
#### Parameters
[Section titled “Parameters”](#parameters-22)
##### build
[Section titled “build”](#build)
[`GuardedQueryFn`](/api/client/type-aliases/guardedqueryfn/)<`TRegistry`, `TRows`>
#### Returns
[Section titled “Returns”](#returns-28)
`Promise`<`TRows`>
***
### queryRaw
[Section titled “queryRaw”](#queryraw)
> **queryRaw**: <`TRows`>(`spec`) => `Promise`<`TRows`>
Defined in: packages/client/src/index.ts:1586
[query](/api/client/interfaces/syncclient/#query) for a builder that embeds a raw `sql` template fragment (ADR-0021). The compiled-SQL scan can miss a lazy relation named as a bare/unquoted identifier inside raw SQL, so declare those in `use` — they are activated and awaited before the query runs. Pure-Drizzle reads should use [query](/api/client/interfaces/syncclient/#query) instead (no `use` needed).
#### Type Parameters
[Section titled “Type Parameters”](#type-parameters-7)
##### TRows
[Section titled “TRows”](#trows-1)
`TRows` *extends* readonly `unknown`\[]
#### Parameters
[Section titled “Parameters”](#parameters-23)
##### spec
[Section titled “spec”](#spec)
[`GuardedRawQuerySpec`](/api/client/interfaces/guardedrawqueryspec/)<`TRegistry`, `TRows`>
#### Returns
[Section titled “Returns”](#returns-29)
`Promise`<`TRows`>
***
### queryRawRow
[Section titled “queryRawRow”](#queryrawrow)
> **queryRawRow**: <`TRows`>(`spec`) => `Promise`<`TRows`\[`number`] | `null`>
Defined in: packages/client/src/index.ts:1588
[queryRaw](/api/client/interfaces/syncclient/#queryraw) returning the first row, or null when empty.
#### Type Parameters
[Section titled “Type Parameters”](#type-parameters-8)
##### TRows
[Section titled “TRows”](#trows-2)
`TRows` *extends* readonly `unknown`\[]
#### Parameters
[Section titled “Parameters”](#parameters-24)
##### spec
[Section titled “spec”](#spec-1)
[`GuardedRawQuerySpec`](/api/client/interfaces/guardedrawqueryspec/)<`TRegistry`, `TRows`>
#### Returns
[Section titled “Returns”](#returns-30)
`Promise`<`TRows`\[`number`] | `null`>
***
### queryRow
[Section titled “queryRow”](#queryrow)
> **queryRow**: <`TRows`>(`build`) => `Promise`<`TRows`\[`number`] | `null`>
Defined in: packages/client/src/index.ts:1577
[query](/api/client/interfaces/syncclient/#query) returning the first row, or null when empty.
#### Type Parameters
[Section titled “Type Parameters”](#type-parameters-9)
##### TRows
[Section titled “TRows”](#trows-3)
`TRows` *extends* readonly `unknown`\[]
#### Parameters
[Section titled “Parameters”](#parameters-25)
##### build
[Section titled “build”](#build-1)
[`GuardedQueryFn`](/api/client/type-aliases/guardedqueryfn/)<`TRegistry`, `TRows`>
#### Returns
[Section titled “Returns”](#returns-31)
`Promise`<`TRows`\[`number`] | `null`>
***
### rawExec
[Section titled “rawExec”](#rawexec)
> **rawExec**: (`sql`, `options?`) => `Promise`<`Results`\[]>
Defined in: packages/client/src/index.ts:1549
[rawQuery](/api/client/interfaces/syncclient/#rawquery) for multi-statement SQL, returning one Results per statement.
#### Parameters
[Section titled “Parameters”](#parameters-26)
##### sql
[Section titled “sql”](#sql-1)
`string`
##### options?
[Section titled “options?”](#options-4)
[`RawQueryOptions`](/api/client/interfaces/rawqueryoptions/)
#### Returns
[Section titled “Returns”](#returns-32)
`Promise`<`Results`\[]>
***
### rawQuery
[Section titled “rawQuery”](#rawquery)
> **rawQuery**: (`sql`, `params?`, `options?`) => `Promise`<`Results`>
Defined in: packages/client/src/index.ts:1547
The INSPECTION read surface (debug pages, REPLs, ad-hoc counts) — identical on the in-process and worker-attached client (on the latter the statement runs in the worker). The statement runs raw against the local store: it BYPASSES the mutation journal and optimistic overlay, and any write it issues stays local and will NOT converge. For app data reads prefer the live-rows hooks / [subscribeLiveRows](/api/client/interfaces/syncclient/#subscribeliverows) (or the guarded [query](/api/client/interfaces/syncclient/#query) family); reach for this only to look at the store, not to read app state or mutate it.
#### Parameters
[Section titled “Parameters”](#parameters-27)
##### sql
[Section titled “sql”](#sql-2)
`string`
##### params?
[Section titled “params?”](#params)
`unknown`\[]
##### options?
[Section titled “options?”](#options-5)
[`RawQueryOptions`](/api/client/interfaces/rawqueryoptions/)
#### Returns
[Section titled “Returns”](#returns-33)
`Promise`<`Results`>
***
### readMutationDetails
[Section titled “readMutationDetails”](#readmutationdetails)
> **readMutationDetails**: (`table?`) => `Promise`<[`MutationDetail`](/api/client/interfaces/mutationdetail/)\[]>
Defined in: packages/client/src/index.ts:1486
#### Parameters
[Section titled “Parameters”](#parameters-28)
##### table?
[Section titled “table?”](#table-8)
`SyncTableName`<`TRegistry`>
#### Returns
[Section titled “Returns”](#returns-34)
`Promise`<[`MutationDetail`](/api/client/interfaces/mutationdetail/)\[]>
***
### ready
[Section titled “ready”](#ready)
> **ready**: `Promise`<`void`>
Defined in: packages/client/src/index.ts:1389
***
### reconcile
[Section titled “reconcile”](#reconcile)
> **reconcile**: (`table?`) => `Promise`<`void`>
Defined in: packages/client/src/index.ts:1483
#### Parameters
[Section titled “Parameters”](#parameters-29)
##### table?
[Section titled “table?”](#table-9)
`SyncTableName`<`TRegistry`>
#### Returns
[Section titled “Returns”](#returns-35)
`Promise`<`void`>
***
### recoverSending
[Section titled “recoverSending”](#recoversending)
> **recoverSending**: (`table?`) => `Promise`<`void`>
Defined in: packages/client/src/index.ts:1485
#### Parameters
[Section titled “Parameters”](#parameters-30)
##### table?
[Section titled “table?”](#table-10)
`SyncTableName`<`TRegistry`>
#### Returns
[Section titled “Returns”](#returns-36)
`Promise`<`void`>
***
### retryFailed
[Section titled “retryFailed”](#retryfailed)
> **retryFailed**: (`table?`) => `Promise`<`void`>
Defined in: packages/client/src/index.ts:1484
#### Parameters
[Section titled “Parameters”](#parameters-31)
##### table?
[Section titled “table?”](#table-11)
`SyncTableName`<`TRegistry`>
#### Returns
[Section titled “Returns”](#returns-37)
`Promise`<`void`>
***
### start
[Section titled “start”](#start)
> **start**: () => `Promise`<`void`>
Defined in: packages/client/src/index.ts:1391
#### Returns
[Section titled “Returns”](#returns-38)
`Promise`<`void`>
***
### status
[Section titled “status”](#status)
> **status**: `SyncRuntimeStatus`
Defined in: packages/client/src/index.ts:1390
***
### stop
[Section titled “stop”](#stop)
> **stop**: () => `Promise`<`void`>
Defined in: packages/client/src/index.ts:1400
#### Returns
[Section titled “Returns”](#returns-39)
`Promise`<`void`>
***
### subscribeLiveRows
[Section titled “subscribeLiveRows”](#subscribeliverows)
> **subscribeLiveRows**: <`TRow`>(`input`, `onRows`) => `Promise`<[`LiveRowsSubscription`](/api/client/interfaces/liverowssubscription/)<`TRow`>>
Defined in: packages/client/src/index.ts:1687
The live-rows seam (ADR-0032 S2 §4): register a reactive query and receive its initial ordered snapshot plus subsequent updates via `onRows`. The React live hooks consume THIS (not `pglite.live` directly), so they run unchanged against both the in-process client (which implements it over `pglite.live`) and the worker-attached client (which implements it over the bridge). Prefer the higher-level `@pgxsinkit/react` hooks; this is the lower-level primitive they build on.
#### Type Parameters
[Section titled “Type Parameters”](#type-parameters-10)
##### TRow
[Section titled “TRow”](#trow)
`TRow` *extends* `Record`<`string`, `unknown`> = `Record`<`string`, `unknown`>
#### Parameters
[Section titled “Parameters”](#parameters-32)
##### input
[Section titled “input”](#input-2)
[`SubscribeLiveRowsInput`](/api/client/interfaces/subscribeliverowsinput/)
##### onRows
[Section titled “onRows”](#onrows)
(`rows`) => `void`
#### Returns
[Section titled “Returns”](#returns-40)
`Promise`<[`LiveRowsSubscription`](/api/client/interfaces/liverowssubscription/)<`TRow`>>
***
### tables
[Section titled “tables”](#tables)
> **tables**: `{ [TKey in string]: SyncClientTableHandle }`
Defined in: packages/client/src/index.ts:1362
***
### transaction
[Section titled “transaction”](#transaction)
> **transaction**: (`options`, `run`) => `Promise`<[`SyncTransactionResult`](/api/client/interfaces/synctransactionresult/)>
Defined in: packages/client/src/index.ts:1658
Author an atomic write-**unit** (ADR-0022 §2). The callback receives collecting table handles; every mutation it issues is tagged into one unit and enqueued atomically when the callback returns.
* `mode: "pessimistic"` — **server-authoritative**: the unit flush-routes to the authoritative endpoint and this call resolves only once the server has decided. The result’s `acks` carry each member’s outcome — `acked`, `conflicted` (overlay kept, ADR-0015), or `rejected` (overlay auto-discarded for the whole unit, surfaced via `onReject`, ADR-0022 §4). Throws on transport failure (overlay kept). A pessimistic block may also issue [SyncTransactionTableHandle.updateBlind](/api/client/interfaces/synctransactiontablehandle/#updateblind) — an update-by-key with no local base row and no overlay, for a write target excluded from the actor’s read shape.
* `mode: "optimistic"` — an atomic batch enqueue that flushes in the background (empty `acks`).
#### Parameters
[Section titled “Parameters”](#parameters-33)
##### options
[Section titled “options”](#options-6)
###### mode
[Section titled “mode”](#mode)
`WriteMode`
##### run
[Section titled “run”](#run)
(`tx`) => `void` | `Promise`<`void`>
#### Returns
[Section titled “Returns”](#returns-41)
`Promise`<[`SyncTransactionResult`](/api/client/interfaces/synctransactionresult/)>
***
### views
[Section titled “views”](#views)
> **views**: `RegistryViews`<`TRegistry`>
Defined in: packages/client/src/index.ts:1361
***
### writeReady
[Section titled “writeReady”](#writeready)
> **writeReady**: `Promise`<`void`>
Defined in: packages/client/src/index.ts:1380
Write readiness (ADR-0041): resolves once the mutation runtime is constructed and boot recovery has completed (plus restore quarantine on a restore boot) — enqueue is safe. Every write-path method awaits this internally, so a write issued the instant `localReadReady` resolves completes once `writeReady` lands rather than failing opaquely. Resolves with NO network I/O. Monotonic and idempotent.
# SyncClientTableHandle
Defined in: packages/client/src/index.ts:1188
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
### TKey
[Section titled “TKey”](#tkey)
`TKey` *extends* `SyncTableName`<`TRegistry`>
## Properties
[Section titled “Properties”](#properties)
### create
[Section titled “create”](#create)
> **create**: (`input`) => `Promise`<`void`>
Defined in: packages/client/src/index.ts:1191
#### Parameters
[Section titled “Parameters”](#parameters)
##### input
[Section titled “input”](#input)
`SyncTableCreateInput`<`TRegistry`, `TKey`>
#### Returns
[Section titled “Returns”](#returns)
`Promise`<`void`>
***
### delete
[Section titled “delete”](#delete)
> **delete**: (`entityKey`) => `Promise`<`void`>
Defined in: packages/client/src/index.ts:1193
#### Parameters
[Section titled “Parameters”](#parameters-1)
##### entityKey
[Section titled “entityKey”](#entitykey)
`Record`<`string`, `string`>
#### Returns
[Section titled “Returns”](#returns-1)
`Promise`<`void`>
***
### key
[Section titled “key”](#key)
> **key**: `TKey`
Defined in: packages/client/src/index.ts:1189
***
### mode
[Section titled “mode”](#mode)
> **mode**: `TRegistry`\[`TKey`]\[`"mode"`]
Defined in: packages/client/src/index.ts:1190
***
### update
[Section titled “update”](#update)
> **update**: (`entityKey`, `patch`) => `Promise`<`void`>
Defined in: packages/client/src/index.ts:1192
#### Parameters
[Section titled “Parameters”](#parameters-2)
##### entityKey
[Section titled “entityKey”](#entitykey-1)
`Record`<`string`, `string`>
##### patch
[Section titled “patch”](#patch)
`SyncTableUpdateInput`<`TRegistry`, `TKey`>
#### Returns
[Section titled “Returns”](#returns-2)
`Promise`<`void`>
# SyncTransaction
Defined in: packages/client/src/index.ts:1229
The handle passed to a [SyncClient.transaction](/api/client/interfaces/syncclient/#transaction) callback: collecting table handles for the unit.
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
## Properties
[Section titled “Properties”](#properties)
### tables
[Section titled “tables”](#tables)
> **tables**: `{ [TKey in string]: SyncTransactionTableHandle }`
Defined in: packages/client/src/index.ts:1230
# SyncTransactionResult
Defined in: packages/client/src/index.ts:1238
The result of a [SyncClient.transaction](/api/client/interfaces/syncclient/#transaction). A `pessimistic` block carries the authoritative server `acks` (each `acked` / `conflicted` / `rejected`); an `optimistic` block enqueues atomically and flushes in the background, so its `acks` are empty.
## Properties
[Section titled “Properties”](#properties)
### acks
[Section titled “acks”](#acks)
> **acks**: `object`\[]
Defined in: packages/client/src/index.ts:1239
#### conflictReason?
[Section titled “conflictReason?”](#conflictreason)
> `optional` **conflictReason?**: `string`
#### entityKey
[Section titled “entityKey”](#entitykey)
> **entityKey**: `Record`<`string`, `string`> = `entityKeySchema`
#### httpStatus?
[Section titled “httpStatus?”](#httpstatus)
> `optional` **httpStatus?**: `number`
#### mutationId
[Section titled “mutationId”](#mutationid)
> **mutationId**: `string`
#### mutationSeq
[Section titled “mutationSeq”](#mutationseq)
> **mutationSeq**: `number`
#### rejectionReason?
[Section titled “rejectionReason?”](#rejectionreason)
> `optional` **rejectionReason?**: `string`
The typed reason a `rejected` ack carries (ADR-0022): the authoritative endpoint’s account of why the write-unit was declined (a capacity/quota/uniqueness rule — e.g. the DB constraint or trigger message). Surfaced to the app when the optimistic overlay is auto-discarded.
#### serverUpdatedAtUs?
[Section titled “serverUpdatedAtUs?”](#serverupdatedatus)
> `optional` **serverUpdatedAtUs?**: `string`
#### status
[Section titled “status”](#status)
> **status**: `"acked"` | `"rejected"` | `"failed"` | `"conflicted"` = `mutationAckStatusSchema`
#### tableName
[Section titled “tableName”](#tablename)
> **tableName**: `string`
# SyncTransactionTableHandle
Defined in: packages/client/src/index.ts:1201
A table handle inside a [SyncClient.transaction](/api/client/interfaces/syncclient/#transaction) block (ADR-0022 §2). It *collects* mutations into the open write-unit rather than enqueuing each immediately; the whole set is enqueued atomically when the block’s callback returns. The calls are synchronous (collection only) — no per-call await needed.
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
### TKey
[Section titled “TKey”](#tkey)
`TKey` *extends* `SyncTableName`<`TRegistry`>
## Properties
[Section titled “Properties”](#properties)
### create
[Section titled “create”](#create)
> **create**: (`input`) => `void`
Defined in: packages/client/src/index.ts:1205
#### Parameters
[Section titled “Parameters”](#parameters)
##### input
[Section titled “input”](#input)
`SyncTableCreateInput`<`TRegistry`, `TKey`>
#### Returns
[Section titled “Returns”](#returns)
`void`
***
### delete
[Section titled “delete”](#delete)
> **delete**: (`entityKey`) => `void`
Defined in: packages/client/src/index.ts:1207
#### Parameters
[Section titled “Parameters”](#parameters-1)
##### entityKey
[Section titled “entityKey”](#entitykey)
`Record`<`string`, `string`>
#### Returns
[Section titled “Returns”](#returns-1)
`void`
***
### update
[Section titled “update”](#update)
> **update**: (`entityKey`, `patch`) => `void`
Defined in: packages/client/src/index.ts:1206
#### Parameters
[Section titled “Parameters”](#parameters-2)
##### entityKey
[Section titled “entityKey”](#entitykey-1)
`Record`<`string`, `string`>
##### patch
[Section titled “patch”](#patch)
`SyncTableUpdateInput`<`TRegistry`, `TKey`>
#### Returns
[Section titled “Returns”](#returns-2)
`void`
***
### updateBlind
[Section titled “updateBlind”](#updateblind)
> **updateBlind**: (`entityKey`, `patch`) => `void`
Defined in: packages/client/src/index.ts:1225
An **update-by-key with no local base row** (ADR-0022 addendum). Ordinary `update` requires the entity to be present in the actor’s local read model (it seeds the optimistic overlay and captures the base server version); `updateBlind` skips that presence check and writes NO overlay — nothing appears in the read model. Use it when the write target is deliberately EXCLUDED from your read shape (a write-only flow, or an anonymity-scoped moderation write whose row streams only to a different projection), so there is no local row to update and nothing to show optimistically.
**Pessimistic-only.** The /unit expander is authoritative for the outcome, so a blind write is meaningful only inside a `transaction({ mode: "pessimistic" })` block (or over a statically-pessimistic table). An optimistic-routed blind write has nothing to converge and THROWS at enqueue.
The acked journal row **retires without a synced echo** (no visible row ever converges for it), so it does not linger — unlike the seed-a-phantom-row workaround this replaces, whose acked row + overlay lingered forever behind the echo barrier. A `conflicted` blind write stays dischargeable via `discardConflict`; a `rejected` one is surfaced via `onReject`, both with no overlay to clean up.
#### Parameters
[Section titled “Parameters”](#parameters-3)
##### entityKey
[Section titled “entityKey”](#entitykey-2)
`Record`<`string`, `string`>
##### patch
[Section titled “patch”](#patch-1)
`SyncTableUpdateInput`<`TRegistry`, `TKey`>
#### Returns
[Section titled “Returns”](#returns-3)
`void`
# SyncWorkerHost
Defined in: packages/client/src/worker/define-sync-worker.ts:239
The worker-side host: attach ports, await the boot, and (for tests) tear down.
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
## Properties
[Section titled “Properties”](#properties)
### close
[Section titled “close”](#close)
> **close**: () => `Promise`<`void`>
Defined in: packages/client/src/worker/define-sync-worker.ts:245
Detach every port and stop the underlying client — for tests and dedicated-worker teardown.
#### Returns
[Section titled “Returns”](#returns)
`Promise`<`void`>
***
### connect
[Section titled “connect”](#connect)
> **connect**: (`port`) => `void`
Defined in: packages/client/src/worker/define-sync-worker.ts:241
Bind a transport port (a SharedWorker connection, the dedicated-worker `self`, or a test channel port).
#### Parameters
[Section titled “Parameters”](#parameters)
##### port
[Section titled “port”](#port)
[`BridgePort`](/api/client/interfaces/bridgeport/)
#### Returns
[Section titled “Returns”](#returns-1)
`void`
***
### whenBooted
[Section titled “whenBooted”](#whenbooted)
> **whenBooted**: () => `Promise`<[`SyncClient`](/api/client/interfaces/syncclient/)<`TRegistry`>>
Defined in: packages/client/src/worker/define-sync-worker.ts:243
Resolves with the booted in-process engine once the first attach’s `createSyncClient` completes.
#### Returns
[Section titled “Returns”](#returns-2)
`Promise`<[`SyncClient`](/api/client/interfaces/syncclient/)<`TRegistry`>>
# TokenRequestPayload
Defined in: packages/client/src/worker/protocol.ts:265
worker → tab: a broadcast pull-request — any attached tab may answer, first response wins (ADR-0032 decision 3).
## Properties
[Section titled “Properties”](#properties)
### requestId
[Section titled “requestId”](#requestid)
> **requestId**: `string`
Defined in: packages/client/src/worker/protocol.ts:266
# TokenResponsePayload
Defined in: packages/client/src/worker/protocol.ts:270
tab → worker: the answer to a [TokenRequestPayload](/api/client/interfaces/tokenrequestpayload/). `token: null` = the tab has none.
## Properties
[Section titled “Properties”](#properties)
### requestId
[Section titled “requestId”](#requestid)
> **requestId**: `string`
Defined in: packages/client/src/worker/protocol.ts:271
***
### token
[Section titled “token”](#token)
> **token**: [`AuthTokenSnapshot`](/api/client/interfaces/authtokensnapshot/) | `null`
Defined in: packages/client/src/worker/protocol.ts:272
# WakePayload
Defined in: packages/client/src/worker/protocol.ts:495
tab → worker: an app-driven wake (online/visibilitychange), treated as a convergence pass request.
## Properties
[Section titled “Properties”](#properties)
### reason
[Section titled “reason”](#reason)
> **reason**: `"online"` | `"manual"` | `"visibility"`
Defined in: packages/client/src/worker/protocol.ts:496
# WorkerTokenCache
Defined in: packages/client/src/worker/token-cache.ts:9
## Properties
[Section titled “Properties”](#properties)
### getToken
[Section titled “getToken”](#gettoken)
> **getToken**: () => `Promise`<`string` | `undefined`>
Defined in: packages/client/src/worker/token-cache.ts:15
Resolve the access token for a shape/flush request. Returns the cached token when it is comfortably ahead of expiry; otherwise broadcasts ONE pull (deduped across concurrent callers) and resolves when a tab answers. Falls back to the (stale) cached token if a pull yields nothing.
#### Returns
[Section titled “Returns”](#returns)
`Promise`<`string` | `undefined`>
***
### push
[Section titled “push”](#push)
> **push**: (`token`) => `void`
Defined in: packages/client/src/worker/token-cache.ts:17
The tab pushed a fresh token (auth state change or the initial attach) — cache it and satisfy any pull.
#### Parameters
[Section titled “Parameters”](#parameters)
##### token
[Section titled “token”](#token)
[`AuthTokenSnapshot`](/api/client/interfaces/authtokensnapshot/) | `null`
#### Returns
[Section titled “Returns”](#returns-1)
`void`
***
### pushIfFresher
[Section titled “pushIfFresher”](#pushiffresher)
> **pushIfFresher**: (`token`) => `void`
Defined in: packages/client/src/worker/token-cache.ts:24
Seed the cache from a LATER attach’s token, but only if it is strictly fresher than what is cached (ADR-0032 FIX 5). Attach seeding must never CLOBBER a fresher cached token with an older/null one — only the explicit `push` path (the tab as auth owner saying “this is current”) may overwrite unconditionally, including null on logout.
#### Parameters
[Section titled “Parameters”](#parameters-1)
##### token
[Section titled “token”](#token-1)
[`AuthTokenSnapshot`](/api/client/interfaces/authtokensnapshot/) | `null`
#### Returns
[Section titled “Returns”](#returns-2)
`void`
***
### respond
[Section titled “respond”](#respond)
> **respond**: (`requestId`, `token`) => `void`
Defined in: packages/client/src/worker/token-cache.ts:26
A tab answered a pull-request. First matching answer wins; later duplicates are ignored.
#### Parameters
[Section titled “Parameters”](#parameters-2)
##### requestId
[Section titled “requestId”](#requestid)
`string`
##### token
[Section titled “token”](#token-2)
[`AuthTokenSnapshot`](/api/client/interfaces/authtokensnapshot/) | `null`
#### Returns
[Section titled “Returns”](#returns-3)
`void`
# @pgxsinkit/client
## Classes
[Section titled “Classes”](#classes)
* [ClientDisposedError](/api/client/classes/clientdisposederror/)
* [CommittedStoreUnreachableError](/api/client/classes/committedstoreunreachableerror/)
* [DataExportDrainError](/api/client/classes/dataexportdrainerror/)
* [ElectedEngineUnconstructibleError](/api/client/classes/electedengineunconstructibleerror/)
* [EngineRelocatedError](/api/client/classes/enginerelocatederror/)
* [EventPayloadInvalidError](/api/client/classes/eventpayloadinvaliderror/)
* [EventPayloadTooLargeError](/api/client/classes/eventpayloadtoolargeerror/)
* [EventStreamsNotRegisteredError](/api/client/classes/eventstreamsnotregisterederror/)
* [ExecutionLimitMismatchError](/api/client/classes/executionlimitmismatcherror/)
* [InvalidStorePathError](/api/client/classes/invalidstorepatherror/)
* [LazyRelationNotActivatedError](/api/client/classes/lazyrelationnotactivatederror/)
* [LifecycleBusyError](/api/client/classes/lifecyclebusyerror/)
* [LiveRowsMaterializer](/api/client/classes/liverowsmaterializer/)
* [NonPersistentStoreError](/api/client/classes/nonpersistentstoreerror/)
* [ProvisionExpiredError](/api/client/classes/provisionexpirederror/)
* [RestoreTargetExistsError](/api/client/classes/restoretargetexistserror/)
* [StoreDestroyRefusedError](/api/client/classes/storedestroyrefusederror/)
* [UnknownEventStreamError](/api/client/classes/unknowneventstreamerror/)
* [WriteNotReadyError](/api/client/classes/writenotreadyerror/)
## Interfaces
[Section titled “Interfaces”](#interfaces)
* [AttachAckPayload](/api/client/interfaces/attachackpayload/)
* [AttachPayload](/api/client/interfaces/attachpayload/)
* [AttachSyncClientOptions](/api/client/interfaces/attachsyncclientoptions/)
* [AuthTokenSnapshot](/api/client/interfaces/authtokensnapshot/)
* [BootReport](/api/client/interfaces/bootreport/)
* [BridgeCodec](/api/client/interfaces/bridgecodec/)
* [BridgeEnvelope](/api/client/interfaces/bridgeenvelope/)
* [BridgePort](/api/client/interfaces/bridgeport/)
* [CloneDumpPhases](/api/client/interfaces/clonedumpphases/)
* [CloneDumpResult](/api/client/interfaces/clonedumpresult/)
* [CommittedStoreUnreachableWire](/api/client/interfaces/committedstoreunreachablewire/)
* [ConvergenceClient](/api/client/interfaces/convergenceclient/)
* [ConvergenceDriver](/api/client/interfaces/convergencedriver/)
* [ConvergenceDriverOptions](/api/client/interfaces/convergencedriveroptions/)
* [ConvergenceTrigger](/api/client/interfaces/convergencetrigger/)
* [CreateClientPGliteOptions](/api/client/interfaces/createclientpgliteoptions/)
* [CreateSyncClientOptions](/api/client/interfaces/createsyncclientoptions/)
* [DataExportDeps](/api/client/interfaces/dataexportdeps/)
* [DataExportOptions](/api/client/interfaces/dataexportoptions/)
* [DataExportReport](/api/client/interfaces/dataexportreport/)
* [DataExportResult](/api/client/interfaces/dataexportresult/)
* [DefineSyncWorkerOptions](/api/client/interfaces/definesyncworkeroptions/)
* [DiagnosticDumpReport](/api/client/interfaces/diagnosticdumpreport/)
* [DiagnosticExportDeps](/api/client/interfaces/diagnosticexportdeps/)
* [DiagnosticExportOptions](/api/client/interfaces/diagnosticexportoptions/)
* [DiagnosticExportResult](/api/client/interfaces/diagnosticexportresult/)
* [DrainJournalOptions](/api/client/interfaces/drainjournaloptions/)
* [DrizzleQueryBuilder](/api/client/interfaces/drizzlequerybuilder/)
* [ElectedEngineWorker](/api/client/interfaces/electedengineworker/)
* [EventAppendResult](/api/client/interfaces/eventappendresult/)
* [EventBackoffOptions](/api/client/interfaces/eventbackoffoptions/)
* [EventFlushDriver](/api/client/interfaces/eventflushdriver/)
* [EventFlushDriverOptions](/api/client/interfaces/eventflushdriveroptions/)
* [EventFlushGate](/api/client/interfaces/eventflushgate/)
* [EventLaneBackoffTransition](/api/client/interfaces/eventlanebackofftransition/)
* [EventLaneDb](/api/client/interfaces/eventlanedb/)
* [EventLaneOptions](/api/client/interfaces/eventlaneoptions/)
* [EventLaneReport](/api/client/interfaces/eventlanereport/)
* [EventLaneRuntime](/api/client/interfaces/eventlaneruntime/)
* [EventLaneVerdict](/api/client/interfaces/eventlaneverdict/)
* [EventStreamFlushOptions](/api/client/interfaces/eventstreamflushoptions/)
* [ExecutionLimitConfig](/api/client/interfaces/executionlimitconfig/)
* [ExportArtefactWire](/api/client/interfaces/exportartefactwire/)
* [ExportReportCommon](/api/client/interfaces/exportreportcommon/)
* [FreshBootResolution](/api/client/interfaces/freshbootresolution/)
* [GuardedRawQuerySpec](/api/client/interfaces/guardedrawqueryspec/)
* [LazyGuardIndex](/api/client/interfaces/lazyguardindex/)
* [LifecycleSlot](/api/client/interfaces/lifecycleslot/)
* [LiveDiffPayload](/api/client/interfaces/livediffpayload/)
* [LiveDiffState](/api/client/interfaces/livediffstate/)
* [LiveInitialPayload](/api/client/interfaces/liveinitialpayload/)
* [LiveQueryDiagnostics](/api/client/interfaces/livequerydiagnostics/)
* [LiveRowsSubscription](/api/client/interfaces/liverowssubscription/)
* [LocalStoreVersionEvent](/api/client/interfaces/localstoreversionevent/)
* [MutationDetail](/api/client/interfaces/mutationdetail/)
* [MutationDiagnostics](/api/client/interfaces/mutationdiagnostics/)
* [MutationListOptions](/api/client/interfaces/mutationlistoptions/)
* [MutationListSubscription](/api/client/interfaces/mutationlistsubscription/)
* [MutationsApi](/api/client/interfaces/mutationsapi/)
* [MutationsApiDeps](/api/client/interfaces/mutationsapideps/)
* [MutationSummary](/api/client/interfaces/mutationsummary/)
* [MutationSummarySubscription](/api/client/interfaces/mutationsummarysubscription/)
* [OpfsEffects](/api/client/interfaces/opfseffects/)
* [OpfsEffectsDeps](/api/client/interfaces/opfseffectsdeps/)
* [OutboxStatus](/api/client/interfaces/outboxstatus/)
* [PgliteBootAssets](/api/client/interfaces/pglitebootassets/)
* [PreparedQueryResult](/api/client/interfaces/preparedqueryresult/)
* [PrepareQueryInput](/api/client/interfaces/preparequeryinput/)
* [ProvisionAckPayload](/api/client/interfaces/provisionackpayload/)
* [ProvisionPayload](/api/client/interfaces/provisionpayload/)
* [RawQueryOptions](/api/client/interfaces/rawqueryoptions/)
* [ReplInspectionSurface](/api/client/interfaces/replinspectionsurface/)
* [ResolveStoreBootOptions](/api/client/interfaces/resolvestorebootoptions/)
* [RpcPayload](/api/client/interfaces/rpcpayload/)
* [RpcResultPayload](/api/client/interfaces/rpcresultpayload/)
* [SetOnlinePayload](/api/client/interfaces/setonlinepayload/)
* [StoreBackupReport](/api/client/interfaces/storebackupreport/)
* [StoreBootResolution](/api/client/interfaces/storebootresolution/)
* [StoreDestructionRetryOptions](/api/client/interfaces/storedestructionretryoptions/)
* [StoreExportDeps](/api/client/interfaces/storeexportdeps/)
* [StoreExportOptions](/api/client/interfaces/storeexportoptions/)
* [StoreExportResult](/api/client/interfaces/storeexportresult/)
* [StoreWorkerQuiesceOptions](/api/client/interfaces/storeworkerquiesceoptions/)
* [StoreWorkerQuiesceOutcome](/api/client/interfaces/storeworkerquiesceoutcome/)
* [SubscribeLiveRowsInput](/api/client/interfaces/subscribeliverowsinput/)
* [SubscribePayload](/api/client/interfaces/subscribepayload/)
* [SyncClient](/api/client/interfaces/syncclient/)
* [SyncClientTableHandle](/api/client/interfaces/syncclienttablehandle/)
* [SyncTransaction](/api/client/interfaces/synctransaction/)
* [SyncTransactionResult](/api/client/interfaces/synctransactionresult/)
* [SyncTransactionTableHandle](/api/client/interfaces/synctransactiontablehandle/)
* [SyncWorkerHost](/api/client/interfaces/syncworkerhost/)
* [TokenRequestPayload](/api/client/interfaces/tokenrequestpayload/)
* [TokenResponsePayload](/api/client/interfaces/tokenresponsepayload/)
* [WakePayload](/api/client/interfaces/wakepayload/)
* [WorkerTokenCache](/api/client/interfaces/workertokencache/)
## Type Aliases
[Section titled “Type Aliases”](#type-aliases)
* [AllMutationsView](/api/client/type-aliases/allmutationsview/)
* [AttachedSyncClient](/api/client/type-aliases/attachedsyncclient/)
* [BridgeEvent](/api/client/type-aliases/bridgeevent/)
* [BridgeMessageType](/api/client/type-aliases/bridgemessagetype/)
* [BridgeTransferable](/api/client/type-aliases/bridgetransferable/)
* [ClientPGlite](/api/client/type-aliases/clientpglite/)
* [DrainJournalOption](/api/client/type-aliases/drainjournaloption/)
* [EngineRelocatedOutcome](/api/client/type-aliases/enginerelocatedoutcome/)
* [ExportReport](/api/client/type-aliases/exportreport/)
* [GuardedQueryFn](/api/client/type-aliases/guardedqueryfn/)
* [JournalTable](/api/client/type-aliases/journaltable/)
* [LocalMetaTable](/api/client/type-aliases/localmetatable/)
* [MutationBatchItem](/api/client/type-aliases/mutationbatchitem/)
* [MutationKind](/api/client/type-aliases/mutationkind/)
* [MutationSummaryDetail](/api/client/type-aliases/mutationsummarydetail/)
* [OutboxTable](/api/client/type-aliases/outboxtable/)
* [OverlayTable](/api/client/type-aliases/overlaytable/)
* [ReadModelView](/api/client/type-aliases/readmodelview/)
* [ResolvedStorageBackend](/api/client/type-aliases/resolvedstoragebackend/)
* [RpcOp](/api/client/type-aliases/rpcop/)
* [SyncStateView](/api/client/type-aliases/syncstateview/)
## Variables
[Section titled “Variables”](#variables)
* [BRIDGE\_CHANNEL](/api/client/variables/bridge_channel/)
* [BRIDGE\_PROTOCOL\_VERSION](/api/client/variables/bridge_protocol_version/)
* [COMMITTED\_STORE\_UNREACHABLE\_CODE](/api/client/variables/committed_store_unreachable_code/)
* [DEFAULT\_DRAIN\_TIMEOUT\_MS](/api/client/variables/default_drain_timeout_ms/)
* [DEFAULT\_EVENT\_BACKOFF\_BASE\_MS](/api/client/variables/default_event_backoff_base_ms/)
* [DEFAULT\_EVENT\_BACKOFF\_CEILING\_MS](/api/client/variables/default_event_backoff_ceiling_ms/)
* [DEFAULT\_EVENT\_BATCH\_SIZE](/api/client/variables/default_event_batch_size/)
* [DEFAULT\_EVENT\_FLUSH\_INTERVAL\_MS](/api/client/variables/default_event_flush_interval_ms/)
* [ENGINE\_RELOCATED\_CODE](/api/client/variables/engine_relocated_code/)
* [identityCodec](/api/client/variables/identitycodec/)
* [OUTBOX\_SEQUENCE](/api/client/variables/outbox_sequence/)
* [OUTBOX\_TABLE](/api/client/variables/outbox_table/)
## Functions
[Section titled “Functions”](#functions)
* [assertLazyRefsActivated](/api/client/functions/assertlazyrefsactivated/)
* [attachSyncClient](/api/client/functions/attachsyncclient/)
* [buildLazyGuardIndex](/api/client/functions/buildlazyguardindex/)
* [buildRegistryReadHandles](/api/client/functions/buildregistryreadhandles/)
* [classifyEventBatchFailure](/api/client/functions/classifyeventbatchfailure/)
* [committedStoreUnreachableFromWire](/api/client/functions/committedstoreunreachablefromwire/)
* [computeEventBackoffMs](/api/client/functions/computeeventbackoffms/)
* [computeLiveDiff](/api/client/functions/computelivediff/)
* [createBrowserConvergenceTrigger](/api/client/functions/createbrowserconvergencetrigger/)
* [createClientPGlite](/api/client/functions/createclientpglite/)
* [createConvergenceDriver](/api/client/functions/createconvergencedriver/)
* [createEventFlushDriver](/api/client/functions/createeventflushdriver/)
* [createEventLaneRuntime](/api/client/functions/createeventlaneruntime/)
* [createIntervalConvergenceTrigger](/api/client/functions/createintervalconvergencetrigger/)
* [createLifecycleSlot](/api/client/functions/createlifecycleslot/)
* [createMutationsApi](/api/client/functions/createmutationsapi/)
* [createOpfsEffects](/api/client/functions/createopfseffects/)
* [createSyncClient](/api/client/functions/createsyncclient/)
* [createWorkerTokenCache](/api/client/functions/createworkertokencache/)
* [defineSyncWorker](/api/client/functions/definesyncworker/)
* [deriveBatchEventUrl](/api/client/functions/derivebatcheventurl/)
* [deriveStoreId](/api/client/functions/derivestoreid/)
* [destroyStoreArtifacts](/api/client/functions/destroystoreartifacts/)
* [encodeEnvelope](/api/client/functions/encodeenvelope/)
* [findReferencedLazyKeysInSql](/api/client/functions/findreferencedlazykeysinsql/)
* [findReferencedSyncedKeysInSql](/api/client/functions/findreferencedsyncedkeysinsql/)
* [generateLocalSchemaSql](/api/client/functions/generatelocalschemasql/)
* [getAllMutationsView](/api/client/functions/getallmutationsview/)
* [getJournalTable](/api/client/functions/getjournaltable/)
* [getLocalMetaTable](/api/client/functions/getlocalmetatable/)
* [getOutboxTable](/api/client/functions/getoutboxtable/)
* [getOverlayTable](/api/client/functions/getoverlaytable/)
* [getReadModelView](/api/client/functions/getreadmodelview/)
* [getSyncedLocalTable](/api/client/functions/getsyncedlocaltable/)
* [getSyncStateView](/api/client/functions/getsyncstateview/)
* [instrumentShapeFetch](/api/client/functions/instrumentshapefetch/)
* [isBridgeEnvelope](/api/client/functions/isbridgeenvelope/)
* [parseRetryAfterMs](/api/client/functions/parseretryafterms/)
* [performDatadirDump](/api/client/functions/performdatadirdump/)
* [performDataExport](/api/client/functions/performdataexport/)
* [performDiagnosticExport](/api/client/functions/performdiagnosticexport/)
* [performStoreExport](/api/client/functions/performstoreexport/)
* [postBridgeMessage](/api/client/functions/postbridgemessage/)
* [provisionSyncWorker](/api/client/functions/provisionsyncworker/)
* [quiesceStoreWorker](/api/client/functions/quiescestoreworker/)
* [replAdapter](/api/client/functions/repladapter/)
* [resolveBatchEventUrl](/api/client/functions/resolvebatcheventurl/)
* [resolveStoreBoot](/api/client/functions/resolvestoreboot/)
* [resolveStoreDataDir](/api/client/functions/resolvestoredatadir/)
* [rowKey](/api/client/functions/rowkey/)
* [runFreshCommitmentBarrier](/api/client/functions/runfreshcommitmentbarrier/)
* [runThrowawayCloneDump](/api/client/functions/runthrowawayclonedump/)
* [seedLiveDiffState](/api/client/functions/seedlivediffstate/)
* [setSyncDebugSink](/api/client/functions/setsyncdebugsink/)
* [storeIndexedDbDatabaseName](/api/client/functions/storeindexeddbdatabasename/)
* [storeTargetExists](/api/client/functions/storetargetexists/)
* [syncDebug](/api/client/functions/syncdebug/)
* [timeAsync](/api/client/functions/timeasync/)
* [wrapEngineWorker](/api/client/functions/wrapengineworker/)
* [wrapLiveQueryForMaterialization](/api/client/functions/wraplivequeryformaterialization/)
# AllMutationsView
> **AllMutationsView** = *typeof* `allMutationsShape`
Defined in: packages/client/src/local-tables.ts:609
The registry-wide `pgxsinkit_all_mutations` cross-journal view (slice 4) as a runtime Drizzle object. Its columns are the fixed journal columns (typed) plus the `table_key` registry-key literal — no per-table PK columns, no payload. Unlike the other view factories this is schema-INDEPENDENT: the view is always emitted `TEMP` (it may reference `pg_temp` ephemeral journals), so it resolves bare via `pg_temp`/search\_path regardless of the registry’s local schema — hence one memoized instance for every registry. The `client.mutations.*` API authors its summary/detail queries over THIS object (tier ①).
# AttachedSyncClient
> **AttachedSyncClient**<`TRegistry`> = [`SyncClient`](/api/client/interfaces/syncclient/)<`TRegistry`> & `object`
Defined in: packages/client/src/worker/attach-sync-client.ts:758
The worker-attached client: `SyncClient`’s shape, worker-proxied, plus `notifyAuthChanged` (re-push the token after an app auth-state change, ADR-0032 decision 3). One-shot Drizzle reads (`query`/`queryRow`/`queryRaw`/`queryRawRow`) and `ensureSynced` ARE proxied to the worker; the members that throw are the structurally unproxiable ones — `pglite`, `destroy`, `dropReadCache`, `isSynced`, and `drizzle.transaction()`.
## Type Declaration
[Section titled “Type Declaration”](#type-declaration)
### notifyAuthChanged
[Section titled “notifyAuthChanged”](#notifyauthchanged)
> **notifyAuthChanged**: () => `void`
#### Returns
[Section titled “Returns”](#returns)
`void`
### setOnline
[Section titled “setOnline”](#setonline)
> **setOnline**: (`online`) => `void`
Forward the app’s Offline toggle to the worker (ADR-0032 S3). The worker owns convergence, so the tab cannot gate a local trigger; this sends `set-online`, which suppresses/resumes the worker’s flush passes (resuming fires one immediate pass). The in-process client gates its own `autoSync` instead.
#### Parameters
[Section titled “Parameters”](#parameters)
##### online
[Section titled “online”](#online)
`boolean`
#### Returns
[Section titled “Returns”](#returns-1)
`void`
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
# BridgeEvent
> **BridgeEvent** = { `kind`: `"status"`; `status`: `SyncRuntimeStatus`; } | { `groupKey`: `string`; `kind`: `"groupReady"`; } | { `kind`: `"milestone"`; `stage`: `BootMilestone`; } | { `error`: `BridgeErrorWire`; `kind`: `"milestone-error"`; `stage`: `BootMilestone`; } | { `details`: `unknown`; `kind`: `"conflict"`; } | { `details`: `unknown`; `kind`: `"quarantine"`; } | { `details`: `unknown`; `kind`: `"reject"`; } | { `kind`: `"outbox-status"`; `status`: `unknown`; } | { `kind`: `"event-lane-report"`; `report`: `unknown`; } | { `event`: `unknown`; `kind`: `"schema-change"`; } | { `kind`: `"sync-error"`; `message`: `string`; } | { `data?`: `Record`<`string`, `unknown`>; `kind`: `"timing"`; `label`: `string`; `ms`: `number`; } | { `kind`: `"boot-report"`; `report`: [`BootReport`](/api/client/interfaces/bootreport/); } | { `data?`: `Record`<`string`, `unknown`>; `kind`: `"debug"`; `line`: `string`; `stamp`: `number`; }
Defined in: packages/client/src/worker/protocol.ts:463
worker → tab: the single broadcast event stream (ADR-0032 decision 7), re-exposed as today’s callbacks.
# BridgeMessageType
> **BridgeMessageType** = `"provision"` | `"attach"` | `"detach"` | `"token-push"` | `"token-response"` | `"rpc"` | `"subscribe"` | `"unsubscribe"` | `"wake"` | `"set-online"` | `"provision-ack"` | `"attach-ack"` | `"token-request"` | `"rpc-result"` | `"live-initial"` | `"live-diff"` | `"live-hydrated"` | `"event"`
Defined in: packages/client/src/worker/protocol.ts:63
# BridgeTransferable
> **BridgeTransferable** = `unknown`
Defined in: packages/client/src/worker/protocol.ts:25
A structured-clone transferable (an `ArrayBuffer`, a `MessagePort`, …). The library carries no DOM lib dependency (like the convergence triggers), so this is the toolkit’s own name for the type a future columnar codec would hand to `postMessage`’s transfer list. The wire-format-1 identity codec ([BRIDGE\_PROTOCOL\_VERSION](/api/client/variables/bridge_protocol_version/)) never produces one.
# ClientPGlite
> **ClientPGlite** = `PGliteWithLive` & `object`
Defined in: packages/client/src/index.ts:366
## Type Declaration
[Section titled “Type Declaration”](#type-declaration)
### electric
[Section titled “electric”](#electric)
> **electric**: `SyncEngine`\[`"namespace"`]
# DrainJournalOption
> **DrainJournalOption** = [`DrainJournalOptions`](/api/client/interfaces/drainjournaloptions/) | `false`
Defined in: packages/client/src/export-data.ts:46
`exportData`’s drain policy: the default `{ timeoutMs }` guard, or `false` — the explicit escape hatch that skips the drain and exports the SYNCED state as-is (unflushed local writes silently absent). A plain JSON shape, so it survives structured clone across the worker bridge unchanged.
# EngineRelocatedOutcome
> **EngineRelocatedOutcome** = `"not-dispatched"` | `"unknown"`
Defined in: packages/client/src/worker/engine-control.ts:327
The two honest relocation outcomes (ADR D10, invariant 5). There is deliberately NO blanket “retryable”:
* `"not-dispatched"` — the call NEVER reached the engine (a queued call failed on the handoff queue’s cap/deadline, or was never sent). SAFE TO RETRY.
* `"unknown"` — a DISPATCHED mutation whose response was lost to relocation. Its journal update MAY already exist and there is NO mutation-dedup key, so it must be inspected/reconciled, NEVER auto-retried.
A dispatched READ that lost its response is NOT a third outcome: repeating a read is safe, so that is the CALLER’S policy (repeat the read), not a distinct wire value.
# ExportReport
> **ExportReport** = [`StoreBackupReport`](/api/client/interfaces/storebackupreport/) | [`DiagnosticDumpReport`](/api/client/interfaces/diagnosticdumpreport/) | [`DataExportReport`](/api/client/interfaces/dataexportreport/)
Defined in: packages/client/src/export-store.ts:174
A structured, versioned record of one local-store export (ADR-0035) — a discriminated union on `kind`. Every member shares [ExportReportCommon](/api/client/interfaces/exportreportcommon/); the `kind`/`scope`/`phases` discriminant tells the three exports apart. `exportStore` resolves a [StoreBackupReport](/api/client/interfaces/storebackupreport/); `exportDiagnostics` a [DiagnosticDumpReport](/api/client/interfaces/diagnosticdumpreport/); `exportData` a [DataExportReport](/api/client/interfaces/dataexportreport/).
# GuardedQueryFn
> **GuardedQueryFn**<`TRegistry`, `TRows`> = (`client`) => [`DrizzleQueryBuilder`](/api/client/interfaces/drizzlequerybuilder/)<`TRows`>
Defined in: packages/client/src/index.ts:1251
The builder callback for a guarded read (ADR-0021): it receives the client and returns a Drizzle select builder. Reach relations through `c.views` / `c.drizzle` / a directly-imported synced table.
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
### TRows
[Section titled “TRows”](#trows)
`TRows` *extends* readonly `unknown`\[]
## Parameters
[Section titled “Parameters”](#parameters)
### client
[Section titled “client”](#client)
[`SyncClient`](/api/client/interfaces/syncclient/)<`TRegistry`>
## Returns
[Section titled “Returns”](#returns)
[`DrizzleQueryBuilder`](/api/client/interfaces/drizzlequerybuilder/)<`TRows`>
# JournalTable
> **JournalTable** = *typeof* `journalShape` & `object`
Defined in: packages/client/src/local-tables.ts:230
The `_mutations` journal: fixed runtime columns + the entry’s PK columns (index-signature access). Kept conservatively indexed: the journal carries ONLY the PK columns, keyed by DB COLUMN NAME and type-erased to `text`, but neither the PK column-name set nor the “PK only” subset is recoverable at the type level from `SyncTableEntry` (its `primaryKey.columns` is an un-narrowed `string[]`), so an honest per-entry type is not representable without a footgun (claiming non-PK columns that do not exist on the journal at runtime). PK/entity columns therefore ride the index signature.
# LocalMetaTable
> **LocalMetaTable** = *typeof* `localMetaShape`
Defined in: packages/client/src/local-tables.ts:238
The `pgxsinkit_local_meta` key/value table (ADR-0006).
# MutationBatchItem
> **MutationBatchItem**<`TRegistry`> = { \[TKey in SyncTableName\]: { input: SyncTableCreateInput\; kind: “create”; table: TKey } | { blind?: boolean; entityKey: Record\; kind: “update”; patch: SyncTableUpdateInput\; table: TKey } | { entityKey: Record\; kind: “delete”; table: TKey } }\[`SyncTableName`<`TRegistry`>]
Defined in: packages/client/src/mutation.ts:127
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
# MutationKind
> **MutationKind** = `"create"` | `"update"` | `"delete"`
Defined in: packages/client/src/mutation.ts:114
# MutationSummaryDetail
> **MutationSummaryDetail** = [`MutationDetail`](/api/client/interfaces/mutationdetail/)
Defined in: packages/client/src/mutations-api.ts:24
# OutboxTable
> **OutboxTable** = *typeof* `outboxShape`
Defined in: packages/client/src/local-tables.ts:240
The `pgxsinkit_outbox` Event-lane staging table (ADR-0053).
# OverlayTable
> **OverlayTable**<`TEntry`> = `PgTableWithColumns`<{ `columns`: `EntryColumns`<`TEntry`> & `OverlayFixedColumns`; `dialect`: `"pg"`; `name`: `string`; `schema`: `string` | `undefined`; }>
Defined in: packages/client/src/local-tables.ts:196
The `_overlay` optimistic-intent table: the entry’s PROJECTED columns (same builders the generator and `entry.localTable` carry) merged with the two fixed overlay columns. The entity columns read back through real drizzle result mapping, so their types mirror the projected table exactly (a `mode: "bigint"` column is `bigint`, etc.). The two overlay columns are `bigintText` passthrough (`local_updated_at_us` reads as a string). `TEntry` defaults to a bare entry, giving the open index-signature form the mutation runtime (generic over any registry) needs.
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TEntry
[Section titled “TEntry”](#tentry)
`TEntry` = `SyncTableEntry`
# ReadModelView
> **ReadModelView**<`TEntry`> = `PgViewWithSelection`<`string`, `true`, `EntryColumns`<`TEntry`> & `ReadModelFixedColumns`>
Defined in: packages/client/src/local-tables.ts:208
The `_read_model` overlay-merged read view (ADR-0004): the entry’s PROJECTED columns under their property keys plus the two fixed overlay columns (`overlay_kind`, `local_updated_at_us`). Read through a live query, so `local_updated_at_us` is an int8 `mode: "bigint"` column.
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TEntry
[Section titled “TEntry”](#tentry)
`TEntry` = `SyncTableEntry`
# ResolvedStorageBackend
> **ResolvedStorageBackend** = `"opfs-repacked"` | `"idbfs"` | `"filesystem"` | `"memory"`
Defined in: packages/client/src/store-boot.ts:50
The resolved storage backend a boot lands on. `opfs-repacked` (the elected/SW-direct opfs engine home), `idbfs` (browser, no sync-access handles), `filesystem` (Bun/Node), or `memory` (the sanctioned test/ephemeral lane). Surfaced on the BootReport as an additive field under the ADR-0034 reportVersion rule (additive fields keep `reportVersion: 1`); ADR-0049 named the backends.
# RpcOp
> **RpcOp** = `"create"` | `"update"` | `"delete"` | `"batch"` | `"transaction"` | `"flush"` | `"reconcile"` | `"retryFailed"` | `"recoverSending"` | `"discardConflict"` | `"discardQuarantined"` | `"desync"` | `"discardEphemeral"` | `"appendEvent"` | `"flushEvents"` | `"outboxStatus"` | `"ensureSynced"` | `"readMutationDetails"` | `"diagnostics"` | `"rawQuery"` | `"rawExec"` | `"guardedQuery"` | `"bootReport"` | `"liveQueryDiagnostics"` | `"exportStore"` | `"exportDiagnostics"` | `"exportData"`
Defined in: packages/client/src/worker/protocol.ts:279
The RPC ops the attach facade proxies to the worker’s booted client — the write API (mirrors `client.mutate`/flush), mutation-state reads, and the one-shot raw inspection reads (`rawQuery`/`rawExec`).
# SyncStateView
> **SyncStateView** = *typeof* `syncStateShape` & `object`
Defined in: packages/client/src/local-tables.ts:236
The `_sync_state` convergence view (ADR-0011): fixed state columns + the entry’s PK columns. Conservatively indexed for the same reason as [JournalTable](/api/client/type-aliases/journaltable/) — it projects only the PK columns (a subset not recoverable at the type level), so the PK columns ride the index signature.
# BRIDGE_CHANNEL
> `const` **BRIDGE\_CHANNEL**: `"pgxsinkit-bridge"`
Defined in: packages/client/src/worker/protocol.ts:16
# BRIDGE_PROTOCOL_VERSION
> `const` **BRIDGE\_PROTOCOL\_VERSION**: `1`
Defined in: packages/client/src/worker/protocol.ts:17
# COMMITTED_STORE_UNREACHABLE_CODE
> `const` **COMMITTED\_STORE\_UNREACHABLE\_CODE**: `"committed-store-unreachable"` = `"committed-store-unreachable"`
Defined in: packages/client/src/store-boot.ts:156
The `detail` tag [CommittedStoreUnreachableError](/api/client/classes/committedstoreunreachableerror/) travels under across the worker bridge.
# DEFAULT_DRAIN_TIMEOUT_MS
> `const` **DEFAULT\_DRAIN\_TIMEOUT\_MS**: `15000` = `15_000`
Defined in: packages/client/src/export-data.ts:31
The default drain budget: actively flush + await the convergence barrier for up to 15s before failing.
# DEFAULT_EVENT_BACKOFF_BASE_MS
> `const` **DEFAULT\_EVENT\_BACKOFF\_BASE\_MS**: `1000` = `1_000`
Defined in: packages/client/src/event-lane.ts:61
First retry delay of the jittered exponential backoff (per-row deferred AND batch-level).
# DEFAULT_EVENT_BACKOFF_CEILING_MS
> `const` **DEFAULT\_EVENT\_BACKOFF\_CEILING\_MS**: `300000` = `300_000`
Defined in: packages/client/src/event-lane.ts:67
The backoff ceiling (ADR-0053 decision 4: “backs off at the ceiling, observably”). Five minutes: long enough that a hard outage costs almost nothing, short enough that a recovered server drains promptly even if no append or online signal nudges the lane.
# DEFAULT_EVENT_BATCH_SIZE
> `const` **DEFAULT\_EVENT\_BATCH\_SIZE**: `200` = `200`
Defined in: packages/client/src/event-lane.ts:54
Max events assembled into one flush batch when the consumer sets nothing. Clamped to MAX\_EVENTS\_PER\_BATCH.
# DEFAULT_EVENT_FLUSH_INTERVAL_MS
> `const` **DEFAULT\_EVENT\_FLUSH\_INTERVAL\_MS**: `5000` = `5_000`
Defined in: packages/client/src/event-lane.ts:59
The event-lane flush driver’s fallback interval. Deliberately slower than the convergence driver’s: an append NUDGES a pass (and reconnect/boot run one), so the interval only has to catch retries and recovery.
# ENGINE_RELOCATED_CODE
> `const` **ENGINE\_RELOCATED\_CODE**: `"engine-relocated"` = `"engine-relocated"`
Defined in: packages/client/src/worker/engine-control.ts:330
The stable clone-safe discriminator carried on the wire (ADR D10). Consumers branch on this, never prose.
# identityCodec
> `const` **identityCodec**: [`BridgeCodec`](/api/client/interfaces/bridgecodec/)
Defined in: packages/client/src/worker/protocol.ts:522
The identity codec, wire-format version 1 ([BRIDGE\_PROTOCOL\_VERSION](/api/client/variables/bridge_protocol_version/)): the body IS the payload, and the transport’s structured clone does the real copying. No transferables. The sole codec at this wire version — swapping in a columnar codec later touches only this object, never the message types or the router.
# OUTBOX_SEQUENCE
> `const` **OUTBOX\_SEQUENCE**: `"pgxsinkit_outbox_seq"`
Defined in: packages/client/src/schema.ts:80
The sequence backing [OUTBOX\_TABLE](/api/client/variables/outbox_table/)’s `seq` append ordinal (the journal’s `mutation_seq` precedent).
# OUTBOX_TABLE
> `const` **OUTBOX\_TABLE**: `"pgxsinkit_outbox"` = `"pgxsinkit_outbox"`
Defined in: packages/client/src/schema.ts:77
The **Outbox** (ADR-0053 decision 2) — the local-only, append-only table where client events are staged until a flush is acknowledged. ONE table for every Event stream (a `stream` column, not a table per stream), library-owned, never in the sync registry and never replicated, and **stream-independent**: registering an Event stream changes nothing about this DDL, so it is emitted unconditionally beside the engine’s other internal relations rather than derived from `registry.streams`.
Its SHAPE IS PUBLIC CONTRACT, because apps compose pending rows with down-synced aggregates into best-guess views. The columns:
* `seq` — the durable, monotonically increasing local **append ordinal**, the ONE ordering key for Outbox selection and batch assembly (UUIDs do not order, `occurred_at_us` collides at these volumes, and SQL row order is undefined). Local machinery: it is never transmitted. Assigned from [OUTBOX\_SEQUENCE](/api/client/variables/outbox_sequence/) exactly as the mutation journal’s `mutation_seq` is.
* `event_id` — the library-stamped uuid carried on the wire; the server-side dedupe key that makes at-least-once delivery idempotent end-to-end. UNIQUE: a duplicate would be a library bug.
* `stream` — the registered Event-stream name.
* `occurred_at_us` — the library-stamped append time (microseconds), carried on the wire. Consumers needing temporal order re-sort on it (ADR-0053 decision 6).
* `payload` — the event body as `jsonb`, validated against the Event stream’s zod schema AT APPEND, so “everything in the Outbox is well-formed” holds for the flush loop and for best-guess views. It is EXACTLY the value the caller passed to `appendEvent` — never the schema’s `parsed.data`. The schema is used at append only to VALIDATE (its output is discarded); the one AUTHORITATIVE parse is at ingest, and that output’s JSON-NORMALIZED form is what the consumer receives (a `Date` becomes its ISO string; a nested `undefined` property is dropped). The schema therefore executes at both boundaries — so transforms must be pure and deterministic — while a best-guess view here reads back precisely what the app appended.
* `enqueued_at_us` — when the row was durably enqueued locally (never transmitted; observability).
* `attempt_count` / `next_retry_at_us` — the per-row DEFERRED backoff (ADR-0053 decision 3): an Event stream the server does not yet know parks its rows here rather than deleting them. A row whose `next_retry_at_us` is in the future is skipped by batch assembly. NULL = eligible now.
* `last_reason` — the most recent server `deferred` reason, kept on the row it belongs to (never a second, retention-bearing verdict table — ADR-0053 rejects that).
# StorageDeclarationRefusedError
Defined in: packages/contracts/src/config.ts:172
A storage declaration was refused (ADR-0050): two sources explicitly disagree on a field, or a later declaration explicitly contradicts the store’s bound declaration. Never resolved silently — the store’s storage contract has exactly one value per field, and a disagreement means one side is wrong. The stable `name` survives bridge serialization (`BridgeErrorWire.name`), so a tab can detect the refusal typed.
## Extends
[Section titled “Extends”](#extends)
* `Error`
## Constructors
[Section titled “Constructors”](#constructors)
### Constructor
[Section titled “Constructor”](#constructor)
> **new StorageDeclarationRefusedError**(`message`): `StorageDeclarationRefusedError`
Defined in: packages/contracts/src/config.ts:173
#### Parameters
[Section titled “Parameters”](#parameters)
##### message
[Section titled “message”](#message)
`string`
#### Returns
[Section titled “Returns”](#returns)
`StorageDeclarationRefusedError`
#### Overrides
[Section titled “Overrides”](#overrides)
`Error.constructor`
## Properties
[Section titled “Properties”](#properties)
### cause?
[Section titled “cause?”](#cause)
> `optional` **cause?**: `unknown`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:24
The cause of the error.
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
`Error.cause`
***
### message
[Section titled “message”](#message-1)
> **message**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1075
#### Inherited from
[Section titled “Inherited from”](#inherited-from-1)
`Error.message`
***
### name
[Section titled “name”](#name)
> **name**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1074
#### Inherited from
[Section titled “Inherited from”](#inherited-from-2)
`Error.name`
***
### stack?
[Section titled “stack?”](#stack)
> `optional` **stack?**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1076
#### Inherited from
[Section titled “Inherited from”](#inherited-from-3)
`Error.stack`
***
### stackTraceLimit
[Section titled “stackTraceLimit”](#stacktracelimit)
> `static` **stackTraceLimit**: `number`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:67
The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`).
The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed.
If set to a non-number value, or set to a negative number, stack traces will not capture any frames.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-4)
`Error.stackTraceLimit`
## Methods
[Section titled “Methods”](#methods)
### captureStackTrace()
[Section titled “captureStackTrace()”](#capturestacktrace)
#### Call Signature
[Section titled “Call Signature”](#call-signature)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:51
Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called.
```js
const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack; // Similar to `new Error().stack`
```
The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`.
The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace.
The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance:
```js
function a() {
b();
}
function b() {
c();
}
function c() {
// Create an error without stack trace to avoid calculating the stack trace twice.
const { stackTraceLimit } = Error;
Error.stackTraceLimit = 0;
const error = new Error();
Error.stackTraceLimit = stackTraceLimit;
// Capture the stack trace above function b
Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
throw error;
}
a();
```
##### Parameters
[Section titled “Parameters”](#parameters-1)
###### targetObject
[Section titled “targetObject”](#targetobject)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt)
`Function`
##### Returns
[Section titled “Returns”](#returns-1)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-5)
`Error.captureStackTrace`
#### Call Signature
[Section titled “Call Signature”](#call-signature-1)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1042
Create .stack property on a target object
##### Parameters
[Section titled “Parameters”](#parameters-2)
###### targetObject
[Section titled “targetObject”](#targetobject-1)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt-1)
`Function`
##### Returns
[Section titled “Returns”](#returns-2)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-6)
`Error.captureStackTrace`
***
### isError()
[Section titled “isError()”](#iserror)
> `static` **isError**(`value`): `value is Error`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1037
Check if a value is an instance of Error
#### Parameters
[Section titled “Parameters”](#parameters-3)
##### value
[Section titled “value”](#value)
`unknown`
The value to check
#### Returns
[Section titled “Returns”](#returns-3)
`value is Error`
True if the value is an instance of Error, false otherwise
#### Inherited from
[Section titled “Inherited from”](#inherited-from-7)
`Error.isError`
***
### prepareStackTrace()
[Section titled “prepareStackTrace()”](#preparestacktrace)
> `static` **prepareStackTrace**(`err`, `stackTraces`): `any`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:55
#### Parameters
[Section titled “Parameters”](#parameters-4)
##### err
[Section titled “err”](#err)
`Error`
##### stackTraces
[Section titled “stackTraces”](#stacktraces)
`CallSite`\[]
#### Returns
[Section titled “Returns”](#returns-4)
`any`
#### See
[Section titled “See”](#see)
#### Inherited from
[Section titled “Inherited from”](#inherited-from-8)
`Error.prepareStackTrace`
# asEphemeral
> **asEphemeral**<`TEntry`>(`entry`): `TEntry`
Defined in: packages/contracts/src/projection.ts:114
The named `ephemeral` lifecycle projection — `withRetention(entry, "ephemeral")` — the lifecycle twin of [asReadonly](/api/contracts/functions/asreadonly/), for the common direction (a client wants no durable trace of a table the authoritative registry keeps `persistent`). Composes with `asReadonly`: `asEphemeral(asReadonly(authoritative.exam))` is a read-only, no-durable-trace projection. The reverse direction (an ephemeral authoritative table a client wants durable) is the bidirectional [withRetention](/api/contracts/functions/withretention/) with `"persistent"`. See [withRetention](/api/contracts/functions/withretention/) for the constraints that carry over (group uniformity; no durable offline queue).
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TEntry
[Section titled “TEntry”](#tentry)
`TEntry` *extends* [`SyncTableEntry`](/api/contracts/interfaces/synctableentry/)<`AnyPgTable`, `AnyPgTable`>
## Parameters
[Section titled “Parameters”](#parameters)
### entry
[Section titled “entry”](#entry)
`TEntry`
## Returns
[Section titled “Returns”](#returns)
`TEntry`
# asReadonly
> **asReadonly**<`TTable`, `TLocalTable`>(`entry`): [`SyncTableEntry`](/api/contracts/interfaces/synctableentry/)<`TTable`, `TLocalTable`>
Defined in: packages/contracts/src/projection.ts:36
Per-client mode projection (ADR-0025). The authoritative (server) registry defines a table once with its full write contract; a client that must only *read* that table consumes the same entry through `asReadonly`. The read/identity contract — table, columns, primary key, synced-table name, column omission, the shape/row filter, the row classification (`rowClass`), AND the column-builder factory (`makeColumns`) — is preserved verbatim; the write-capability metadata is dropped:
* `mode` flips to `readonly`;
* the overlay-merged read-model `view` and the overlay/journal client projection (the local write machinery `@pgxsinkit/client` provisions for a writable table) are removed — a readonly client reads the synced base table directly;
* `conflictPolicy`, `governance` (managed fields), and `writeMode` are removed — a readonly table has no write path, and `defineSyncRegistry` would otherwise still treat them as a writable declaration.
The result is the same entry `defineSyncTable` would have produced for this table with `mode: "readonly"`, so `defineSyncRegistry` accepts it without the writable-table requirements (server-version field + `conflictPolicy`).
Lifecycle axes (`consistencyGroup`, `subscription`, `retention`) are preserved — a projection may keep the authoritative grouping/timing/durability; change them on the projected entry if a client needs to.
`makeColumns` (the column-builder factory `defineSyncTable` stashes) is carried too: since ADR-0029 P1 the client derives EVERY synced-table object from it (`getSyncedLocalTable` → `projectedColumnBuilders`), so a readonly projection that dropped it could not build its own local synced read cache — the member-boot failure this keep-list’s NOTE below predicted. It is read-derivation machinery, not a write handle.
NOTE: if a new *read-relevant* field is added to [SyncTableEntry](/api/contracts/interfaces/synctableentry/), carry it here too (this builds the readonly entry by listing what to keep, so a new field is otherwise silently dropped).
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TTable
[Section titled “TTable”](#ttable)
`TTable` *extends* `AnyPgTable`
### TLocalTable
[Section titled “TLocalTable”](#tlocaltable)
`TLocalTable` *extends* `AnyPgTable`
## Parameters
[Section titled “Parameters”](#parameters)
### entry
[Section titled “entry”](#entry)
[`SyncTableEntry`](/api/contracts/interfaces/synctableentry/)<`TTable`, `TLocalTable`>
## Returns
[Section titled “Returns”](#returns)
[`SyncTableEntry`](/api/contracts/interfaces/synctableentry/)<`TTable`, `TLocalTable`>
# assertReadContractPreserved
> **assertReadContractPreserved**(`authoritative`, `projection`, `options?`): `void`
Defined in: packages/contracts/src/projection.ts:132
Assert that a per-client `projection` registry preserves the **read contract** (ADR-0025) of the `authoritative` registry it projects from. For every table the projection declares, its [fingerprintReadContract](/api/contracts/functions/fingerprintreadcontract/) must equal the authoritative entry’s: a projection may differ only in write capability and lifecycle orchestration, never in the data it syncs (columns, primary key, row-filter shape). A table present in the authoritative registry but absent from the projection is a permitted subset; a table in the projection with no authoritative source is an error (no contract to project from).
Throws, naming the divergent tables, on any mismatch. Call it where the client registries are assembled (module-eval or a test) so a drifted projection fails closed instead of silently serving different rows to different clients. The `customWhere` body is invisible to the fingerprint — bump [RowFilterSpec.revision](/api/contracts/interfaces/rowfilterspec/#revision) so a logic-only divergence is caught.
## Parameters
[Section titled “Parameters”](#parameters)
### authoritative
[Section titled “authoritative”](#authoritative)
[`SyncTableRegistry`](/api/contracts/type-aliases/synctableregistry/)
### projection
[Section titled “projection”](#projection)
[`SyncTableRegistry`](/api/contracts/type-aliases/synctableregistry/)
### options?
[Section titled “options?”](#options)
#### label?
[Section titled “label?”](#label)
`string`
## Returns
[Section titled “Returns”](#returns)
`void`
# assertRegistryInvariant
> **assertRegistryInvariant**(`registry`, `spec`): `void`
Defined in: packages/contracts/src/registry-invariant.ts:161
Assert a [RegistryInvariantSpec](/api/contracts/interfaces/registryinvariantspec/) over a registry: for every entry the invariant binds, and every claims fixture, evaluate `holds` against the entry’s RENDERED read filter and write policies. Pure audit — it renders through the production code paths but changes no runtime behaviour.
Call it at module eval beside the registry (or in a test), the same way [assertReadContractPreserved](/api/contracts/functions/assertreadcontractpreserved/) is called, so a violation fails closed rather than shipping.
```ts
assertRegistryInvariant(registry, {
name: "private rows are never visible to an anonymous caller",
appliesTo: ["private"],
claimsFixtures: { anonymous: {}, member: { sub: "u-1" } },
holds: ({ fixtureName, renderedWhere }) =>
fixtureName !== "anonymous" || renderedWhere?.where === "false" || "anonymous read is not denied",
});
```
Two deliberate fail-closed behaviours:
* An `appliesTo` class the registry’s declared vocabulary does not contain throws immediately (a typo must not pass as “nothing to check”).
* An invariant that binds ZERO entries throws. A spec that checks nothing is a bug in the spec — nearly always a wrong class name or an invariant left behind after its class was renamed — and silently passing is the exact failure mode this whole mechanism exists to remove.
Every failing cell is collected and reported together: the header names the invariant, then one `entry (fixture): reason` line per violation. Never first-failure-only — you fix a classification-wide problem in one pass, not one re-run per entry.
## Parameters
[Section titled “Parameters”](#parameters)
### registry
[Section titled “registry”](#registry)
[`SyncTableRegistry`](/api/contracts/type-aliases/synctableregistry/)
### spec
[Section titled “spec”](#spec)
[`RegistryInvariantSpec`](/api/contracts/interfaces/registryinvariantspec/)
## Returns
[Section titled “Returns”](#returns)
`void`
# assertStorageDeclarationCompatible
> **assertStorageDeclarationCompatible**(`bound`, `incoming`): `void`
Defined in: packages/contracts/src/config.ts:224
Check a LATER declaration against a store’s bound resolution (ADR-0050): an unset or equal field is idempotent; an explicit field disagreeing with the bound value is a [StorageDeclarationRefusedError](/api/contracts/classes/storagedeclarationrefusederror/). The bound declaration is immutable — first arrival binds, later arrivals only confirm.
## Parameters
[Section titled “Parameters”](#parameters)
### bound
[Section titled “bound”](#bound)
[`ResolvedStorageDeclaration`](/api/contracts/interfaces/resolvedstoragedeclaration/)
### incoming
[Section titled “incoming”](#incoming)
[`SyncStorageDeclaration`](/api/contracts/interfaces/syncstoragedeclaration/) | `undefined`
## Returns
[Section titled “Returns”](#returns)
`void`
# attachSyncRegistryRowClasses
> **attachSyncRegistryRowClasses**<`TRegistry`>(`registry`, `rowClasses`): `TRegistry`
Defined in: packages/contracts/src/registry.ts:1447
Stamp the registry’s declared row-class vocabulary (ADR-0052) onto the registry value as a non-enumerable symbol — the classification twin of [attachSyncRegistryStorage](/api/contracts/functions/attachsyncregistrystorage/). An absent/empty declaration attaches nothing (the bare-registry-map overload, and any definition that declares no vocabulary), so [getSyncRegistryRowClasses](/api/contracts/functions/getsyncregistryrowclasses/) reads back `undefined` and classification stays unconstrained.
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* [`SyncTableRegistry`](/api/contracts/type-aliases/synctableregistry/)
## Parameters
[Section titled “Parameters”](#parameters)
### registry
[Section titled “registry”](#registry)
`TRegistry`
### rowClasses
[Section titled “rowClasses”](#rowclasses)
readonly `string`\[] | `undefined`
## Returns
[Section titled “Returns”](#returns)
`TRegistry`
# attachSyncRegistrySchema
> **attachSyncRegistrySchema**<`TRegistry`>(`registry`, `schema?`): `TRegistry`
Defined in: packages/contracts/src/registry.ts:1344
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* [`SyncTableRegistry`](/api/contracts/type-aliases/synctableregistry/)
## Parameters
[Section titled “Parameters”](#parameters)
### registry
[Section titled “registry”](#registry)
`TRegistry`
### schema?
[Section titled “schema?”](#schema)
`string`
## Returns
[Section titled “Returns”](#returns)
`TRegistry`
# attachSyncRegistryStorage
> **attachSyncRegistryStorage**<`TRegistry`>(`registry`, `storage`): `TRegistry`
Defined in: packages/contracts/src/registry.ts:1398
Stamp the registry’s storage declaration (ADR-0049 decision 1, ADR-0047) onto the registry value as a non-enumerable symbol — the storage twin of [attachSyncRegistrySchema](/api/contracts/functions/attachsyncregistryschema/). Carries the data-contract storage through [defineSyncRegistry](/api/contracts/functions/definesyncregistry/) so client code reads it back via [getSyncRegistryStorage](/api/contracts/functions/getsyncregistrystorage/). A `null`/`undefined` declaration attaches nothing (the bare-registry-map overload), leaving the reader to resolve the ADR-0047 defaults at the mint seam.
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* [`SyncTableRegistry`](/api/contracts/type-aliases/synctableregistry/)
## Parameters
[Section titled “Parameters”](#parameters)
### registry
[Section titled “registry”](#registry)
`TRegistry`
### storage
[Section titled “storage”](#storage)
[`SyncStorageDeclaration`](/api/contracts/interfaces/syncstoragedeclaration/) | `undefined`
## Returns
[Section titled “Returns”](#returns)
`TRegistry`
# attachSyncRegistryStreams
> **attachSyncRegistryStreams**<`TRegistry`>(`registry`, `streams`): `TRegistry`
Defined in: packages/contracts/src/registry.ts:1498
Stamp the registry’s registered Event streams (ADR-0053 decision 1) onto the registry value as a non-enumerable symbol — the Event-lane twin of [attachSyncRegistryRowClasses](/api/contracts/functions/attachsyncregistryrowclasses/). An absent declaration attaches nothing (the bare-registry-map overload, and any definition that registers no Event stream), so [getSyncRegistryStreams](/api/contracts/functions/getsyncregistrystreams/) reads back `undefined` and the registry simply has no Event lane.
Non-enumerable is load-bearing beyond tidiness: `canonicalizeRegistry` walks the registry’s own enumerable keys, so riding a symbol is what keeps Event streams OUT of the persisted fingerprint.
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* [`SyncTableRegistry`](/api/contracts/type-aliases/synctableregistry/)
## Parameters
[Section titled “Parameters”](#parameters)
### registry
[Section titled “registry”](#registry)
`TRegistry`
### streams
[Section titled “streams”](#streams)
[`EventStreamRegistry`](/api/contracts/type-aliases/eventstreamregistry/) | `undefined`
## Returns
[Section titled “Returns”](#returns)
`TRegistry`
# buildGrantScopeAccessShapeWhere
> **buildGrantScopeAccessShapeWhere**(`scopeColumn`, `claims`, `options`): `SQL`<`unknown`> | `null`
Defined in: packages/contracts/src/supabase-rls.ts:900
The Electric shape `where` for a grant-scope table, bypass included — the read counterpart of the `select` policy in [buildSupabaseGrantScopeNativePolicies](/api/contracts/functions/buildsupabasegrantscopenativepolicies/), from the same declaration. A caller holding a bypass grant gets `null` (no filter, every row — the policy’s OR branch); anyone else gets [buildGrantScopeShapeWhere](/api/contracts/functions/buildgrantscopeshapewhere/) over their resolved ids, i.e. the [DENY\_ALL](/api/contracts/variables/deny_all/) sentinel *by reference* when the set is empty (which is what lets a `customWhere` built on this probe as claims-dependent). Use [buildGrantScopeShapeWhere](/api/contracts/functions/buildgrantscopeshapewhere/) directly only when there is no bypass to mirror.
## Parameters
[Section titled “Parameters”](#parameters)
### scopeColumn
[Section titled “scopeColumn”](#scopecolumn)
`AnyColumn`
### claims
[Section titled “claims”](#claims)
{\[`key`: `string`]: `unknown`; `app_metadata?`: {\[`key`: `string`]: `unknown`; `roles?`: `string`\[]; }; `sub?`: `string`; } | `null`
### options
[Section titled “options”](#options)
[`GrantScopeAccessOptions`](/api/contracts/type-aliases/grantscopeaccessoptions/)
## Returns
[Section titled “Returns”](#returns)
`SQL`<`unknown`> | `null`
# buildGrantScopeShapeWhere
> **buildGrantScopeShapeWhere**(`scopeColumn`, `ids`): `SQL`
Defined in: packages/contracts/src/supabase-rls.ts:813
The Electric shape `where` for a grant-scope table: an `IN (…)` over the resolved ids (what the proxy injects). Takes the real Drizzle scope column — referenced bare via `c()` (Electric’s grammar requires bare columns), so the reference is rename-safe — and returns a typed fragment whose ids are **bound params** once `buildRowFilterShape` serializes it (never hand-escaped literals). An empty id set denies all rows ([DENY\_ALL](/api/contracts/variables/deny_all/)), mirroring the policy returning no rows.
## Parameters
[Section titled “Parameters”](#parameters)
### scopeColumn
[Section titled “scopeColumn”](#scopecolumn)
`AnyColumn`
### ids
[Section titled “ids”](#ids)
`string`\[]
## Returns
[Section titled “Returns”](#returns)
`SQL`
# buildMembershipShapeWhere
> **buildMembershipShapeWhere**(`columns`, `claims`): `SQL`
Defined in: packages/contracts/src/supabase-rls.ts:564
The Electric shape `where` for a membership-scoped table — the read counterpart of the `select` policy in [buildSupabaseMembershipNativePolicies](/api/contracts/functions/buildsupabasemembershipnativepolicies/): the row’s container must be one the caller is a member of. Columns are referenced **bare** via [c](/api/contracts/functions/c/) (Electric’s grammar requires plain column refs) yet still through the real Drizzle column objects, so the reference is rename-safe; the subject rides as a typed interpolation and becomes a bound param once `buildRowFilterShape` serializes it (never a hand-escaped literal). The subquery is **self-contained** (uncorrelated) — it gets its own `FROM`, so the membership table’s bare column names resolve to it.
No subject → [DENY\_ALL](/api/contracts/variables/deny_all/) (by reference, so a `customWhere` built on it probes claims-dependent), mirroring the policy, whose membership subquery matches nothing without a JWT sub.
## Parameters
[Section titled “Parameters”](#parameters)
### columns
[Section titled “columns”](#columns)
[`SupabaseMembershipShapeColumns`](/api/contracts/type-aliases/supabasemembershipshapecolumns/)
### claims
[Section titled “claims”](#claims)
{\[`key`: `string`]: `unknown`; `app_metadata?`: {\[`key`: `string`]: `unknown`; `roles?`: `string`\[]; }; `sub?`: `string`; } | `null`
## Returns
[Section titled “Returns”](#returns)
`SQL`
# buildOverlayResolutionBarrier
> **buildOverlayResolutionBarrier**<`TTable`>(`entry`, `options`): `string`
Defined in: packages/contracts/src/convergence-model.ts:27
The Convergence barrier predicate (ADR-0010): an acked create/update is resolved only once the synced echo’s Server version has reached the write’s acked version. Emitted identically by the reconcile trigger (schema.ts), `reconcileTable` (mutation.ts), **and** the per-table sync-state view (decision 4) — one rule, three consumers, no drift (ADR-0004).
`syncedAlias` is the synced-side reference (a table alias, or `NEW` inside the trigger); `journalAlias` qualifies the journal’s `server_updated_at_us` (omit it where the journal is the unaliased target of the `DELETE`).
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TTable
[Section titled “TTable”](#ttable)
`TTable` *extends* `AnyPgTable`
## Parameters
[Section titled “Parameters”](#parameters)
### entry
[Section titled “entry”](#entry)
[`SyncTableEntry`](/api/contracts/interfaces/synctableentry/)<`TTable`>
### options
[Section titled “options”](#options)
#### journalAlias?
[Section titled “journalAlias?”](#journalalias)
`string`
#### syncedAlias
[Section titled “syncedAlias”](#syncedalias)
`string`
## Returns
[Section titled “Returns”](#returns)
`string`
# buildOwnerOrAdminShapeWhere
> **buildOwnerOrAdminShapeWhere**(`ownerColumn`, `claims`, `options?`): `SQL`<`unknown`> | `null`
Defined in: packages/contracts/src/supabase-rls.ts:336
The Electric shape `where` for an owner-or-admin table — the read counterpart of [buildSupabaseOwnerOrAdminNativePolicies](/api/contracts/functions/buildsupabaseowneroradminnativepolicies/), built from the same owner column. An admin gets `null` (no filter, every row — the policy’s bypass branch); anyone else gets [buildOwnershipShapeWhere](/api/contracts/functions/buildownershipshapewhere/), i.e. their own rows, or the [DENY\_ALL](/api/contracts/variables/deny_all/) sentinel when there is no subject. Returning the sentinel *by reference* is what lets a `customWhere` built on this probe as claims-dependent (`isClaimsDependentRowFilter`).
## Parameters
[Section titled “Parameters”](#parameters)
### ownerColumn
[Section titled “ownerColumn”](#ownercolumn)
`AnyColumn`
### claims
[Section titled “claims”](#claims)
{\[`key`: `string`]: `unknown`; `app_metadata?`: {\[`key`: `string`]: `unknown`; `roles?`: `string`\[]; }; `sub?`: `string`; } | `null`
### options?
[Section titled “options?”](#options)
[`OwnerOrAdminAccessOptions`](/api/contracts/type-aliases/owneroradminaccessoptions/) = `{}`
## Returns
[Section titled “Returns”](#returns)
`SQL`<`unknown`> | `null`
# buildOwnershipShapeWhere
> **buildOwnershipShapeWhere**(`ownerColumn`, `subject`): `SQL`
Defined in: packages/contracts/src/config.ts:514
The ownership shape `where` — the read-path mirror of an owner-column RLS policy: rows whose owner column equals the caller’s subject, [DENY\_ALL](/api/contracts/variables/deny_all/) for an unauthenticated caller. Takes the real Drizzle owner column (bare via [c](/api/contracts/functions/c/), rename-safe); the subject rides as a typed interpolation — a bound param through `buildRowFilterShape`, or a drizzle-escaped literal when a proxy renders it inline for a shape URL.
## Parameters
[Section titled “Parameters”](#parameters)
### ownerColumn
[Section titled “ownerColumn”](#ownercolumn)
`AnyColumn`
### subject
[Section titled “subject”](#subject)
`string` | `null` | `undefined`
## Returns
[Section titled “Returns”](#returns)
`SQL`
# buildRegistryLock
> **buildRegistryLock**(`registry`): [`RegistryLock`](/api/contracts/interfaces/registrylock/)
Defined in: packages/contracts/src/registry-diff.ts:166
Build the committed lock (fingerprint + canonical shape + row classification + Event streams) for a registry.
## Parameters
[Section titled “Parameters”](#parameters)
### registry
[Section titled “registry”](#registry)
[`SyncTableRegistry`](/api/contracts/type-aliases/synctableregistry/)
## Returns
[Section titled “Returns”](#returns)
[`RegistryLock`](/api/contracts/interfaces/registrylock/)
# buildRoleGuardedStatement
> **buildRoleGuardedStatement**(`roleName`, `statementSql`): `string`
Defined in: packages/contracts/src/sql-role-guard.ts:23
Wrap one already-rendered SQL statement (no trailing semicolon) in the role-existence guard.
`statementSql` is embedded as a PL/pgSQL string literal, so it is escaped as a whole — callers pass ordinary SQL with real single quotes and real double-quoted identifiers.
## Parameters
[Section titled “Parameters”](#parameters)
### roleName
[Section titled “roleName”](#rolename)
`string`
### statementSql
[Section titled “statementSql”](#statementsql)
`string`
## Returns
[Section titled “Returns”](#returns)
`string`
# buildRowFilterShape
> **buildRowFilterShape**(`filter`, `claims`, `params?`): [`RowFilterShape`](/api/contracts/interfaces/rowfiltershape/) | `null`
Defined in: packages/contracts/src/config.ts:530
The shape filter the proxy sends to Electric: the `where` plus its positional `params` (`$1`, `$2`, …). A `customWhere` returning a Drizzle `SQL` fragment is serialized here, so request-derived values become **bound params** — never hand-escaped literals; a string `customWhere` is the raw escape hatch (no params). Returns `null` when there is no filter (all rows visible).
## Parameters
[Section titled “Parameters”](#parameters)
### filter
[Section titled “filter”](#filter)
[`RowFilterSpec`](/api/contracts/interfaces/rowfilterspec/)
### claims
[Section titled “claims”](#claims)
{\[`key`: `string`]: `unknown`; `app_metadata?`: {\[`key`: `string`]: `unknown`; `roles?`: `string`\[]; }; `sub?`: `string`; } | `null`
### params?
[Section titled “params?”](#params)
`Record`<`string`, `unknown`>
## Returns
[Section titled “Returns”](#returns)
[`RowFilterShape`](/api/contracts/interfaces/rowfiltershape/) | `null`
# buildSupabaseGrantScopeNativePolicies
> **buildSupabaseGrantScopeNativePolicies**(`options`): `PgPolicy`\[]
Defined in: packages/contracts/src/supabase-rls.ts:739
Native Drizzle RLS policies for a JWT-resident grant set (see the section comment). Pass the real scope column; the governed table name is derived from it. By default the predicate is the InitPlan-correct uncorrelated `IN (subquery)`; pass `naive: true` for the correlated cliff variant.
## Parameters
[Section titled “Parameters”](#parameters)
### options
[Section titled “options”](#options)
[`SupabaseGrantScopeNativePoliciesOptions`](/api/contracts/type-aliases/supabasegrantscopenativepoliciesoptions/)
## Returns
[Section titled “Returns”](#returns)
`PgPolicy`\[]
# buildSupabaseMembershipNativePolicies
> **buildSupabaseMembershipNativePolicies**(`options`): `PgPolicy`\[]
Defined in: packages/contracts/src/supabase-rls.ts:476
## Parameters
[Section titled “Parameters”](#parameters)
### options
[Section titled “options”](#options)
[`SupabaseMembershipNativePoliciesOptions`](/api/contracts/type-aliases/supabasemembershipnativepoliciesoptions/)
## Returns
[Section titled “Returns”](#returns)
`PgPolicy`\[]
# buildSupabaseOwnerOrAdminNativePolicies
> **buildSupabaseOwnerOrAdminNativePolicies**(`options`): `PgPolicy`\[]
Defined in: packages/contracts/src/supabase-rls.ts:248
## Parameters
[Section titled “Parameters”](#parameters)
### options
[Section titled “options”](#options)
[`SupabaseOwnerOrAdminNativePoliciesOptions`](/api/contracts/type-aliases/supabaseowneroradminnativepoliciesoptions/)
## Returns
[Section titled “Returns”](#returns)
`PgPolicy`\[]
# buildSupabaseOwnerOrAdminPredicateSqlText
> **buildSupabaseOwnerOrAdminPredicateSqlText**(`options?`): `string`
Defined in: packages/contracts/src/supabase-rls.ts:236
The owner-or-admin predicate as raw SQL **text** — the escape hatch for when you need the predicate as a string (a hand-written trigger, a manual migration). For attaching RLS to a Drizzle table, prefer `buildSupabaseOwnerOrAdminNativePolicies`, which takes the real column and is rename-tracked.
## Parameters
[Section titled “Parameters”](#parameters)
### options?
[Section titled “options?”](#options)
[`SupabaseOwnerOrAdminPredicateOptions`](/api/contracts/type-aliases/supabaseowneroradminpredicateoptions/) = `{}`
## Returns
[Section titled “Returns”](#returns)
`string`
# buildSyncStateView
> **buildSyncStateView**<`TTable`>(`entry`, `projection`): `string`
Defined in: packages/contracts/src/convergence-model.ts:88
Generate the per-writable-table `_sync_state` **view** (ADR-0011 decision 2): a derived projection — never a stored copy — over synced + overlay + journal that answers “what is this entity’s convergence state?” with one authoritative, queryable row per entity that has local activity (an overlay or journal row). Keyed on the **real PK columns**, never a generic `entity_key_json` (decision 2).
Columns:
* `observed_server_version` — the synced row’s Server version (NULL when no echo has landed).
* `acked_server_version` — the highest Server version the server assigned to our acked writes.
* `pending_count` — journal rows still owed to the server (pending/sending/failed).
* `has_acked_unobserved_write` — an acked create/update whose echo has not yet caught up. Derived from the **same** [buildOverlayResolutionBarrier](/api/contracts/functions/buildoverlayresolutionbarrier/) predicate the resolver uses (decision 4), so what the UI shows can never drift from what the resolver does.
* `local_delete_pending` — an optimistic delete is staged in the overlay.
* `conflict_state` — the reason a `conflicted` (stale, reject-if-stale) write was declined, or NULL when the entity has no conflicted mutation (ADR-0015). Surfaced from the journal’s `conflict_reason`, scoped to `status = 'conflicted'` so a stale failure reason never leaks in.
* `quarantined_count` — terminal local writes the server permanently rejected (ADR-0006): a poison mutation named in a batch rejection, or one that exhausted the attempt cap. Quarantine blocks sync and *keeps* the optimistic overlay, so without this the view would show an entity with `pending_count = 0` and `conflict_state = NULL` yet still carrying un-converged local intent.
* `quarantine_state` — the `last_error` of a quarantined write (the rejection reason), or NULL when the entity has none. The reason counterpart to `quarantined_count`, mirroring `conflict_state`.
Note `pending_count` is the *retryable* owed set (pending/sending/failed); a quarantined write is terminal, counted separately so a blocked write is never mistaken for one still in flight.
The Read model stays lean (it already carries `overlay_kind`); an app that wants per-row convergence status joins this view on the PK.
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TTable
[Section titled “TTable”](#ttable)
`TTable` *extends* `AnyPgTable`
## Parameters
[Section titled “Parameters”](#parameters)
### entry
[Section titled “entry”](#entry)
[`SyncTableEntry`](/api/contracts/interfaces/synctableentry/)<`TTable`>
### projection
[Section titled “projection”](#projection)
[`SyncStateViewProjection`](/api/contracts/interfaces/syncstateviewprojection/)
## Returns
[Section titled “Returns”](#returns)
`string`
# c
> **c**(`column`): `SQL`
Defined in: packages/contracts/src/config.ts:467
A **bare** (table-unqualified) quoted identifier for a Drizzle column — `"workspace_id"`, never `"work_items"."workspace_id"`. Electric’s shape `where` grammar requires *plain* column references (it rejects a qualified one with “Expected a plain column reference”), and Drizzle qualifies columns by default — so reference columns through `c()` when authoring a `customWhere` Drizzle fragment. The column object keeps the reference rename-safe and existence-checked at compile time; only the bare name reaches the wire. Subqueries must stay self-contained (not correlated), since bare names then resolve unambiguously to each FROM — a correlated subquery would need qualification Electric rejects.
## Parameters
[Section titled “Parameters”](#parameters)
### column
[Section titled “column”](#column)
`AnyColumn`
## Returns
[Section titled “Returns”](#returns)
`SQL`
# canonicalizeReadContract
> **canonicalizeReadContract**(`entry`): [`CanonicalReadContract`](/api/contracts/interfaces/canonicalreadcontract/)
Defined in: packages/contracts/src/fingerprint.ts:231
The canonical [CanonicalReadContract](/api/contracts/interfaces/canonicalreadcontract/) of a sync table entry (see the interface for what it omits).
## Parameters
[Section titled “Parameters”](#parameters)
### entry
[Section titled “entry”](#entry)
[`SyncTableEntry`](/api/contracts/interfaces/synctableentry/)
## Returns
[Section titled “Returns”](#returns)
[`CanonicalReadContract`](/api/contracts/interfaces/canonicalreadcontract/)
# canonicalizeRegistry
> **canonicalizeRegistry**(`registry`): [`CanonicalTable`](/api/contracts/interfaces/canonicaltable/)\[]
Defined in: packages/contracts/src/fingerprint.ts:161
The canonical, order-independent shape of a registry. Tables are sorted by key so declaration order never affects the result.
## Parameters
[Section titled “Parameters”](#parameters)
### registry
[Section titled “registry”](#registry)
[`SyncTableRegistry`](/api/contracts/type-aliases/synctableregistry/)
## Returns
[Section titled “Returns”](#returns)
[`CanonicalTable`](/api/contracts/interfaces/canonicaltable/)\[]
# canonicalReadContractString
> **canonicalReadContractString**(`entry`): `string`
Defined in: packages/contracts/src/fingerprint.ts:254
A stable string serialization of a table’s [CanonicalReadContract](/api/contracts/interfaces/canonicalreadcontract/).
## Parameters
[Section titled “Parameters”](#parameters)
### entry
[Section titled “entry”](#entry)
[`SyncTableEntry`](/api/contracts/interfaces/synctableentry/)
## Returns
[Section titled “Returns”](#returns)
`string`
# canonicalRegistryString
> **canonicalRegistryString**(`registry`): `string`
Defined in: packages/contracts/src/fingerprint.ts:168
A stable string serialization of the canonical registry shape.
## Parameters
[Section titled “Parameters”](#parameters)
### registry
[Section titled “registry”](#registry)
[`SyncTableRegistry`](/api/contracts/type-aliases/synctableregistry/)
## Returns
[Section titled “Returns”](#returns)
`string`
# classifyApplyStrategy
> **classifyApplyStrategy**(`columns`): [`ApplyStrategy`](/api/contracts/type-aliases/applystrategy/)
Defined in: packages/contracts/src/apply-strategy.ts:119
Chooses the bulk-insert strategy for a table from its column types (ADR-0009 decision 3):
* every column COPY-safe → `copy`;
* else every column COPY-safe ∪ array/json/jsonb → `json`;
* else → `insert` (the always-correct floor for anything not positively whitelisted).
Pure and total: an empty column list falls to `insert`.
## Parameters
[Section titled “Parameters”](#parameters)
### columns
[Section titled “columns”](#columns)
readonly [`SyncColumnType`](/api/contracts/interfaces/synccolumntype/)\[]
## Returns
[Section titled “Returns”](#returns)
[`ApplyStrategy`](/api/contracts/type-aliases/applystrategy/)
# classifyTableApplyStrategy
> **classifyTableApplyStrategy**<`TTable`>(`entry`): [`ApplyStrategy`](/api/contracts/type-aliases/applystrategy/)
Defined in: packages/contracts/src/registry.ts:1308
The statically-chosen bulk-insert strategy for a synced table (ADR-0009 decision 3).
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TTable
[Section titled “TTable”](#ttable)
`TTable` *extends* `AnyPgTable`
## Parameters
[Section titled “Parameters”](#parameters)
### entry
[Section titled “entry”](#entry)
[`SyncTableEntry`](/api/contracts/interfaces/synctableentry/)<`TTable`>
## Returns
[Section titled “Returns”](#returns)
[`ApplyStrategy`](/api/contracts/type-aliases/applystrategy/)
# compareRegistries
> **compareRegistries**(`previous`, `next`): [`RegistryDiff`](/api/contracts/interfaces/registrydiff/)
Defined in: packages/contracts/src/registry-diff.ts:405
Classify the change between two registries.
## Parameters
[Section titled “Parameters”](#parameters)
### previous
[Section titled “previous”](#previous)
[`SyncTableRegistry`](/api/contracts/type-aliases/synctableregistry/)
### next
[Section titled “next”](#next)
[`SyncTableRegistry`](/api/contracts/type-aliases/synctableregistry/)
## Returns
[Section titled “Returns”](#returns)
[`RegistryDiff`](/api/contracts/interfaces/registrydiff/)
# defineEventStream
> **defineEventStream**<`TPayload`>(`input`): [`EventStreamEntry`](/api/contracts/interfaces/eventstreamentry/)<`TPayload`>
Defined in: packages/contracts/src/event-stream.ts:136
Declare one Event stream for a registry’s `streams` map.
The Event-stream NAME is the record key it is registered under, so it is not named here — the name (and this entry’s payload/identity shape) is validated fail-closed at `defineSyncRegistry`, which sees the whole map and reports EVERY offender in one error rather than one per build.
```ts
import { z } from "zod";
export const registry = defineSyncRegistry({
tables: { issue },
streams: {
board_issue_viewed: defineEventStream({
payload: z.object({ issueId: z.uuid() }).strict(),
identity: { viewerId: { claimPath: ["sub"] } },
}),
},
});
```
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TPayload
[Section titled “TPayload”](#tpayload)
`TPayload` *extends* `ZodType`<`unknown`, `unknown`, `$ZodTypeInternals`<`unknown`, `unknown`>>
## Parameters
[Section titled “Parameters”](#parameters)
### input
[Section titled “input”](#input)
#### identity
[Section titled “identity”](#identity)
`Record`<`string`, [`EventStreamIdentityField`](/api/contracts/interfaces/eventstreamidentityfield/)>
#### payload
[Section titled “payload”](#payload)
`TPayload`
#### revision?
[Section titled “revision?”](#revision)
`number`
Bump on every change the JSON-Schema hash cannot see. See [EventStreamEntry.revision](/api/contracts/interfaces/eventstreamentry/#revision).
## Returns
[Section titled “Returns”](#returns)
[`EventStreamEntry`](/api/contracts/interfaces/eventstreamentry/)<`TPayload`>
# defineReadProjection
> **defineReadProjection**<`TOwnerTable`, `TOwnerLocal`, `TAs`, `TColumns`>(`owner`, `opts`): [`SyncTableEntry`](/api/contracts/interfaces/synctableentry/)<`TOwnerTable`, `ProjectionLocalTable`<`TOwnerTable`, `TAs`, `TColumns`\[`number`]>> & `object`
Defined in: packages/contracts/src/registry.ts:847
Define a **read projection**: a second client shape over a table an `owner` entry already owns. The projection reads the SAME physical rows under a DISTINCT local identity (`as`) and its own narrower shape — a typed column subset and/or an admin/role-scoped `rowFilter` — without owning, migrating, or RLS-guarding any new table. The first use is a light admin view of a heavy authoring table (titles, not the jsonb), while the learner keeps reading the full table through the owner’s shape.
It is the *obvious, DRY* way to express “another shape over this table”, versus the bare `shape.electricTable` string it replaces (a footgun — config that silently un-asserts table ownership):
* **Owns nothing.** The returned entry’s `table` IS `owner.table` (the same object), so there is no new `pgTable` to migrate or to leak into a drizzle-kit schema barrel. Only `localTable` (named `as`) and `shape` are its own. `readProjection` is set so generators skip it.
* **DRY columns.** `columns` is a typed subset of the owner’s column keys; the local table is built by filtering the owner’s own column definitions (never restated), and the same subset becomes the Electric `columns` allow-list so an omitted (e.g. heavy jsonb) column never crosses the wire. The primary key is always kept. Omit `columns` to sync every column.
* **Source is derived, never named.** The physical Electric target is taken from the owner — there is no consumer-facing source field to get wrong (see [ShapeSpec.electricTable](/api/contracts/interfaces/shapespec/#electrictable)).
* **Readonly.** A projection has no write path; the engine resolves an incoming shape request by its unique `shapeKey` (= `as`) and consults the derived physical target only on egress.
The `rowFilter` callback receives the OWNER’s full columns — `customWhere` runs in Electric against the physical table, so it may reference a column the local subset omits. RLS for the projection’s reads lives on the OWNER’s table (a projection adds no DDL to a table it does not own); its `customWhere` must be a subset of what that RLS allows.
The `owner` may be a `defineSyncTable` entry OR an `asReadonly` of one — an `asReadonly` projection preserves the full read contract (physical table, columns, primary key) and only drops the write path, so projecting off it is equivalent to projecting off its writable source. A CHAINED read projection (an owner that is itself a `defineReadProjection`) is rejected, because it composes wrongly — see below.
### Server-side egress redaction (`serverProjection` + `serverOnlyColumns`)
[Section titled “Server-side egress redaction (serverProjection + serverOnlyColumns)”](#server-side-egress-redaction-serverprojection--serveronlycolumns)
A projection may carry its own `serverProjection` (a [ServerProjectionSpec](/api/contracts/interfaces/serverprojectionspec/), typically a `rowTransform`) — resolved by the projection’s `shapeKey` and run on the proxy egress path for this shape only. The order on egress is **transform first, then omission**: the transform runs against the fetched row, then column omission strips this projection’s omitted columns (the client keep-set is `columns ∪ primaryKey`) before the row reaches the client wire. This lets a “secure window” over a keyed table stream the body while stripping the keys per row.
`serverOnlyColumns` are owner column keys the transform must READ but that are NOT in the client shape (e.g. a `keysWithheld` control flag). Such a key stays omitted from the client keep-set, yet is ADDED to the Electric fetch allow-list — so it is fetched from Electric, visible to the transform, and then stripped on egress by the same omission pass, never reaching the client. It requires a `serverProjection.rowTransform` (a fetch no transform reads is dead weight) AND `columns` (with `columns` omitted every column is already kept, so “server-only” is a contradiction), and must be disjoint from `columns` and the primary key.
**No inheritance — ENFORCED.** A projection does NOT inherit its owner’s `serverProjection`. That is deliberate: an inherited transform whose input column is absent from the projection’s fetch list would read `undefined` and silently fail OPEN (serving the un-redacted body) — half-protection worse than none. Because a bare projection over a redacting owner would therefore egress RAW owner rows, the registry no longer merely warns: when `owner.serverProjection?.rowTransform` exists, this function THROWS at definition time unless the projection declares a posture. You must either declare your own `serverProjection` on the projection (typically the same transform fn, plus `serverOnlyColumns` for its control-flag inputs), or — only after confirming the projection’s kept columns leak nothing — opt out explicitly with the literal `serverProjection: "unredacted"`, which attaches no transform (egress raw) but records that as a visible, reviewed decision at the definition site. The opt-out is meaningful only where it applies: `"unredacted"` over an owner with NO egress `rowTransform` is itself rejected, so a stale opt-out cannot silently pre-authorize a leak the day the owner grows one.
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TOwnerTable
[Section titled “TOwnerTable”](#townertable)
`TOwnerTable` *extends* `AnyPgTable`
### TOwnerLocal
[Section titled “TOwnerLocal”](#townerlocal)
`TOwnerLocal` *extends* `AnyPgTable`
### TAs
[Section titled “TAs”](#tas)
`TAs` *extends* `string`
### TColumns
[Section titled “TColumns”](#tcolumns)
`TColumns` *extends* readonly `Extract`\, `string`>\[] = readonly `Extract`\, `string`>\[]
## Parameters
[Section titled “Parameters”](#parameters)
### owner
[Section titled “owner”](#owner)
[`SyncTableEntry`](/api/contracts/interfaces/synctableentry/)<`TOwnerTable`, `TOwnerLocal`>
### opts
[Section titled “opts”](#opts)
#### as
[Section titled “as”](#as)
`TAs`
The projection’s distinct local identity — its PGlite table name AND its `shapeKey`.
#### columns?
[Section titled “columns?”](#columns)
`TColumns`
Column keys (of the owner) to sync locally + fetch from Electric. The PK is always kept. Omit → all.
#### consistencyGroup?
[Section titled “consistencyGroup?”](#consistencygroup)
`string`
#### retention?
[Section titled “retention?”](#retention)
[`Retention`](/api/contracts/type-aliases/retention/)
#### rowClass?
[Section titled “rowClass?”](#rowclass)
`string`
Row classification (ADR-0052) for THIS projection. Defaults to the OWNER’s `rowClass` — a projection reads the owner’s rows, so it carries the owner’s classification (and the invariants bound to it) unless you say otherwise. Override when the narrower shape genuinely changes the KIND of row the client receives (e.g. a redacting window over private rows that egresses only public fields).
#### rowFilter?
[Section titled “rowFilter?”](#rowfilter)
(`columns`) => [`RowFilterSpec`](/api/contracts/interfaces/rowfilterspec/)
Row filter for this shape; the callback form receives the owner’s full (physical) columns.
#### serverOnlyColumns?
[Section titled “serverOnlyColumns?”](#serveronlycolumns)
readonly `Extract`\, `string`>\[]
Owner column keys the `serverProjection.rowTransform` must READ but which are NOT part of the client shape (e.g. a `keysWithheld` control flag). They are added to the Electric fetch allow-list so the transform can see them, then stripped on egress before the client wire. Requires `serverProjection.rowTransform` and `columns`; must be disjoint from `columns` and the primary key.
#### serverProjection?
[Section titled “serverProjection?”](#serverprojection)
[`ServerProjectionSpec`](/api/contracts/interfaces/serverprojectionspec/) | `"unredacted"`
Server-side egress projection (ADR-0004) for THIS shape — typically a `rowTransform` that redacts a sub-document of a kept column conditionally on row data. A projection does NOT inherit its owner’s `serverProjection` (see the docblock’s no-inheritance caution): an inherited transform whose input column is unfetched would fail OPEN, so inheritance is refused, not silent. When the OWNER declares an egress `rowTransform`, this is therefore **required** — the registry throws at definition time unless you either declare your own spec here (typically the same transform fn, plus `serverOnlyColumns` for its control-flag inputs) OR opt out with the literal `"unredacted"`. Use `"unredacted"` only after confirming this projection’s kept columns leak nothing; it attaches NO egress transform (the shape streams raw owner rows), but records that as a visible, reviewed decision at the definition site. `"unredacted"` over a transform-less owner is itself rejected — a stale opt-out would silently pre-authorize a leak the day the owner grows a transform.
#### subscription?
[Section titled “subscription?”](#subscription)
[`SubscriptionTiming`](/api/contracts/type-aliases/subscriptiontiming/)
## Returns
[Section titled “Returns”](#returns)
[`SyncTableEntry`](/api/contracts/interfaces/synctableentry/)<`TOwnerTable`, `ProjectionLocalTable`<`TOwnerTable`, `TAs`, `TColumns`\[`number`]>> & `object`
# defineSyncRegistry
## Call Signature
[Section titled “Call Signature”](#call-signature)
> **defineSyncRegistry**<`TRegistry`>(`registry`): `TRegistry`
Defined in: packages/contracts/src/registry.ts:1091
### Type Parameters
[Section titled “Type Parameters”](#type-parameters)
#### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* { \[TKey in string | number | symbol]: SyncTableEntry\ }
### Parameters
[Section titled “Parameters”](#parameters)
#### registry
[Section titled “registry”](#registry)
`TRegistry`
### Returns
[Section titled “Returns”](#returns)
`TRegistry`
## Call Signature
[Section titled “Call Signature”](#call-signature-1)
> **defineSyncRegistry**<`TRegistry`>(`definition`): `TRegistry`
Defined in: packages/contracts/src/registry.ts:1094
### Type Parameters
[Section titled “Type Parameters”](#type-parameters-1)
#### TRegistry
[Section titled “TRegistry”](#tregistry-1)
`TRegistry` *extends* { \[TKey in string | number | symbol]: SyncTableEntry\ }
### Parameters
[Section titled “Parameters”](#parameters-1)
#### definition
[Section titled “definition”](#definition)
[`SyncRegistryDefinition`](/api/contracts/interfaces/syncregistrydefinition/)<`TRegistry`>
### Returns
[Section titled “Returns”](#returns-1)
`TRegistry`
# defineSyncTable
> **defineSyncTable**<`TName`, `TColumns`, `TOmittedColumns`, `TGovernance`, `TMode`>(`input`): `object` & `SyncTableInputGovernanceMarker`<`TGovernance`>
Defined in: packages/contracts/src/registry.ts:604
Defines a sync table entry. Provide `tableName` and `makeColumns` — the Drizzle `pgTable` (and, for `readwrite` mode, the `_read_model` view) are created here.
Access the built objects via `.table` and `.view` on the returned entry.
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TName
[Section titled “TName”](#tname)
`TName` *extends* `string`
### TColumns
[Section titled “TColumns”](#tcolumns)
`TColumns` *extends* `Record`<`string`, `ColumnBuilderBase`<`ColumnBuilderBaseConfig`<`ColumnType`>>>
### TOmittedColumns
[Section titled “TOmittedColumns”](#tomittedcolumns)
`TOmittedColumns` *extends* readonly `ColumnKeys`<`TColumns`>\[] = \[]
### TGovernance
[Section titled “TGovernance”](#tgovernance)
`TGovernance` *extends* [`SyncTableInputGovernance`](/api/contracts/type-aliases/synctableinputgovernance/)<`TColumns`> | `undefined` = `undefined`
### TMode
[Section titled “TMode”](#tmode)
`TMode` *extends* [`TableMode`](/api/contracts/type-aliases/tablemode/) = `"readonly"`
## Parameters
[Section titled “Parameters”](#parameters)
### input
[Section titled “input”](#input)
`Omit`<[`SyncTableInput`](/api/contracts/type-aliases/synctableinput/)<`TName`, `TColumns`, `TOmittedColumns`>, `"mode"` | `"governance"`> & `object`
## Returns
[Section titled “Returns”](#returns)
# deriveSyncColumnTypes
> **deriveSyncColumnTypes**<`TTable`>(`entry`): [`SyncColumnType`](/api/contracts/interfaces/synccolumntype/)\[]
Defined in: packages/contracts/src/registry.ts:1264
Resolves the [SyncColumnType](/api/contracts/interfaces/synccolumntype/)s of a synced table’s client-projected columns from its Drizzle definition (ADR-0009 decision 3) — the same `getSQLType()`/`dimensions` introspection the local schema generator uses, so the apply ladder and the generated DDL can never disagree about a column’s type. Drives both [classifyTableApplyStrategy](/api/contracts/functions/classifytableapplystrategy/) and the engine’s `json` apply cast, removing the runtime `information_schema` round-trip.
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TTable
[Section titled “TTable”](#ttable)
`TTable` *extends* `AnyPgTable`
## Parameters
[Section titled “Parameters”](#parameters)
### entry
[Section titled “entry”](#entry)
[`SyncTableEntry`](/api/contracts/interfaces/synctableentry/)<`TTable`>
## Returns
[Section titled “Returns”](#returns)
[`SyncColumnType`](/api/contracts/interfaces/synccolumntype/)\[]
# diffCanonicalRegistries
> **diffCanonicalRegistries**(`previous`, `next`, `rowClasses?`, `streams?`): [`RegistryDiff`](/api/contracts/interfaces/registrydiff/)
Defined in: packages/contracts/src/registry-diff.ts:371
Classify the change from one canonical registry shape to another. `rowClasses` carries each side’s row classification (ADR-0052) and `streams` each side’s Event-stream contract hashes (ADR-0053) — both siblings of the canonical tables, never fields of them (see [RegistryLock.rowClasses](/api/contracts/interfaces/registrylock/#rowclasses) / [RegistryLock.streams](/api/contracts/interfaces/registrylock/#streams)). An omitted side reads as entirely unclassified / streamless, which is how a lock predating either field diffs as adoption rather than as spurious risk.
`RegistryChange.table` names the SUBJECT of the change, which for an Event-stream change is the Event-stream name rather than a registry table key; each such `detail` says “event stream …” so the summary line stays unambiguous.
## Parameters
[Section titled “Parameters”](#parameters)
### previous
[Section titled “previous”](#previous)
readonly [`CanonicalTable`](/api/contracts/interfaces/canonicaltable/)\[]
### next
[Section titled “next”](#next)
readonly [`CanonicalTable`](/api/contracts/interfaces/canonicaltable/)\[]
### rowClasses?
[Section titled “rowClasses?”](#rowclasses)
#### next?
[Section titled “next?”](#next-1)
[`RegistryRowClasses`](/api/contracts/type-aliases/registryrowclasses/)
#### previous?
[Section titled “previous?”](#previous-1)
[`RegistryRowClasses`](/api/contracts/type-aliases/registryrowclasses/)
### streams?
[Section titled “streams?”](#streams)
#### next?
[Section titled “next?”](#next-2)
[`RegistryEventStreams`](/api/contracts/type-aliases/registryeventstreams/)
#### previous?
[Section titled “previous?”](#previous-2)
[`RegistryEventStreams`](/api/contracts/type-aliases/registryeventstreams/)
## Returns
[Section titled “Returns”](#returns)
[`RegistryDiff`](/api/contracts/interfaces/registrydiff/)
# diffRegistryAgainstLock
> **diffRegistryAgainstLock**(`registry`, `lock`): [`RegistryDiff`](/api/contracts/interfaces/registrydiff/)
Defined in: packages/contracts/src/registry-diff.ts:415
Classify a registry against a committed lock baseline.
## Parameters
[Section titled “Parameters”](#parameters)
### registry
[Section titled “registry”](#registry)
[`SyncTableRegistry`](/api/contracts/type-aliases/synctableregistry/)
### lock
[Section titled “lock”](#lock)
[`RegistryLock`](/api/contracts/interfaces/registrylock/)
## Returns
[Section titled “Returns”](#returns)
[`RegistryDiff`](/api/contracts/interfaces/registrydiff/)
# escapeSqlLiteral
> **escapeSqlLiteral**(`value`): `string`
Defined in: packages/contracts/src/sql-identifier.ts:102
Escape a SQL string-literal body (`'` -> `''`); does not add the surrounding quotes.
## Parameters
[Section titled “Parameters”](#parameters)
### value
[Section titled “value”](#value)
`string`
## Returns
[Section titled “Returns”](#returns)
`string`
# fingerprintReadContract
> **fingerprintReadContract**(`entry`): `string`
Defined in: packages/contracts/src/fingerprint.ts:263
A stable fingerprint (hex) of a table’s [CanonicalReadContract](/api/contracts/interfaces/canonicalreadcontract/). Equal for a writable entry and its [asReadonly](/api/contracts/functions/asreadonly/) projection; the basis of the projection-consistency invariant (`assertReadContractPreserved`).
## Parameters
[Section titled “Parameters”](#parameters)
### entry
[Section titled “entry”](#entry)
[`SyncTableEntry`](/api/contracts/interfaces/synctableentry/)
## Returns
[Section titled “Returns”](#returns)
`string`
# fingerprintRegistry
> **fingerprintRegistry**(`registry`): `string`
Defined in: packages/contracts/src/fingerprint.ts:190
A stable fingerprint (hex) of the registry’s shape. Identical shapes — even with tables declared in a different order — produce the same fingerprint; any structural change produces a different one. Memoised per registry object (see registryFingerprintMemo); two structurally equal registries still fingerprint equal, memo or not.
## Parameters
[Section titled “Parameters”](#parameters)
### registry
[Section titled “registry”](#registry)
[`SyncTableRegistry`](/api/contracts/type-aliases/synctableregistry/)
## Returns
[Section titled “Returns”](#returns)
`string`
# getLocalSyncedTablePrimaryKeyColumns
> **getLocalSyncedTablePrimaryKeyColumns**<`TTable`>(`entry`): `string`\[]
Defined in: packages/contracts/src/registry.ts:1316
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TTable
[Section titled “TTable”](#ttable)
`TTable` *extends* `AnyPgTable`
## Parameters
[Section titled “Parameters”](#parameters)
### entry
[Section titled “entry”](#entry)
[`SyncTableEntry`](/api/contracts/interfaces/synctableentry/)<`TTable`>
## Returns
[Section titled “Returns”](#returns)
`string`\[]
# getLocalSyncPrimaryKey
> **getLocalSyncPrimaryKey**(`source`): [`PrimaryKeySpec`](/api/contracts/interfaces/primarykeyspec/)
Defined in: packages/contracts/src/config.ts:413
## Parameters
[Section titled “Parameters”](#parameters)
### source
[Section titled “source”](#source)
#### clientProjection?
[Section titled “clientProjection?”](#clientprojection)
`Pick`<[`ClientProjectionSpec`](/api/contracts/interfaces/clientprojectionspec/), `"localPrimaryKey"`>
#### primaryKey
[Section titled “primaryKey”](#primarykey)
[`PrimaryKeySpec`](/api/contracts/interfaces/primarykeyspec/)
## Returns
[Section titled “Returns”](#returns)
[`PrimaryKeySpec`](/api/contracts/interfaces/primarykeyspec/)
# getLocalSyncPrimaryKeyColumns
> **getLocalSyncPrimaryKeyColumns**(`source`): `string`\[]
Defined in: packages/contracts/src/config.ts:420
## Parameters
[Section titled “Parameters”](#parameters)
### source
[Section titled “source”](#source)
#### clientProjection?
[Section titled “clientProjection?”](#clientprojection)
`Pick`<[`ClientProjectionSpec`](/api/contracts/interfaces/clientprojectionspec/), `"localPrimaryKey"`>
#### primaryKey
[Section titled “primaryKey”](#primarykey)
[`PrimaryKeySpec`](/api/contracts/interfaces/primarykeyspec/)
## Returns
[Section titled “Returns”](#returns)
`string`\[]
# getOmittedProjectedColumnNames
> **getOmittedProjectedColumnNames**<`TTable`>(`entry`): `string`\[]
Defined in: packages/contracts/src/registry.ts:1312
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TTable
[Section titled “TTable”](#ttable)
`TTable` *extends* `AnyPgTable`
## Parameters
[Section titled “Parameters”](#parameters)
### entry
[Section titled “entry”](#entry)
[`SyncTableEntry`](/api/contracts/interfaces/synctableentry/)<`TTable`>
## Returns
[Section titled “Returns”](#returns)
`string`\[]
# getOmittedProjectedColumns
> **getOmittedProjectedColumns**<`TTable`>(`entry`): `object`\[]
Defined in: packages/contracts/src/registry.ts:1235
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TTable
[Section titled “TTable”](#ttable)
`TTable` *extends* `AnyPgTable`
## Parameters
[Section titled “Parameters”](#parameters)
### entry
[Section titled “entry”](#entry)
[`SyncTableEntry`](/api/contracts/interfaces/synctableentry/)<`TTable`>
## Returns
[Section titled “Returns”](#returns)
`object`\[]
# getProjectedColumnNames
> **getProjectedColumnNames**<`TTable`>(`entry`): `string`\[]
Defined in: packages/contracts/src/registry.ts:1253
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TTable
[Section titled “TTable”](#ttable)
`TTable` *extends* `AnyPgTable`
## Parameters
[Section titled “Parameters”](#parameters)
### entry
[Section titled “entry”](#entry)
[`SyncTableEntry`](/api/contracts/interfaces/synctableentry/)<`TTable`>
## Returns
[Section titled “Returns”](#returns)
`string`\[]
# getProjectedColumns
> **getProjectedColumns**<`TTable`>(`entry`): `object`\[]
Defined in: packages/contracts/src/registry.ts:1217
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TTable
[Section titled “TTable”](#ttable)
`TTable` *extends* `AnyPgTable`
## Parameters
[Section titled “Parameters”](#parameters)
### entry
[Section titled “entry”](#entry)
[`SyncTableEntry`](/api/contracts/interfaces/synctableentry/)<`TTable`>
## Returns
[Section titled “Returns”](#returns)
`object`\[]
# getSyncRegistryRowClasses
> **getSyncRegistryRowClasses**<`TRegistry`>(`registry`): readonly `string`\[] | `undefined`
Defined in: packages/contracts/src/registry.ts:1482
Read the row-class vocabulary (ADR-0052) a registry was built with, or `undefined` when none was declared (a bare registry map, or a definition with no `rowClasses`) — the classification twin of [getSyncRegistryStorage](/api/contracts/functions/getsyncregistrystorage/). [assertRegistryInvariant](/api/contracts/functions/assertregistryinvariant/) consults it to reject an invariant whose `appliesTo` names a class this registry does not define (a typo that would otherwise bind nothing).
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* [`SyncTableRegistry`](/api/contracts/type-aliases/synctableregistry/)
## Parameters
[Section titled “Parameters”](#parameters)
### registry
[Section titled “registry”](#registry)
`TRegistry`
## Returns
[Section titled “Returns”](#returns)
readonly `string`\[] | `undefined`
# getSyncRegistrySchema
> **getSyncRegistrySchema**<`TRegistry`>(`registry`): `string`
Defined in: packages/contracts/src/registry.ts:1386
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* [`SyncTableRegistry`](/api/contracts/type-aliases/synctableregistry/)
## Parameters
[Section titled “Parameters”](#parameters)
### registry
[Section titled “registry”](#registry)
`TRegistry`
## Returns
[Section titled “Returns”](#returns)
`string`
# getSyncRegistryStorage
> **getSyncRegistryStorage**<`TRegistry`>(`registry`): [`SyncStorageDeclaration`](/api/contracts/interfaces/syncstoragedeclaration/) | `undefined`
Defined in: packages/contracts/src/registry.ts:1434
Read the storage declaration (ADR-0049 decision 1, ADR-0047) a registry was built with, or `undefined` when none was declared (a bare registry map, or a definition with no `storage`). The client resolves the effective mint durability as `getSyncRegistryStorage(registry)?.durability ?? "relaxed"` at its single mint seam — the storage twin of [getSyncRegistrySchema](/api/contracts/functions/getsyncregistryschema/).
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* [`SyncTableRegistry`](/api/contracts/type-aliases/synctableregistry/)
## Parameters
[Section titled “Parameters”](#parameters)
### registry
[Section titled “registry”](#registry)
`TRegistry`
## Returns
[Section titled “Returns”](#returns)
[`SyncStorageDeclaration`](/api/contracts/interfaces/syncstoragedeclaration/) | `undefined`
# getSyncRegistryStreams
> **getSyncRegistryStreams**<`TRegistry`>(`registry`): [`EventStreamRegistry`](/api/contracts/type-aliases/eventstreamregistry/) | `undefined`
Defined in: packages/contracts/src/registry.ts:1539
Read the Event streams (ADR-0053 decision 1) a registry was built with, or `undefined` when none were registered — the Event-lane twin of [getSyncRegistryRowClasses](/api/contracts/functions/getsyncregistryrowclasses/). The server mounts the ingestion route and provisions one pgmq queue per entry from this; the client resolves an `appendEvent` payload schema through it.
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* [`SyncTableRegistry`](/api/contracts/type-aliases/synctableregistry/)
## Parameters
[Section titled “Parameters”](#parameters)
### registry
[Section titled “registry”](#registry)
`TRegistry`
## Returns
[Section titled “Returns”](#returns)
[`EventStreamRegistry`](/api/contracts/type-aliases/eventstreamregistry/) | `undefined`
# hashString
> **hashString**(`input`): `string`
Defined in: packages/contracts/src/fingerprint.ts:285
FNV-1a over UTF-8 bytes, returned as 16 hex chars. Pure and dependency-free so it runs identically in the browser and in Bun (no crypto import). A fingerprint, not a security primitive — used both for the registry shape fingerprint (ADR-0004) and for the apply-function DDL fingerprint embedded in the generated migration (ADR-0018).
The 64-bit state is carried in TWO 32-bit Number lanes rather than a BigInt, because this runs over multi-KB payloads on the boot critical path (the local-schema fingerprint hashes the whole generated durable DDL) and a BigInt allocates per byte. The output is IDENTICAL — same algorithm, same offset basis, same prime, same modulo-2^64 truncation — and it MUST stay that way: the values are PERSISTED (`registry_fingerprint`, the `lsf1` local-schema fingerprint, the `apply` DDL fingerprint), so a changed value would silently wipe every existing store’s read cache. `tests/unit/registry-fingerprint` pins goldens against the original BigInt implementation as the oracle.
Why plain Numbers are exact here: the prime 0x100000001b3 splits into small halves (high 0x100, low 0x1b3), so every partial product stays under 2^42 — far inside the 2^53 integer-exact range. The lane arithmetic therefore needs no `Math.imul` truncation games; only the final `% 2^32` per lane.
## Parameters
[Section titled “Parameters”](#parameters)
### input
[Section titled “input”](#input)
`string`
## Returns
[Section titled “Returns”](#returns)
`string`
# hasNonStrictObjectRoot
> **hasNonStrictObjectRoot**(`schema`): `boolean`
Defined in: packages/contracts/src/event-stream.ts:194
Whether an Event stream’s payload schema has a NON-strict object at its root — the one shape `validateEventStreams` refuses (ADR-0053 decision 1: the payload contract is strict, and the promise is enforced rather than advertised).
zod v4 models strictness as the object’s **catchall**: `.strict()` / `z.strictObject()` set it to `ZodNever`, a bare `z.object()` leaves it unset (unknown keys are silently STRIPPED), and `z.looseObject()` / `.catchall(…)` set something else. So the rule is exactly “an object root’s catchall must be `never`”, read off `_zod.def` — the same duck-typed posture as [isZodSchema](/api/contracts/functions/iszodschema/), never an `instanceof` a peer-installed class.
A union (or discriminated union) is walked recursively, because each member is equally a root the client may append against. Every other root — string, array, record, a `.transform()` pipeline — is NOT an object and is accepted unchanged. A schema whose internals cannot be read at all (a duplicated zod install, a hand-rolled duck-typed schema) is likewise accepted: this check can only refuse what it can actually see.
## Parameters
[Section titled “Parameters”](#parameters)
### schema
[Section titled “schema”](#schema)
`unknown`
## Returns
[Section titled “Returns”](#returns)
`boolean`
# isClaimsDependentRowFilter
> **isClaimsDependentRowFilter**(`filter`): `boolean`
Defined in: packages/contracts/src/config.ts:495
Whether a row filter denies (or cannot serve) an unauthenticated caller — a *claims-dependent* filter (ADR-0039). The client probes this at lazy-group activation: a group whose members probe claims-dependent, activated with no auth token, opens an empty subscription by construction, so the client warns.
A filter is claims-dependent when its `customWhere`, evaluated with **empty claims** (`{}` — exactly what the proxy passes for an unauthenticated request) and no params, either **throws** or returns the [DENY\_ALL](/api/contracts/variables/deny_all/) sentinel by **reference identity** (which every contracts helper — [buildOwnershipShapeWhere](/api/contracts/functions/buildownershipshapewhere/) and friends — returns for a missing subject, and which is already the documented deny-anonymous pattern). Any other result — `null` (no filtering), a string, or a different `SQL` fragment — is not claims-dependent as far as this probe can tell.
Requires `customWhere` to be pure (its contract; see [RowFilterSpec.customWhere](/api/contracts/interfaces/rowfilterspec/#customwhere)).
## Parameters
[Section titled “Parameters”](#parameters)
### filter
[Section titled “filter”](#filter)
[`RowFilterSpec`](/api/contracts/interfaces/rowfilterspec/) | `undefined`
## Returns
[Section titled “Returns”](#returns)
`boolean`
# isConflictPolicy
> **isConflictPolicy**(`value`): `value is ConflictPolicy`
Defined in: packages/contracts/src/config.ts:31
Type guard: is `value` one of the v1 [ConflictPolicy](/api/contracts/type-aliases/conflictpolicy/) values?
## Parameters
[Section titled “Parameters”](#parameters)
### value
[Section titled “value”](#value)
`unknown`
## Returns
[Section titled “Returns”](#returns)
`value is ConflictPolicy`
# isManagedFieldGuarded
> **isManagedFieldGuarded**(`field`, `operation`): `boolean`
Defined in: packages/contracts/src/registry.ts:542
Whether a managed field is **SERVER-OWNED** for an operation — the ONE definition of the guard rule, so the surfaces that enforce it cannot drift apart:
* **create** ⇒ the field is guarded when its `applyOn` includes `"create"`. The server stamps it after validation, so a client value for it is never honoured.
* **update** ⇒ **every** managed field is guarded, a **create-only** one included. An update-managed field is stamped by the server on every write; a `applyOn: ["create"]` field is stamped at birth and **inert** on update (the generated apply function offers no UPDATE SET candidate for it). Neither is ever a settable update key, so both are flagged, stripped, and omitted rather than left for consumer RLS to neutralise.
* **delete** ⇒ none. A delete carries no payload, so nothing can be owned.
“Guarded” is NOT “stamped”: stamping is operation-scoped (`applyOn.includes(operation)`) and decides what the applier writes; guarding decides what a client may not send. They coincide on create and deliberately diverge on update.
The consuming surfaces, all of which must go through this predicate:
* the write route’s `getGuardedManagedFields` (`packages/server/src/mutations/route.ts`) — the 400 violation check, the sanitizer, and the create/update validation schemas;
* the client’s `stripManagedFields` (`packages/client/src/mutation.ts`) — the outgoing-payload strip;
* the apply-function generator’s `buildTableBranch` (`packages/server/src/mutations/plpgsql-apply.ts`) — the INSERT/UPDATE candidate-column exclusions;
* ManagedFieldColumnKeys / `ManagedFieldColumnKeysForOperation` — the type-level twin behind [SyncTableCreateInput](/api/contracts/type-aliases/synctablecreateinput/) / [SyncTableUpdateInput](/api/contracts/type-aliases/synctableupdateinput/).
## Parameters
[Section titled “Parameters”](#parameters)
### field
[Section titled “field”](#field)
#### applyOn
[Section titled “applyOn”](#applyon)
readonly [`ManagedFieldApplyOn`](/api/contracts/type-aliases/managedfieldapplyon/)\[]
### operation
[Section titled “operation”](#operation)
`"create"` | `"update"` | `"delete"`
## Returns
[Section titled “Returns”](#returns)
`boolean`
# isRetention
> **isRetention**(`value`): `value is Retention`
Defined in: packages/contracts/src/config.ts:73
Type guard: is `value` a [Retention](/api/contracts/type-aliases/retention/)?
## Parameters
[Section titled “Parameters”](#parameters)
### value
[Section titled “value”](#value)
`unknown`
## Returns
[Section titled “Returns”](#returns)
`value is Retention`
# isStorageBackend
> **isStorageBackend**(`value`): `value is StorageBackend`
Defined in: packages/contracts/src/config.ts:118
Type guard: is `value` a [StorageBackend](/api/contracts/type-aliases/storagebackend/)?
## Parameters
[Section titled “Parameters”](#parameters)
### value
[Section titled “value”](#value)
`unknown`
## Returns
[Section titled “Returns”](#returns)
`value is StorageBackend`
# isStorageDurability
> **isStorageDurability**(`value`): `value is StorageDurability`
Defined in: packages/contracts/src/config.ts:136
Type guard: is `value` a [StorageDurability](/api/contracts/type-aliases/storagedurability/)?
## Parameters
[Section titled “Parameters”](#parameters)
### value
[Section titled “value”](#value)
`unknown`
## Returns
[Section titled “Returns”](#returns)
`value is StorageDurability`
# isSubscriptionTiming
> **isSubscriptionTiming**(`value`): `value is SubscriptionTiming`
Defined in: packages/contracts/src/config.ts:52
Type guard: is `value` a [SubscriptionTiming](/api/contracts/type-aliases/subscriptiontiming/)?
## Parameters
[Section titled “Parameters”](#parameters)
### value
[Section titled “value”](#value)
`unknown`
## Returns
[Section titled “Returns”](#returns)
`value is SubscriptionTiming`
# isWriteMode
> **isWriteMode**(`value`): `value is WriteMode`
Defined in: packages/contracts/src/config.ts:99
Type guard: is `value` a [WriteMode](/api/contracts/type-aliases/writemode/)?
## Parameters
[Section titled “Parameters”](#parameters)
### value
[Section titled “value”](#value)
`unknown`
## Returns
[Section titled “Returns”](#returns)
`value is WriteMode`
# isZodSchema
> **isZodSchema**(`value`): `value is ZodType>`
Defined in: packages/contracts/src/event-stream.ts:156
Whether a value walks and quacks like a zod schema.
Duck-typed on `safeParse` (the capability `appendEvent` and the ingest endpoint actually use) rather than `instanceof z.ZodType`: zod is a PEER dependency, so a consumer with a duplicated zod install would fail an `instanceof` against a schema that is perfectly usable.
## Parameters
[Section titled “Parameters”](#parameters)
### value
[Section titled “value”](#value)
`unknown`
## Returns
[Section titled “Returns”](#returns)
`value is ZodType>`
# maybeQuoteIdentifier
> **maybeQuoteIdentifier**(`value`): `string`
Defined in: packages/contracts/src/sql-identifier.ts:125
Quote only when required: bare for a simple, non-reserved identifier; quoted otherwise. Keeps generated SQL stable for the common case while never emitting a reserved word or mixed-case name unquoted.
## Parameters
[Section titled “Parameters”](#parameters)
### value
[Section titled “value”](#value)
`string`
## Returns
[Section titled “Returns”](#returns)
`string`
# normalizeCastPositionType
> **normalizeCastPositionType**(`sqlType`): `string`
Defined in: packages/contracts/src/registry.ts:1294
`serial`/`bigserial`/`smallserial` are DDL-position conveniences (an integer column plus a sequence default), NOT real cast-position types: `json_to_recordset(… AS x(id serial))` and `value::serial` are both invalid SQL. [SyncColumnType.sqlType](/api/contracts/interfaces/synccolumntype/#sqltype) is contracted to be usable verbatim as a cast type, so we normalise the serial family to its underlying integer type here — the single derivation point — which also lets the column classify as COPY-safe rather than falling to the `insert` floor.
Exported so any code deriving a [SyncColumnType](/api/contracts/interfaces/synccolumntype/) from a bare Drizzle column (e.g. test-support `makeApplyTarget`, which cannot go through [deriveSyncColumnTypes](/api/contracts/functions/derivesynccolumntypes/) without a registry entry) stays byte-faithful to this single normalisation rather than re-`getSQLType()`ing without it.
## Parameters
[Section titled “Parameters”](#parameters)
### sqlType
[Section titled “sqlType”](#sqltype)
`string`
## Returns
[Section titled “Returns”](#returns)
`string`
# quoteIdentifier
> **quoteIdentifier**(`value`): `string`
Defined in: packages/contracts/src/sql-identifier.ts:97
Always wrap an identifier in double quotes, escaping any embedded quote.
## Parameters
[Section titled “Parameters”](#parameters)
### value
[Section titled “value”](#value)
`string`
## Returns
[Section titled “Returns”](#returns)
`string`
# quoteSqlLiteral
> **quoteSqlLiteral**(`value`): `string`
Defined in: packages/contracts/src/sql-identifier.ts:107
A SQL string literal, surrounding quotes included.
## Parameters
[Section titled “Parameters”](#parameters)
### value
[Section titled “value”](#value)
`string`
## Returns
[Section titled “Returns”](#returns)
`string`
# registryEventStreams
> **registryEventStreams**(`registry`): [`RegistryEventStreams`](/api/contracts/type-aliases/registryeventstreams/)
Defined in: packages/contracts/src/registry-diff.ts:138
The canonical contract hash of every registered Event stream, with sorted keys so a lock is stable.
## Parameters
[Section titled “Parameters”](#parameters)
### registry
[Section titled “registry”](#registry)
[`SyncTableRegistry`](/api/contracts/type-aliases/synctableregistry/)
## Returns
[Section titled “Returns”](#returns)
[`RegistryEventStreams`](/api/contracts/type-aliases/registryeventstreams/)
# registryRowClasses
> **registryRowClasses**(`registry`): [`RegistryRowClasses`](/api/contracts/type-aliases/registryrowclasses/)
Defined in: packages/contracts/src/registry-diff.ts:72
The row classification of every entry, with sorted keys so a serialized lock is stable.
## Parameters
[Section titled “Parameters”](#parameters)
### registry
[Section titled “registry”](#registry)
[`SyncTableRegistry`](/api/contracts/type-aliases/synctableregistry/)
## Returns
[Section titled “Returns”](#returns)
[`RegistryRowClasses`](/api/contracts/type-aliases/registryrowclasses/)
# resolveGrantScopeAccess
> **resolveGrantScopeAccess**(`claims`, `options`): [`GrantScopeAccess`](/api/contracts/type-aliases/grantscopeaccess/)
Defined in: packages/contracts/src/supabase-rls.ts:885
The caller’s grant-scope standing, resolved from the JWT grants — the JS mirror of the whole policy predicate, both branches: `ids` is [resolveGrantScopeIds](/api/contracts/functions/resolvegrantscopeids/) (the scope-set subquery) and `bypass` mirrors the policy’s OR `exists (…)` branch. One declaration, two enforcement surfaces: pass the SAME options object you gave [buildSupabaseGrantScopeNativePolicies](/api/contracts/functions/buildsupabasegrantscopenativepolicies/) and a bypass grant cannot be enforced on writes yet invisible on reads.
Never throws on malformed claims (they reach a `customWhere` unverified in shape) — a wrong shape simply confers nothing. A malformed `grantsClaimPath`/`scopeIdField` **option** is still a loud error.
## Parameters
[Section titled “Parameters”](#parameters)
### claims
[Section titled “claims”](#claims)
{\[`key`: `string`]: `unknown`; `app_metadata?`: {\[`key`: `string`]: `unknown`; `roles?`: `string`\[]; }; `sub?`: `string`; } | `null`
### options
[Section titled “options”](#options)
[`GrantScopeAccessOptions`](/api/contracts/type-aliases/grantscopeaccessoptions/)
## Returns
[Section titled “Returns”](#returns)
[`GrantScopeAccess`](/api/contracts/type-aliases/grantscopeaccess/)
# resolveGrantScopeIds
> **resolveGrantScopeIds**(`claims`, `options`): `string`\[]
Defined in: packages/contracts/src/supabase-rls.ts:771
The set of scope ids the caller can see, resolved from the JWT grants — the JS mirror of the grant-scope RLS subquery. Returns a de-duplicated list (empty → no rows visible).
## Parameters
[Section titled “Parameters”](#parameters)
### claims
[Section titled “claims”](#claims)
{\[`key`: `string`]: `unknown`; `app_metadata?`: {\[`key`: `string`]: `unknown`; `roles?`: `string`\[]; }; `sub?`: `string`; } | `null`
### options
[Section titled “options”](#options)
[`GrantScopeClaimOptions`](/api/contracts/type-aliases/grantscopeclaimoptions/)
## Returns
[Section titled “Returns”](#returns)
`string`\[]
# resolveOwnerOrAdminAccess
> **resolveOwnerOrAdminAccess**(`claims`, `options?`): [`OwnerOrAdminAccess`](/api/contracts/type-aliases/owneroradminaccess/)
Defined in: packages/contracts/src/supabase-rls.ts:317
The caller’s owner-or-admin standing, resolved from the JWT claims — the JS mirror of the policy’s admin `EXISTS` and its subject comparison. `admin` is true when the roles array at `adminRolesClaimPath` (`app_metadata.roles` by default — the very array [buildSupabaseOwnerOrAdminNativePolicies](/api/contracts/functions/buildsupabaseowneroradminnativepolicies/) renders its `-> 'seg'` chain from, so the two surfaces read one declaration) contains `adminRoleName`; `subject` is the `sub` claim when it is a non-empty string, else null.
Claims arrive unverified in shape, so this never throws: a missing/non-object claims bag, a non-array `roles`, or non-string members simply confer nothing.
## Parameters
[Section titled “Parameters”](#parameters)
### claims
[Section titled “claims”](#claims)
{\[`key`: `string`]: `unknown`; `app_metadata?`: {\[`key`: `string`]: `unknown`; `roles?`: `string`\[]; }; `sub?`: `string`; } | `null`
### options?
[Section titled “options?”](#options)
[`OwnerOrAdminAccessOptions`](/api/contracts/type-aliases/owneroradminaccessoptions/) = `{}`
## Returns
[Section titled “Returns”](#returns)
[`OwnerOrAdminAccess`](/api/contracts/type-aliases/owneroradminaccess/)
# resolveServerVersionColumnName
> **resolveServerVersionColumnName**<`TTable`>(`entry`): `string` | `undefined`
Defined in: packages/contracts/src/registry.ts:1327
The Server version column (ADR-0010): the `nowMicroseconds`-on-update managed field a writable table stamps on every write (conventionally `updated_at_us`), made strictly monotonic by the applier. Returns its **column name**, resolving the managed field’s drizzle property key. Returns `undefined` when none is declared — registry validation rejects that for writable tables, so the convergence barrier never has to degrade.
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TTable
[Section titled “TTable”](#ttable)
`TTable` *extends* `AnyPgTable`
## Parameters
[Section titled “Parameters”](#parameters)
### entry
[Section titled “entry”](#entry)
[`SyncTableEntry`](/api/contracts/interfaces/synctableentry/)<`TTable`>
## Returns
[Section titled “Returns”](#returns)
`string` | `undefined`
# resolveStorageDeclaration
> **resolveStorageDeclaration**(`staticDeclaration`, `wireDeclaration`): [`ResolvedStorageDeclaration`](/api/contracts/interfaces/resolvedstoragedeclaration/)
Defined in: packages/contracts/src/config.ts:204
Resolve a store’s storage declaration from its two sources (ADR-0050): the registry-attached STATIC declaration (authoritative) and the tab’s WIRE declaration (honoured only where the registry is silent). Per field: an unset field is “no opinion” and can never conflict; both explicit and disagreeing is a [StorageDeclarationRefusedError](/api/contracts/classes/storagedeclarationrefusederror/); unresolved fields take the capability defaults (`backend: "opfs"`, `durability: "relaxed"`).
## Parameters
[Section titled “Parameters”](#parameters)
### staticDeclaration
[Section titled “staticDeclaration”](#staticdeclaration)
[`SyncStorageDeclaration`](/api/contracts/interfaces/syncstoragedeclaration/) | `undefined`
### wireDeclaration
[Section titled “wireDeclaration”](#wiredeclaration)
[`SyncStorageDeclaration`](/api/contracts/interfaces/syncstoragedeclaration/) | `undefined`
## Returns
[Section titled “Returns”](#returns)
[`ResolvedStorageDeclaration`](/api/contracts/interfaces/resolvedstoragedeclaration/)
# runRegistryCheck
> **runRegistryCheck**(`input`): `object`
Defined in: packages/contracts/src/registry-diff.ts:429
The consumer-facing check: `ok` is false on a breaking diff. The consumer wires the exit code (e.g. `process.exit(result.ok ? 0 : 1)`) into their own CI — pgxsinkit does not reach into anyone’s pipeline.
## Parameters
[Section titled “Parameters”](#parameters)
### input
[Section titled “input”](#input)
#### lock
[Section titled “lock”](#lock)
[`RegistryLock`](/api/contracts/interfaces/registrylock/)
#### registry
[Section titled “registry”](#registry)
[`SyncTableRegistry`](/api/contracts/type-aliases/synctableregistry/)
## Returns
[Section titled “Returns”](#returns)
`object`
### diff
[Section titled “diff”](#diff)
> **diff**: [`RegistryDiff`](/api/contracts/interfaces/registrydiff/)
### ok
[Section titled “ok”](#ok)
> **ok**: `boolean`
# summarizeRegistryDiff
> **summarizeRegistryDiff**(`diff`): `string`
Defined in: packages/contracts/src/registry-diff.ts:438
A human-readable, stable summary of a diff (one line per change).
## Parameters
[Section titled “Parameters”](#parameters)
### diff
[Section titled “diff”](#diff)
[`RegistryDiff`](/api/contracts/interfaces/registrydiff/)
## Returns
[Section titled “Returns”](#returns)
`string`
# withRetention
> **withRetention**<`TEntry`>(`entry`, `retention`): `TEntry`
Defined in: packages/contracts/src/projection.ts:102
Project an entry onto a different **retention** (ADR-0021) — the per-table local-persistence axis: `persistent` (the durable PGlite/OPFS backend) | `ephemeral` (the table’s whole local cluster — read cache, overlay, journal, sequence, views, reconcile function — emitted as `TEMP`/`pg_temp`, leaving no durable trace). Returns a copy of `entry` with `retention` overridden and **everything else preserved verbatim** (table, columns, mode, write contract, shape/row filter, the other lifecycle axes).
Retention is a **lifecycle** axis, not a read-contract one, so a per-client registry may legitimately differ on it: [fingerprintReadContract](/api/contracts/functions/fingerprintreadcontract/) excludes retention, so a `withRetention(...)` of an authoritative entry still satisfies [assertReadContractPreserved](/api/contracts/functions/assertreadcontractpreserved/). This is how one authoritative registry yields a table that is durable for one client and ephemeral for another, e.g. `withRetention(asReadonly(authoritative.exam), "ephemeral")`.
Unlike `mode` (whose overlay/journal/view fields [asReadonly](/api/contracts/functions/asreadonly/) must re-resolve), retention has **no entry-derived fields** — the durable-vs-`TEMP` decision is taken by the client’s schema generator at runtime from this scalar — so overriding it needs no re-resolution and a plain copy is correct. The return type is the input entry’s exact type (write handles, create/update typing, governance marker all carry through); the cast restates what a generic object spread cannot prove.
Two constraints carry over (enforced elsewhere, not by this helper):
* **Consistency-group uniformity** (ADR-0021 §4): every table sharing a `consistencyGroup` must agree on retention — override the whole group, not one member, or `defineSyncRegistry` rejects the mixed group. A singleton-group table can be flipped alone.
* **No durable offline write queue for `ephemeral`** (ADR-0021 composition rule): an ephemeral writable table’s journal is `TEMP`, so a write staged offline does not survive session end — pair a must-not-lose write with a `pessimistic` write-mode (ADR-0022) or a prompt flush.
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TEntry
[Section titled “TEntry”](#tentry)
`TEntry` *extends* [`SyncTableEntry`](/api/contracts/interfaces/synctableentry/)<`AnyPgTable`, `AnyPgTable`>
## Parameters
[Section titled “Parameters”](#parameters)
### entry
[Section titled “entry”](#entry)
`TEntry`
### retention
[Section titled “retention”](#retention)
[`Retention`](/api/contracts/type-aliases/retention/)
## Returns
[Section titled “Returns”](#returns)
`TEntry`
# CanonicalColumn
Defined in: packages/contracts/src/fingerprint.ts:19
The registry fingerprint (ADR-0004): a stable, order-independent description of the shape-relevant registry metadata, plus a hash of it.
This is the single source of “has the shape changed” — consumed as the local-DB version key and as the basis of the registry-diff gate (ADR-0006). Function *bodies* (`rowTransform`, `customWhere`) cannot be fingerprinted and are excluded — but their *presence* and the surrounding **static** filter structure (the projected columns) participate. For the invisible *logic* itself, a consumer-bumped `rowFilter.revision` is folded in: changing it is how a `customWhere` authorization change is forced to shift the fingerprint (and so rebuild the cache + reset the subscription).
## Properties
[Section titled “Properties”](#properties)
### hasDefault
[Section titled “hasDefault”](#hasdefault)
> **hasDefault**: `boolean`
Defined in: packages/contracts/src/fingerprint.ts:23
***
### name
[Section titled “name”](#name)
> **name**: `string`
Defined in: packages/contracts/src/fingerprint.ts:20
***
### notNull
[Section titled “notNull”](#notnull)
> **notNull**: `boolean`
Defined in: packages/contracts/src/fingerprint.ts:22
***
### primary
[Section titled “primary”](#primary)
> **primary**: `boolean`
Defined in: packages/contracts/src/fingerprint.ts:24
***
### type
[Section titled “type”](#type)
> **type**: `string`
Defined in: packages/contracts/src/fingerprint.ts:21
# CanonicalReadContract
Defined in: packages/contracts/src/fingerprint.ts:216
The **read contract** of a single sync table: the subset of its canonical shape that decides what data streams down and how a row is identified and filtered — synced-table name, columns, primary key (and any local-PK override), column omission, and the shape (electric table + row filter). It is the stable identity a writable entry shares with its [asReadonly](/api/contracts/functions/asreadonly/) projection.
Deliberately EXCLUDES the two axes a per-client projection may legitimately differ on:
* **write capability** — `mode`, the overlay/journal projection, `managedFields`, `conflictPolicy`, `writeMode` (one client writes the table, another only reads it);
* **lifecycle orchestration** — `consistencyGroup`, `subscription`, `retention` (a client may eager- or lazy-load, or group differently, without changing the data it sees).
What it pins is the data itself: two registries that present “the same” logical table to different clients must agree here, or those clients are silently seeing different rows/columns. As with the full registry fingerprint, the `customWhere` *body* is invisible — only its presence and the consumer-bumped [RowFilterSpec.revision](/api/contracts/interfaces/rowfilterspec/#revision) participate, so bump `revision` to force a divergence a logic-only change would otherwise hide.
## Properties
[Section titled “Properties”](#properties)
### columns
[Section titled “columns”](#columns)
> **columns**: [`CanonicalColumn`](/api/contracts/interfaces/canonicalcolumn/)\[]
Defined in: packages/contracts/src/fingerprint.ts:220
***
### localPrimaryKey
[Section titled “localPrimaryKey”](#localprimarykey)
> **localPrimaryKey**: `string`\[] | `null`
Defined in: packages/contracts/src/fingerprint.ts:219
***
### omitColumns
[Section titled “omitColumns”](#omitcolumns)
> **omitColumns**: `string`\[]
Defined in: packages/contracts/src/fingerprint.ts:221
***
### primaryKey
[Section titled “primaryKey”](#primarykey)
> **primaryKey**: `string`\[]
Defined in: packages/contracts/src/fingerprint.ts:218
***
### shape
[Section titled “shape”](#shape)
> **shape**: { `electricTable`: `string` | `null`; `rowFilter`: [`CanonicalRowFilter`](/api/contracts/interfaces/canonicalrowfilter/) | `null`; `shapeKey`: `string`; `tableName`: `string`; } | `null`
Defined in: packages/contracts/src/fingerprint.ts:222
***
### syncedTable
[Section titled “syncedTable”](#syncedtable)
> **syncedTable**: `string`
Defined in: packages/contracts/src/fingerprint.ts:217
# CanonicalRowFilter
Defined in: packages/contracts/src/fingerprint.ts:68
The static, fingerprint-able structure of a row filter. A changed projection shifts the fingerprint, so the local store rebuilds and the diff gate flags it. `customWhere`’s body is invisible — only its presence (`hasCustomWhere`) is recorded — so a `customWhere` *logic* change is surfaced only by bumping `revision`.
## Properties
[Section titled “Properties”](#properties)
### columns
[Section titled “columns”](#columns)
> **columns**: `string`\[] | `null`
Defined in: packages/contracts/src/fingerprint.ts:70
***
### hasCustomWhere
[Section titled “hasCustomWhere”](#hascustomwhere)
> **hasCustomWhere**: `boolean`
Defined in: packages/contracts/src/fingerprint.ts:69
***
### revision
[Section titled “revision”](#revision)
> **revision**: `string` | `null`
Defined in: packages/contracts/src/fingerprint.ts:76
The consumer-supplied version tag for the non-fingerprintable filter logic (the `customWhere` body). Changing it shifts the fingerprint, which is the only way a `customWhere` *logic* change forces a cache + subscription reset.
# CanonicalTable
Defined in: packages/contracts/src/fingerprint.ts:27
## Properties
[Section titled “Properties”](#properties)
### columns
[Section titled “columns”](#columns)
> **columns**: [`CanonicalColumn`](/api/contracts/interfaces/canonicalcolumn/)\[]
Defined in: packages/contracts/src/fingerprint.ts:32
***
### consistencyGroup
[Section titled “consistencyGroup”](#consistencygroup)
> **consistencyGroup**: `string` | `null`
Defined in: packages/contracts/src/fingerprint.ts:52
Consistency group (ADR-0009 decision 2). Part of the fingerprint because it decides which subscription-state row a table persists under: moving a table between groups must shift the fingerprint (forcing a cache rebuild + subscription reset) and surface in the diff gate. `null` = the default singleton group.
***
### key
[Section titled “key”](#key)
> **key**: `string`
Defined in: packages/contracts/src/fingerprint.ts:28
***
### localPrimaryKey
[Section titled “localPrimaryKey”](#localprimarykey)
> **localPrimaryKey**: `string`\[] | `null`
Defined in: packages/contracts/src/fingerprint.ts:31
***
### managedFields
[Section titled “managedFields”](#managedfields)
> **managedFields**: `object`\[]
Defined in: packages/contracts/src/fingerprint.ts:45
#### applyOn
[Section titled “applyOn”](#applyon)
> **applyOn**: `string`\[]
#### field
[Section titled “field”](#field)
> **field**: `string`
#### strategy
[Section titled “strategy”](#strategy)
> **strategy**: `string`
***
### mode
[Section titled “mode”](#mode)
> **mode**: `string`
Defined in: packages/contracts/src/fingerprint.ts:29
***
### primaryKey
[Section titled “primaryKey”](#primarykey)
> **primaryKey**: `string`\[]
Defined in: packages/contracts/src/fingerprint.ts:30
***
### projection
[Section titled “projection”](#projection)
> **projection**: { `journalTable`: `string` | `null`; `omitColumns`: `string`\[]; `overlayTable`: `string` | `null`; `syncedTable`: `string` | `null`; } | `null`
Defined in: packages/contracts/src/fingerprint.ts:33
***
### retention
[Section titled “retention”](#retention)
> **retention**: `string`
Defined in: packages/contracts/src/fingerprint.ts:59
Retention (ADR-0021). Part of the fingerprint because it changes the cluster DDL — an `ephemeral` table’s whole cluster is emitted as `TEMP`/`pg_temp` — so flipping persistent↔ephemeral must force a cache rebuild + subscription reset. (Subscription timing is NOT included: it is pure runtime orchestration over identical tables and needs no rebuild.)
***
### shape
[Section titled “shape”](#shape)
> **shape**: { `electricTable`: `string` | `null`; `rowFilter`: [`CanonicalRowFilter`](/api/contracts/interfaces/canonicalrowfilter/) | `null`; `shapeKey`: `string`; `tableName`: `string`; } | `null`
Defined in: packages/contracts/src/fingerprint.ts:39
# ClientProjectionSpec
Defined in: packages/contracts/src/config.ts:309
## Properties
[Section titled “Properties”](#properties)
### journalTable?
[Section titled “journalTable?”](#journaltable)
> `optional` **journalTable?**: `string`
Defined in: packages/contracts/src/config.ts:312
***
### localPrimaryKey?
[Section titled “localPrimaryKey?”](#localprimarykey)
> `optional` **localPrimaryKey?**: [`PrimaryKeySpec`](/api/contracts/interfaces/primarykeyspec/)
Defined in: packages/contracts/src/config.ts:314
***
### omitColumns?
[Section titled “omitColumns?”](#omitcolumns)
> `optional` **omitColumns?**: readonly `string`\[]
Defined in: packages/contracts/src/config.ts:313
***
### overlayTable?
[Section titled “overlayTable?”](#overlaytable)
> `optional` **overlayTable?**: `string`
Defined in: packages/contracts/src/config.ts:311
***
### syncedTable?
[Section titled “syncedTable?”](#syncedtable)
> `optional` **syncedTable?**: `string`
Defined in: packages/contracts/src/config.ts:310
# DeferrableConstraintSpec
Defined in: packages/contracts/src/config.ts:332
## Properties
[Section titled “Properties”](#properties)
### columns
[Section titled “columns”](#columns)
> **columns**: `string`\[]
Defined in: packages/contracts/src/config.ts:334
***
### constraintName
[Section titled “constraintName”](#constraintname)
> **constraintName**: `string`
Defined in: packages/contracts/src/config.ts:333
***
### initiallyDeferred?
[Section titled “initiallyDeferred?”](#initiallydeferred)
> `optional` **initiallyDeferred?**: `boolean`
Defined in: packages/contracts/src/config.ts:335
# EventStreamEntry
Defined in: packages/contracts/src/event-stream.ts:66
A registered Event stream: its payload contract and its claim→identity stamping rule.
`payload` is a **strict** zod schema, and strictness is ENFORCED, not merely advertised: an object-rooted payload (including every object inside a union or discriminated union) must carry a `never` catchall — `.strict()` or `z.strictObject()` — or `defineSyncRegistry` rejects the registration. A stripping object would let a misspelled or newly-added key vanish between the caller and the consumer with no verdict anywhere, which is exactly the silence the Event lane’s “everything in the Outbox is well-formed” invariant exists to prevent. A NON-object root (a string, array, record, a transform pipeline, …) is accepted unchanged and follows ordinary parse-contract semantics: what the schema ACCEPTS is what `appendEvent` validates, and the JSON-NORMALIZED form of what the schema PRODUCES is what the consumer receives.
**The schema EXECUTES at both boundaries; only ONE execution’s output is taken.** `appendEvent` runs it as pure validation — the result is discarded and the Outbox stores the value the caller supplied — and the ingestion endpoint runs it again as the one AUTHORITATIVE parse, whose output is what is enqueued and what the consumer callback receives. A transform callback therefore runs twice, so **transforms must be pure and deterministic**: an effectful or environment-dependent one is unsupported, because the server’s run is the one that counts and nothing reconciles it against the client’s. Parse output the JSON value domain cannot carry (a `BigInt`, `undefined`) is a terminal per-event `rejected` at ingest, never a batch fault.
**What is enqueued is the JSON-NORMALIZED parse output, not the parse output itself.** Ingest serializes the authoritative parse’s result and enqueues the round-trip, so every queue backend — the in-memory fake and real pgmq’s `jsonb` alike — delivers the identical value. The consequence is worth designing against: a transform producing a `Date` reaches the consumer as that date’s ISO STRING, and a nested `undefined` property is dropped (an `undefined` array member becomes `null`). A transform that must round-trip as a rich type has to encode it itself.
**Schema evolution is compatibility-bound** (ADR-0053 decision 1): a payload schema may evolve only backward-compatibly — it must keep accepting every previously-valid payload, because events written offline under the old schema are still in flight. An incompatible change requires a NEW Event-stream name. The registry lock records a hash of this entry so a change is a reviewable `risky` diff; the hash DETECTS change, it cannot judge compatibility (see `registryEventStreams`).
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TPayload
[Section titled “TPayload”](#tpayload)
`TPayload` *extends* `z.ZodType` = `z.ZodType`
## Properties
[Section titled “Properties”](#properties)
### identity
[Section titled “identity”](#identity)
> **identity**: `Record`<`string`, [`EventStreamIdentityField`](/api/contracts/interfaces/eventstreamidentityfield/)>
Defined in: packages/contracts/src/event-stream.ts:72
The identity fields stamped server-side from verified claims, keyed by field name. The stamped record (field name → value) is what the consumer callback receives on every event.
***
### payload
[Section titled “payload”](#payload)
> **payload**: `TPayload`
Defined in: packages/contracts/src/event-stream.ts:67
***
### revision?
[Section titled “revision?”](#revision)
> `optional` **revision?**: `number`
Defined in: packages/contracts/src/event-stream.ts:85
An opaque version counter for the part of this payload contract the lock’s hash cannot see — the same role `RowFilterSpec.revision` plays for a `customWhere` closure. The lock hashes the payload schema as a **JSON Schema**, and refinements/transforms are simply not representable there: a reviewer demonstrated that `z.string().refine(v => v.length >= 3)` and the INCOMPATIBLE `>= 10` version produce the identical stream hash, so the `risky` diff the ADR relies on never fires.
So: whenever you change acceptance logic a JSON Schema cannot express (any `.refine`/`.superRefine` threshold, a cross-field check, a transform), you MUST bump `revision` (positive integer). That is what surfaces the change in the lock diff, where the compatibility rule can actually be reviewed. Leaving it unchanged after a refinement change silently bypasses the gate.
# EventStreamIdentityField
Defined in: packages/contracts/src/event-stream.ts:27
One claim-derived identity field of an Event stream: the JSON path into the **verified** request claims the value is stamped from at ingest.
The same addressing as a managed field’s `authClaim` strategy (`ManagedFieldSpec.claimPath`) — `["sub"]` for the auth subject, `["app_metadata", "person_id"]` for an app-minted identity — so the two claim-stamping surfaces of the toolkit read identically. Identity is **server-stamped**, never client-trusted: the client’s envelope carries no identity at all (see `eventEnvelopeSchema`), and the stamped value reaches the consumer callback on the queue envelope (`stampedEventSchema`).
## Properties
[Section titled “Properties”](#properties)
### claimPath
[Section titled “claimPath”](#claimpath)
> **claimPath**: readonly `string`\[]
Defined in: packages/contracts/src/event-stream.ts:29
JSON path into the verified claims; each segment a plain identifier. Must be non-empty.
# ManagedFieldSpec
Defined in: packages/contracts/src/config.ts:354
## Properties
[Section titled “Properties”](#properties)
### applyOn
[Section titled “applyOn”](#applyon)
> **applyOn**: [`ManagedFieldApplyOn`](/api/contracts/type-aliases/managedfieldapplyon/)\[]
Defined in: packages/contracts/src/config.ts:356
***
### cast?
[Section titled “cast?”](#cast)
> `optional` **cast?**: `string`
Defined in: packages/contracts/src/config.ts:370
Optional SQL cast for an `authClaim` value (`jsonb #>>` yields text). Defaults to the **target column’s own SQL type** (so a `uuid` column casts to `uuid` with no declaration needed). Override only to force a different cast; must be a plain SQL type name.
***
### claimPath?
[Section titled “claimPath?”](#claimpath)
> `optional` **claimPath?**: `string`\[]
Defined in: packages/contracts/src/config.ts:364
For `strategy: "authClaim"` only (required there, forbidden otherwise): the JSON path into the verified request claims to stamp from — `["sub"]`, `["app_metadata", "person_id"]`, etc. Each segment must be a plain identifier (`[A-Za-z_][A-Za-z0-9_]*`); it is emitted into the apply-function DDL as a `jsonb #>>` text-array path, so it is never a value-injection surface.
***
### column
[Section titled “column”](#column)
> **column**: `string`
Defined in: packages/contracts/src/config.ts:355
***
### strategy
[Section titled “strategy”](#strategy)
> **strategy**: [`ManagedFieldStrategy`](/api/contracts/type-aliases/managedfieldstrategy/)
Defined in: packages/contracts/src/config.ts:357
# MutationDiagnostics
Defined in: packages/contracts/src/runtime.ts:28
## Properties
[Section titled “Properties”](#properties)
### ackedCount
[Section titled “ackedCount”](#ackedcount)
> **ackedCount**: `number`
Defined in: packages/contracts/src/runtime.ts:31
***
### conflictedCount
[Section titled “conflictedCount”](#conflictedcount)
> **conflictedCount**: `number`
Defined in: packages/contracts/src/runtime.ts:39
Stale writes the server declined under the `reject-if-stale` Conflict policy (terminal, ADR-0015). The optimistic Overlay is kept; the user resolves each as a new write or discards it.
***
### failedCount
[Section titled “failedCount”](#failedcount)
> **failedCount**: `number`
Defined in: packages/contracts/src/runtime.ts:32
***
### lastAckAtUs?
[Section titled “lastAckAtUs?”](#lastackatus)
> `optional` **lastAckAtUs?**: `string`
Defined in: packages/contracts/src/runtime.ts:47
***
### lastFlushAtUs?
[Section titled “lastFlushAtUs?”](#lastflushatus)
> `optional` **lastFlushAtUs?**: `string`
Defined in: packages/contracts/src/runtime.ts:46
***
### pendingCount
[Section titled “pendingCount”](#pendingcount)
> **pendingCount**: `number`
Defined in: packages/contracts/src/runtime.ts:29
***
### quarantinedCount
[Section titled “quarantinedCount”](#quarantinedcount)
> **quarantinedCount**: `number`
Defined in: packages/contracts/src/runtime.ts:34
Mutations the server permanently rejected (terminal); surfaced, never retried (ADR-0006).
***
### rejectedCount
[Section titled “rejectedCount”](#rejectedcount)
> **rejectedCount**: `number`
Defined in: packages/contracts/src/runtime.ts:45
Whole write-**units** the authoritative endpoint declined for a business reason (terminal, ADR-0022): the optimistic Overlay is auto-discarded for every member and the typed reason is surfaced via `onReject`. Never retried (the server’s answer is authoritative).
***
### sendingCount
[Section titled “sendingCount”](#sendingcount)
> **sendingCount**: `number`
Defined in: packages/contracts/src/runtime.ts:30
# MutationSummary
Defined in: packages/contracts/src/runtime.ts:73
A registry-wide mutation-journal summary for warm-store observability: the per-status counts across EVERY writable table’s journal, folded from one aggregate query/subscription over the `pgxsinkit_all_mutations` view — so a consumer renders a global sync indicator with ONE subscription instead of one live query per writable journal. Cheap enough to mount permanently.
`unsettledCount` and `settledCount` PARTITION the total — the user-facing “is any local edit still owed?” split, NOT the automatic state machine’s terminal/non-terminal split:
* `unsettledCount` = `pending` + `sending` + `failed` + `conflicted` + `quarantined` — every write still needing work or user action. `conflicted` and `quarantined` are journal-TERMINAL in the state machine (no auto-transition — see `MUTATION_TRANSITIONS`) yet BOTH count as unsettled: their optimistic Overlay is KEPT, later writes for the entity stay blocked, `destroy()` refuses them without `force`, and local-store reconciliation counts them owed. The user must act (`discardConflict` / `discardQuarantined`, then re-author) — so from the consumer’s data-safety standpoint they are NOT done. This is exactly the restore case, where pgxsinkit deliberately quarantines recovered writes for the user to resolve, so a global “unsynced changes” indicator MUST include them.
* `settledCount` = `acked` + `rejected` — the writes that are truly done from the user’s standpoint (acked awaits only its synced echo to be reconciled away; rejected’s Overlay was auto-discarded, nothing owed).
The field is `settledCount` (not `terminalCount`): “terminal” is the state-machine word, and quarantine is legitimately terminal there while being unsettled here — the old name invited exactly that confusion.
## Properties
[Section titled “Properties”](#properties)
### ackedCount
[Section titled “ackedCount”](#ackedcount)
> **ackedCount**: `number`
Defined in: packages/contracts/src/runtime.ts:76
***
### conflictedCount
[Section titled “conflictedCount”](#conflictedcount)
> **conflictedCount**: `number`
Defined in: packages/contracts/src/runtime.ts:79
***
### failedCount
[Section titled “failedCount”](#failedcount)
> **failedCount**: `number`
Defined in: packages/contracts/src/runtime.ts:77
***
### pendingCount
[Section titled “pendingCount”](#pendingcount)
> **pendingCount**: `number`
Defined in: packages/contracts/src/runtime.ts:74
***
### quarantinedCount
[Section titled “quarantinedCount”](#quarantinedcount)
> **quarantinedCount**: `number`
Defined in: packages/contracts/src/runtime.ts:80
***
### rejectedCount
[Section titled “rejectedCount”](#rejectedcount)
> **rejectedCount**: `number`
Defined in: packages/contracts/src/runtime.ts:78
***
### sendingCount
[Section titled “sendingCount”](#sendingcount)
> **sendingCount**: `number`
Defined in: packages/contracts/src/runtime.ts:75
***
### settledCount
[Section titled “settledCount”](#settledcount)
> **settledCount**: `number`
Defined in: packages/contracts/src/runtime.ts:87
`acked` + `rejected` — settled writes; the complement of [unsettledCount](/api/contracts/interfaces/mutationsummary/#unsettledcount).
***
### unsettledCount
[Section titled “unsettledCount”](#unsettledcount)
> **unsettledCount**: `number`
Defined in: packages/contracts/src/runtime.ts:85
`pending` + `sending` + `failed` + `conflicted` + `quarantined` — every write still needing work or user action (see the interface JSDoc; quarantined + conflicted are owed local edits, not settled).
# PrimaryKeySpec
Defined in: packages/contracts/src/config.ts:258
## Properties
[Section titled “Properties”](#properties)
### columns
[Section titled “columns”](#columns)
> **columns**: `string`\[]
Defined in: packages/contracts/src/config.ts:259
# ProjectedTableColumn
Defined in: packages/contracts/src/registry.ts:102
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TTable
[Section titled “TTable”](#ttable)
`TTable` *extends* `AnyPgTable` = `AnyPgTable`
## Properties
[Section titled “Properties”](#properties)
### column
[Section titled “column”](#column)
> **column**: `PgColumn`<`any`, `PgColumnBaseConfig`<`any`>, { }>
Defined in: packages/contracts/src/registry.ts:105
***
### columnName
[Section titled “columnName”](#columnname)
> **columnName**: `string`
Defined in: packages/contracts/src/registry.ts:104
***
### propertyKey
[Section titled “propertyKey”](#propertykey)
> **propertyKey**: `Extract`\>
Defined in: packages/contracts/src/registry.ts:103
# RegistryChange
Defined in: packages/contracts/src/registry-diff.ts:22
## Properties
[Section titled “Properties”](#properties)
### detail
[Section titled “detail”](#detail)
> **detail**: `string`
Defined in: packages/contracts/src/registry-diff.ts:25
***
### severity
[Section titled “severity”](#severity)
> **severity**: [`RegistryChangeSeverity`](/api/contracts/type-aliases/registrychangeseverity/)
Defined in: packages/contracts/src/registry-diff.ts:23
***
### table
[Section titled “table”](#table)
> **table**: `string`
Defined in: packages/contracts/src/registry-diff.ts:24
# RegistryDiff
Defined in: packages/contracts/src/registry-diff.ts:28
## Properties
[Section titled “Properties”](#properties)
### changes
[Section titled “changes”](#changes)
> **changes**: [`RegistryChange`](/api/contracts/interfaces/registrychange/)\[]
Defined in: packages/contracts/src/registry-diff.ts:30
***
### severity
[Section titled “severity”](#severity)
> **severity**: [`RegistryChangeSeverity`](/api/contracts/type-aliases/registrychangeseverity/)
Defined in: packages/contracts/src/registry-diff.ts:29
# RegistryInvariantCell
Defined in: packages/contracts/src/registry-invariant.ts:41
One (entry × claims fixture) cell handed to a [RegistryInvariantSpec.holds](/api/contracts/interfaces/registryinvariantspec/#holds) predicate.
## Properties
[Section titled “Properties”](#properties)
### claims
[Section titled “claims”](#claims)
> **claims**: `object`
Defined in: packages/contracts/src/registry-invariant.ts:49
#### Index Signature
[Section titled “Index Signature”](#index-signature)
\[`key`: `string`]: `unknown`
#### app\_metadata?
[Section titled “app\_metadata?”](#app_metadata)
> `optional` **app\_metadata?**: `object`
##### Index Signature
[Section titled “Index Signature”](#index-signature-1)
\[`key`: `string`]: `unknown`
##### app\_metadata.roles?
[Section titled “app\_metadata.roles?”](#app_metadataroles)
> `optional` **roles?**: `string`\[]
#### sub?
[Section titled “sub?”](#sub)
> `optional` **sub?**: `string`
***
### entry
[Section titled “entry”](#entry)
> **entry**: [`SyncTableEntry`](/api/contracts/interfaces/synctableentry/)
Defined in: packages/contracts/src/registry-invariant.ts:44
***
### fixtureName
[Section titled “fixtureName”](#fixturename)
> **fixtureName**: `string`
Defined in: packages/contracts/src/registry-invariant.ts:48
The fixture’s name, as declared in [RegistryInvariantSpec.claimsFixtures](/api/contracts/interfaces/registryinvariantspec/#claimsfixtures).
***
### key
[Section titled “key”](#key)
> **key**: `string`
Defined in: packages/contracts/src/registry-invariant.ts:43
The entry’s key in the registry.
***
### renderedPolicies
[Section titled “renderedPolicies”](#renderedpolicies)
> **renderedPolicies**: [`RenderedPolicy`](/api/contracts/interfaces/renderedpolicy/)\[]
Defined in: packages/contracts/src/registry-invariant.ts:62
Every RLS policy attached to the entry’s Postgres table, rendered to inline SQL text.
***
### renderedWhere
[Section titled “renderedWhere”](#renderedwhere)
> **renderedWhere**: [`RowFilterShape`](/api/contracts/interfaces/rowfiltershape/) | `null`
Defined in: packages/contracts/src/registry-invariant.ts:60
The read filter this entry sends to Electric FOR THESE CLAIMS — the real read pipeline’s output (`buildRowFilterShape`, exactly what the proxy calls per shape request).
`null` means **unfiltered**, and covers both ways that arises: the entry declares no shape/`rowFilter` at all, or its `customWhere` returned `null` for these claims (the documented “bypass filtering, every row is visible” answer — e.g. an admin persona). Both are the same statement about what the client receives, which is what an invariant reasons about; distinguish them via `entry.shape?.rowFilter` if a predicate genuinely needs to.
***
### rowClass
[Section titled “rowClass”](#rowclass)
> **rowClass**: `string` | `undefined`
Defined in: packages/contracts/src/registry-invariant.ts:46
The entry’s classification, or `undefined` when it carries none (possible only for a `appliesTo` predicate).
# RegistryInvariantSpec
Defined in: packages/contracts/src/registry-invariant.ts:65
## Properties
[Section titled “Properties”](#properties)
### appliesTo
[Section titled “appliesTo”](#appliesto)
> **appliesTo**: readonly `string`\[] | ((`entry`, `key`) => `boolean`)
Defined in: packages/contracts/src/registry-invariant.ts:74
Which entries the invariant binds: a list of [SyncTableEntry.rowClass](/api/contracts/interfaces/synctableentry/#rowclass) values (the normal form — coverage then grows with the registry), or a predicate over the entry for the rare case a class cannot express. When the registry declares its `rowClasses`, a class named here that is not in that vocabulary throws immediately — a typo would otherwise bind nothing and pass vacuously.
***
### claimsFixtures
[Section titled “claimsFixtures”](#claimsfixtures)
> **claimsFixtures**: `Record`<`string`, [`JwtClaims`](/api/contracts/type-aliases/jwtclaims/)>
Defined in: packages/contracts/src/registry-invariant.ts:79
The claims personas the invariant is evaluated against, by name (`{ anonymous: {}, owner: {...} }`). Every bound entry is checked against every fixture; the names appear in the failure report.
***
### holds
[Section titled “holds”](#holds)
> **holds**: (`cell`) => `string` | `boolean`
Defined in: packages/contracts/src/registry-invariant.ts:85
The invariant itself, over ONE (entry × fixture) cell’s rendered artifacts. Return `true` when it holds, `false` or a reason string when it does not — a reason string is reproduced verbatim in the error, so prefer it.
#### Parameters
[Section titled “Parameters”](#parameters)
##### cell
[Section titled “cell”](#cell)
[`RegistryInvariantCell`](/api/contracts/interfaces/registryinvariantcell/)
#### Returns
[Section titled “Returns”](#returns)
`string` | `boolean`
***
### name
[Section titled “name”](#name)
> **name**: `string`
Defined in: packages/contracts/src/registry-invariant.ts:67
Human name for the invariant, used as the error header (e.g. “private rows never leave their owner”).
# RegistryLock
Defined in: packages/contracts/src/registry-diff.ts:34
A committed baseline of a registry’s shape — the enforcement surface for the gate.
## Properties
[Section titled “Properties”](#properties)
### rowClasses?
[Section titled “rowClasses?”](#rowclasses)
> `optional` **rowClasses?**: [`RegistryRowClasses`](/api/contracts/type-aliases/registryrowclasses/)
Defined in: packages/contracts/src/registry-diff.ts:49
Each entry’s row classification (ADR-0052), keyed by registry key; `null` = unclassified. A SIBLING of `tables`, deliberately not a field of [CanonicalTable](/api/contracts/interfaces/canonicaltable/): the canonical table shape is what `fingerprintRegistry` hashes, and that hash is PERSISTED as the local store’s cache key — so folding classification into it would make merely classifying a table wipe every store’s read cache. The lock still records it, so an un-enrolment (a class removed or changed) is a reviewable diff.
Optional because a committed lock is JSON on disk: one written before this field existed simply has no `rowClasses`, and the diff then reads the whole baseline as unclassified — so adopting classification against an old lock is a set of `compatible` declarations, never spurious risk. [buildRegistryLock](/api/contracts/functions/buildregistrylock/) always populates it.
***
### streams?
[Section titled “streams?”](#streams)
> `optional` **streams?**: [`RegistryEventStreams`](/api/contracts/type-aliases/registryeventstreams/)
Defined in: packages/contracts/src/registry-diff.ts:62
Each registered Event stream (ADR-0053 decision 1), keyed by Event-stream name → a canonical hash of its payload schema AND its identity declaration. A SIBLING of `tables` for the same reason `rowClasses` is: the canonical table shape is what `fingerprintRegistry` hashes and that hash is the local store’s PERSISTED cache key, so folding an Event stream into it would make registering one wipe every store’s read cache — for a lane that touches no synced table, no local schema and no apply function.
Optional because a committed lock is JSON on disk: one written before this field existed simply has no `streams`, and the diff then reads the whole baseline as having none — so adopting the Event lane against an old lock is a set of `compatible` additions, never spurious risk. [buildRegistryLock](/api/contracts/functions/buildregistrylock/) always populates it.
***
### tables
[Section titled “tables”](#tables)
> **tables**: [`CanonicalTable`](/api/contracts/interfaces/canonicaltable/)\[]
Defined in: packages/contracts/src/registry-diff.ts:36
***
### version
[Section titled “version”](#version)
> **version**: `string`
Defined in: packages/contracts/src/registry-diff.ts:35
# RenderedPolicy
Defined in: packages/contracts/src/registry-invariant.ts:29
One rendered write policy on a bound entry’s Postgres table, as the invariant predicate sees it.
## Properties
[Section titled “Properties”](#properties)
### command
[Section titled “command”](#command)
> **command**: `string`
Defined in: packages/contracts/src/registry-invariant.ts:33
The command the policy governs: `select` | `insert` | `update` | `delete`, or `all` when undeclared.
***
### name
[Section titled “name”](#name)
> **name**: `string`
Defined in: packages/contracts/src/registry-invariant.ts:31
The policy name as declared (e.g. `widgets_select_owner_or_admin`).
***
### using
[Section titled “using”](#using)
> **using**: `string` | `null`
Defined in: packages/contracts/src/registry-invariant.ts:35
The `USING` predicate rendered to inline SQL text, or `null` when the policy declares none.
***
### withCheck
[Section titled “withCheck”](#withcheck)
> **withCheck**: `string` | `null`
Defined in: packages/contracts/src/registry-invariant.ts:37
The `WITH CHECK` predicate rendered to inline SQL text, or `null` when the policy declares none.
# ResolvedManagedFieldSpecForTable
Defined in: packages/contracts/src/registry.ts:92
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TTable
[Section titled “TTable”](#ttable)
`TTable` *extends* `AnyPgTable`
## Properties
[Section titled “Properties”](#properties)
### applyOn
[Section titled “applyOn”](#applyon)
> **applyOn**: [`ManagedFieldApplyOn`](/api/contracts/type-aliases/managedfieldapplyon/)\[]
Defined in: packages/contracts/src/registry.ts:94
***
### column
[Section titled “column”](#column)
> **column**: `Extract`\>
Defined in: packages/contracts/src/registry.ts:93
***
### strategy
[Section titled “strategy”](#strategy)
> **strategy**: [`ManagedFieldStrategy`](/api/contracts/type-aliases/managedfieldstrategy/)
Defined in: packages/contracts/src/registry.ts:95
# ResolvedStorageDeclaration
Defined in: packages/contracts/src/config.ts:161
A [SyncStorageDeclaration](/api/contracts/interfaces/syncstoragedeclaration/) with every field resolved (ADR-0050) — the store’s BOUND declaration. Produced once per store by [resolveStorageDeclaration](/api/contracts/functions/resolvestoragedeclaration/) and immutable for the store’s lifetime: a preference change mints a fresh store under a fresh path, never rebinds an existing one.
## Properties
[Section titled “Properties”](#properties)
### backend
[Section titled “backend”](#backend)
> **backend**: [`StorageBackend`](/api/contracts/type-aliases/storagebackend/)
Defined in: packages/contracts/src/config.ts:162
***
### durability
[Section titled “durability”](#durability)
> **durability**: [`StorageDurability`](/api/contracts/type-aliases/storagedurability/)
Defined in: packages/contracts/src/config.ts:163
# RowFilterShape
Defined in: packages/contracts/src/config.ts:519
The parameterized shape filter the proxy sends to Electric: a `where` and its positional params.
## Properties
[Section titled “Properties”](#properties)
### params
[Section titled “params”](#params)
> **params**: `string`\[]
Defined in: packages/contracts/src/config.ts:521
***
### where
[Section titled “where”](#where)
> **where**: `string`
Defined in: packages/contracts/src/config.ts:520
# RowFilterSpec
Defined in: packages/contracts/src/config.ts:427
## Properties
[Section titled “Properties”](#properties)
### columns?
[Section titled “columns?”](#columns)
> `optional` **columns?**: `string`\[]
Defined in: packages/contracts/src/config.ts:447
Column projection for the shape URL (e.g. \[“id”, “source\_text”]).
***
### customWhere?
[Section titled “customWhere?”](#customwhere)
> `optional` **customWhere?**: (`claims`, `params?`) => `string` | `SQL`<`unknown`> | `null`
Defined in: packages/contracts/src/config.ts:445
The row filter: returns the Electric shape `where` for this request, or `null` to bypass filtering (e.g. admin access). **Prefer returning a Drizzle `SQL` fragment** built from the table’s columns: reference each column through [c](/api/contracts/functions/c/) (a bare, rename-safe identifier) and embed request-derived values directly — they become **bound `$n` params**, never hand-escaped literals. Enum columns must be cast to text (`${c(col)}::text = 'x'`) for Electric’s grammar, and subqueries must be self-contained (not correlated), since Electric needs plain column refs.
Returning a raw **string** is the escape hatch for a predicate Drizzle can’t express. SECURITY: a string is interpolated verbatim into the `where` — it is NOT escaped, so any request-derived value you embed must be escaped/validated (`escapeSqlLiteral`) inside this function, or it is a SQL-injection vector. Reach for the string form only when the Drizzle fragment cannot express it.
**Must be pure.** The proxy already calls this fresh on every shape request; the client also *probes* it with empty claims (`{}`) to detect claims-dependence (ADR-0039 — [isClaimsDependentRowFilter](/api/contracts/functions/isclaimsdependentrowfilter/)). Do not memoize, mutate external state, or assume it runs once.
#### Parameters
[Section titled “Parameters”](#parameters)
##### claims
[Section titled “claims”](#claims)
###### app\_metadata?
[Section titled “app\_metadata?”](#app_metadata)
{\[`key`: `string`]: `unknown`; `roles?`: `string`\[]; } = `...`
###### app\_metadata.roles?
[Section titled “app\_metadata.roles?”](#app_metadataroles)
`string`\[] = `...`
###### sub?
[Section titled “sub?”](#sub)
`string` = `...`
##### params?
[Section titled “params?”](#params)
`Record`<`string`, `unknown`>
#### Returns
[Section titled “Returns”](#returns)
`string` | `SQL`<`unknown`> | `null`
***
### revision?
[Section titled “revision?”](#revision)
> `optional` **revision?**: `string` | `number`
Defined in: packages/contracts/src/config.ts:455
An opaque version tag for the part of this filter the fingerprint cannot see — the `customWhere` body (you cannot hash a closure; only its *presence* is fingerprinted). Bump this (any new string/number) whenever you change that logic so the fingerprint shifts and the local read cache rebuilds + the shape subscription resets. Leaving it unchanged after a `customWhere` authorization change would silently serve the stale shape.
# RowTransformContext
Defined in: packages/contracts/src/config.ts:291
Context available to a [RowTransform](/api/contracts/type-aliases/rowtransform/): the verified claims and any extra runtime params.
## Properties
[Section titled “Properties”](#properties)
### claims
[Section titled “claims”](#claims)
> **claims**: {\[`key`: `string`]: `unknown`; `app_metadata?`: {\[`key`: `string`]: `unknown`; `roles?`: `string`\[]; }; `sub?`: `string`; } | `null`
Defined in: packages/contracts/src/config.ts:292
***
### params?
[Section titled “params?”](#params)
> `optional` **params?**: `Record`<`string`, `unknown`>
Defined in: packages/contracts/src/config.ts:293
# ServerProjectionSpec
Defined in: packages/contracts/src/config.ts:322
Server-side projection applied in the proxy response path. This is server authority, not client shape — it never alters the local PGlite schema or the Electric shape URL — so it lives apart from [ClientProjectionSpec](/api/contracts/interfaces/clientprojectionspec/) (ADR-0004).
## Properties
[Section titled “Properties”](#properties)
### rowTransform?
[Section titled “rowTransform?”](#rowtransform)
> `optional` **rowTransform?**: [`RowTransform`](/api/contracts/type-aliases/rowtransform/)
Defined in: packages/contracts/src/config.ts:329
Optional per-row rewrite applied in the proxy response path. Runs before column omission, so it may read a column (e.g. a control flag) that `clientProjection.omitColumns` then removes from the client-visible row. See [RowTransform](/api/contracts/type-aliases/rowtransform/).
# ShapeSpec
Defined in: packages/contracts/src/config.ts:262
## Properties
[Section titled “Properties”](#properties)
### electricTable?
[Section titled “electricTable?”](#electrictable)
> `optional` **electricTable?**: `string`
Defined in: packages/contracts/src/config.ts:276
INTERNAL / resolved — the physical Postgres table this shape reads, when it differs from the shape’s own `tableName`. A read PROJECTION (`defineReadProjection`) sets it to the OWNING table’s name so several shapes can read one physical table under distinct `shapeKey`s; the engine resolves an incoming request by `shapeKey` and consults this only on egress, to build the upstream Electric `table` param. `attachSyncRegistrySchema` also fills/qualifies it for schema-bound registries.
Not a consumer input — there is no valid reason to hand-set it (it can only be redundant with, or wrong about, the table you are reading), so it is omitted from [ShapeSpecInput](/api/contracts/type-aliases/shapespecinput/). The combinator derives it from the owner; `defineSyncTable` never sets it from input.
***
### rowFilter?
[Section titled “rowFilter?”](#rowfilter)
> `optional` **rowFilter?**: [`RowFilterSpec`](/api/contracts/interfaces/rowfilterspec/)
Defined in: packages/contracts/src/config.ts:277
***
### shapeKey
[Section titled “shapeKey”](#shapekey)
> **shapeKey**: `string`
Defined in: packages/contracts/src/config.ts:264
***
### tableName
[Section titled “tableName”](#tablename)
> **tableName**: `string`
Defined in: packages/contracts/src/config.ts:263
# SyncColumnType
Defined in: packages/contracts/src/apply-strategy.ts:15
Resolved column type descriptor for the apply ladder — derived from the registry, not the DB.
## Properties
[Section titled “Properties”](#properties)
### isArray
[Section titled “isArray”](#isarray)
> **isArray**: `boolean`
Defined in: packages/contracts/src/apply-strategy.ts:26
True when the column is an array of any dimension.
***
### isEnum
[Section titled “isEnum”](#isenum)
> **isEnum**: `boolean`
Defined in: packages/contracts/src/apply-strategy.ts:35
True when the column is a Drizzle `pgEnum` column. An enum’s [sqlType](/api/contracts/interfaces/synccolumntype/#sqltype) is its custom type NAME (not a base type), so it is not in COPY\_SAFE\_BASE\_TYPES — but enum labels round-trip losslessly through both the COPY text format and a `json_to_recordset` cast (whose cast type is the enum type name), so an enum column is COPY-safe / JSON-safe. This flag carries that positively rather than trying to whitelist every enum type name. When true, [sqlType](/api/contracts/interfaces/synccolumntype/#sqltype) is the enum type name, usable as a cast type once the applier identifier-quotes (and schema-qualifies) it.
***
### name
[Section titled “name”](#name)
> **name**: `string`
Defined in: packages/contracts/src/apply-strategy.ts:17
SQL column name (not the Drizzle property key).
***
### sqlType
[Section titled “sqlType”](#sqltype)
> **sqlType**: `string`
Defined in: packages/contracts/src/apply-strategy.ts:24
The column’s base SQL type from Drizzle’s `getSQLType()` (e.g. `"uuid"`, `"text"`, `"jsonb"`, `"bigint"`, `"timestamp with time zone"`). Array-ness is carried separately in [isArray](/api/contracts/interfaces/synccolumntype/#isarray); this string is the element/base type and is usable verbatim as a `json_to_recordset` cast type (with `[]` appended when `isArray`).
# SyncConfigInput
Defined in: packages/contracts/src/config.ts:407
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TTables
[Section titled “TTables”](#ttables)
`TTables` *extends* `Record`<`string`, [`TableSpecInput`](/api/contracts/interfaces/tablespecinput/)> = `Record`<`string`, [`TableSpecInput`](/api/contracts/interfaces/tablespecinput/)>
## Properties
[Section titled “Properties”](#properties)
### electricUrl
[Section titled “electricUrl”](#electricurl)
> **electricUrl**: `string`
Defined in: packages/contracts/src/config.ts:408
***
### localSchema?
[Section titled “localSchema?”](#localschema)
> `optional` **localSchema?**: `string`
Defined in: packages/contracts/src/config.ts:409
***
### tables
[Section titled “tables”](#tables)
> **tables**: `TTables`
Defined in: packages/contracts/src/config.ts:410
# SyncRegistryDefinition
Defined in: packages/contracts/src/registry.ts:417
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* [`SyncTableRegistry`](/api/contracts/type-aliases/synctableregistry/)
## Properties
[Section titled “Properties”](#properties)
### rowClasses?
[Section titled “rowClasses?”](#rowclasses)
> `optional` **rowClasses?**: readonly `string`\[]
Defined in: packages/contracts/src/registry.ts:440
The registry’s CLOSED row-classification vocabulary (ADR-0052) — the consumer’s own set of [SyncTableEntry.rowClass](/api/contracts/interfaces/synctableentry/#rowclass) values (pgxsinkit defines none). Declaring it turns classification into a fail-closed obligation: EVERY entry must then carry a `rowClass` drawn from this exact set, validated at [defineSyncRegistry](/api/contracts/functions/definesyncregistry/) (module eval), so a newly added entry cannot join the registry unclassified and silently escape whatever invariants its class enrols it in. The declared set is carried on the returned registry and read back with [getSyncRegistryRowClasses](/api/contracts/functions/getsyncregistryrowclasses/), which [assertRegistryInvariant](/api/contracts/functions/assertregistryinvariant/) uses to reject a misspelled class in an invariant’s `appliesTo`.
Omit it and `rowClass` is unconstrained (the bare-registry-map overload has nowhere to declare a set, so it is always unconstrained). Purely authoring metadata: it never enters the registry fingerprint.
***
### schema?
[Section titled “schema?”](#schema)
> `optional` **schema?**: `string`
Defined in: packages/contracts/src/registry.ts:418
***
### storage?
[Section titled “storage?”](#storage)
> `optional` **storage?**: [`SyncStorageDeclaration`](/api/contracts/interfaces/syncstoragedeclaration/)
Defined in: packages/contracts/src/registry.ts:427
The storage contract for every store this registry mints (ADR-0049 decision 1, ADR-0047). Part of the DATA contract, not a per-open knob: `durability` binds every toolkit-minted open (relaxed default), and `backend` scopes the BROWSER store only (`opfs` default; Node/`file` and `memory` clones unaffected). Validated at [defineSyncRegistry](/api/contracts/functions/definesyncregistry/) (fail-closed at module-eval); carried through on the returned registry and read back with [getSyncRegistryStorage](/api/contracts/functions/getsyncregistrystorage/). See [SyncStorageDeclaration](/api/contracts/interfaces/syncstoragedeclaration/).
***
### streams?
[Section titled “streams?”](#streams)
> `optional` **streams?**: [`EventStreamRegistry`](/api/contracts/type-aliases/eventstreamregistry/)
Defined in: packages/contracts/src/registry.ts:457
The registry’s **Event streams** (ADR-0053 decision 1) — the Event lane’s registration surface, keyed by Event-stream NAME (the record key IS the name; [defineEventStream](/api/contracts/functions/defineeventstream/) declares the entry). Registration lives here rather than in a parallel artifact so there is ONE contract to lock, diff and thread through `createSyncServer` and `defineSyncWorker`.
Validated fail-closed at [defineSyncRegistry](/api/contracts/functions/definesyncregistry/) (module eval, every offender named at once): the name must match `[a-z][a-z0-9_]*` and fit the pgmq queue-name budget, the payload must be a zod schema, and each identity field must name a non-empty claim path. Carried on the returned registry and read back with [getSyncRegistryStreams](/api/contracts/functions/getsyncregistrystreams/).
Streams follow the `rowClasses` precedent: they ride the registry LOCK as a sibling (added → `compatible`, removed → `breaking`, payload/identity changed → `risky`) and stay OUT of the canonical fingerprint — an Event stream touches no synced table, no local schema and no apply function, so registering one must never wipe a store’s read cache. The bare-registry-map overload has nowhere to declare streams.
***
### tables
[Section titled “tables”](#tables)
> **tables**: `TRegistry`
Defined in: packages/contracts/src/registry.ts:419
# SyncRuntimeStatus
Defined in: packages/contracts/src/runtime.ts:3
## Properties
[Section titled “Properties”](#properties)
### groups?
[Section titled “groups?”](#groups)
> `optional` **groups?**: `Record`<`string`, `boolean`>
Defined in: packages/contracts/src/runtime.ts:20
Per-consistency-group readiness (ADR-0032 decision 6): `groupKey → isReady`, updated as each group catches up. Exposed on both the in-process and worker-attached clients so an app can drive progressive per-group paint without waiting on the all-eager-groups `ready` gate. Absent until the sync runtime has started (e.g. while `syncEnabled` is false there are no groups to report).
***
### isRunning
[Section titled “isRunning”](#isrunning)
> **isRunning**: `boolean`
Defined in: packages/contracts/src/runtime.ts:12
***
### lastError?
[Section titled “lastError?”](#lasterror)
> `optional` **lastError?**: `string`
Defined in: packages/contracts/src/runtime.ts:13
***
### phase
[Section titled “phase”](#phase)
> **phase**: [`SyncRuntimePhase`](/api/contracts/type-aliases/syncruntimephase/)
Defined in: packages/contracts/src/runtime.ts:11
`auth-needed` (ADR-0013): the read path is hitting auth errors (401/403) and is retrying forever with backoff — distinct from `degraded`, which covers the read path not being SERVED (a shape stream erroring, or unable to reach the server at all) and a sync commit that exhausted its retries. The app should prompt re-login; sync auto-resumes (phase returns to `ready`/`syncing`) the instant re-authentication makes the token valid again. It never silently wedges or permanently stops.
# SyncServerAddress
Defined in: packages/contracts/src/runtime.ts:23
## Properties
[Section titled “Properties”](#properties)
### host
[Section titled “host”](#host)
> **host**: `string`
Defined in: packages/contracts/src/runtime.ts:24
***
### port
[Section titled “port”](#port)
> **port**: `number`
Defined in: packages/contracts/src/runtime.ts:25
# SyncStateViewProjection
Defined in: packages/contracts/src/convergence-model.ts:48
The qualified object names the sync-state view is generated over. The synced read cache is droppable (ADR-0006); overlay + journal are the authority. The view joins all three but stores nothing — it is a derived projection (ADR-0011 decision 2).
## Properties
[Section titled “Properties”](#properties)
### journalTable
[Section titled “journalTable”](#journaltable)
> **journalTable**: `string`
Defined in: packages/contracts/src/convergence-model.ts:54
Qualified mutation-journal table.
***
### overlayTable
[Section titled “overlayTable”](#overlaytable)
> **overlayTable**: `string`
Defined in: packages/contracts/src/convergence-model.ts:52
Qualified overlay (optimistic intent) table.
***
### syncedTable
[Section titled “syncedTable”](#syncedtable)
> **syncedTable**: `string`
Defined in: packages/contracts/src/convergence-model.ts:50
Qualified synced read-cache table.
# SyncStorageDeclaration
Defined in: packages/contracts/src/config.ts:151
The registry’s storage contract (ADR-0049 decision 1, ADR-0047). Storage is PART OF THE DATA CONTRACT: whether losing the last not-yet-flushed action is acceptable, and whether OPFS may be used at all, is decided by what the data IS — so it is declared once on the registry, not at any minting/open site.
* [durability](/api/contracts/type-aliases/storagedurability/) defaults to `"relaxed"` and binds every toolkit-minted open of every store the registry mints (its own boot AND the provision/spare path). No open-site option exists to contradict it; a capability fallback keeps the declared mode. A no-op on `memory` clones.
* [backend](/api/contracts/type-aliases/storagebackend/) defaults to `"opfs"` and scopes the BROWSER store only; Node/`file` and `memory` clones are unaffected. `"idbfs"` opts the store out of the capability/election machinery.
## Properties
[Section titled “Properties”](#properties)
### backend?
[Section titled “backend?”](#backend)
> `optional` **backend?**: [`StorageBackend`](/api/contracts/type-aliases/storagebackend/)
Defined in: packages/contracts/src/config.ts:152
***
### durability?
[Section titled “durability?”](#durability)
> `optional` **durability?**: [`StorageDurability`](/api/contracts/type-aliases/storagedurability/)
Defined in: packages/contracts/src/config.ts:153
# SyncTableEntry
Defined in: packages/contracts/src/registry.ts:122
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TTable
[Section titled “TTable”](#ttable)
`TTable` *extends* `AnyPgTable` = `AnyPgTable`
### TLocalTable
[Section titled “TLocalTable”](#tlocaltable)
`TLocalTable` *extends* `AnyPgTable` = `TTable`
## Properties
[Section titled “Properties”](#properties)
### applyMode
[Section titled “applyMode”](#applymode)
> **applyMode**: `"insert"` | `"upsert"`
Defined in: packages/contracts/src/registry.ts:143
CDC insert-apply policy (ADR-0045). Default `"insert"`: a server CDC `insert` is applied as a plain INSERT, so a genuine primary-key collision surfaces (the ADR-0014 collision-surfacing invariant). `"upsert"`: this table legitimately receives locally-derived provisional rows (e.g. written by a local trigger from another synced table), so server CDC inserts are applied idempotently as `INSERT … ON CONFLICT (pk) DO UPDATE` — the authoritative server row overwrites the provisional local row instead of failing the commit. Resolved to `"insert"` when omitted.
***
### clientProjection?
[Section titled “clientProjection?”](#clientprojection)
> `optional` **clientProjection?**: [`ClientProjectionSpecForTable`](/api/contracts/type-aliases/clientprojectionspecfortable/)<`TTable`>
Defined in: packages/contracts/src/registry.ts:164
***
### conflictPolicy?
[Section titled “conflictPolicy?”](#conflictpolicy)
> `optional` **conflictPolicy?**: [`ConflictPolicy`](/api/contracts/type-aliases/conflictpolicy/)
Defined in: packages/contracts/src/registry.ts:172
Conflict policy (ADR-0015): what happens to a stale write on this table. **Required for writable tables** (registry validation rejects an undeclared one — the third hard-require); ignored for `readonly` tables (they have no write path). See [ConflictPolicy](/api/contracts/type-aliases/conflictpolicy/).
***
### consistencyGroup?
[Section titled “consistencyGroup?”](#consistencygroup)
> `optional` **consistencyGroup?**: `string`
Defined in: packages/contracts/src/registry.ts:181
Consistency group (ADR-0009 decision 2). Tables sharing a `consistencyGroup` are synced on one `MultiShapeStream` and committed atomically at a shared LSN frontier, so a local reader never sees one grouped table advanced past another for the same server transaction. Omitted → the table is its own singleton group (independent frontier, no cross-table atomicity — the resolution for a table that declares no group). The latency cost (a group advances only as fast as its slowest shape) is contained to the tables that opt in.
***
### governance?
[Section titled “governance?”](#governance)
> `optional` **governance?**: [`TableGovernanceSpecForTable`](/api/contracts/type-aliases/tablegovernancespecfortable/)<`TTable`>
Defined in: packages/contracts/src/registry.ts:166
***
### localTable
[Section titled “localTable”](#localtable)
> **localTable**: `TLocalTable`
Defined in: packages/contracts/src/registry.ts:129
Projected client-side table for PGlite use. Columns listed in `clientProjection.omitColumns` (e.g. `created_by_id`) are absent from both the runtime table definition and the TypeScript shape of this table.
***
### makeColumns?
[Section titled “makeColumns?”](#makecolumns)
> `optional` **makeColumns?**: () => `Record`<`string`, `ColumnBuilderBase`>
Defined in: packages/contracts/src/registry.ts:222
The column-builder factory that produced this entry’s table (set by [defineSyncTable](/api/contracts/functions/definesynctable/)). Retained so [defineReadProjection](/api/contracts/functions/definereadprojection/) can reuse the owner’s column definitions to build a typed column subset without restating them, AND — since ADR-0029 P1 — so the client can derive every synced-table object (the local synced read cache, overlay, journal) via `getSyncedLocalTable` → `projectedColumnBuilders`. It is therefore read-derivation machinery, not a write handle: it is carried through every projection ([asReadonly](/api/contracts/functions/asreadonly/), `withRetention`, `defineReadProjection`), NOT fingerprinted (functions are invisible to the read-contract hash), and required on every registered entry — `defineSyncRegistry`/`validateSyncTableEntry` reject a hand-assembled entry that lacks it, since the client hard-requires it at boot.
#### Returns
[Section titled “Returns”](#returns)
`Record`<`string`, `ColumnBuilderBase`>
***
### mode
[Section titled “mode”](#mode)
> **mode**: [`TableMode`](/api/contracts/type-aliases/tablemode/)
Defined in: packages/contracts/src/registry.ts:133
***
### primaryKey
[Section titled “primaryKey”](#primarykey)
> **primaryKey**: [`PrimaryKeySpec`](/api/contracts/interfaces/primarykeyspec/)
Defined in: packages/contracts/src/registry.ts:134
***
### readProjection?
[Section titled “readProjection?”](#readprojection)
> `optional` **readProjection?**: `boolean`
Defined in: packages/contracts/src/registry.ts:210
True when this entry is a read PROJECTION over a table OWNED by another entry (built by [defineReadProjection](/api/contracts/functions/definereadprojection/)). Such an entry owns no physical table — its `table` is the owner’s, and only its `localTable` + `shape` are its own — so migration/apply/RLS generation skips it and a consumer’s schema barrel must never export a fresh table for it. Absent → the entry owns its table.
***
### retention?
[Section titled “retention?”](#retention)
> `optional` **retention?**: [`Retention`](/api/contracts/type-aliases/retention/)
Defined in: packages/contracts/src/registry.ts:195
Retention (ADR-0021): `persistent` (default) | `ephemeral`. An `ephemeral` table’s whole local cluster is emitted as `TEMP` — no durable trace, no durable offline write queue. A property of the consistency group — every table sharing a `consistencyGroup` must agree (validated). See [Retention](/api/contracts/type-aliases/retention/).
***
### rowClass?
[Section titled “rowClass?”](#rowclass)
> `optional` **rowClass?**: `string`
Defined in: packages/contracts/src/registry.ts:162
Consumer-defined ROW CLASSIFICATION (ADR-0052) — documentation-as-code for what KIND of rows this entry carries. The vocabulary is entirely the CONSUMER’s: pgxsinkit defines no values and attaches no behaviour to any of them. Its two jobs:
* **Fail-closed enumeration.** When the registry declares its vocabulary ([SyncRegistryDefinition.rowClasses](/api/contracts/interfaces/syncregistrydefinition/#rowclasses)), EVERY entry must carry a `rowClass` drawn from that set — validated at `defineSyncRegistry`, i.e. at module eval. A new entry therefore cannot join the registry without its author classifying it, which is what stops a privacy/visibility obligation from being silently inherited by tables nobody remembered to enumerate.
* **The binding key for [assertRegistryInvariant](/api/contracts/functions/assertregistryinvariant/).** An invariant binds to `rowClass` values rather than to a hand-maintained table list, so coverage grows with the registry instead of drifting behind it.
When the registry declares no `rowClasses`, this field is unconstrained (any string, or none). It is authoring metadata only: it is deliberately absent from the registry fingerprint and the read-contract fingerprint, so classifying a table never shifts a persisted cache key (ADR-0052).
***
### serverProjection?
[Section titled “serverProjection?”](#serverprojection)
> `optional` **serverProjection?**: [`ServerProjectionSpec`](/api/contracts/interfaces/serverprojectionspec/)
Defined in: packages/contracts/src/registry.ts:165
***
### shape?
[Section titled “shape?”](#shape)
> `optional` **shape?**: [`ShapeSpec`](/api/contracts/interfaces/shapespec/)
Defined in: packages/contracts/src/registry.ts:163
***
### subscription?
[Section titled “subscription?”](#subscription)
> `optional` **subscription?**: [`SubscriptionTiming`](/api/contracts/type-aliases/subscriptiontiming/)
Defined in: packages/contracts/src/registry.ts:188
Subscription timing (ADR-0021): `eager` (default) | `lazy`. A `lazy` table is excluded from the boot subscription set and subscribed on first query-reference. A property of the **consistency group** — every table sharing a `consistencyGroup` must agree (validated). See [SubscriptionTiming](/api/contracts/type-aliases/subscriptiontiming/).
***
### table
[Section titled “table”](#table)
> **table**: `TTable`
Defined in: packages/contracts/src/registry.ts:123
***
### view?
[Section titled “view?”](#view)
> `optional` **view?**: `AnyPgView`
Defined in: packages/contracts/src/registry.ts:132
***
### writeMode?
[Section titled “writeMode?”](#writemode)
> `optional` **writeMode?**: [`WriteMode`](/api/contracts/type-aliases/writemode/)
Defined in: packages/contracts/src/registry.ts:203
Write-mode (ADR-0022): `optimistic` (default) | `pessimistic`. A `pessimistic` consistency group is a standing server-authoritative write-unit — its writes flush-route to the authoritative endpoint and the UI shows success only after the server confirms. Write-mode is a property of the **write-unit**; the static write-unit is the consistency group, so every table sharing a `consistencyGroup` must agree (validated). See [WriteMode](/api/contracts/type-aliases/writemode/).
# TableGovernanceSpec
Defined in: packages/contracts/src/config.ts:373
## Properties
[Section titled “Properties”](#properties)
### deferrableConstraints?
[Section titled “deferrableConstraints?”](#deferrableconstraints)
> `optional` **deferrableConstraints?**: [`DeferrableConstraintSpec`](/api/contracts/interfaces/deferrableconstraintspec/)\[]
Defined in: packages/contracts/src/config.ts:374
***
### managedFields?
[Section titled “managedFields?”](#managedfields)
> `optional` **managedFields?**: [`ManagedFieldSpec`](/api/contracts/interfaces/managedfieldspec/)\[]
Defined in: packages/contracts/src/config.ts:375
# TableSpecInput
Defined in: packages/contracts/src/config.ts:378
## Properties
[Section titled “Properties”](#properties)
### clientProjection?
[Section titled “clientProjection?”](#clientprojection)
> `optional` **clientProjection?**: [`ClientProjectionSpec`](/api/contracts/interfaces/clientprojectionspec/)
Defined in: packages/contracts/src/config.ts:382
***
### consistencyGroup?
[Section titled “consistencyGroup?”](#consistencygroup)
> `optional` **consistencyGroup?**: `string`
Defined in: packages/contracts/src/config.ts:388
Consistency group (ADR-0009 decision 2): tables sharing a group sync on one `MultiShapeStream` and commit atomically at a shared LSN frontier. Absent → the table is its own singleton group.
***
### governance?
[Section titled “governance?”](#governance)
> `optional` **governance?**: [`TableGovernanceSpec`](/api/contracts/interfaces/tablegovernancespec/)
Defined in: packages/contracts/src/config.ts:383
***
### mode
[Section titled “mode”](#mode)
> **mode**: [`TableMode`](/api/contracts/type-aliases/tablemode/)
Defined in: packages/contracts/src/config.ts:379
***
### primaryKey
[Section titled “primaryKey”](#primarykey)
> **primaryKey**: [`PrimaryKeySpec`](/api/contracts/interfaces/primarykeyspec/)
Defined in: packages/contracts/src/config.ts:380
***
### retention?
[Section titled “retention?”](#retention)
> `optional` **retention?**: [`Retention`](/api/contracts/type-aliases/retention/)
Defined in: packages/contracts/src/config.ts:398
Retention (ADR-0021). Absent → `persistent`. An `ephemeral` table’s whole local cluster is emitted as `TEMP` — no durable trace. See [Retention](/api/contracts/type-aliases/retention/).
***
### shape?
[Section titled “shape?”](#shape)
> `optional` **shape?**: [`ShapeSpec`](/api/contracts/interfaces/shapespec/)
Defined in: packages/contracts/src/config.ts:381
***
### subscription?
[Section titled “subscription?”](#subscription)
> `optional` **subscription?**: [`SubscriptionTiming`](/api/contracts/type-aliases/subscriptiontiming/)
Defined in: packages/contracts/src/config.ts:393
Subscription timing (ADR-0021). Absent → `eager`. A `lazy` table is excluded from the boot subscription set and subscribed on first query-reference. See [SubscriptionTiming](/api/contracts/type-aliases/subscriptiontiming/).
***
### writeMode?
[Section titled “writeMode?”](#writemode)
> `optional` **writeMode?**: [`WriteMode`](/api/contracts/type-aliases/writemode/)
Defined in: packages/contracts/src/config.ts:404
Write-mode (ADR-0022). Absent → `optimistic`. A `pessimistic` consistency group is a standing server-authoritative write-unit whose writes flush-route to the authoritative endpoint. See [WriteMode](/api/contracts/type-aliases/writemode/).
# @pgxsinkit/contracts
## Classes
[Section titled “Classes”](#classes)
* [StorageDeclarationRefusedError](/api/contracts/classes/storagedeclarationrefusederror/)
## Interfaces
[Section titled “Interfaces”](#interfaces)
* [CanonicalColumn](/api/contracts/interfaces/canonicalcolumn/)
* [CanonicalReadContract](/api/contracts/interfaces/canonicalreadcontract/)
* [CanonicalRowFilter](/api/contracts/interfaces/canonicalrowfilter/)
* [CanonicalTable](/api/contracts/interfaces/canonicaltable/)
* [ClientProjectionSpec](/api/contracts/interfaces/clientprojectionspec/)
* [DeferrableConstraintSpec](/api/contracts/interfaces/deferrableconstraintspec/)
* [EventStreamEntry](/api/contracts/interfaces/eventstreamentry/)
* [EventStreamIdentityField](/api/contracts/interfaces/eventstreamidentityfield/)
* [ManagedFieldSpec](/api/contracts/interfaces/managedfieldspec/)
* [MutationDiagnostics](/api/contracts/interfaces/mutationdiagnostics/)
* [MutationSummary](/api/contracts/interfaces/mutationsummary/)
* [PrimaryKeySpec](/api/contracts/interfaces/primarykeyspec/)
* [ProjectedTableColumn](/api/contracts/interfaces/projectedtablecolumn/)
* [RegistryChange](/api/contracts/interfaces/registrychange/)
* [RegistryDiff](/api/contracts/interfaces/registrydiff/)
* [RegistryInvariantCell](/api/contracts/interfaces/registryinvariantcell/)
* [RegistryInvariantSpec](/api/contracts/interfaces/registryinvariantspec/)
* [RegistryLock](/api/contracts/interfaces/registrylock/)
* [RenderedPolicy](/api/contracts/interfaces/renderedpolicy/)
* [ResolvedManagedFieldSpecForTable](/api/contracts/interfaces/resolvedmanagedfieldspecfortable/)
* [ResolvedStorageDeclaration](/api/contracts/interfaces/resolvedstoragedeclaration/)
* [RowFilterShape](/api/contracts/interfaces/rowfiltershape/)
* [RowFilterSpec](/api/contracts/interfaces/rowfilterspec/)
* [RowTransformContext](/api/contracts/interfaces/rowtransformcontext/)
* [ServerProjectionSpec](/api/contracts/interfaces/serverprojectionspec/)
* [ShapeSpec](/api/contracts/interfaces/shapespec/)
* [SyncColumnType](/api/contracts/interfaces/synccolumntype/)
* [SyncConfigInput](/api/contracts/interfaces/syncconfiginput/)
* [SyncRegistryDefinition](/api/contracts/interfaces/syncregistrydefinition/)
* [SyncRuntimeStatus](/api/contracts/interfaces/syncruntimestatus/)
* [SyncServerAddress](/api/contracts/interfaces/syncserveraddress/)
* [SyncStateViewProjection](/api/contracts/interfaces/syncstateviewprojection/)
* [SyncStorageDeclaration](/api/contracts/interfaces/syncstoragedeclaration/)
* [SyncTableEntry](/api/contracts/interfaces/synctableentry/)
* [TableGovernanceSpec](/api/contracts/interfaces/tablegovernancespec/)
* [TableSpecInput](/api/contracts/interfaces/tablespecinput/)
## Type Aliases
[Section titled “Type Aliases”](#type-aliases)
* [ApplyStrategy](/api/contracts/type-aliases/applystrategy/)
* [AuthoritativeWriteRequest](/api/contracts/type-aliases/authoritativewriterequest/)
* [BatchEventAck](/api/contracts/type-aliases/batcheventack/)
* [BatchEventError](/api/contracts/type-aliases/batcheventerror/)
* [BatchEventRequest](/api/contracts/type-aliases/batcheventrequest/)
* [BatchMutationAck](/api/contracts/type-aliases/batchmutationack/)
* [BatchMutationError](/api/contracts/type-aliases/batchmutationerror/)
* [BatchMutationRequest](/api/contracts/type-aliases/batchmutationrequest/)
* [ClientProjectionSpecForTable](/api/contracts/type-aliases/clientprojectionspecfortable/)
* [ConflictPolicy](/api/contracts/type-aliases/conflictpolicy/)
* [DeferrableConstraintSpecForTable](/api/contracts/type-aliases/deferrableconstraintspecfortable/)
* [EntityKey](/api/contracts/type-aliases/entitykey/)
* [EventAck](/api/contracts/type-aliases/eventack/)
* [EventAckStatus](/api/contracts/type-aliases/eventackstatus/)
* [EventEnvelope](/api/contracts/type-aliases/eventenvelope/)
* [EventQueueMessage](/api/contracts/type-aliases/eventqueuemessage/)
* [EventStreamPayload](/api/contracts/type-aliases/eventstreampayload/)
* [EventStreamRegistry](/api/contracts/type-aliases/eventstreamregistry/)
* [GrantScopeAccess](/api/contracts/type-aliases/grantscopeaccess/)
* [GrantScopeAccessOptions](/api/contracts/type-aliases/grantscopeaccessoptions/)
* [GrantScopeBypassOptions](/api/contracts/type-aliases/grantscopebypassoptions/)
* [GrantScopeClaimOptions](/api/contracts/type-aliases/grantscopeclaimoptions/)
* [JwtClaims](/api/contracts/type-aliases/jwtclaims/)
* [ManagedFieldApplyOn](/api/contracts/type-aliases/managedfieldapplyon/)
* [ManagedFieldSpecForTable](/api/contracts/type-aliases/managedfieldspecfortable/)
* [ManagedFieldStrategy](/api/contracts/type-aliases/managedfieldstrategy/)
* [MutationAck](/api/contracts/type-aliases/mutationack/)
* [MutationAckStatus](/api/contracts/type-aliases/mutationackstatus/)
* [MutationEnvelope](/api/contracts/type-aliases/mutationenvelope/)
* [MutationKind](/api/contracts/type-aliases/mutationkind/)
* [MutationRejection](/api/contracts/type-aliases/mutationrejection/)
* [MutationStatus](/api/contracts/type-aliases/mutationstatus/)
* [OwnerOrAdminAccess](/api/contracts/type-aliases/owneroradminaccess/)
* [OwnerOrAdminAccessOptions](/api/contracts/type-aliases/owneroradminaccessoptions/)
* [RegistryChangeSeverity](/api/contracts/type-aliases/registrychangeseverity/)
* [RegistryEventStreams](/api/contracts/type-aliases/registryeventstreams/)
* [RegistryRelations](/api/contracts/type-aliases/registryrelations/)
* [RegistryRowClasses](/api/contracts/type-aliases/registryrowclasses/)
* [RegistryTables](/api/contracts/type-aliases/registrytables/)
* [RegistryViews](/api/contracts/type-aliases/registryviews/)
* [Retention](/api/contracts/type-aliases/retention/)
* [RowFilterInput](/api/contracts/type-aliases/rowfilterinput/)
* [RowTransform](/api/contracts/type-aliases/rowtransform/)
* [ShapeSpecInput](/api/contracts/type-aliases/shapespecinput/)
* [ShapeSpecInputFor](/api/contracts/type-aliases/shapespecinputfor/)
* [StampedEvent](/api/contracts/type-aliases/stampedevent/)
* [StorageBackend](/api/contracts/type-aliases/storagebackend/)
* [StorageDurability](/api/contracts/type-aliases/storagedurability/)
* [SubscriptionTiming](/api/contracts/type-aliases/subscriptiontiming/)
* [SupabaseGrantScopeNativePoliciesOptions](/api/contracts/type-aliases/supabasegrantscopenativepoliciesoptions/)
* [SupabaseGrantScopePredicateColumns](/api/contracts/type-aliases/supabasegrantscopepredicatecolumns/)
* [SupabaseMembershipNativePoliciesOptions](/api/contracts/type-aliases/supabasemembershipnativepoliciesoptions/)
* [SupabaseMembershipPredicateColumns](/api/contracts/type-aliases/supabasemembershippredicatecolumns/)
* [SupabaseMembershipShapeColumns](/api/contracts/type-aliases/supabasemembershipshapecolumns/)
* [SupabaseMembershipWriteGateColumns](/api/contracts/type-aliases/supabasemembershipwritegatecolumns/)
* [SupabaseOwnerOrAdminNativePoliciesOptions](/api/contracts/type-aliases/supabaseowneroradminnativepoliciesoptions/)
* [SupabaseOwnerOrAdminPredicateOptions](/api/contracts/type-aliases/supabaseowneroradminpredicateoptions/)
* [SyncRuntimePhase](/api/contracts/type-aliases/syncruntimephase/)
* [SyncTableCreateInput](/api/contracts/type-aliases/synctablecreateinput/)
* [SyncTableInput](/api/contracts/type-aliases/synctableinput/)
* [SyncTableInputGovernance](/api/contracts/type-aliases/synctableinputgovernance/)
* [SyncTableInputProjection](/api/contracts/type-aliases/synctableinputprojection/)
* [SyncTableName](/api/contracts/type-aliases/synctablename/)
* [SyncTableRecord](/api/contracts/type-aliases/synctablerecord/)
* [SyncTableRegistry](/api/contracts/type-aliases/synctableregistry/)
* [SyncTableUpdateInput](/api/contracts/type-aliases/synctableupdateinput/)
* [TableColumnKey](/api/contracts/type-aliases/tablecolumnkey/)
* [TableGovernanceSpecForTable](/api/contracts/type-aliases/tablegovernancespecfortable/)
* [TableMode](/api/contracts/type-aliases/tablemode/)
* [WriteMode](/api/contracts/type-aliases/writemode/)
## Variables
[Section titled “Variables”](#variables)
* [authoritativeWriteRequestSchema](/api/contracts/variables/authoritativewriterequestschema/)
* [batchEventAckSchema](/api/contracts/variables/batcheventackschema/)
* [batchEventErrorSchema](/api/contracts/variables/batcheventerrorschema/)
* [batchEventPaths](/api/contracts/variables/batcheventpaths/)
* [batchEventRequestSchema](/api/contracts/variables/batcheventrequestschema/)
* [batchMutationAckSchema](/api/contracts/variables/batchmutationackschema/)
* [batchMutationErrorSchema](/api/contracts/variables/batchmutationerrorschema/)
* [batchMutationRequestSchema](/api/contracts/variables/batchmutationrequestschema/)
* [CLOCK\_US\_CALL\_SQL\_TEXT](/api/contracts/variables/clock_us_call_sql_text/)
* [clockMicrosecondsSql](/api/contracts/variables/clockmicrosecondssql/)
* [CONFLICT\_POLICIES](/api/contracts/variables/conflict_policies/)
* [CONVERGENCE\_EVENTS](/api/contracts/variables/convergence_events/)
* [DENY\_ALL](/api/contracts/variables/deny_all/)
* [entityKeySchema](/api/contracts/variables/entitykeyschema/)
* [EVENT\_STREAM\_NAME\_MAX\_LENGTH](/api/contracts/variables/event_stream_name_max_length/)
* [EVENT\_STREAM\_NAME\_PATTERN](/api/contracts/variables/event_stream_name_pattern/)
* [EVENT\_STREAM\_QUEUE\_PREFIX](/api/contracts/variables/event_stream_queue_prefix/)
* [eventAckSchema](/api/contracts/variables/eventackschema/)
* [eventAckStatusSchema](/api/contracts/variables/eventackstatusschema/)
* [eventEnvelopeSchema](/api/contracts/variables/eventenvelopeschema/)
* [eventQueueMessageSchema](/api/contracts/variables/eventqueuemessageschema/)
* [jwtClaimsSchema](/api/contracts/variables/jwtclaimsschema/)
* [MAX\_EVENT\_PAYLOAD\_BYTES](/api/contracts/variables/max_event_payload_bytes/)
* [MAX\_EVENT\_REQUEST\_BYTES](/api/contracts/variables/max_event_request_bytes/)
* [MAX\_EVENTS\_PER\_BATCH](/api/contracts/variables/max_events_per_batch/)
* [mutationAckSchema](/api/contracts/variables/mutationackschema/)
* [mutationAckStatusSchema](/api/contracts/variables/mutationackstatusschema/)
* [mutationEnvelopeSchema](/api/contracts/variables/mutationenvelopeschema/)
* [mutationKindSchema](/api/contracts/variables/mutationkindschema/)
* [mutationRejectionSchema](/api/contracts/variables/mutationrejectionschema/)
* [mutationStatusSchema](/api/contracts/variables/mutationstatusschema/)
* [NOW\_MICROSECONDS\_SQL\_TEXT](/api/contracts/variables/now_microseconds_sql_text/)
* [RETENTIONS](/api/contracts/variables/retentions/)
* [stampedEventSchema](/api/contracts/variables/stampedeventschema/)
* [STORAGE\_BACKENDS](/api/contracts/variables/storage_backends/)
* [STORAGE\_DURABILITIES](/api/contracts/variables/storage_durabilities/)
* [SUBSCRIPTION\_TIMINGS](/api/contracts/variables/subscription_timings/)
* [supabaseOwnerOrAdminDefaults](/api/contracts/variables/supabaseowneroradmindefaults/)
* [syncRegistryRowClassesSymbol](/api/contracts/variables/syncregistryrowclassessymbol/)
* [syncRegistrySchemaSymbol](/api/contracts/variables/syncregistryschemasymbol/)
* [syncRegistryStorageSymbol](/api/contracts/variables/syncregistrystoragesymbol/)
* [syncRegistryStreamsSymbol](/api/contracts/variables/syncregistrystreamssymbol/)
* [unixMicrosecondsSchema](/api/contracts/variables/unixmicrosecondsschema/)
* [WRITE\_MODES](/api/contracts/variables/write_modes/)
## Functions
[Section titled “Functions”](#functions)
* [asEphemeral](/api/contracts/functions/asephemeral/)
* [asReadonly](/api/contracts/functions/asreadonly/)
* [assertReadContractPreserved](/api/contracts/functions/assertreadcontractpreserved/)
* [assertRegistryInvariant](/api/contracts/functions/assertregistryinvariant/)
* [assertStorageDeclarationCompatible](/api/contracts/functions/assertstoragedeclarationcompatible/)
* [attachSyncRegistryRowClasses](/api/contracts/functions/attachsyncregistryrowclasses/)
* [attachSyncRegistrySchema](/api/contracts/functions/attachsyncregistryschema/)
* [attachSyncRegistryStorage](/api/contracts/functions/attachsyncregistrystorage/)
* [attachSyncRegistryStreams](/api/contracts/functions/attachsyncregistrystreams/)
* [buildGrantScopeAccessShapeWhere](/api/contracts/functions/buildgrantscopeaccessshapewhere/)
* [buildGrantScopeShapeWhere](/api/contracts/functions/buildgrantscopeshapewhere/)
* [buildMembershipShapeWhere](/api/contracts/functions/buildmembershipshapewhere/)
* [buildOverlayResolutionBarrier](/api/contracts/functions/buildoverlayresolutionbarrier/)
* [buildOwnerOrAdminShapeWhere](/api/contracts/functions/buildowneroradminshapewhere/)
* [buildOwnershipShapeWhere](/api/contracts/functions/buildownershipshapewhere/)
* [buildRegistryLock](/api/contracts/functions/buildregistrylock/)
* [buildRoleGuardedStatement](/api/contracts/functions/buildroleguardedstatement/)
* [buildRowFilterShape](/api/contracts/functions/buildrowfiltershape/)
* [buildSupabaseGrantScopeNativePolicies](/api/contracts/functions/buildsupabasegrantscopenativepolicies/)
* [buildSupabaseMembershipNativePolicies](/api/contracts/functions/buildsupabasemembershipnativepolicies/)
* [buildSupabaseOwnerOrAdminNativePolicies](/api/contracts/functions/buildsupabaseowneroradminnativepolicies/)
* [buildSupabaseOwnerOrAdminPredicateSqlText](/api/contracts/functions/buildsupabaseowneroradminpredicatesqltext/)
* [buildSyncStateView](/api/contracts/functions/buildsyncstateview/)
* [c](/api/contracts/functions/c/)
* [canonicalizeReadContract](/api/contracts/functions/canonicalizereadcontract/)
* [canonicalizeRegistry](/api/contracts/functions/canonicalizeregistry/)
* [canonicalReadContractString](/api/contracts/functions/canonicalreadcontractstring/)
* [canonicalRegistryString](/api/contracts/functions/canonicalregistrystring/)
* [classifyApplyStrategy](/api/contracts/functions/classifyapplystrategy/)
* [classifyTableApplyStrategy](/api/contracts/functions/classifytableapplystrategy/)
* [compareRegistries](/api/contracts/functions/compareregistries/)
* [defineEventStream](/api/contracts/functions/defineeventstream/)
* [defineReadProjection](/api/contracts/functions/definereadprojection/)
* [defineSyncRegistry](/api/contracts/functions/definesyncregistry/)
* [defineSyncTable](/api/contracts/functions/definesynctable/)
* [deriveSyncColumnTypes](/api/contracts/functions/derivesynccolumntypes/)
* [diffCanonicalRegistries](/api/contracts/functions/diffcanonicalregistries/)
* [diffRegistryAgainstLock](/api/contracts/functions/diffregistryagainstlock/)
* [escapeSqlLiteral](/api/contracts/functions/escapesqlliteral/)
* [fingerprintReadContract](/api/contracts/functions/fingerprintreadcontract/)
* [fingerprintRegistry](/api/contracts/functions/fingerprintregistry/)
* [getLocalSyncedTablePrimaryKeyColumns](/api/contracts/functions/getlocalsyncedtableprimarykeycolumns/)
* [getLocalSyncPrimaryKey](/api/contracts/functions/getlocalsyncprimarykey/)
* [getLocalSyncPrimaryKeyColumns](/api/contracts/functions/getlocalsyncprimarykeycolumns/)
* [getOmittedProjectedColumnNames](/api/contracts/functions/getomittedprojectedcolumnnames/)
* [getOmittedProjectedColumns](/api/contracts/functions/getomittedprojectedcolumns/)
* [getProjectedColumnNames](/api/contracts/functions/getprojectedcolumnnames/)
* [getProjectedColumns](/api/contracts/functions/getprojectedcolumns/)
* [getSyncRegistryRowClasses](/api/contracts/functions/getsyncregistryrowclasses/)
* [getSyncRegistrySchema](/api/contracts/functions/getsyncregistryschema/)
* [getSyncRegistryStorage](/api/contracts/functions/getsyncregistrystorage/)
* [getSyncRegistryStreams](/api/contracts/functions/getsyncregistrystreams/)
* [hashString](/api/contracts/functions/hashstring/)
* [hasNonStrictObjectRoot](/api/contracts/functions/hasnonstrictobjectroot/)
* [isClaimsDependentRowFilter](/api/contracts/functions/isclaimsdependentrowfilter/)
* [isConflictPolicy](/api/contracts/functions/isconflictpolicy/)
* [isManagedFieldGuarded](/api/contracts/functions/ismanagedfieldguarded/)
* [isRetention](/api/contracts/functions/isretention/)
* [isStorageBackend](/api/contracts/functions/isstoragebackend/)
* [isStorageDurability](/api/contracts/functions/isstoragedurability/)
* [isSubscriptionTiming](/api/contracts/functions/issubscriptiontiming/)
* [isWriteMode](/api/contracts/functions/iswritemode/)
* [isZodSchema](/api/contracts/functions/iszodschema/)
* [maybeQuoteIdentifier](/api/contracts/functions/maybequoteidentifier/)
* [normalizeCastPositionType](/api/contracts/functions/normalizecastpositiontype/)
* [quoteIdentifier](/api/contracts/functions/quoteidentifier/)
* [quoteSqlLiteral](/api/contracts/functions/quotesqlliteral/)
* [registryEventStreams](/api/contracts/functions/registryeventstreams/)
* [registryRowClasses](/api/contracts/functions/registryrowclasses/)
* [resolveGrantScopeAccess](/api/contracts/functions/resolvegrantscopeaccess/)
* [resolveGrantScopeIds](/api/contracts/functions/resolvegrantscopeids/)
* [resolveOwnerOrAdminAccess](/api/contracts/functions/resolveowneroradminaccess/)
* [resolveServerVersionColumnName](/api/contracts/functions/resolveserverversioncolumnname/)
* [resolveStorageDeclaration](/api/contracts/functions/resolvestoragedeclaration/)
* [runRegistryCheck](/api/contracts/functions/runregistrycheck/)
* [summarizeRegistryDiff](/api/contracts/functions/summarizeregistrydiff/)
* [withRetention](/api/contracts/functions/withretention/)
# ApplyStrategy
> **ApplyStrategy** = `"copy"` | `"json"` | `"insert"`
Defined in: packages/contracts/src/apply-strategy.ts:12
The bulk-insert path the engine uses for a fresh subscription’s initial backfill.
# AuthoritativeWriteRequest
> **AuthoritativeWriteRequest** = `z.infer`<*typeof* [`authoritativeWriteRequestSchema`](/api/contracts/variables/authoritativewriterequestschema/)>
Defined in: packages/contracts/src/mutation.ts:143
# BatchEventAck
> **BatchEventAck** = `z.infer`<*typeof* [`batchEventAckSchema`](/api/contracts/variables/batcheventackschema/)>
Defined in: packages/contracts/src/events.ts:151
# BatchEventError
> **BatchEventError** = `z.infer`<*typeof* [`batchEventErrorSchema`](/api/contracts/variables/batcheventerrorschema/)>
Defined in: packages/contracts/src/events.ts:152
# BatchEventRequest
> **BatchEventRequest** = `z.infer`<*typeof* [`batchEventRequestSchema`](/api/contracts/variables/batcheventrequestschema/)>
Defined in: packages/contracts/src/events.ts:148
# BatchMutationAck
> **BatchMutationAck** = `z.infer`<*typeof* [`batchMutationAckSchema`](/api/contracts/variables/batchmutationackschema/)>
Defined in: packages/contracts/src/mutation.ts:144
# BatchMutationError
> **BatchMutationError** = `z.infer`<*typeof* [`batchMutationErrorSchema`](/api/contracts/variables/batchmutationerrorschema/)>
Defined in: packages/contracts/src/mutation.ts:146
# BatchMutationRequest
> **BatchMutationRequest** = `z.infer`<*typeof* [`batchMutationRequestSchema`](/api/contracts/variables/batchmutationrequestschema/)>
Defined in: packages/contracts/src/mutation.ts:142
# ClientProjectionSpecForTable
> **ClientProjectionSpecForTable**<`TTable`> = `Omit`<[`ClientProjectionSpec`](/api/contracts/interfaces/clientprojectionspec/), `"omitColumns"`> & `object`
Defined in: packages/contracts/src/registry.ts:98
## Type Declaration
[Section titled “Type Declaration”](#type-declaration)
### omitColumns?
[Section titled “omitColumns?”](#omitcolumns)
> `optional` **omitColumns?**: readonly [`TableColumnKey`](/api/contracts/type-aliases/tablecolumnkey/)<`TTable`>\[]
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TTable
[Section titled “TTable”](#ttable)
`TTable` *extends* `AnyPgTable`
# ConflictPolicy
> **ConflictPolicy** = `"last-write-wins"` | `"reject-if-stale"`
Defined in: packages/contracts/src/config.ts:25
The per-writable-table Conflict policy (ADR-0015): what happens to a **stale** write — one whose Base server version is behind the row’s current Server version at apply (an external write interleaved). It is a **required** declaration on every writable table; there is no silent default (registry validation rejects an undeclared writable table — the third hard-require). v1:
* `last-write-wins` — apply the stale write anyway. A required, named declaration: the toolkit never silently clobbers under an unspecified default, so choosing this is an explicit acceptance of the stale-overwrite semantics.
* `reject-if-stale` — do not apply; surface the conflict so the user’s edit is kept (the optimistic Overlay stays, marked conflicted) and resolved as a new write.
`field-merge` (apply only the changed fields over the current row) and `custom-resolver` (a client re-resolution protocol) are reserved values for future policies — declared here so the policy surface names its full intended range.
# DeferrableConstraintSpecForTable
> **DeferrableConstraintSpecForTable**<`TTable`> = `Omit`<[`DeferrableConstraintSpec`](/api/contracts/interfaces/deferrableconstraintspec/), `"columns"`> & `object`
Defined in: packages/contracts/src/registry.ts:84
## Type Declaration
[Section titled “Type Declaration”](#type-declaration)
### columns
[Section titled “columns”](#columns)
> **columns**: [`TableColumnKey`](/api/contracts/type-aliases/tablecolumnkey/)<`TTable`>\[]
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TTable
[Section titled “TTable”](#ttable)
`TTable` *extends* `AnyPgTable`
# EntityKey
> **EntityKey** = `z.infer`<*typeof* [`entityKeySchema`](/api/contracts/variables/entitykeyschema/)>
Defined in: packages/contracts/src/mutation.ts:139
# EventAck
> **EventAck** = `z.infer`<*typeof* [`eventAckSchema`](/api/contracts/variables/eventackschema/)>
Defined in: packages/contracts/src/events.ts:150
# EventAckStatus
> **EventAckStatus** = `z.infer`<*typeof* [`eventAckStatusSchema`](/api/contracts/variables/eventackstatusschema/)>
Defined in: packages/contracts/src/events.ts:149
# EventEnvelope
> **EventEnvelope** = `z.infer`<*typeof* [`eventEnvelopeSchema`](/api/contracts/variables/eventenvelopeschema/)>
Defined in: packages/contracts/src/events.ts:147
# EventQueueMessage
> **EventQueueMessage** = `z.infer`<*typeof* [`eventQueueMessageSchema`](/api/contracts/variables/eventqueuemessageschema/)>
Defined in: packages/contracts/src/events.ts:154
# EventStreamPayload
> **EventStreamPayload**<`TEntry`> = `TEntry` *extends* [`EventStreamEntry`](/api/contracts/interfaces/eventstreamentry/)\ ? `z.infer`<`TPayload`> : `never`
Defined in: packages/contracts/src/event-stream.ts:92
The payload TYPE of a registered Event stream — what `appendEvent` accepts for it.
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TEntry
[Section titled “TEntry”](#tentry)
`TEntry` *extends* [`EventStreamEntry`](/api/contracts/interfaces/eventstreamentry/)
# EventStreamRegistry
> **EventStreamRegistry** = `Record`<`string`, [`EventStreamEntry`](/api/contracts/interfaces/eventstreamentry/)>
Defined in: packages/contracts/src/event-stream.ts:89
A registry’s Event streams, keyed by Event-stream name (the key IS the name).
# GrantScopeAccess
> **GrantScopeAccess** = `object`
Defined in: packages/contracts/src/supabase-rls.ts:830
The caller’s standing on a grant-scope table, as resolved from the claims.
## Properties
[Section titled “Properties”](#properties)
### bypass
[Section titled “bypass”](#bypass)
> **bypass**: `boolean`
Defined in: packages/contracts/src/supabase-rls.ts:832
A bypass grant is present — the policy’s OR branch; every row is visible.
***
### ids
[Section titled “ids”](#ids)
> **ids**: `string`\[]
Defined in: packages/contracts/src/supabase-rls.ts:834
The de-duplicated scope ids the caller can see (empty → no rows, absent a bypass).
# GrantScopeAccessOptions
> **GrantScopeAccessOptions** = [`GrantScopeClaimOptions`](/api/contracts/type-aliases/grantscopeclaimoptions/) & `object`
Defined in: packages/contracts/src/supabase-rls.ts:824
The full grant-scope declaration the read mirror needs: the claim options plus the optional bypass.
## Type Declaration
[Section titled “Type Declaration”](#type-declaration)
### bypass?
[Section titled “bypass?”](#bypass)
> `optional` **bypass?**: [`GrantScopeBypassOptions`](/api/contracts/type-aliases/grantscopebypassoptions/)
The same [GrantScopeBypassOptions](/api/contracts/type-aliases/grantscopebypassoptions/) handed to [buildSupabaseGrantScopeNativePolicies](/api/contracts/functions/buildsupabasegrantscopenativepolicies/).
# GrantScopeBypassOptions
> **GrantScopeBypassOptions** = `object`
Defined in: packages/contracts/src/supabase-rls.ts:612
An unconditional bypass declaration: any grant whose `role` ∈ `roleValues` and whose `scope.kind` = `scopeKind` (default `"platform"`) confers access to every row — e.g. a platform-scoped `platform_admin`. Hand the SAME object to the policy builder and to [resolveGrantScopeAccess](/api/contracts/functions/resolvegrantscopeaccess/) / [buildGrantScopeAccessShapeWhere](/api/contracts/functions/buildgrantscopeaccessshapewhere/).
## Properties
[Section titled “Properties”](#properties)
### roleValues
[Section titled “roleValues”](#rolevalues)
> **roleValues**: `string`\[]
Defined in: packages/contracts/src/supabase-rls.ts:613
***
### scopeKind?
[Section titled “scopeKind?”](#scopekind)
> `optional` **scopeKind?**: `string`
Defined in: packages/contracts/src/supabase-rls.ts:614
# GrantScopeClaimOptions
> **GrantScopeClaimOptions** = `object`
Defined in: packages/contracts/src/supabase-rls.ts:617
## Properties
[Section titled “Properties”](#properties)
### grantsClaimPath?
[Section titled “grantsClaimPath?”](#grantsclaimpath)
> `optional` **grantsClaimPath?**: `string`\[]
Defined in: packages/contracts/src/supabase-rls.ts:625
Path to the grants array in the claims. Defaults to `app_metadata.authorization.grants`.
***
### roleValues
[Section titled “roleValues”](#rolevalues)
> **roleValues**: `string`\[]
Defined in: packages/contracts/src/supabase-rls.ts:621
Grant `role` values that confer access (e.g. \[“teacher”, “assistant”]).
***
### scopeIdField?
[Section titled “scopeIdField?”](#scopeidfield)
> `optional` **scopeIdField?**: `string`
Defined in: packages/contracts/src/supabase-rls.ts:623
Field within `scope` holding the id (e.g. “offeringId”). Defaults to `${scopeKind}Id`.
***
### scopeKind
[Section titled “scopeKind”](#scopekind)
> **scopeKind**: `string`
Defined in: packages/contracts/src/supabase-rls.ts:619
Value matched against each grant’s `scope.kind` (e.g. “offering”).
# JwtClaims
> **JwtClaims** = `z.infer`<*typeof* [`jwtClaimsSchema`](/api/contracts/variables/jwtclaimsschema/)>
Defined in: packages/contracts/src/config.ts:256
# ManagedFieldApplyOn
> **ManagedFieldApplyOn** = `"create"` | `"update"`
Defined in: packages/contracts/src/config.ts:338
# ManagedFieldSpecForTable
> **ManagedFieldSpecForTable**<`TTable`> = `Omit`<[`ManagedFieldSpec`](/api/contracts/interfaces/managedfieldspec/), `"column"`> & `object`
Defined in: packages/contracts/src/registry.ts:88
## Type Declaration
[Section titled “Type Declaration”](#type-declaration)
### column
[Section titled “column”](#column)
> **column**: [`TableColumnKey`](/api/contracts/type-aliases/tablecolumnkey/)<`TTable`>
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TTable
[Section titled “TTable”](#ttable)
`TTable` *extends* `AnyPgTable`
# ManagedFieldStrategy
> **ManagedFieldStrategy** = `"nowMicroseconds"` | `"authClaim"`
Defined in: packages/contracts/src/config.ts:352
How the applier stamps a server-managed column (it overrides any client-sent value — the client write payload omits managed fields, and the apply function re-derives them under the verified request claims):
* `nowMicroseconds` — `clock_timestamp()` microseconds, stamped via the canonical `pgxsinkit_clock_us()` DB function (installed by the utilities migration). The audit/version columns (`created_at_us`, `updated_at_us`); the `updated_at_us`-on-update field is the strictly-monotonic Server version (ADR-0010).
* `authClaim` — a value read from the **verified JWT claims** at a JSON [ManagedFieldSpec.claimPath](/api/contracts/interfaces/managedfieldspec/#claimpath) (e.g. `["sub"]` for the auth subject, or `["app_metadata","person_id"]` for an app-minted identity). This is the single claim-stamping strategy: the old `authUid` is exactly `{ claimPath: ["sub"], cast: "uuid" }`, so there is one mechanism, not a `sub`-only special case beside a general one.
# MutationAck
> **MutationAck** = `z.infer`<*typeof* [`mutationAckSchema`](/api/contracts/variables/mutationackschema/)>
Defined in: packages/contracts/src/mutation.ts:141
# MutationAckStatus
> **MutationAckStatus** = `z.infer`<*typeof* [`mutationAckStatusSchema`](/api/contracts/variables/mutationackstatusschema/)>
Defined in: packages/contracts/src/mutation.ts:138
# MutationEnvelope
> **MutationEnvelope** = `z.infer`<*typeof* [`mutationEnvelopeSchema`](/api/contracts/variables/mutationenvelopeschema/)>
Defined in: packages/contracts/src/mutation.ts:140
# MutationKind
> **MutationKind** = `z.infer`<*typeof* [`mutationKindSchema`](/api/contracts/variables/mutationkindschema/)>
Defined in: packages/contracts/src/mutation.ts:136
# MutationRejection
> **MutationRejection** = `z.infer`<*typeof* [`mutationRejectionSchema`](/api/contracts/variables/mutationrejectionschema/)>
Defined in: packages/contracts/src/mutation.ts:145
# MutationStatus
> **MutationStatus** = `z.infer`<*typeof* [`mutationStatusSchema`](/api/contracts/variables/mutationstatusschema/)>
Defined in: packages/contracts/src/mutation.ts:137
# OwnerOrAdminAccess
> **OwnerOrAdminAccess** = `object`
Defined in: packages/contracts/src/supabase-rls.ts:299
The caller’s standing on an owner-or-admin table, as resolved from the claims.
## Properties
[Section titled “Properties”](#properties)
### admin
[Section titled “admin”](#admin)
> **admin**: `boolean`
Defined in: packages/contracts/src/supabase-rls.ts:301
The caller holds the admin role — the policy’s bypass branch; every row is visible.
***
### subject
[Section titled “subject”](#subject)
> **subject**: `string` | `null`
Defined in: packages/contracts/src/supabase-rls.ts:303
The JWT subject when it is a non-empty string, else null (no subject → nothing is visible).
# OwnerOrAdminAccessOptions
> **OwnerOrAdminAccessOptions** = `object`
Defined in: packages/contracts/src/supabase-rls.ts:291
Claim-reading options for the read mirror; every field must match the value the policies were built with, or the two surfaces stop agreeing.
## Properties
[Section titled “Properties”](#properties)
### adminRoleName?
[Section titled “adminRoleName?”](#adminrolename)
> `optional` **adminRoleName?**: `string`
Defined in: packages/contracts/src/supabase-rls.ts:293
Role value (in `app_metadata.roles`) that bypasses ownership (default “admin”).
***
### adminRolesClaimPath?
[Section titled “adminRolesClaimPath?”](#adminrolesclaimpath)
> `optional` **adminRolesClaimPath?**: readonly `string`\[]
Defined in: packages/contracts/src/supabase-rls.ts:295
Path to the roles array in the claims. Defaults to [adminRolesClaimPath](/api/contracts/type-aliases/owneroradminaccessoptions/#adminrolesclaimpath).
# RegistryChangeSeverity
> **RegistryChangeSeverity** = `"compatible"` | `"risky"` | `"breaking"`
Defined in: packages/contracts/src/registry-diff.ts:18
The registry-diff gate (ADR-0006): classify a registry change as `compatible | risky | breaking` so loss-detection happens at *authoring* time, not as a runtime surprise. A breaking diff is a conscious release decision — rework to expand/contract, or accept-and-notify. This catches the one case the runtime cannot: silent column *repurposing* (a same-named column whose type/meaning changed).
pgxsinkit ships this mechanism; enforcement (whether a non-zero check blocks CI) is the consumer’s, via a committed lock that makes a breaking change a reviewable diff.
# RegistryEventStreams
> **RegistryEventStreams** = `Record`<`string`, `string`>
Defined in: packages/contracts/src/registry-diff.ts:69
Event-stream name → canonical contract hash. See [RegistryLock.streams](/api/contracts/interfaces/registrylock/#streams).
# RegistryRelations
> **RegistryRelations**<`TRegistry`> = `ExtractTablesWithRelations`<{ }, [`RegistryTables`](/api/contracts/type-aliases/registrytables/)<`TRegistry`>>
Defined in: packages/contracts/src/registry.ts:478
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* [`SyncTableRegistry`](/api/contracts/type-aliases/synctableregistry/)
# RegistryRowClasses
> **RegistryRowClasses** = `Record`<`string`, `string` | `null`>
Defined in: packages/contracts/src/registry-diff.ts:66
Registry key → row class (`null` = unclassified). See [RegistryLock.rowClasses](/api/contracts/interfaces/registrylock/#rowclasses).
# RegistryTables
> **RegistryTables**<`TRegistry`> = `{ [TKey in keyof TRegistry]: TRegistry[TKey]["table"] }`
Defined in: packages/contracts/src/registry.ts:468
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* [`SyncTableRegistry`](/api/contracts/type-aliases/synctableregistry/)
# RegistryViews
> **RegistryViews**<`TRegistry`> = `{ [TKey in keyof TRegistry as TRegistry[TKey] extends { view: AnyPgView } ? TKey : never]: NonNullable }`
Defined in: packages/contracts/src/registry.ts:472
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* [`SyncTableRegistry`](/api/contracts/type-aliases/synctableregistry/)
# Retention
> **Retention** = `"persistent"` | `"ephemeral"`
Defined in: packages/contracts/src/config.ts:67
Retention for a synced table (ADR-0021): **whether** its local copy is durable.
* `persistent` (default) — the durable PGlite backend with a resumable subscription-state.
* `ephemeral` — the table’s whole per-table local cluster (read cache, overlay, journal, sequence, views, reconcile trigger/function) is emitted as `TEMP`, so reads **and** writes leave no durable trace. Consequence: no durable offline write queue — pair a must-not-lose write with a pessimistic flush (ADR-0022).
Like [SubscriptionTiming](/api/contracts/type-aliases/subscriptiontiming/), a property of the consistency group: every table in a group agrees.
# RowFilterInput
> **RowFilterInput**<`TColumns`> = (`columns`) => [`RowFilterSpec`](/api/contracts/interfaces/rowfilterspec/)
Defined in: packages/contracts/src/registry.ts:314
A row filter for [defineSyncTable](/api/contracts/functions/definesynctable/)’s `shape`, authored from the table’s built, typed columns. Reference columns through `c(columns.x)` exactly as `extras` does with its `self` argument, so `customWhere` builds parameterized Electric `where`s from real, rename-safe column objects instead of hand-written column-name strings.
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TColumns
[Section titled “TColumns”](#tcolumns)
`TColumns` *extends* `Record`<`string`, `ColumnBuilderBase`>
## Parameters
[Section titled “Parameters”](#parameters)
### columns
[Section titled “columns”](#columns)
`PgBuildExtraConfigColumns`<`TColumns`>
## Returns
[Section titled “Returns”](#returns)
[`RowFilterSpec`](/api/contracts/interfaces/rowfilterspec/)
# RowTransform
> **RowTransform** = (`row`, `context`) => `Record`<`string`, `unknown`>
Defined in: packages/contracts/src/config.ts:307
Per-row rewrite applied in the proxy response path (after the row filter, before column omission). Receives a shape-log row’s column map (keys are wire/column names) and returns a possibly-rewritten one — letting the server strip a *sub-document* of a jsonb column, or otherwise rewrite a value, *conditionally on row data*. This expresses what a static, whole-column `omitColumns` cannot.
It runs only in the proxy’s per-response path: it never alters the local PGlite schema, never changes the Electric shape URL, and so never pollutes Electric’s shared shape cache. Return the same `row` reference to signal “no change”.
## Parameters
[Section titled “Parameters”](#parameters)
### row
[Section titled “row”](#row)
`Record`<`string`, `unknown`>
### context
[Section titled “context”](#context)
[`RowTransformContext`](/api/contracts/interfaces/rowtransformcontext/)
## Returns
[Section titled “Returns”](#returns)
`Record`<`string`, `unknown`>
# ShapeSpecInput
> **ShapeSpecInput** = `Omit`<[`ShapeSpec`](/api/contracts/interfaces/shapespec/), `"tableName"` | `"shapeKey"` | `"electricTable"`> & `object`
Defined in: packages/contracts/src/config.ts:285
Input variant of [ShapeSpec](/api/contracts/interfaces/shapespec/) where `tableName` and `shapeKey` are optional. When omitted, both default to the top-level `tableName` of the `defineSyncTable` call. `electricTable` is deliberately absent — it is a resolved/internal field, never a consumer input (see [ShapeSpec.electricTable](/api/contracts/interfaces/shapespec/#electrictable)); a read projection over an existing table is authored with `defineReadProjection`, which derives it from the owner.
## Type Declaration
[Section titled “Type Declaration”](#type-declaration)
### shapeKey?
[Section titled “shapeKey?”](#shapekey)
> `optional` **shapeKey?**: `string`
### tableName?
[Section titled “tableName?”](#tablename)
> `optional` **tableName?**: `string`
# ShapeSpecInputFor
> **ShapeSpecInputFor**<`TColumns`> = `Omit`<[`ShapeSpecInput`](/api/contracts/type-aliases/shapespecinput/), `"rowFilter"`> & `object`
Defined in: packages/contracts/src/registry.ts:319
[ShapeSpecInput](/api/contracts/type-aliases/shapespecinput/) whose `rowFilter` is authored from the built columns (typed by `TColumns`).
## Type Declaration
[Section titled “Type Declaration”](#type-declaration)
### rowFilter?
[Section titled “rowFilter?”](#rowfilter)
> `optional` **rowFilter?**: [`RowFilterInput`](/api/contracts/type-aliases/rowfilterinput/)<`TColumns`>
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TColumns
[Section titled “TColumns”](#tcolumns)
`TColumns` *extends* `Record`<`string`, `ColumnBuilderBase`>
# StampedEvent
> **StampedEvent** = `z.infer`<*typeof* [`stampedEventSchema`](/api/contracts/variables/stampedeventschema/)>
Defined in: packages/contracts/src/events.ts:153
# StorageBackend
> **StorageBackend** = `"opfs"` | `"idbfs"`
Defined in: packages/contracts/src/config.ts:112
The registry-declared BROWSER storage backend (ADR-0049 decision 1). `opfs` (default) is the store’s normal boot on every platform — the capability machinery selects the `opfs-repacked` VFS where a home can hold sync-access handles, and falls back to in-SharedWorker `idbfs` when no home can. `idbfs` is the one way to opt out of that machinery entirely: no probe, no election, the engine boots on idb.
Scopes the BROWSER store only. Environment resolution is orthogonal and unchanged (ADR-0049 decision 14): a Node mint stays `file://` and the export clone stays memory regardless of this declaration.
# StorageDurability
> **StorageDurability** = `"relaxed"` | `"strict"`
Defined in: packages/contracts/src/config.ts:130
The registry-declared durability mode (ADR-0047; ADR-0049 decision 9). `relaxed` (default) returns a write before its durable flush and schedules the flush asynchronously — the local-first “instant write” the toolkit exists to deliver. `strict` reinstates the synchronous flush boundary (a \~50ms+ per-statement floor on idb; cheap on `opfs-repacked`). It binds EVERY open of EVERY store minted from the registry, so no minting/open site takes a durability option to contradict it, and an opfs→idbfs capability fallback keeps the declared mode.
# SubscriptionTiming
> **SubscriptionTiming** = `"eager"` | `"lazy"`
Defined in: packages/contracts/src/config.ts:46
Subscription timing for a synced table (ADR-0021): **when** its Electric shape subscribes.
* `eager` (default) — subscribed in the boot set, as today.
* `lazy` — excluded from boot; subscribed on first query-reference. With `persistent` retention, first use is a one-time ignition that promotes the table to a normal eager table for subsequent sessions; with `ephemeral` retention it is session-scoped.
A property of the **consistency group**: every table sharing a `consistencyGroup` must agree, since a group commits atomically on one `MultiShapeStream` and cannot be partly lazy (ADR-0021 §4).
# SupabaseGrantScopeNativePoliciesOptions
> **SupabaseGrantScopeNativePoliciesOptions** = [`SupabaseGrantScopePredicateColumns`](/api/contracts/type-aliases/supabasegrantscopepredicatecolumns/) & `object`
Defined in: packages/contracts/src/supabase-rls.ts:643
## Type Declaration
[Section titled “Type Declaration”](#type-declaration)
### naive?
[Section titled “naive?”](#naive)
> `optional` **naive?**: `boolean`
Emit the deliberately-naive per-row correlated form (cliff demo / regression guard only).
### role
[Section titled “role”](#role)
> **role**: `PgRole`
# SupabaseGrantScopePredicateColumns
> **SupabaseGrantScopePredicateColumns** = [`GrantScopeClaimOptions`](/api/contracts/type-aliases/grantscopeclaimoptions/) & `object`
Defined in: packages/contracts/src/supabase-rls.ts:628
## Type Declaration
[Section titled “Type Declaration”](#type-declaration)
### bypass?
[Section titled “bypass?”](#bypass)
> `optional` **bypass?**: [`GrantScopeBypassOptions`](/api/contracts/type-aliases/grantscopebypassoptions/)
Optional unconditional bypass ([GrantScopeBypassOptions](/api/contracts/type-aliases/grantscopebypassoptions/)): any grant whose `role` ∈ `bypass.roleValues` and whose `scope.kind` = `bypass.scopeKind` (default “platform”) grants all rows — e.g. a platform-scoped `platform_admin`. The bypass is an **uncorrelated** EXISTS, so it stays InitPlan-hoisted in both the correct and naive forms. Its read-path twin is [buildGrantScopeAccessShapeWhere](/api/contracts/functions/buildgrantscopeaccessshapewhere/) (bypass → `null`, i.e. no shape filter at all).
### scopeCastType?
[Section titled “scopeCastType?”](#scopecasttype)
> `optional` **scopeCastType?**: `string`
SQL type the extracted grant id is cast to before comparison (default “uuid”).
### scopeColumn
[Section titled “scopeColumn”](#scopecolumn)
> **scopeColumn**: `AnyColumn`
Column on the governed row naming its scope (e.g. `offerings.id` reference column). Table name is derived from it.
# SupabaseMembershipNativePoliciesOptions
> **SupabaseMembershipNativePoliciesOptions** = [`SupabaseMembershipPredicateColumns`](/api/contracts/type-aliases/supabasemembershippredicatecolumns/) & `object`
Defined in: packages/contracts/src/supabase-rls.ts:407
## Type Declaration
[Section titled “Type Declaration”](#type-declaration)
### role
[Section titled “role”](#role)
> **role**: `PgRole`
### writeGate?
[Section titled “writeGate?”](#writegate)
> `optional` **writeGate?**: [`SupabaseMembershipWriteGateColumns`](/api/contracts/type-aliases/supabasemembershipwritegatecolumns/)
Optional write-state gate applied to INSERT and UPDATE only.
# SupabaseMembershipPredicateColumns
> **SupabaseMembershipPredicateColumns** = `object`
Defined in: packages/contracts/src/supabase-rls.ts:372
## Properties
[Section titled “Properties”](#properties)
### containerColumn
[Section titled “containerColumn”](#containercolumn)
> **containerColumn**: `AnyColumn`
Defined in: packages/contracts/src/supabase-rls.ts:374
Column on the governed row naming its container (e.g. `workItems.workspaceId`).
***
### managerRoleColumn?
[Section titled “managerRoleColumn?”](#managerrolecolumn)
> `optional` **managerRoleColumn?**: `AnyColumn`
Defined in: packages/contracts/src/supabase-rls.ts:384
Optional role column on the membership link enabling manager moderation.
***
### managerRoleValue?
[Section titled “managerRoleValue?”](#managerrolevalue)
> `optional` **managerRoleValue?**: `string`
Defined in: packages/contracts/src/supabase-rls.ts:386
Role value that grants moderation (default “manager”); only used with managerRoleColumn.
***
### membershipContainerColumn
[Section titled “membershipContainerColumn”](#membershipcontainercolumn)
> **membershipContainerColumn**: `AnyColumn`
Defined in: packages/contracts/src/supabase-rls.ts:378
Container column on the membership link table (e.g. `workspaceMembers.workspaceId`).
***
### membershipSubjectColumn
[Section titled “membershipSubjectColumn”](#membershipsubjectcolumn)
> **membershipSubjectColumn**: `AnyColumn`
Defined in: packages/contracts/src/supabase-rls.ts:380
Subject (member) column on the membership link table, compared to the JWT sub.
***
### membershipTable
[Section titled “membershipTable”](#membershiptable)
> **membershipTable**: `AnyPgTable`
Defined in: packages/contracts/src/supabase-rls.ts:376
Membership link table (e.g. the `workspace_members` table).
***
### ownerColumn
[Section titled “ownerColumn”](#ownercolumn)
> **ownerColumn**: `AnyColumn`
Defined in: packages/contracts/src/supabase-rls.ts:382
Owner column on the governed row (e.g. `workItems.ownerId`).
***
### subjectCastType?
[Section titled “subjectCastType?”](#subjectcasttype)
> `optional` **subjectCastType?**: `string`
Defined in: packages/contracts/src/supabase-rls.ts:388
SQL type the JWT subject is cast to before comparison (default “uuid”).
# SupabaseMembershipShapeColumns
> **SupabaseMembershipShapeColumns** = `Pick`<[`SupabaseMembershipPredicateColumns`](/api/contracts/type-aliases/supabasemembershippredicatecolumns/), `"containerColumn"` | `"membershipTable"` | `"membershipContainerColumn"` | `"membershipSubjectColumn"`> & `Partial`<[`SupabaseMembershipNativePoliciesOptions`](/api/contracts/type-aliases/supabasemembershipnativepoliciesoptions/)>
Defined in: packages/contracts/src/supabase-rls.ts:546
The columns [buildMembershipShapeWhere](/api/contracts/functions/buildmembershipshapewhere/) needs: the SELECT-relevant subset of [SupabaseMembershipPredicateColumns](/api/contracts/type-aliases/supabasemembershippredicatecolumns/). The write-only fields (owner, manager role, subject cast, write gate, policy role) are accepted but ignored, so the **same declaration object** you hand to [buildSupabaseMembershipNativePolicies](/api/contracts/functions/buildsupabasemembershipnativepolicies/) can be handed to the read mirror verbatim.
# SupabaseMembershipWriteGateColumns
> **SupabaseMembershipWriteGateColumns** = `object`
Defined in: packages/contracts/src/supabase-rls.ts:396
## Properties
[Section titled “Properties”](#properties)
### containerLockColumn
[Section titled “containerLockColumn”](#containerlockcolumn)
> **containerLockColumn**: `AnyColumn`
Defined in: packages/contracts/src/supabase-rls.ts:402
Boolean column on the container table; when true, only a manager may write (e.g. `workspaces.locked`).
***
### containerPkColumn
[Section titled “containerPkColumn”](#containerpkcolumn)
> **containerPkColumn**: `AnyColumn`
Defined in: packages/contracts/src/supabase-rls.ts:400
PK column on the container table the governed row’s container column references (e.g. `workspaces.id`).
***
### containerTable
[Section titled “containerTable”](#containertable)
> **containerTable**: `AnyPgTable`
Defined in: packages/contracts/src/supabase-rls.ts:398
Container table holding the lock flag (e.g. the `workspaces` table).
***
### membershipMutedColumn
[Section titled “membershipMutedColumn”](#membershipmutedcolumn)
> **membershipMutedColumn**: `AnyColumn`
Defined in: packages/contracts/src/supabase-rls.ts:404
Boolean column on the membership table; when true, that member may not write (e.g. `workspaceMembers.muted`).
# SupabaseOwnerOrAdminNativePoliciesOptions
> **SupabaseOwnerOrAdminNativePoliciesOptions** = `object`
Defined in: packages/contracts/src/supabase-rls.ts:56
## Properties
[Section titled “Properties”](#properties)
### adminRoleName?
[Section titled “adminRoleName?”](#adminrolename)
> `optional` **adminRoleName?**: `string`
Defined in: packages/contracts/src/supabase-rls.ts:61
Role value (in `app_metadata.roles`) that bypasses ownership (default “admin”).
***
### adminRolesClaimPath?
[Section titled “adminRolesClaimPath?”](#adminrolesclaimpath)
> `optional` **adminRolesClaimPath?**: readonly `string`\[]
Defined in: packages/contracts/src/supabase-rls.ts:68
Path to the roles array in the claims. Defaults to [adminRolesClaimPath](/api/contracts/type-aliases/supabaseowneroradminnativepoliciesoptions/#adminrolesclaimpath); pass the SAME value to [resolveOwnerOrAdminAccess](/api/contracts/functions/resolveowneroradminaccess/) / [buildOwnerOrAdminShapeWhere](/api/contracts/functions/buildowneroradminshapewhere/) so both surfaces read one claim.
***
### ownerColumn
[Section titled “ownerColumn”](#ownercolumn)
> **ownerColumn**: `AnyColumn`
Defined in: packages/contracts/src/supabase-rls.ts:58
Owner column on the governed row (e.g. `authors.ownerId`). The governed table name is derived from it.
***
### role
[Section titled “role”](#role)
> **role**: `PgRole`
Defined in: packages/contracts/src/supabase-rls.ts:59
***
### subjectCastType?
[Section titled “subjectCastType?”](#subjectcasttype)
> `optional` **subjectCastType?**: `string`
Defined in: packages/contracts/src/supabase-rls.ts:63
SQL type the JWT subject is cast to before comparison (default “uuid”).
# SupabaseOwnerOrAdminPredicateOptions
> **SupabaseOwnerOrAdminPredicateOptions** = `object`
Defined in: packages/contracts/src/supabase-rls.ts:48
## Properties
[Section titled “Properties”](#properties)
### adminRoleName?
[Section titled “adminRoleName?”](#adminrolename)
> `optional` **adminRoleName?**: `string`
Defined in: packages/contracts/src/supabase-rls.ts:50
***
### adminRolesClaimPath?
[Section titled “adminRolesClaimPath?”](#adminrolesclaimpath)
> `optional` **adminRolesClaimPath?**: readonly `string`\[]
Defined in: packages/contracts/src/supabase-rls.ts:53
Path to the roles array in the claims. Defaults to [adminRolesClaimPath](/api/contracts/type-aliases/supabaseowneroradminpredicateoptions/#adminrolesclaimpath).
***
### ownerSqlColumn?
[Section titled “ownerSqlColumn?”](#ownersqlcolumn)
> `optional` **ownerSqlColumn?**: `string`
Defined in: packages/contracts/src/supabase-rls.ts:49
***
### subjectCastType?
[Section titled “subjectCastType?”](#subjectcasttype)
> `optional` **subjectCastType?**: `string`
Defined in: packages/contracts/src/supabase-rls.ts:51
# SyncRuntimePhase
> **SyncRuntimePhase** = `"booting"` | `"syncing"` | `"ready"` | `"degraded"` | `"auth-needed"`
Defined in: packages/contracts/src/runtime.ts:1
# SyncTableCreateInput
> **SyncTableCreateInput**<`TRegistry`, `TKey`> = `TRegistry`\[`TKey`] *extends* [`SyncTableEntry`](/api/contracts/interfaces/synctableentry/)<`AnyPgTable`, infer TLocalTable> ? `Omit`<`InferInsertModel`<`TLocalTable`>, `ManagedFieldColumnKeysForOperation`<`TRegistry`\[`TKey`], `"create"`>> : `never`
Defined in: packages/contracts/src/registry.ts:556
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* [`SyncTableRegistry`](/api/contracts/type-aliases/synctableregistry/)
### TKey
[Section titled “TKey”](#tkey)
`TKey` *extends* keyof `TRegistry`
# SyncTableInput
> **SyncTableInput**<`TName`, `TColumns`, `TOmittedColumns`> = `object`
Defined in: packages/contracts/src/registry.ts:332
Input for `defineSyncTable`. Supply `tableName + makeColumns` — the Drizzle table (and, for `readwrite` mode, the read-model view) are created internally.
Access the built objects via `.table` and `.view` on the returned entry.
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TName
[Section titled “TName”](#tname)
`TName` *extends* `string`
### TColumns
[Section titled “TColumns”](#tcolumns)
`TColumns` *extends* `Record`<`string`, `ColumnBuilderBase`>
### TOmittedColumns
[Section titled “TOmittedColumns”](#tomittedcolumns)
`TOmittedColumns` *extends* readonly `ColumnKeys`<`TColumns`>\[] = \[]
## Properties
[Section titled “Properties”](#properties)
### applyMode?
[Section titled “applyMode?”](#applymode)
> `optional` **applyMode?**: `"insert"` | `"upsert"`
Defined in: packages/contracts/src/registry.ts:376
CDC insert-apply policy (ADR-0045). **Default `"insert"`** — a CDC insert is a plain INSERT, so a genuine PK collision must surface (the ADR-0014 collision-surfacing invariant; a synced cache table is server-authoritative and a duplicate insert is a real bug). Set `"upsert"` **only** when this table legitimately receives locally-DERIVED provisional rows — e.g. a local trigger on another synced table inserts a provisional row here, and the server independently creates the same row so its CDC insert would collide (23505). With `"upsert"`, server CDC inserts are applied idempotently as `INSERT … ON CONFLICT (pk) DO UPDATE`; the authoritative server row overwrites the provisional local row. Declare the exception here, where it lives — do not weaken the invariant repo-wide.
***
### clientProjection?
[Section titled “clientProjection?”](#clientprojection)
> `optional` **clientProjection?**: [`SyncTableInputProjection`](/api/contracts/type-aliases/synctableinputprojection/)<`TColumns`, `TOmittedColumns`>
Defined in: packages/contracts/src/registry.ts:384
***
### conflictPolicy?
[Section titled “conflictPolicy?”](#conflictpolicy)
> `optional` **conflictPolicy?**: [`ConflictPolicy`](/api/contracts/type-aliases/conflictpolicy/)
Defined in: packages/contracts/src/registry.ts:393
Conflict policy (ADR-0015): what happens to a stale write on this table. **Required for writable tables** — `defineSyncTable`/`defineSyncRegistry` reject a writable table without one. See [ConflictPolicy](/api/contracts/type-aliases/conflictpolicy/).
***
### consistencyGroup?
[Section titled “consistencyGroup?”](#consistencygroup)
> `optional` **consistencyGroup?**: `string`
Defined in: packages/contracts/src/registry.ts:399
Bind this table into a consistency group (ADR-0009 decision 2): grouped tables sync on one `MultiShapeStream` and commit atomically. Omit for the default singleton group. See [SyncTableEntry.consistencyGroup](/api/contracts/interfaces/synctableentry/#consistencygroup).
***
### extras?
[Section titled “extras?”](#extras)
> `optional` **extras?**: (`self`) => `PgTableExtraConfigValue`\[]
Defined in: packages/contracts/src/registry.ts:346
Extra constraints or indexes on the server-side Postgres table (unique, index, etc.). Receives the built column map — same signature as `pgTable`’s third argument. Not applied to `localTable`.
#### Parameters
[Section titled “Parameters”](#parameters)
##### self
[Section titled “self”](#self)
`PgBuildExtraConfigColumns`<`TColumns`>
#### Returns
[Section titled “Returns”](#returns)
`PgTableExtraConfigValue`\[]
***
### governance?
[Section titled “governance?”](#governance)
> `optional` **governance?**: [`SyncTableInputGovernance`](/api/contracts/type-aliases/synctableinputgovernance/)<`TColumns`>
Defined in: packages/contracts/src/registry.ts:387
***
### makeColumns
[Section titled “makeColumns”](#makecolumns)
> **makeColumns**: () => `TColumns`
Defined in: packages/contracts/src/registry.ts:338
#### Returns
[Section titled “Returns”](#returns-1)
`TColumns`
***
### mode?
[Section titled “mode?”](#mode)
> `optional` **mode?**: [`TableMode`](/api/contracts/type-aliases/tablemode/)
Defined in: packages/contracts/src/registry.ts:350
#### Default
[Section titled “Default”](#default)
```ts
"readonly"
```
***
### policies?
[Section titled “policies?”](#policies)
> `optional` **policies?**: `PgPolicy`\[]
Defined in: packages/contracts/src/registry.ts:340
RLS policies (or other table extras) attached to the Postgres table.
***
### primaryKey?
[Section titled “primaryKey?”](#primarykey)
> `optional` **primaryKey?**: `string`\[] | { `columns`: `string`\[]; `name`: `string`; }
Defined in: packages/contracts/src/registry.ts:365
THE primary key of the server table — the single source of truth for its physical `PRIMARY KEY` constraint. `defineSyncTable` emits it as the constraint, named `` `${tableName}_pkey` `` (matching Postgres’s inline-PK default, so existing consumer databases see no rename churn) unless the object form overrides `name`.
Defaults to `["id"]`. Use an array with multiple entries for composite keys, or the object form `{ name, columns }` to name the constraint.
A single-column key MAY equivalently be declared via the column’s own `.primaryKey()` (idiomatic drizzle); it must match this spec, and emission is then skipped because the column already carries the constraint. A table-level `primaryKey(...)` in `extras`/`policies` is REJECTED — declare the key here instead.
***
### retention?
[Section titled “retention?”](#retention)
> `optional` **retention?**: [`Retention`](/api/contracts/type-aliases/retention/)
Defined in: packages/contracts/src/registry.ts:408
Retention (ADR-0021): `persistent` (default) | `ephemeral`. See [SyncTableEntry.retention](/api/contracts/interfaces/synctableentry/#retention).
***
### rowClass?
[Section titled “rowClass?”](#rowclass)
> `optional` **rowClass?**: `string`
Defined in: packages/contracts/src/registry.ts:382
Consumer-defined row classification (ADR-0052) — see [SyncTableEntry.rowClass](/api/contracts/interfaces/synctableentry/#rowclass). Required (and checked against the declared set) when the registry declares [SyncRegistryDefinition.rowClasses](/api/contracts/interfaces/syncregistrydefinition/#rowclasses); unconstrained otherwise.
***
### schema?
[Section titled “schema?”](#schema)
> `optional` **schema?**: `PgSchemaType`
Defined in: packages/contracts/src/registry.ts:348
Place the table in this schema (e.g. for perf-lab schemed tables).
***
### serverProjection?
[Section titled “serverProjection?”](#serverprojection)
> `optional` **serverProjection?**: [`ServerProjectionSpec`](/api/contracts/interfaces/serverprojectionspec/)
Defined in: packages/contracts/src/registry.ts:386
Server-side response-path projection (e.g. `rowTransform`). Server authority, not client shape.
***
### shape?
[Section titled “shape?”](#shape)
> `optional` **shape?**: [`ShapeSpecInputFor`](/api/contracts/type-aliases/shapespecinputfor/)<`TColumns`>
Defined in: packages/contracts/src/registry.ts:383
***
### subscription?
[Section titled “subscription?”](#subscription)
> `optional` **subscription?**: [`SubscriptionTiming`](/api/contracts/type-aliases/subscriptiontiming/)
Defined in: packages/contracts/src/registry.ts:404
Subscription timing (ADR-0021): `eager` (default) | `lazy`. See [SyncTableEntry.subscription](/api/contracts/interfaces/synctableentry/#subscription).
***
### tableName
[Section titled “tableName”](#tablename)
> **tableName**: `TName`
Defined in: packages/contracts/src/registry.ts:337
***
### writeMode?
[Section titled “writeMode?”](#writemode)
> `optional` **writeMode?**: [`WriteMode`](/api/contracts/type-aliases/writemode/)
Defined in: packages/contracts/src/registry.ts:412
Write-mode (ADR-0022): `optimistic` (default) | `pessimistic`. See [SyncTableEntry.writeMode](/api/contracts/interfaces/synctableentry/#writemode).
# SyncTableInputGovernance
> **SyncTableInputGovernance**<`TColumns`> = `Omit`<[`TableGovernanceSpec`](/api/contracts/interfaces/tablegovernancespec/), `"deferrableConstraints"` | `"managedFields"`> & `object`
Defined in: packages/contracts/src/registry.ts:292
Governance spec for `defineSyncTable` — columns are typed from `makeColumns`.
## Type Declaration
[Section titled “Type Declaration”](#type-declaration)
### deferrableConstraints?
[Section titled “deferrableConstraints?”](#deferrableconstraints)
> `optional` **deferrableConstraints?**: `Omit`<[`DeferrableConstraintSpec`](/api/contracts/interfaces/deferrableconstraintspec/), `"columns"`> & `object`\[]
### managedFields?
[Section titled “managedFields?”](#managedfields)
> `optional` **managedFields?**: `Omit`<[`ManagedFieldSpec`](/api/contracts/interfaces/managedfieldspec/), `"column"`> & `object`\[]
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TColumns
[Section titled “TColumns”](#tcolumns)
`TColumns` *extends* `Record`<`string`, `ColumnBuilderBase`>
# SyncTableInputProjection
> **SyncTableInputProjection**<`TColumns`, `TOmittedColumns`> = `Omit`<[`ClientProjectionSpec`](/api/contracts/interfaces/clientprojectionspec/), `"omitColumns"`> & `object`
Defined in: packages/contracts/src/registry.ts:301
Projection spec for `defineSyncTable` — omitColumns typed from `makeColumns`.
## Type Declaration
[Section titled “Type Declaration”](#type-declaration)
### omitColumns?
[Section titled “omitColumns?”](#omitcolumns)
> `optional` **omitColumns?**: `TOmittedColumns`
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TColumns
[Section titled “TColumns”](#tcolumns)
`TColumns` *extends* `Record`<`string`, `ColumnBuilderBase`>
### TOmittedColumns
[Section titled “TOmittedColumns”](#tomittedcolumns)
`TOmittedColumns` *extends* readonly `ColumnKeys`<`TColumns`>\[] = \[]
# SyncTableName
> **SyncTableName**<`TRegistry`> = keyof `TRegistry` & `string`
Defined in: packages/contracts/src/registry.ts:483
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* [`SyncTableRegistry`](/api/contracts/type-aliases/synctableregistry/)
# SyncTableRecord
> **SyncTableRecord**<`TRegistry`, `TKey`> = `TRegistry`\[`TKey`] *extends* [`SyncTableEntry`](/api/contracts/interfaces/synctableentry/)\ ? `InferSelectModel`<`TTable`> : `never`
Defined in: packages/contracts/src/registry.ts:574
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* [`SyncTableRegistry`](/api/contracts/type-aliases/synctableregistry/)
### TKey
[Section titled “TKey”](#tkey)
`TKey` *extends* keyof `TRegistry`
# SyncTableRegistry
> **SyncTableRegistry** = `Record`<`string`, [`SyncTableEntry`](/api/contracts/interfaces/synctableentry/)>
Defined in: packages/contracts/src/registry.ts:415
# SyncTableUpdateInput
> **SyncTableUpdateInput**<`TRegistry`, `TKey`> = `TRegistry`\[`TKey`] *extends* [`SyncTableEntry`](/api/contracts/interfaces/synctableentry/)<`AnyPgTable`, infer TLocalTable> ? `Partial`<`Omit`<`InferInsertModel`<`TLocalTable`>, `ManagedFieldColumnKeys`<`TRegistry`\[`TKey`]>>> : `never`
Defined in: packages/contracts/src/registry.ts:569
The patch type for an update: the insert model made partial, minus **every** managed key — including a **create-only** one. A field declared `applyOn: ["create"]` is stamped at birth and inert on update (the generated apply function offers no UPDATE SET candidate for it), so it is never a settable update key; the write route 400-rejects an update payload that carries one. This omit set is the type-level statement of [isManagedFieldGuarded](/api/contracts/functions/ismanagedfieldguarded/) at `"update"` — that predicate is the single definition of the rule, and every runtime surface derives from it.
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* [`SyncTableRegistry`](/api/contracts/type-aliases/synctableregistry/)
### TKey
[Section titled “TKey”](#tkey)
`TKey` *extends* keyof `TRegistry`
# TableColumnKey
> **TableColumnKey**<`TTable`> = `Extract`\, `string`>
Defined in: packages/contracts/src/registry.ts:82
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TTable
[Section titled “TTable”](#ttable)
`TTable` *extends* `AnyPgTable`
# TableGovernanceSpecForTable
> **TableGovernanceSpecForTable**<`TTable`> = `Omit`<[`TableGovernanceSpec`](/api/contracts/interfaces/tablegovernancespec/), `"deferrableConstraints"` | `"managedFields"`> & `object`
Defined in: packages/contracts/src/registry.ts:108
## Type Declaration
[Section titled “Type Declaration”](#type-declaration)
### deferrableConstraints?
[Section titled “deferrableConstraints?”](#deferrableconstraints)
> `optional` **deferrableConstraints?**: [`DeferrableConstraintSpecForTable`](/api/contracts/type-aliases/deferrableconstraintspecfortable/)<`TTable`>\[]
### managedFields?
[Section titled “managedFields?”](#managedfields)
> `optional` **managedFields?**: [`ManagedFieldSpecForTable`](/api/contracts/type-aliases/managedfieldspecfortable/)<`TTable`>\[]
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TTable
[Section titled “TTable”](#ttable)
`TTable` *extends* `AnyPgTable`
# TableMode
> **TableMode** = `"readonly"` | `"writeonly"` | `"readwrite"`
Defined in: packages/contracts/src/config.ts:7
# WriteMode
> **WriteMode** = `"optimistic"` | `"pessimistic"`
Defined in: packages/contracts/src/config.ts:93
Write-mode (ADR-0022): **how** a write reaches the server — the write-side twin of [Retention](/api/contracts/type-aliases/retention/).
* `optimistic` (default) — the write enters the local journal with an optimistic overlay, the UI updates immediately, and the convergence loop flushes the journal as one all-or-nothing batch; the canonical row returns via the sync echo. The path that has always existed.
* `pessimistic` — the write is **server-authoritative**: it flush-routes to an authoritative endpoint that applies it in its own isolated, serialised transaction and returns a per-mutation result (accepted, or rejected-with-typed-reason) **before** the UI shows success. For invariants the client cannot evaluate locally — a capacity/quota/uniqueness gate enforced by a server-side rule.
Write-mode is a property of an atomic **write-unit**, not a single table (ADR-0022 §1): a unit is uniformly one mode. The *static* write-unit is the **consistency group** — so, like [SubscriptionTiming](/api/contracts/type-aliases/subscriptiontiming/) and [Retention](/api/contracts/type-aliases/retention/), every table sharing a `consistencyGroup` must agree (validated). A *dynamic* override is the imperative `transaction({ mode })` block, which scopes a mode to an ad-hoc set of mutations.
# authoritativeWriteRequestSchema
> `const` **authoritativeWriteRequestSchema**: `ZodObject`<{ `mutations`: `ZodArray`<`ZodObject`<{ `baseServerVersion`: `ZodOptional`<`ZodString`>; `clientTimestampUs`: `ZodString`; `entityKey`: `ZodRecord`<`ZodString`, `ZodString`>; `kind`: `ZodEnum`<{ `create`: `"create"`; `delete`: `"delete"`; `update`: `"update"`; }>; `mutationId`: `ZodUUID`; `mutationSeq`: `ZodNumber`; `payload`: `ZodUnknown`; `tableName`: `ZodString`; }, `$strict`>>; `writeUnit`: `ZodOptional`<`ZodString`>; }, `$strict`>
Defined in: packages/contracts/src/mutation.ts:95
The body of an **authoritative write** (ADR-0022 §3, mechanism c): one pessimistic write-**unit** — a set of co-committed mutations the server applies in its **own isolated transaction**, atomically, returning a per-mutation ack. `writeUnit` is the client’s unit id (the dynamic `transaction` tag, or the static consistency-group key), carried for attribution. Distinct from `batchMutationRequestSchema` only in *semantics* (atomic unit + a constraint exception → a clean `rejected` ack, never a whole-batch 500) and its endpoint path; the mutation envelope is identical.
# batchEventAckSchema
> `const` **batchEventAckSchema**: `ZodObject`<{ `acks`: `ZodArray`<`ZodObject`<{ `eventId`: `ZodUUID`; `reason`: `ZodOptional`<`ZodString`>; `status`: `ZodEnum`<{ `acked`: `"acked"`; `deferred`: `"deferred"`; `refused`: `"refused"`; `rejected`: `"rejected"`; }>; }, `$strict`>>; }, `$strict`>
Defined in: packages/contracts/src/events.ts:100
The 2xx body: one verdict per well-formed envelope in the request.
# batchEventErrorSchema
> `const` **batchEventErrorSchema**: `ZodObject`<{ `message`: `ZodOptional`<`ZodString`>; }, `$strip`>
Defined in: packages/contracts/src/events.ts:112
The body of a non-2xx batch-event response. Unlike the mutation path there is NO per-item rejection field: a well-formed envelope always gets a per-event verdict in a 2xx `acks` array, so a non-2xx is *always* a whole-batch fault (framing, limits, auth, queue unavailable) with nothing to attribute. Unknown extra fields are stripped.
# batchEventPaths
> `const` **batchEventPaths**: readonly \[`"/api/events"`]
Defined in: packages/contracts/src/events.ts:44
The batch event-ingestion endpoint path(s) — the Event lane’s twin of the mutation route’s `batchMutationPaths`. It lives HERE, in contracts, rather than beside the server route: the same constant is the client flush loop’s target and the server’s mount point, and the neighbouring request-shape limits are already contracts-level toolkit constants both sides read.
# batchEventRequestSchema
> `const` **batchEventRequestSchema**: `ZodObject`<{ `events`: `ZodArray`<`ZodObject`<{ `eventId`: `ZodUUID`; `occurredAtUs`: `ZodString`; `payload`: `ZodUnknown`; `stream`: `ZodString`; }, `$strict`>>; }, `$strict`>
Defined in: packages/contracts/src/events.ts:70
The `POST /api/events` body. A request failing this parse — or exceeding the request-shape limits — is a batch-level 400/413: a malformed item without a parseable `eventId` cannot receive a per-event verdict. That layer is unreachable through the library (appends are validated, envelopes are library-built); it exists for library bugs and non-library callers.
# batchMutationAckSchema
> `const` **batchMutationAckSchema**: `ZodObject`<{ `acks`: `ZodArray`<`ZodObject`<{ `conflictReason`: `ZodOptional`<`ZodString`>; `entityKey`: `ZodRecord`<`ZodString`, `ZodString`>; `httpStatus`: `ZodOptional`<`ZodNumber`>; `mutationId`: `ZodUUID`; `mutationSeq`: `ZodNumber`; `rejectionReason`: `ZodOptional`<`ZodString`>; `serverUpdatedAtUs`: `ZodOptional`<`ZodString`>; `status`: `ZodEnum`<{ `acked`: `"acked"`; `conflicted`: `"conflicted"`; `failed`: `"failed"`; `rejected`: `"rejected"`; }>; `tableName`: `ZodString`; }, `$strict`>>; }, `$strict`>
Defined in: packages/contracts/src/mutation.ts:102
# batchMutationErrorSchema
> `const` **batchMutationErrorSchema**: `ZodObject`<{ `message`: `ZodOptional`<`ZodString`>; `rejections`: `ZodOptional`<`ZodArray`<`ZodObject`<{ `mutationId`: `ZodUUID`; `mutationSeq`: `ZodNumber`; `reason`: `ZodString`; `tableName`: `ZodString`; }, `$strict`>>>; }, `$strip`>
Defined in: packages/contracts/src/mutation.ts:131
The body of a non-2xx batch-mutation response. `rejections` is present only when the fault is attributable to specific mutations (payload validation); whole-batch faults (execution 5xx, auth, malformed envelope) carry just a `message`. Unknown extra fields are stripped.
# batchMutationRequestSchema
> `const` **batchMutationRequestSchema**: `ZodObject`<{ `mutations`: `ZodArray`<`ZodObject`<{ `baseServerVersion`: `ZodOptional`<`ZodString`>; `clientTimestampUs`: `ZodString`; `entityKey`: `ZodRecord`<`ZodString`, `ZodString`>; `kind`: `ZodEnum`<{ `create`: `"create"`; `delete`: `"delete"`; `update`: `"update"`; }>; `mutationId`: `ZodUUID`; `mutationSeq`: `ZodNumber`; `payload`: `ZodUnknown`; `tableName`: `ZodString`; }, `$strict`>>; }, `$strict`>
Defined in: packages/contracts/src/mutation.ts:81
# CLOCK_US_CALL_SQL_TEXT
> `const` **CLOCK\_US\_CALL\_SQL\_TEXT**: `"public.pgxsinkit_clock_us()"` = `"public.pgxsinkit_clock_us()"`
Defined in: packages/contracts/src/sql-defaults.ts:20
The CALL form of the canonical microsecond clock, for string-rendering contexts (the apply-function generator’s PL/pgSQL literals). Deliberately `clock_timestamp()` (via the function body), never `now()`/`transaction_timestamp()`: `now()` freezes at transaction start, but LWW/audit ordering of a multi-row apply within one transaction needs an ADVANCING clock. The DB function is the canonical home — never inline the expression at a call site.
# clockMicrosecondsSql
> `const` **clockMicrosecondsSql**: `SQL`
Defined in: packages/contracts/src/sql-defaults.ts:29
The canonical microsecond clock as a Drizzle fragment, for `.default(...)` column positions — the CALL form `public.pgxsinkit_clock_us()`, never the inline expression. Deliberately `clock_timestamp()` (inside the function), never `now()`/`transaction_timestamp()`: `now()` freezes at transaction start, whereas LWW/audit ordering of a multi-row apply within one transaction needs an advancing clock. The DB function (installed by the utilities migration) is the canonical home; never inline the expression.
# CONFLICT_POLICIES
> `const` **CONFLICT\_POLICIES**: readonly \[`"last-write-wins"`, `"reject-if-stale"`]
Defined in: packages/contracts/src/config.ts:28
The Conflict policy values accepted in v1 (ADR-0015). Source of truth for registry validation.
# CONVERGENCE_EVENTS
> `const` **CONVERGENCE\_EVENTS**: readonly \[{ `effect`: `"overlay upserted; a journal mutation appended (records base server version for ADR-0015)"`; `event`: `"local create/update/delete enqueued"`; }, { `effect`: `"journal row → sending"`; `event`: `"mutation sent"`; }, { `effect`: `"journal row → acked, server_updated_at_us stamped; entity shows acked_unobserved until the echo catches up"`; `event`: `"mutation acked"`; }, { `effect`: `"synced row applied; the barrier predicate runs; resolved entities clear overlay + acked journal"`; `event`: `"Electric insert/update observed"`; }, { `effect`: `"resolved by synced-row absence (deletes carry no Server version — ADR-0010)"`; `event`: `"Electric delete observed"`; }, { `effect`: `"clear overlay/journal only through the shared barrier predicate (decision 4)"`; `event`: `"resolution"`; }, { `effect`: `"conflict_state recorded on the journal row (ADR-0015); surfaced in the view"`; `event`: `"conflict detected"`; }, { `effect`: `"journal row → quarantined (terminal, ADR-0006), overlay kept; surfaced as quarantined_count/quarantine_state"`; `event`: `"mutation quarantined"`; }, { `effect`: `"subscription reset + re-stream; affected entity state re-derives from the fresh synced rows"`; `event`: `"shape must-refetch"`; }]
Defined in: packages/contracts/src/convergence-model.ts:160
The event model the Convergence model implements (ADR-0011). The per-entity convergence state is **derived** from synced + overlay + journal — never a mutated row — so this table is the spec the derivation answers to, not a state machine that is stepped. Exported so a test can assert the implemented derivation covers every event.
# DENY_ALL
> `const` **DENY\_ALL**: `SQL`
Defined in: packages/contracts/src/config.ts:478
The deny-all row filter: a `customWhere` returns this to make **no** rows visible (e.g. an unauthenticated request), the counterpart to returning `null` (which bypasses filtering — all rows visible). It is a Drizzle `SQL` fragment (`false`), so it stays on the typed/parameterized path with the rest of the filter rather than being a hand-written `"1 = 0"` string. `WHERE false` matches nothing; Electric accepts it (verified) exactly as it accepts `1 = 0`.
# entityKeySchema
> `const` **entityKeySchema**: `ZodRecord`<`ZodString`, `ZodString`>
Defined in: packages/contracts/src/mutation.ts:30
# EVENT_STREAM_NAME_MAX_LENGTH
> `const` **EVENT\_STREAM\_NAME\_MAX\_LENGTH**: `number`
Defined in: packages/contracts/src/event-stream.ts:107
The maximum length of an Event-stream name: **30**. pgmq’s queue-name limit is 47 characters and [EVENT\_STREAM\_QUEUE\_PREFIX](/api/contracts/variables/event_stream_queue_prefix/) consumes 17 of them, so a longer name could not be provisioned. The bound is enforced at `defineSyncRegistry` (module eval) rather than at deployment DDL, because a name that fails only when the queue is created fails far too late.
# EVENT_STREAM_NAME_PATTERN
> `const` **EVENT\_STREAM\_NAME\_PATTERN**: `RegExp`
Defined in: packages/contracts/src/event-stream.ts:113
Event-stream names are lowercase `[a-z][a-z0-9_]*` — the intersection of what pgmq accepts unquoted and what stays legible as a queue/table suffix.
# EVENT_STREAM_QUEUE_PREFIX
> `const` **EVENT\_STREAM\_QUEUE\_PREFIX**: `"pgxsinkit_events_"` = `"pgxsinkit_events_"`
Defined in: packages/contracts/src/event-stream.ts:99
The queue-name prefix each Event stream’s pgmq queue is provisioned under (`pgxsinkit_events_`, ADR-0053 decision 5). Its length is what bounds [EVENT\_STREAM\_NAME\_MAX\_LENGTH](/api/contracts/variables/event_stream_name_max_length/).
# eventAckSchema
> `const` **eventAckSchema**: `ZodObject`<{ `eventId`: `ZodUUID`; `reason`: `ZodOptional`<`ZodString`>; `status`: `ZodEnum`<{ `acked`: `"acked"`; `deferred`: `"deferred"`; `refused`: `"refused"`; `rejected`: `"rejected"`; }>; }, `$strict`>
Defined in: packages/contracts/src/events.ts:91
# eventAckStatusSchema
> `const` **eventAckStatusSchema**: `ZodEnum`<{ `acked`: `"acked"`; `deferred`: `"deferred"`; `refused`: `"refused"`; `rejected`: `"rejected"`; }>
Defined in: packages/contracts/src/events.ts:89
The per-event verdict. Three of the four are TERMINAL — the client deletes the Outbox row:
* `acked` — enqueued.
* `refused` — the consent/entitlement gating hook said no.
* `rejected` — a schema-invalid payload for a KNOWN Event stream, or an oversized payload. Given append-time validation and the backward-compatibility rule, this means a non-library caller or a broken consumer deployment — a bug, not a rollout.
`deferred` is NOT terminal: an Event stream the server does not (yet) know is ordinary deployment skew (a client rolled out ahead of its server), and deleting those events would be data loss on a normal path. Deferred rows stay in the Outbox, retry with backoff, and drain when the rollout completes.
# eventEnvelopeSchema
> `const` **eventEnvelopeSchema**: `ZodObject`<{ `eventId`: `ZodUUID`; `occurredAtUs`: `ZodString`; `payload`: `ZodUnknown`; `stream`: `ZodString`; }, `$strict`>
Defined in: packages/contracts/src/events.ts:55
One client-produced event as it crosses the wire. The library builds it (`appendEvent` stamps `eventId` and `occurredAtUs`); `stream` is the registered Event-stream name.
It carries NO identity: identity is stamped server-side from verified claims per the stream’s registration (see `EventStreamIdentityField`), never client-trusted. `stream` is validated only as a non-empty string here — an unregistered name is a per-event `deferred` verdict (ordinary rollout skew), not a framing fault.
# eventQueueMessageSchema
> `const` **eventQueueMessageSchema**: `ZodObject`<{ `events`: `ZodArray`<`ZodObject`<{ `eventId`: `ZodUUID`; `identity`: `ZodRecord`<`ZodString`, `ZodString`>; `occurredAtUs`: `ZodString`; `payload`: `ZodUnknown`; }, `$strict`>>; `stream`: `ZodString`; }, `$strict`>
Defined in: packages/contracts/src/events.ts:140
One queue message: a single-stream sub-batch. The endpoint splits a mixed flush batch by Event stream (preserving array order) and enqueues each sub-batch as ONE message on that stream’s queue — one visibility timeout, one ack, one consumer transaction per message. Events arrive at the callback in append order WITHIN a message; across messages there is no ordering promise (ADR-0053 decision 6).
# jwtClaimsSchema
> `const` **jwtClaimsSchema**: `ZodObject`<{ `app_metadata`: `ZodOptional`<`ZodObject`<{ `roles`: `ZodOptional`<`ZodArray`<`ZodString`>>; }, `$loose`>>; `sub`: `ZodOptional`<`ZodString`>; }, `$loose`>
Defined in: packages/contracts/src/config.ts:247
Minimal verified-JWT claim shape the sync layer understands. Providers may attach arbitrary extra claims; those stay reachable through index access and ownership claim paths (e.g. “app\_metadata.person\_id”). Parse decoded JWT payloads with this schema at the auth boundary so the static type is honest.
# MAX_EVENT_PAYLOAD_BYTES
> `const` **MAX\_EVENT\_PAYLOAD\_BYTES**: `number`
Defined in: packages/contracts/src/events.ts:34
See [MAX\_EVENTS\_PER\_BATCH](/api/contracts/variables/max_events_per_batch/). Serialized bytes of one event’s payload.
# MAX_EVENT_REQUEST_BYTES
> `const` **MAX\_EVENT\_REQUEST\_BYTES**: `number`
Defined in: packages/contracts/src/events.ts:36
See [MAX\_EVENTS\_PER\_BATCH](/api/contracts/variables/max_events_per_batch/). Bytes of the whole `POST /api/events` body.
# MAX_EVENTS_PER_BATCH
> `const` **MAX\_EVENTS\_PER\_BATCH**: `1000` = `1000`
Defined in: packages/contracts/src/events.ts:32
The batch ingestion endpoint. Deliberate constants, not tuning: the client flush loop clamps its batching to them and the server enforces them independently of any client’s configuration.
**`MAX_EVENTS_PER_BATCH` (1000)** — one flush batch. Large enough that a device draining an offline backlog makes real progress per round trip, small enough that one batch stays a modest request and one queue transaction.
**`MAX_EVENT_PAYLOAD_BYTES` (64 KiB)** — the serialized payload of ONE event. Events are facts, not documents; a payload approaching this is a modelling smell. Enforced at `appendEvent` (so the library caller fails at the call site) AND at ingest, where a single oversized payload is a per-event `rejected` verdict rather than a batch fault.
**`MAX_EVENT_REQUEST_BYTES` (4 MiB)** — the whole request body, the backstop for the two above (1000 events × 64 KiB would exceed it, deliberately: the body cap binds first). A violation is a batch-level 413, reachable only under deployment skew or from a non-library caller.
# mutationAckSchema
> `const` **mutationAckSchema**: `ZodObject`<{ `conflictReason`: `ZodOptional`<`ZodString`>; `entityKey`: `ZodRecord`<`ZodString`, `ZodString`>; `httpStatus`: `ZodOptional`<`ZodNumber`>; `mutationId`: `ZodUUID`; `mutationSeq`: `ZodNumber`; `rejectionReason`: `ZodOptional`<`ZodString`>; `serverUpdatedAtUs`: `ZodOptional`<`ZodString`>; `status`: `ZodEnum`<{ `acked`: `"acked"`; `conflicted`: `"conflicted"`; `failed`: `"failed"`; `rejected`: `"rejected"`; }>; `tableName`: `ZodString`; }, `$strict`>
Defined in: packages/contracts/src/mutation.ts:58
# mutationAckStatusSchema
> `const` **mutationAckStatusSchema**: `ZodEnum`<{ `acked`: `"acked"`; `conflicted`: `"conflicted"`; `failed`: `"failed"`; `rejected`: `"rejected"`; }>
Defined in: packages/contracts/src/mutation.ts:28
The *transport* statuses a server ack may carry. `acked` (applied), `failed` (transient, retry), `conflicted` (ADR-0015 stale write — overlay KEPT, resolve as a new write), and `rejected` (ADR-0022 — a business rejection from the authoritative endpoint: a server-side invariant the client cannot evaluate said no, so the optimistic overlay is auto-discarded for the whole write-unit and the typed reason is surfaced).
# mutationEnvelopeSchema
> `const` **mutationEnvelopeSchema**: `ZodObject`<{ `baseServerVersion`: `ZodOptional`<`ZodString`>; `clientTimestampUs`: `ZodString`; `entityKey`: `ZodRecord`<`ZodString`, `ZodString`>; `kind`: `ZodEnum`<{ `create`: `"create"`; `delete`: `"delete"`; `update`: `"update"`; }>; `mutationId`: `ZodUUID`; `mutationSeq`: `ZodNumber`; `payload`: `ZodUnknown`; `tableName`: `ZodString`; }, `$strict`>
Defined in: packages/contracts/src/mutation.ts:32
# mutationKindSchema
> `const` **mutationKindSchema**: `ZodEnum`<{ `create`: `"create"`; `delete`: `"delete"`; `update`: `"update"`; }>
Defined in: packages/contracts/src/mutation.ts:5
# mutationRejectionSchema
> `const` **mutationRejectionSchema**: `ZodObject`<{ `mutationId`: `ZodUUID`; `mutationSeq`: `ZodNumber`; `reason`: `ZodString`; `tableName`: `ZodString`; }, `$strict`>
Defined in: packages/contracts/src/mutation.ts:115
Per-mutation attribution for a structural batch rejection. The batch write is atomic — one structurally-invalid mutation rejects the whole POST with a single non-2xx — so the server names the offending mutation(s) here. That lets the client quarantine exactly those and keep the innocent siblings retryable, instead of dragging the whole offline queue to quarantine at the shared attempt cap.
# mutationStatusSchema
> `const` **mutationStatusSchema**: `ZodEnum`<{ `acked`: `"acked"`; `conflicted`: `"conflicted"`; `failed`: `"failed"`; `pending`: `"pending"`; `quarantined`: `"quarantined"`; `rejected`: `"rejected"`; `sending`: `"sending"`; }>
Defined in: packages/contracts/src/mutation.ts:12
The full Mutation-journal status machine — every status a journal row can hold, including the two terminal states (`quarantined`, ADR-0006; `conflicted`, ADR-0015). Kept in lockstep with the client’s `MutationStatus` (packages/client/src/mutation-state.ts). Distinct from [mutationAckStatusSchema](/api/contracts/variables/mutationackstatusschema/), the narrower *transport* subset a server ack may carry.
# NOW_MICROSECONDS_SQL_TEXT
> `const` **NOW\_MICROSECONDS\_SQL\_TEXT**: `"CAST(FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000000) AS BIGINT)"` = `"CAST(FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000000) AS BIGINT)"`
Defined in: packages/contracts/src/sql-defaults.ts:11
The BODY of the canonical microsecond clock — `clock_timestamp()` epoch microseconds as BIGINT. This text is the SINGLE source for the `public.pgxsinkit_clock_us()` function body rendered by the server’s utilities migration (renderPgxsinkitUtilitiesMigration) and NOTHING else. No column DEFAULT and no generated apply-function DDL may embed this expression — every surface CALLS the function ([CLOCK\_US\_CALL\_SQL\_TEXT](/api/contracts/variables/clock_us_call_sql_text/) / [clockMicrosecondsSql](/api/contracts/variables/clockmicrosecondssql/)), so the semantic choices (`clock_timestamp()` over `now()`; `FLOOR` + `BIGINT`) live in exactly one reviewable body.
# RETENTIONS
> `const` **RETENTIONS**: readonly \[`"persistent"`, `"ephemeral"`]
Defined in: packages/contracts/src/config.ts:70
The [Retention](/api/contracts/type-aliases/retention/) values. Source of truth for registry validation.
# stampedEventSchema
> `const` **stampedEventSchema**: `ZodObject`<{ `eventId`: `ZodUUID`; `identity`: `ZodRecord`<`ZodString`, `ZodString`>; `occurredAtUs`: `ZodString`; `payload`: `ZodUnknown`; }, `$strict`>
Defined in: packages/contracts/src/events.ts:125
The **stamped** envelope (ADR-0053 decision 5): what the server produces at ingest, carries through the queue, and delivers to the consumer callback as-is. `identity` is the record of claim-derived fields the Event stream’s registration declares, resolved from the VERIFIED claims of the ingesting request.
This is the security-sensitive interface of the lane — the point where a client-supplied event becomes an attributed fact — so its shape is contracts-defined, never implementation-defined. It carries no `stream` (the queue message names it once, below) and no client-supplied identity of any kind.
# STORAGE_BACKENDS
> `const` **STORAGE\_BACKENDS**: readonly \[`"opfs"`, `"idbfs"`]
Defined in: packages/contracts/src/config.ts:115
The [StorageBackend](/api/contracts/type-aliases/storagebackend/) values. Source of truth for [SyncStorageDeclaration](/api/contracts/interfaces/syncstoragedeclaration/) validation.
# STORAGE_DURABILITIES
> `const` **STORAGE\_DURABILITIES**: readonly \[`"relaxed"`, `"strict"`]
Defined in: packages/contracts/src/config.ts:133
The [StorageDurability](/api/contracts/type-aliases/storagedurability/) values. Source of truth for [SyncStorageDeclaration](/api/contracts/interfaces/syncstoragedeclaration/) validation.
# SUBSCRIPTION_TIMINGS
> `const` **SUBSCRIPTION\_TIMINGS**: readonly \[`"eager"`, `"lazy"`]
Defined in: packages/contracts/src/config.ts:49
The [SubscriptionTiming](/api/contracts/type-aliases/subscriptiontiming/) values. Source of truth for registry validation.
# supabaseOwnerOrAdminDefaults
> `const` **supabaseOwnerOrAdminDefaults**: `object`
Defined in: packages/contracts/src/supabase-rls.ts:268
## Type Declaration
[Section titled “Type Declaration”](#type-declaration)
### adminRoleName
[Section titled “adminRoleName”](#adminrolename)
> `readonly` **adminRoleName**: `"admin"` = `defaultAdminRoleName`
### authenticatedRoleName
[Section titled “authenticatedRoleName”](#authenticatedrolename)
> `readonly` **authenticatedRoleName**: `"authenticated"` = `defaultAuthenticatedRoleName`
### ownerPropertyKey
[Section titled “ownerPropertyKey”](#ownerpropertykey)
> `readonly` **ownerPropertyKey**: `"ownerId"` = `defaultOwnerPropertyKey`
### ownerSqlColumn
[Section titled “ownerSqlColumn”](#ownersqlcolumn)
> `readonly` **ownerSqlColumn**: `"owner_id"` = `defaultOwnerSqlColumn`
### subjectCastType
[Section titled “subjectCastType”](#subjectcasttype)
> `readonly` **subjectCastType**: `"uuid"` = `defaultSubjectCastType`
# syncRegistryRowClassesSymbol
> `const` **syncRegistryRowClassesSymbol**: *typeof* `syncRegistryRowClassesSymbol`
Defined in: packages/contracts/src/registry.ts:464
# syncRegistrySchemaSymbol
> `const` **syncRegistrySchemaSymbol**: *typeof* `syncRegistrySchemaSymbol`
Defined in: packages/contracts/src/registry.ts:460
# syncRegistryStorageSymbol
> `const` **syncRegistryStorageSymbol**: *typeof* `syncRegistryStorageSymbol`
Defined in: packages/contracts/src/registry.ts:462
# syncRegistryStreamsSymbol
> `const` **syncRegistryStreamsSymbol**: *typeof* `syncRegistryStreamsSymbol`
Defined in: packages/contracts/src/registry.ts:466
# unixMicrosecondsSchema
> `const` **unixMicrosecondsSchema**: `ZodString`
Defined in: packages/contracts/src/common.ts:3
# WRITE_MODES
> `const` **WRITE\_MODES**: readonly \[`"optimistic"`, `"pessimistic"`]
Defined in: packages/contracts/src/config.ts:96
The [WriteMode](/api/contracts/type-aliases/writemode/) values. Source of truth for registry validation.
# CorruptStoreError
Defined in: packages/pglite-opfs-repacked/src/core/errors.ts:43
Activated bytes violate format integrity or semantic invariants.
## Extends
[Section titled “Extends”](#extends)
* `Error`
## Constructors
[Section titled “Constructors”](#constructors)
### Constructor
[Section titled “Constructor”](#constructor)
> **new CorruptStoreError**(`message`, `options?`): `CorruptStoreError`
Defined in: packages/pglite-opfs-repacked/src/core/errors.ts:46
#### Parameters
[Section titled “Parameters”](#parameters)
##### message
[Section titled “message”](#message)
`string`
##### options?
[Section titled “options?”](#options)
`ErrorOptions`
#### Returns
[Section titled “Returns”](#returns)
`CorruptStoreError`
#### Overrides
[Section titled “Overrides”](#overrides)
`Error.constructor`
## Properties
[Section titled “Properties”](#properties)
### cause?
[Section titled “cause?”](#cause)
> `optional` **cause?**: `unknown`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:24
The cause of the error.
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
`Error.cause`
***
### message
[Section titled “message”](#message-1)
> **message**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1075
#### Inherited from
[Section titled “Inherited from”](#inherited-from-1)
`Error.message`
***
### name
[Section titled “name”](#name)
> **name**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1074
#### Inherited from
[Section titled “Inherited from”](#inherited-from-2)
`Error.name`
***
### stack?
[Section titled “stack?”](#stack)
> `optional` **stack?**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1076
#### Inherited from
[Section titled “Inherited from”](#inherited-from-3)
`Error.stack`
***
### storeCode
[Section titled “storeCode”](#storecode)
> `readonly` **storeCode**: `"CORRUPT_STORE"` = `"CORRUPT_STORE"`
Defined in: packages/pglite-opfs-repacked/src/core/errors.ts:44
***
### stackTraceLimit
[Section titled “stackTraceLimit”](#stacktracelimit)
> `static` **stackTraceLimit**: `number`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:67
The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`).
The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed.
If set to a non-number value, or set to a negative number, stack traces will not capture any frames.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-4)
`Error.stackTraceLimit`
## Methods
[Section titled “Methods”](#methods)
### captureStackTrace()
[Section titled “captureStackTrace()”](#capturestacktrace)
#### Call Signature
[Section titled “Call Signature”](#call-signature)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:51
Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called.
```js
const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack; // Similar to `new Error().stack`
```
The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`.
The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace.
The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance:
```js
function a() {
b();
}
function b() {
c();
}
function c() {
// Create an error without stack trace to avoid calculating the stack trace twice.
const { stackTraceLimit } = Error;
Error.stackTraceLimit = 0;
const error = new Error();
Error.stackTraceLimit = stackTraceLimit;
// Capture the stack trace above function b
Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
throw error;
}
a();
```
##### Parameters
[Section titled “Parameters”](#parameters-1)
###### targetObject
[Section titled “targetObject”](#targetobject)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt)
`Function`
##### Returns
[Section titled “Returns”](#returns-1)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-5)
`Error.captureStackTrace`
#### Call Signature
[Section titled “Call Signature”](#call-signature-1)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1042
Create .stack property on a target object
##### Parameters
[Section titled “Parameters”](#parameters-2)
###### targetObject
[Section titled “targetObject”](#targetobject-1)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt-1)
`Function`
##### Returns
[Section titled “Returns”](#returns-2)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-6)
`Error.captureStackTrace`
***
### isError()
[Section titled “isError()”](#iserror)
#### Call Signature
[Section titled “Call Signature”](#call-signature-2)
> `static` **isError**(`error`): `error is Error`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.esnext.error.d.ts:21
Indicates whether the argument provided is a built-in Error instance or not.
##### Parameters
[Section titled “Parameters”](#parameters-3)
###### error
[Section titled “error”](#error)
`unknown`
##### Returns
[Section titled “Returns”](#returns-3)
`error is Error`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-7)
`Error.isError`
#### Call Signature
[Section titled “Call Signature”](#call-signature-3)
> `static` **isError**(`value`): `value is Error`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1037
Check if a value is an instance of Error
##### Parameters
[Section titled “Parameters”](#parameters-4)
###### value
[Section titled “value”](#value)
`unknown`
The value to check
##### Returns
[Section titled “Returns”](#returns-4)
`value is Error`
True if the value is an instance of Error, false otherwise
##### Inherited from
[Section titled “Inherited from”](#inherited-from-8)
`Error.isError`
***
### prepareStackTrace()
[Section titled “prepareStackTrace()”](#preparestacktrace)
> `static` **prepareStackTrace**(`err`, `stackTraces`): `any`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:55
#### Parameters
[Section titled “Parameters”](#parameters-5)
##### err
[Section titled “err”](#err)
`Error`
##### stackTraces
[Section titled “stackTraces”](#stacktraces)
`CallSite`\[]
#### Returns
[Section titled “Returns”](#returns-5)
`any`
#### See
[Section titled “See”](#see)
#### Inherited from
[Section titled “Inherited from”](#inherited-from-9)
`Error.prepareStackTrace`
# DurabilityModeMismatchError
Defined in: packages/pglite-opfs-repacked/src/core/errors.ts:93
The host attempted a non-awaited sync, proving the factory wiring was bypassed.
## Extends
[Section titled “Extends”](#extends)
* `Error`
## Constructors
[Section titled “Constructors”](#constructors)
### Constructor
[Section titled “Constructor”](#constructor)
> **new DurabilityModeMismatchError**(): `DurabilityModeMismatchError`
Defined in: packages/pglite-opfs-repacked/src/core/errors.ts:96
#### Returns
[Section titled “Returns”](#returns)
`DurabilityModeMismatchError`
#### Overrides
[Section titled “Overrides”](#overrides)
`Error.constructor`
## Properties
[Section titled “Properties”](#properties)
### cause?
[Section titled “cause?”](#cause)
> `optional` **cause?**: `unknown`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:24
The cause of the error.
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
`Error.cause`
***
### message
[Section titled “message”](#message)
> **message**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1075
#### Inherited from
[Section titled “Inherited from”](#inherited-from-1)
`Error.message`
***
### name
[Section titled “name”](#name)
> **name**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1074
#### Inherited from
[Section titled “Inherited from”](#inherited-from-2)
`Error.name`
***
### stack?
[Section titled “stack?”](#stack)
> `optional` **stack?**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1076
#### Inherited from
[Section titled “Inherited from”](#inherited-from-3)
`Error.stack`
***
### storeCode
[Section titled “storeCode”](#storecode)
> `readonly` **storeCode**: `"DURABILITY_MODE_MISMATCH"` = `"DURABILITY_MODE_MISMATCH"`
Defined in: packages/pglite-opfs-repacked/src/core/errors.ts:94
***
### stackTraceLimit
[Section titled “stackTraceLimit”](#stacktracelimit)
> `static` **stackTraceLimit**: `number`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:67
The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`).
The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed.
If set to a non-number value, or set to a negative number, stack traces will not capture any frames.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-4)
`Error.stackTraceLimit`
## Methods
[Section titled “Methods”](#methods)
### captureStackTrace()
[Section titled “captureStackTrace()”](#capturestacktrace)
#### Call Signature
[Section titled “Call Signature”](#call-signature)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:51
Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called.
```js
const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack; // Similar to `new Error().stack`
```
The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`.
The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace.
The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance:
```js
function a() {
b();
}
function b() {
c();
}
function c() {
// Create an error without stack trace to avoid calculating the stack trace twice.
const { stackTraceLimit } = Error;
Error.stackTraceLimit = 0;
const error = new Error();
Error.stackTraceLimit = stackTraceLimit;
// Capture the stack trace above function b
Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
throw error;
}
a();
```
##### Parameters
[Section titled “Parameters”](#parameters)
###### targetObject
[Section titled “targetObject”](#targetobject)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt)
`Function`
##### Returns
[Section titled “Returns”](#returns-1)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-5)
`Error.captureStackTrace`
#### Call Signature
[Section titled “Call Signature”](#call-signature-1)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1042
Create .stack property on a target object
##### Parameters
[Section titled “Parameters”](#parameters-1)
###### targetObject
[Section titled “targetObject”](#targetobject-1)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt-1)
`Function`
##### Returns
[Section titled “Returns”](#returns-2)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-6)
`Error.captureStackTrace`
***
### isError()
[Section titled “isError()”](#iserror)
#### Call Signature
[Section titled “Call Signature”](#call-signature-2)
> `static` **isError**(`error`): `error is Error`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.esnext.error.d.ts:21
Indicates whether the argument provided is a built-in Error instance or not.
##### Parameters
[Section titled “Parameters”](#parameters-2)
###### error
[Section titled “error”](#error)
`unknown`
##### Returns
[Section titled “Returns”](#returns-3)
`error is Error`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-7)
`Error.isError`
#### Call Signature
[Section titled “Call Signature”](#call-signature-3)
> `static` **isError**(`value`): `value is Error`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1037
Check if a value is an instance of Error
##### Parameters
[Section titled “Parameters”](#parameters-3)
###### value
[Section titled “value”](#value)
`unknown`
The value to check
##### Returns
[Section titled “Returns”](#returns-4)
`value is Error`
True if the value is an instance of Error, false otherwise
##### Inherited from
[Section titled “Inherited from”](#inherited-from-8)
`Error.isError`
***
### prepareStackTrace()
[Section titled “prepareStackTrace()”](#preparestacktrace)
> `static` **prepareStackTrace**(`err`, `stackTraces`): `any`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:55
#### Parameters
[Section titled “Parameters”](#parameters-4)
##### err
[Section titled “err”](#err)
`Error`
##### stackTraces
[Section titled “stackTraces”](#stacktraces)
`CallSite`\[]
#### Returns
[Section titled “Returns”](#returns-5)
`any`
#### See
[Section titled “See”](#see)
#### Inherited from
[Section titled “Inherited from”](#inherited-from-9)
`Error.prepareStackTrace`
# ExtentSizeMismatchError
Defined in: packages/pglite-opfs-repacked/src/core/errors.ts:63
A supplied creation extent size disagrees with the existing store identity.
## Extends
[Section titled “Extends”](#extends)
* `Error`
## Constructors
[Section titled “Constructors”](#constructors)
### Constructor
[Section titled “Constructor”](#constructor)
> **new ExtentSizeMismatchError**(`expected`, `actual`): `ExtentSizeMismatchError`
Defined in: packages/pglite-opfs-repacked/src/core/errors.ts:66
#### Parameters
[Section titled “Parameters”](#parameters)
##### expected
[Section titled “expected”](#expected)
`number`
##### actual
[Section titled “actual”](#actual)
`number`
#### Returns
[Section titled “Returns”](#returns)
`ExtentSizeMismatchError`
#### Overrides
[Section titled “Overrides”](#overrides)
`Error.constructor`
## Properties
[Section titled “Properties”](#properties)
### cause?
[Section titled “cause?”](#cause)
> `optional` **cause?**: `unknown`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:24
The cause of the error.
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
`Error.cause`
***
### message
[Section titled “message”](#message)
> **message**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1075
#### Inherited from
[Section titled “Inherited from”](#inherited-from-1)
`Error.message`
***
### name
[Section titled “name”](#name)
> **name**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1074
#### Inherited from
[Section titled “Inherited from”](#inherited-from-2)
`Error.name`
***
### stack?
[Section titled “stack?”](#stack)
> `optional` **stack?**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1076
#### Inherited from
[Section titled “Inherited from”](#inherited-from-3)
`Error.stack`
***
### storeCode
[Section titled “storeCode”](#storecode)
> `readonly` **storeCode**: `"EXTENT_SIZE_MISMATCH"` = `"EXTENT_SIZE_MISMATCH"`
Defined in: packages/pglite-opfs-repacked/src/core/errors.ts:64
***
### stackTraceLimit
[Section titled “stackTraceLimit”](#stacktracelimit)
> `static` **stackTraceLimit**: `number`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:67
The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`).
The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed.
If set to a non-number value, or set to a negative number, stack traces will not capture any frames.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-4)
`Error.stackTraceLimit`
## Methods
[Section titled “Methods”](#methods)
### captureStackTrace()
[Section titled “captureStackTrace()”](#capturestacktrace)
#### Call Signature
[Section titled “Call Signature”](#call-signature)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:51
Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called.
```js
const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack; // Similar to `new Error().stack`
```
The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`.
The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace.
The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance:
```js
function a() {
b();
}
function b() {
c();
}
function c() {
// Create an error without stack trace to avoid calculating the stack trace twice.
const { stackTraceLimit } = Error;
Error.stackTraceLimit = 0;
const error = new Error();
Error.stackTraceLimit = stackTraceLimit;
// Capture the stack trace above function b
Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
throw error;
}
a();
```
##### Parameters
[Section titled “Parameters”](#parameters-1)
###### targetObject
[Section titled “targetObject”](#targetobject)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt)
`Function`
##### Returns
[Section titled “Returns”](#returns-1)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-5)
`Error.captureStackTrace`
#### Call Signature
[Section titled “Call Signature”](#call-signature-1)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1042
Create .stack property on a target object
##### Parameters
[Section titled “Parameters”](#parameters-2)
###### targetObject
[Section titled “targetObject”](#targetobject-1)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt-1)
`Function`
##### Returns
[Section titled “Returns”](#returns-2)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-6)
`Error.captureStackTrace`
***
### isError()
[Section titled “isError()”](#iserror)
#### Call Signature
[Section titled “Call Signature”](#call-signature-2)
> `static` **isError**(`error`): `error is Error`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.esnext.error.d.ts:21
Indicates whether the argument provided is a built-in Error instance or not.
##### Parameters
[Section titled “Parameters”](#parameters-3)
###### error
[Section titled “error”](#error)
`unknown`
##### Returns
[Section titled “Returns”](#returns-3)
`error is Error`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-7)
`Error.isError`
#### Call Signature
[Section titled “Call Signature”](#call-signature-3)
> `static` **isError**(`value`): `value is Error`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1037
Check if a value is an instance of Error
##### Parameters
[Section titled “Parameters”](#parameters-4)
###### value
[Section titled “value”](#value)
`unknown`
The value to check
##### Returns
[Section titled “Returns”](#returns-4)
`value is Error`
True if the value is an instance of Error, false otherwise
##### Inherited from
[Section titled “Inherited from”](#inherited-from-8)
`Error.isError`
***
### prepareStackTrace()
[Section titled “prepareStackTrace()”](#preparestacktrace)
> `static` **prepareStackTrace**(`err`, `stackTraces`): `any`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:55
#### Parameters
[Section titled “Parameters”](#parameters-5)
##### err
[Section titled “err”](#err)
`Error`
##### stackTraces
[Section titled “stackTraces”](#stacktraces)
`CallSite`\[]
#### Returns
[Section titled “Returns”](#returns-5)
`any`
#### See
[Section titled “See”](#see)
#### Inherited from
[Section titled “Inherited from”](#inherited-from-9)
`Error.prepareStackTrace`
# FsError
Defined in: packages/pglite-opfs-repacked/src/core/errors.ts:14
A normal virtual-filesystem rejection. The live store remains usable.
## Extends
[Section titled “Extends”](#extends)
* `Error`
## Constructors
[Section titled “Constructors”](#constructors)
### Constructor
[Section titled “Constructor”](#constructor)
> **new FsError**(`name`, `message`, `options?`): `FsError`
Defined in: packages/pglite-opfs-repacked/src/core/errors.ts:19
#### Parameters
[Section titled “Parameters”](#parameters)
##### name
[Section titled “name”](#name)
`"EBADF"` | `"EEXIST"` | `"EINVAL"` | `"EISDIR"` | `"ENOENT"` | `"ENOTDIR"` | `"ENOTEMPTY"`
##### message
[Section titled “message”](#message)
`string`
##### options?
[Section titled “options?”](#options)
###### cause?
[Section titled “cause?”](#cause)
`unknown`
###### operation?
[Section titled “operation?”](#operation)
`string`
###### path?
[Section titled “path?”](#path)
`string`
#### Returns
[Section titled “Returns”](#returns)
`FsError`
#### Overrides
[Section titled “Overrides”](#overrides)
`Error.constructor`
## Properties
[Section titled “Properties”](#properties)
### cause?
[Section titled “cause?”](#cause-1)
> `optional` **cause?**: `unknown`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:24
The cause of the error.
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
`Error.cause`
***
### code
[Section titled “code”](#code)
> `readonly` **code**: `number`
Defined in: packages/pglite-opfs-repacked/src/core/errors.ts:15
***
### message
[Section titled “message”](#message-1)
> **message**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1075
#### Inherited from
[Section titled “Inherited from”](#inherited-from-1)
`Error.message`
***
### name
[Section titled “name”](#name-1)
> **name**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1074
#### Inherited from
[Section titled “Inherited from”](#inherited-from-2)
`Error.name`
***
### operation
[Section titled “operation”](#operation-1)
> `readonly` **operation**: `string` | `undefined`
Defined in: packages/pglite-opfs-repacked/src/core/errors.ts:16
***
### path
[Section titled “path”](#path-1)
> `readonly` **path**: `string` | `undefined`
Defined in: packages/pglite-opfs-repacked/src/core/errors.ts:17
***
### stack?
[Section titled “stack?”](#stack)
> `optional` **stack?**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1076
#### Inherited from
[Section titled “Inherited from”](#inherited-from-3)
`Error.stack`
***
### stackTraceLimit
[Section titled “stackTraceLimit”](#stacktracelimit)
> `static` **stackTraceLimit**: `number`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:67
The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`).
The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed.
If set to a non-number value, or set to a negative number, stack traces will not capture any frames.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-4)
`Error.stackTraceLimit`
## Methods
[Section titled “Methods”](#methods)
### captureStackTrace()
[Section titled “captureStackTrace()”](#capturestacktrace)
#### Call Signature
[Section titled “Call Signature”](#call-signature)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:51
Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called.
```js
const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack; // Similar to `new Error().stack`
```
The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`.
The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace.
The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance:
```js
function a() {
b();
}
function b() {
c();
}
function c() {
// Create an error without stack trace to avoid calculating the stack trace twice.
const { stackTraceLimit } = Error;
Error.stackTraceLimit = 0;
const error = new Error();
Error.stackTraceLimit = stackTraceLimit;
// Capture the stack trace above function b
Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
throw error;
}
a();
```
##### Parameters
[Section titled “Parameters”](#parameters-1)
###### targetObject
[Section titled “targetObject”](#targetobject)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt)
`Function`
##### Returns
[Section titled “Returns”](#returns-1)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-5)
`Error.captureStackTrace`
#### Call Signature
[Section titled “Call Signature”](#call-signature-1)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1042
Create .stack property on a target object
##### Parameters
[Section titled “Parameters”](#parameters-2)
###### targetObject
[Section titled “targetObject”](#targetobject-1)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt-1)
`Function`
##### Returns
[Section titled “Returns”](#returns-2)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-6)
`Error.captureStackTrace`
***
### isError()
[Section titled “isError()”](#iserror)
#### Call Signature
[Section titled “Call Signature”](#call-signature-2)
> `static` **isError**(`error`): `error is Error`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.esnext.error.d.ts:21
Indicates whether the argument provided is a built-in Error instance or not.
##### Parameters
[Section titled “Parameters”](#parameters-3)
###### error
[Section titled “error”](#error)
`unknown`
##### Returns
[Section titled “Returns”](#returns-3)
`error is Error`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-7)
`Error.isError`
#### Call Signature
[Section titled “Call Signature”](#call-signature-3)
> `static` **isError**(`value`): `value is Error`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1037
Check if a value is an instance of Error
##### Parameters
[Section titled “Parameters”](#parameters-4)
###### value
[Section titled “value”](#value)
`unknown`
The value to check
##### Returns
[Section titled “Returns”](#returns-4)
`value is Error`
True if the value is an instance of Error, false otherwise
##### Inherited from
[Section titled “Inherited from”](#inherited-from-8)
`Error.isError`
***
### prepareStackTrace()
[Section titled “prepareStackTrace()”](#preparestacktrace)
> `static` **prepareStackTrace**(`err`, `stackTraces`): `any`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:55
#### Parameters
[Section titled “Parameters”](#parameters-5)
##### err
[Section titled “err”](#err)
`Error`
##### stackTraces
[Section titled “stackTraces”](#stacktraces)
`CallSite`\[]
#### Returns
[Section titled “Returns”](#returns-5)
`any`
#### See
[Section titled “See”](#see)
#### Inherited from
[Section titled “Inherited from”](#inherited-from-9)
`Error.prepareStackTrace`
# OpfsRepackedFS
Defined in: packages/pglite-opfs-repacked/src/opfs-repacked-fs.ts:25
PGlite filesystem adapter owned by `createOpfsRepackedPGlite`.
Direct construction is unsupported. The factory retains this adapter so it can close all four handles when host initialization or shutdown fails.
## Extends
[Section titled “Extends”](#extends)
* `BaseFilesystem`
## Properties
[Section titled “Properties”](#properties)
### debug
[Section titled “debug”](#debug)
> `readonly` **debug**: `boolean`
Defined in: node\_modules/.bun/@pgxsinkit+pglite\@0.5.4-pgx.11/node\_modules/@pgxsinkit/pglite/dist/pglite-DWiE6ARG.d.ts:395
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
`BaseFilesystem.debug`
## Accessors
[Section titled “Accessors”](#accessors)
### recentSyscallErrors
[Section titled “recentSyscallErrors”](#recentsyscallerrors)
#### Get Signature
[Section titled “Get Signature”](#get-signature)
> **get** **recentSyscallErrors**(): readonly `SyscallError`\[]
Defined in: node\_modules/.bun/@pgxsinkit+pglite\@0.5.4-pgx.11/node\_modules/@pgxsinkit/pglite/dist/pglite-DWiE6ARG.d.ts:403
The most recent syscall failures (up to MAX\_SYSCALL\_ERRORS) that the emscripten FS wrapper converted into errno results. Oldest first.
##### Returns
[Section titled “Returns”](#returns)
readonly `SyscallError`\[]
#### Inherited from
[Section titled “Inherited from”](#inherited-from-1)
`BaseFilesystem.recentSyscallErrors`
## Methods
[Section titled “Methods”](#methods)
### chmod()
[Section titled “chmod()”](#chmod)
> **chmod**(`path`, `mode`): `void`
Defined in: packages/pglite-opfs-repacked/src/opfs-repacked-fs.ts:68
#### Parameters
[Section titled “Parameters”](#parameters)
##### path
[Section titled “path”](#path)
`string`
##### mode
[Section titled “mode”](#mode)
`number`
#### Returns
[Section titled “Returns”](#returns-1)
`void`
#### Overrides
[Section titled “Overrides”](#overrides)
`BaseFilesystem.chmod`
***
### close()
[Section titled “close()”](#close)
> **close**(`fd`): `void`
Defined in: packages/pglite-opfs-repacked/src/opfs-repacked-fs.ts:72
#### Parameters
[Section titled “Parameters”](#parameters-1)
##### fd
[Section titled “fd”](#fd)
`number`
#### Returns
[Section titled “Returns”](#returns-2)
`void`
#### Overrides
[Section titled “Overrides”](#overrides-1)
`BaseFilesystem.close`
***
### closeFs()
[Section titled “closeFs()”](#closefs)
> **closeFs**(): `Promise`<`void`>
Defined in: packages/pglite-opfs-repacked/src/opfs-repacked-fs.ts:60
#### Returns
[Section titled “Returns”](#returns-3)
`Promise`<`void`>
#### Overrides
[Section titled “Overrides”](#overrides-2)
`BaseFilesystem.closeFs`
***
### dumpTar()
[Section titled “dumpTar()”](#dumptar)
> **dumpTar**(`dbname`, `compression?`): `Promise`<`Blob` | `File`>
Defined in: node\_modules/.bun/@pgxsinkit+pglite\@0.5.4-pgx.11/node\_modules/@pgxsinkit/pglite/dist/pglite-DWiE6ARG.d.ts:413
Dump the PGDATA dir from the filesystem to a gzipped tarball.
#### Parameters
[Section titled “Parameters”](#parameters-2)
##### dbname
[Section titled “dbname”](#dbname)
`string`
##### compression?
[Section titled “compression?”](#compression)
`DumpTarCompressionOptions`
#### Returns
[Section titled “Returns”](#returns-4)
`Promise`<`Blob` | `File`>
#### Inherited from
[Section titled “Inherited from”](#inherited-from-2)
`BaseFilesystem.dumpTar`
***
### fstat()
[Section titled “fstat()”](#fstat)
> **fstat**(`fd`): `FsStats`
Defined in: packages/pglite-opfs-repacked/src/opfs-repacked-fs.ts:76
#### Parameters
[Section titled “Parameters”](#parameters-3)
##### fd
[Section titled “fd”](#fd-1)
`number`
#### Returns
[Section titled “Returns”](#returns-5)
`FsStats`
#### Overrides
[Section titled “Overrides”](#overrides-3)
`BaseFilesystem.fstat`
***
### init()
[Section titled “init()”](#init)
> **init**(`pg`, `emscriptenOptions`): `Promise`<{ `emscriptenOpts`: `Partial`<`PostgresMod`>; }>
Defined in: node\_modules/.bun/@pgxsinkit+pglite\@0.5.4-pgx.11/node\_modules/@pgxsinkit/pglite/dist/pglite-DWiE6ARG.d.ts:414
Initiate the filesystem and return the options to pass to the emscripten module.
#### Parameters
[Section titled “Parameters”](#parameters-4)
##### pg
[Section titled “pg”](#pg)
`PGlite`
##### emscriptenOptions
[Section titled “emscriptenOptions”](#emscriptenoptions)
`Partial`<`PostgresMod`>
#### Returns
[Section titled “Returns”](#returns-6)
`Promise`<{ `emscriptenOpts`: `Partial`<`PostgresMod`>; }>
#### Inherited from
[Section titled “Inherited from”](#inherited-from-3)
`BaseFilesystem.init`
***
### initialSyncFs()
[Section titled “initialSyncFs()”](#initialsyncfs)
> **initialSyncFs**(): `Promise`<`void`>
Defined in: packages/pglite-opfs-repacked/src/opfs-repacked-fs.ts:35
#### Returns
[Section titled “Returns”](#returns-7)
`Promise`<`void`>
#### Overrides
[Section titled “Overrides”](#overrides-4)
`BaseFilesystem.initialSyncFs`
***
### lstat()
[Section titled “lstat()”](#lstat)
> **lstat**(`path`): `FsStats`
Defined in: packages/pglite-opfs-repacked/src/opfs-repacked-fs.ts:80
#### Parameters
[Section titled “Parameters”](#parameters-5)
##### path
[Section titled “path”](#path-1)
`string`
#### Returns
[Section titled “Returns”](#returns-8)
`FsStats`
#### Overrides
[Section titled “Overrides”](#overrides-5)
`BaseFilesystem.lstat`
***
### mkdir()
[Section titled “mkdir()”](#mkdir)
> **mkdir**(`path`, `options?`): `void`
Defined in: packages/pglite-opfs-repacked/src/opfs-repacked-fs.ts:84
#### Parameters
[Section titled “Parameters”](#parameters-6)
##### path
[Section titled “path”](#path-2)
`string`
##### options?
[Section titled “options?”](#options)
###### mode?
[Section titled “mode?”](#mode-1)
`number`
###### recursive?
[Section titled “recursive?”](#recursive)
`boolean`
#### Returns
[Section titled “Returns”](#returns-9)
`void`
#### Overrides
[Section titled “Overrides”](#overrides-6)
`BaseFilesystem.mkdir`
***
### open()
[Section titled “open()”](#open)
> **open**(`path`, `flags?`, `mode?`): `number`
Defined in: packages/pglite-opfs-repacked/src/opfs-repacked-fs.ts:92
#### Parameters
[Section titled “Parameters”](#parameters-7)
##### path
[Section titled “path”](#path-3)
`string`
##### flags?
[Section titled “flags?”](#flags)
`string` = `"r+"`
##### mode?
[Section titled “mode?”](#mode-2)
`number` = `0o100666`
#### Returns
[Section titled “Returns”](#returns-10)
`number`
#### Overrides
[Section titled “Overrides”](#overrides-7)
`BaseFilesystem.open`
***
### read()
[Section titled “read()”](#read)
> **read**(`fd`, `buffer`, `offset`, `length`, `position`): `number`
Defined in: packages/pglite-opfs-repacked/src/opfs-repacked-fs.ts:100
#### Parameters
[Section titled “Parameters”](#parameters-8)
##### fd
[Section titled “fd”](#fd-2)
`number`
##### buffer
[Section titled “buffer”](#buffer)
`Uint8Array`
##### offset
[Section titled “offset”](#offset)
`number`
##### length
[Section titled “length”](#length)
`number`
##### position
[Section titled “position”](#position)
`number`
#### Returns
[Section titled “Returns”](#returns-11)
`number`
#### Overrides
[Section titled “Overrides”](#overrides-8)
`BaseFilesystem.read`
***
### readdir()
[Section titled “readdir()”](#readdir)
> **readdir**(`path`): `string`\[]
Defined in: packages/pglite-opfs-repacked/src/opfs-repacked-fs.ts:96
#### Parameters
[Section titled “Parameters”](#parameters-9)
##### path
[Section titled “path”](#path-4)
`string`
#### Returns
[Section titled “Returns”](#returns-12)
`string`\[]
#### Overrides
[Section titled “Overrides”](#overrides-9)
`BaseFilesystem.readdir`
***
### recordSyscallError()
[Section titled “recordSyscallError()”](#recordsyscallerror)
> **recordSyscallError**(`op`, `path`, `errno`, `message`): `void`
Defined in: node\_modules/.bun/@pgxsinkit+pglite\@0.5.4-pgx.11/node\_modules/@pgxsinkit/pglite/dist/pglite-DWiE6ARG.d.ts:409
Record a syscall failure into the ring buffer. Called by the emscripten FS wrapper at the point it converts a thrown error into an errno result. The allocation only happens when an error actually occurs.
#### Parameters
[Section titled “Parameters”](#parameters-10)
##### op
[Section titled “op”](#op)
`string`
##### path
[Section titled “path”](#path-5)
`string`
##### errno
[Section titled “errno”](#errno)
`number`
##### message
[Section titled “message”](#message)
`string`
#### Returns
[Section titled “Returns”](#returns-13)
`void`
#### Inherited from
[Section titled “Inherited from”](#inherited-from-4)
`BaseFilesystem.recordSyscallError`
***
### rename()
[Section titled “rename()”](#rename)
> **rename**(`oldPath`, `newPath`): `void`
Defined in: packages/pglite-opfs-repacked/src/opfs-repacked-fs.ts:104
#### Parameters
[Section titled “Parameters”](#parameters-11)
##### oldPath
[Section titled “oldPath”](#oldpath)
`string`
##### newPath
[Section titled “newPath”](#newpath)
`string`
#### Returns
[Section titled “Returns”](#returns-14)
`void`
#### Overrides
[Section titled “Overrides”](#overrides-10)
`BaseFilesystem.rename`
***
### rmdir()
[Section titled “rmdir()”](#rmdir)
> **rmdir**(`path`): `void`
Defined in: packages/pglite-opfs-repacked/src/opfs-repacked-fs.ts:108
#### Parameters
[Section titled “Parameters”](#parameters-12)
##### path
[Section titled “path”](#path-6)
`string`
#### Returns
[Section titled “Returns”](#returns-15)
`void`
#### Overrides
[Section titled “Overrides”](#overrides-11)
`BaseFilesystem.rmdir`
***
### strictSync()
[Section titled “strictSync()”](#strictsync)
> **strictSync**(): `void`
Defined in: packages/pglite-opfs-repacked/src/opfs-repacked-fs.ts:47
Stabilize every preceding data and metadata operation in strict order.
#### Returns
[Section titled “Returns”](#returns-16)
`void`
***
### syncToFs()
[Section titled “syncToFs()”](#synctofs)
> **syncToFs**(`relaxedDurability?`): `Promise`<`void`>
Defined in: packages/pglite-opfs-repacked/src/opfs-repacked-fs.ts:39
#### Parameters
[Section titled “Parameters”](#parameters-13)
##### relaxedDurability?
[Section titled “relaxedDurability?”](#relaxeddurability)
`boolean` = `false`
#### Returns
[Section titled “Returns”](#returns-17)
`Promise`<`void`>
#### Overrides
[Section titled “Overrides”](#overrides-12)
`BaseFilesystem.syncToFs`
***
### truncate()
[Section titled “truncate()”](#truncate)
> **truncate**(`path`, `length`): `void`
Defined in: packages/pglite-opfs-repacked/src/opfs-repacked-fs.ts:112
#### Parameters
[Section titled “Parameters”](#parameters-14)
##### path
[Section titled “path”](#path-7)
`string`
##### length
[Section titled “length”](#length-1)
`number`
#### Returns
[Section titled “Returns”](#returns-18)
`void`
#### Overrides
[Section titled “Overrides”](#overrides-13)
`BaseFilesystem.truncate`
***
### unlink()
[Section titled “unlink()”](#unlink)
> **unlink**(`path`): `void`
Defined in: packages/pglite-opfs-repacked/src/opfs-repacked-fs.ts:116
#### Parameters
[Section titled “Parameters”](#parameters-15)
##### path
[Section titled “path”](#path-8)
`string`
#### Returns
[Section titled “Returns”](#returns-19)
`void`
#### Overrides
[Section titled “Overrides”](#overrides-14)
`BaseFilesystem.unlink`
***
### utimes()
[Section titled “utimes()”](#utimes)
> **utimes**(`path`, `atime`, `mtime`): `void`
Defined in: packages/pglite-opfs-repacked/src/opfs-repacked-fs.ts:120
#### Parameters
[Section titled “Parameters”](#parameters-16)
##### path
[Section titled “path”](#path-9)
`string`
##### atime
[Section titled “atime”](#atime)
`number`
##### mtime
[Section titled “mtime”](#mtime)
`number`
#### Returns
[Section titled “Returns”](#returns-20)
`void`
#### Overrides
[Section titled “Overrides”](#overrides-15)
`BaseFilesystem.utimes`
***
### write()
[Section titled “write()”](#write)
> **write**(`fd`, `buffer`, `offset`, `length`, `position`): `number`
Defined in: packages/pglite-opfs-repacked/src/opfs-repacked-fs.ts:137
#### Parameters
[Section titled “Parameters”](#parameters-17)
##### fd
[Section titled “fd”](#fd-3)
`number`
##### buffer
[Section titled “buffer”](#buffer-1)
`Uint8Array`
##### offset
[Section titled “offset”](#offset-1)
`number`
##### length
[Section titled “length”](#length-2)
`number`
##### position
[Section titled “position”](#position-1)
`number`
#### Returns
[Section titled “Returns”](#returns-21)
`number`
#### Overrides
[Section titled “Overrides”](#overrides-16)
`BaseFilesystem.write`
***
### writeFile()
[Section titled “writeFile()”](#writefile)
> **writeFile**(`path`, `data`, `options?`): `void`
Defined in: packages/pglite-opfs-repacked/src/opfs-repacked-fs.ts:124
#### Parameters
[Section titled “Parameters”](#parameters-18)
##### path
[Section titled “path”](#path-10)
`string`
##### data
[Section titled “data”](#data)
`string` | `Uint8Array`<`ArrayBufferLike`>
##### options?
[Section titled “options?”](#options-1)
###### encoding?
[Section titled “encoding?”](#encoding)
`string`
###### flag?
[Section titled “flag?”](#flag)
`string`
###### mode?
[Section titled “mode?”](#mode-3)
`number`
#### Returns
[Section titled “Returns”](#returns-22)
`void`
#### Overrides
[Section titled “Overrides”](#overrides-17)
`BaseFilesystem.writeFile`
# StoreClosedError
Defined in: packages/pglite-opfs-repacked/src/core/errors.ts:103
The adapter was used after all owned handles were closed.
## Extends
[Section titled “Extends”](#extends)
* `Error`
## Constructors
[Section titled “Constructors”](#constructors)
### Constructor
[Section titled “Constructor”](#constructor)
> **new StoreClosedError**(): `StoreClosedError`
Defined in: packages/pglite-opfs-repacked/src/core/errors.ts:106
#### Returns
[Section titled “Returns”](#returns)
`StoreClosedError`
#### Overrides
[Section titled “Overrides”](#overrides)
`Error.constructor`
## Properties
[Section titled “Properties”](#properties)
### cause?
[Section titled “cause?”](#cause)
> `optional` **cause?**: `unknown`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:24
The cause of the error.
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
`Error.cause`
***
### message
[Section titled “message”](#message)
> **message**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1075
#### Inherited from
[Section titled “Inherited from”](#inherited-from-1)
`Error.message`
***
### name
[Section titled “name”](#name)
> **name**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1074
#### Inherited from
[Section titled “Inherited from”](#inherited-from-2)
`Error.name`
***
### stack?
[Section titled “stack?”](#stack)
> `optional` **stack?**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1076
#### Inherited from
[Section titled “Inherited from”](#inherited-from-3)
`Error.stack`
***
### storeCode
[Section titled “storeCode”](#storecode)
> `readonly` **storeCode**: `"STORE_CLOSED"` = `"STORE_CLOSED"`
Defined in: packages/pglite-opfs-repacked/src/core/errors.ts:104
***
### stackTraceLimit
[Section titled “stackTraceLimit”](#stacktracelimit)
> `static` **stackTraceLimit**: `number`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:67
The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`).
The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed.
If set to a non-number value, or set to a negative number, stack traces will not capture any frames.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-4)
`Error.stackTraceLimit`
## Methods
[Section titled “Methods”](#methods)
### captureStackTrace()
[Section titled “captureStackTrace()”](#capturestacktrace)
#### Call Signature
[Section titled “Call Signature”](#call-signature)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:51
Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called.
```js
const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack; // Similar to `new Error().stack`
```
The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`.
The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace.
The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance:
```js
function a() {
b();
}
function b() {
c();
}
function c() {
// Create an error without stack trace to avoid calculating the stack trace twice.
const { stackTraceLimit } = Error;
Error.stackTraceLimit = 0;
const error = new Error();
Error.stackTraceLimit = stackTraceLimit;
// Capture the stack trace above function b
Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
throw error;
}
a();
```
##### Parameters
[Section titled “Parameters”](#parameters)
###### targetObject
[Section titled “targetObject”](#targetobject)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt)
`Function`
##### Returns
[Section titled “Returns”](#returns-1)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-5)
`Error.captureStackTrace`
#### Call Signature
[Section titled “Call Signature”](#call-signature-1)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1042
Create .stack property on a target object
##### Parameters
[Section titled “Parameters”](#parameters-1)
###### targetObject
[Section titled “targetObject”](#targetobject-1)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt-1)
`Function`
##### Returns
[Section titled “Returns”](#returns-2)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-6)
`Error.captureStackTrace`
***
### isError()
[Section titled “isError()”](#iserror)
#### Call Signature
[Section titled “Call Signature”](#call-signature-2)
> `static` **isError**(`error`): `error is Error`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.esnext.error.d.ts:21
Indicates whether the argument provided is a built-in Error instance or not.
##### Parameters
[Section titled “Parameters”](#parameters-2)
###### error
[Section titled “error”](#error)
`unknown`
##### Returns
[Section titled “Returns”](#returns-3)
`error is Error`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-7)
`Error.isError`
#### Call Signature
[Section titled “Call Signature”](#call-signature-3)
> `static` **isError**(`value`): `value is Error`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1037
Check if a value is an instance of Error
##### Parameters
[Section titled “Parameters”](#parameters-3)
###### value
[Section titled “value”](#value)
`unknown`
The value to check
##### Returns
[Section titled “Returns”](#returns-4)
`value is Error`
True if the value is an instance of Error, false otherwise
##### Inherited from
[Section titled “Inherited from”](#inherited-from-8)
`Error.isError`
***
### prepareStackTrace()
[Section titled “prepareStackTrace()”](#preparestacktrace)
> `static` **prepareStackTrace**(`err`, `stackTraces`): `any`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:55
#### Parameters
[Section titled “Parameters”](#parameters-4)
##### err
[Section titled “err”](#err)
`Error`
##### stackTraces
[Section titled “stackTraces”](#stacktraces)
`CallSite`\[]
#### Returns
[Section titled “Returns”](#returns-5)
`any`
#### See
[Section titled “See”](#see)
#### Inherited from
[Section titled “Inherited from”](#inherited-from-9)
`Error.prepareStackTrace`
# StoreFailedError
Defined in: packages/pglite-opfs-repacked/src/core/errors.ts:113
The live instance is poisoned and retains its first terminal cause.
## Extends
[Section titled “Extends”](#extends)
* `Error`
## Constructors
[Section titled “Constructors”](#constructors)
### Constructor
[Section titled “Constructor”](#constructor)
> **new StoreFailedError**(`cause`): `StoreFailedError`
Defined in: packages/pglite-opfs-repacked/src/core/errors.ts:117
#### Parameters
[Section titled “Parameters”](#parameters)
##### cause
[Section titled “cause”](#cause)
`unknown`
#### Returns
[Section titled “Returns”](#returns)
`StoreFailedError`
#### Overrides
[Section titled “Overrides”](#overrides)
`Error.constructor`
## Properties
[Section titled “Properties”](#properties)
### cause
[Section titled “cause”](#cause-1)
> `readonly` **cause**: `unknown`
Defined in: packages/pglite-opfs-repacked/src/core/errors.ts:115
The cause of the error.
#### Overrides
[Section titled “Overrides”](#overrides-1)
`Error.cause`
***
### message
[Section titled “message”](#message)
> **message**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1075
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
`Error.message`
***
### name
[Section titled “name”](#name)
> **name**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1074
#### Inherited from
[Section titled “Inherited from”](#inherited-from-1)
`Error.name`
***
### stack?
[Section titled “stack?”](#stack)
> `optional` **stack?**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1076
#### Inherited from
[Section titled “Inherited from”](#inherited-from-2)
`Error.stack`
***
### storeCode
[Section titled “storeCode”](#storecode)
> `readonly` **storeCode**: `"STORE_FAILED"` = `"STORE_FAILED"`
Defined in: packages/pglite-opfs-repacked/src/core/errors.ts:114
***
### stackTraceLimit
[Section titled “stackTraceLimit”](#stacktracelimit)
> `static` **stackTraceLimit**: `number`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:67
The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`).
The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed.
If set to a non-number value, or set to a negative number, stack traces will not capture any frames.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-3)
`Error.stackTraceLimit`
## Methods
[Section titled “Methods”](#methods)
### captureStackTrace()
[Section titled “captureStackTrace()”](#capturestacktrace)
#### Call Signature
[Section titled “Call Signature”](#call-signature)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:51
Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called.
```js
const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack; // Similar to `new Error().stack`
```
The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`.
The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace.
The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance:
```js
function a() {
b();
}
function b() {
c();
}
function c() {
// Create an error without stack trace to avoid calculating the stack trace twice.
const { stackTraceLimit } = Error;
Error.stackTraceLimit = 0;
const error = new Error();
Error.stackTraceLimit = stackTraceLimit;
// Capture the stack trace above function b
Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
throw error;
}
a();
```
##### Parameters
[Section titled “Parameters”](#parameters-1)
###### targetObject
[Section titled “targetObject”](#targetobject)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt)
`Function`
##### Returns
[Section titled “Returns”](#returns-1)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-4)
`Error.captureStackTrace`
#### Call Signature
[Section titled “Call Signature”](#call-signature-1)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1042
Create .stack property on a target object
##### Parameters
[Section titled “Parameters”](#parameters-2)
###### targetObject
[Section titled “targetObject”](#targetobject-1)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt-1)
`Function`
##### Returns
[Section titled “Returns”](#returns-2)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-5)
`Error.captureStackTrace`
***
### isError()
[Section titled “isError()”](#iserror)
#### Call Signature
[Section titled “Call Signature”](#call-signature-2)
> `static` **isError**(`error`): `error is Error`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.esnext.error.d.ts:21
Indicates whether the argument provided is a built-in Error instance or not.
##### Parameters
[Section titled “Parameters”](#parameters-3)
###### error
[Section titled “error”](#error)
`unknown`
##### Returns
[Section titled “Returns”](#returns-3)
`error is Error`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-6)
`Error.isError`
#### Call Signature
[Section titled “Call Signature”](#call-signature-3)
> `static` **isError**(`value`): `value is Error`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1037
Check if a value is an instance of Error
##### Parameters
[Section titled “Parameters”](#parameters-4)
###### value
[Section titled “value”](#value)
`unknown`
The value to check
##### Returns
[Section titled “Returns”](#returns-4)
`value is Error`
True if the value is an instance of Error, false otherwise
##### Inherited from
[Section titled “Inherited from”](#inherited-from-7)
`Error.isError`
***
### prepareStackTrace()
[Section titled “prepareStackTrace()”](#preparestacktrace)
> `static` **prepareStackTrace**(`err`, `stackTraces`): `any`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:55
#### Parameters
[Section titled “Parameters”](#parameters-5)
##### err
[Section titled “err”](#err)
`Error`
##### stackTraces
[Section titled “stackTraces”](#stacktraces)
`CallSite`\[]
#### Returns
[Section titled “Returns”](#returns-5)
`any`
#### See
[Section titled “See”](#see)
#### Inherited from
[Section titled “Inherited from”](#inherited-from-8)
`Error.prepareStackTrace`
# StoreLimitError
Defined in: packages/pglite-opfs-repacked/src/core/errors.ts:33
A configured or format hard limit was reached.
## Extends
[Section titled “Extends”](#extends)
* `Error`
## Constructors
[Section titled “Constructors”](#constructors)
### Constructor
[Section titled “Constructor”](#constructor)
> **new StoreLimitError**(`message`, `options?`): `StoreLimitError`
Defined in: packages/pglite-opfs-repacked/src/core/errors.ts:36
#### Parameters
[Section titled “Parameters”](#parameters)
##### message
[Section titled “message”](#message)
`string`
##### options?
[Section titled “options?”](#options)
`ErrorOptions`
#### Returns
[Section titled “Returns”](#returns)
`StoreLimitError`
#### Overrides
[Section titled “Overrides”](#overrides)
`Error.constructor`
## Properties
[Section titled “Properties”](#properties)
### cause?
[Section titled “cause?”](#cause)
> `optional` **cause?**: `unknown`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:24
The cause of the error.
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
`Error.cause`
***
### message
[Section titled “message”](#message-1)
> **message**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1075
#### Inherited from
[Section titled “Inherited from”](#inherited-from-1)
`Error.message`
***
### name
[Section titled “name”](#name)
> **name**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1074
#### Inherited from
[Section titled “Inherited from”](#inherited-from-2)
`Error.name`
***
### stack?
[Section titled “stack?”](#stack)
> `optional` **stack?**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1076
#### Inherited from
[Section titled “Inherited from”](#inherited-from-3)
`Error.stack`
***
### storeCode
[Section titled “storeCode”](#storecode)
> `readonly` **storeCode**: `"STORE_LIMIT"` = `"STORE_LIMIT"`
Defined in: packages/pglite-opfs-repacked/src/core/errors.ts:34
***
### stackTraceLimit
[Section titled “stackTraceLimit”](#stacktracelimit)
> `static` **stackTraceLimit**: `number`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:67
The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`).
The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed.
If set to a non-number value, or set to a negative number, stack traces will not capture any frames.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-4)
`Error.stackTraceLimit`
## Methods
[Section titled “Methods”](#methods)
### captureStackTrace()
[Section titled “captureStackTrace()”](#capturestacktrace)
#### Call Signature
[Section titled “Call Signature”](#call-signature)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:51
Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called.
```js
const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack; // Similar to `new Error().stack`
```
The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`.
The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace.
The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance:
```js
function a() {
b();
}
function b() {
c();
}
function c() {
// Create an error without stack trace to avoid calculating the stack trace twice.
const { stackTraceLimit } = Error;
Error.stackTraceLimit = 0;
const error = new Error();
Error.stackTraceLimit = stackTraceLimit;
// Capture the stack trace above function b
Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
throw error;
}
a();
```
##### Parameters
[Section titled “Parameters”](#parameters-1)
###### targetObject
[Section titled “targetObject”](#targetobject)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt)
`Function`
##### Returns
[Section titled “Returns”](#returns-1)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-5)
`Error.captureStackTrace`
#### Call Signature
[Section titled “Call Signature”](#call-signature-1)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1042
Create .stack property on a target object
##### Parameters
[Section titled “Parameters”](#parameters-2)
###### targetObject
[Section titled “targetObject”](#targetobject-1)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt-1)
`Function`
##### Returns
[Section titled “Returns”](#returns-2)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-6)
`Error.captureStackTrace`
***
### isError()
[Section titled “isError()”](#iserror)
#### Call Signature
[Section titled “Call Signature”](#call-signature-2)
> `static` **isError**(`error`): `error is Error`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.esnext.error.d.ts:21
Indicates whether the argument provided is a built-in Error instance or not.
##### Parameters
[Section titled “Parameters”](#parameters-3)
###### error
[Section titled “error”](#error)
`unknown`
##### Returns
[Section titled “Returns”](#returns-3)
`error is Error`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-7)
`Error.isError`
#### Call Signature
[Section titled “Call Signature”](#call-signature-3)
> `static` **isError**(`value`): `value is Error`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1037
Check if a value is an instance of Error
##### Parameters
[Section titled “Parameters”](#parameters-4)
###### value
[Section titled “value”](#value)
`unknown`
The value to check
##### Returns
[Section titled “Returns”](#returns-4)
`value is Error`
True if the value is an instance of Error, false otherwise
##### Inherited from
[Section titled “Inherited from”](#inherited-from-8)
`Error.isError`
***
### prepareStackTrace()
[Section titled “prepareStackTrace()”](#preparestacktrace)
> `static` **prepareStackTrace**(`err`, `stackTraces`): `any`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:55
#### Parameters
[Section titled “Parameters”](#parameters-5)
##### err
[Section titled “err”](#err)
`Error`
##### stackTraces
[Section titled “stackTraces”](#stacktraces)
`CallSite`\[]
#### Returns
[Section titled “Returns”](#returns-5)
`any`
#### See
[Section titled “See”](#see)
#### Inherited from
[Section titled “Inherited from”](#inherited-from-9)
`Error.prepareStackTrace`
# StoreOwnedError
Defined in: packages/pglite-opfs-repacked/src/core/errors.ts:73
Another live instance owns at least one required exclusive OPFS handle.
## Extends
[Section titled “Extends”](#extends)
* `Error`
## Constructors
[Section titled “Constructors”](#constructors)
### Constructor
[Section titled “Constructor”](#constructor)
> **new StoreOwnedError**(`options?`): `StoreOwnedError`
Defined in: packages/pglite-opfs-repacked/src/core/errors.ts:76
#### Parameters
[Section titled “Parameters”](#parameters)
##### options?
[Section titled “options?”](#options)
`ErrorOptions`
#### Returns
[Section titled “Returns”](#returns)
`StoreOwnedError`
#### Overrides
[Section titled “Overrides”](#overrides)
`Error.constructor`
## Properties
[Section titled “Properties”](#properties)
### cause?
[Section titled “cause?”](#cause)
> `optional` **cause?**: `unknown`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:24
The cause of the error.
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
`Error.cause`
***
### message
[Section titled “message”](#message)
> **message**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1075
#### Inherited from
[Section titled “Inherited from”](#inherited-from-1)
`Error.message`
***
### name
[Section titled “name”](#name)
> **name**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1074
#### Inherited from
[Section titled “Inherited from”](#inherited-from-2)
`Error.name`
***
### stack?
[Section titled “stack?”](#stack)
> `optional` **stack?**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1076
#### Inherited from
[Section titled “Inherited from”](#inherited-from-3)
`Error.stack`
***
### storeCode
[Section titled “storeCode”](#storecode)
> `readonly` **storeCode**: `"STORE_OWNED"` = `"STORE_OWNED"`
Defined in: packages/pglite-opfs-repacked/src/core/errors.ts:74
***
### stackTraceLimit
[Section titled “stackTraceLimit”](#stacktracelimit)
> `static` **stackTraceLimit**: `number`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:67
The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`).
The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed.
If set to a non-number value, or set to a negative number, stack traces will not capture any frames.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-4)
`Error.stackTraceLimit`
## Methods
[Section titled “Methods”](#methods)
### captureStackTrace()
[Section titled “captureStackTrace()”](#capturestacktrace)
#### Call Signature
[Section titled “Call Signature”](#call-signature)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:51
Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called.
```js
const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack; // Similar to `new Error().stack`
```
The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`.
The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace.
The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance:
```js
function a() {
b();
}
function b() {
c();
}
function c() {
// Create an error without stack trace to avoid calculating the stack trace twice.
const { stackTraceLimit } = Error;
Error.stackTraceLimit = 0;
const error = new Error();
Error.stackTraceLimit = stackTraceLimit;
// Capture the stack trace above function b
Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
throw error;
}
a();
```
##### Parameters
[Section titled “Parameters”](#parameters-1)
###### targetObject
[Section titled “targetObject”](#targetobject)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt)
`Function`
##### Returns
[Section titled “Returns”](#returns-1)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-5)
`Error.captureStackTrace`
#### Call Signature
[Section titled “Call Signature”](#call-signature-1)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1042
Create .stack property on a target object
##### Parameters
[Section titled “Parameters”](#parameters-2)
###### targetObject
[Section titled “targetObject”](#targetobject-1)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt-1)
`Function`
##### Returns
[Section titled “Returns”](#returns-2)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-6)
`Error.captureStackTrace`
***
### isError()
[Section titled “isError()”](#iserror)
#### Call Signature
[Section titled “Call Signature”](#call-signature-2)
> `static` **isError**(`error`): `error is Error`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.esnext.error.d.ts:21
Indicates whether the argument provided is a built-in Error instance or not.
##### Parameters
[Section titled “Parameters”](#parameters-3)
###### error
[Section titled “error”](#error)
`unknown`
##### Returns
[Section titled “Returns”](#returns-3)
`error is Error`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-7)
`Error.isError`
#### Call Signature
[Section titled “Call Signature”](#call-signature-3)
> `static` **isError**(`value`): `value is Error`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1037
Check if a value is an instance of Error
##### Parameters
[Section titled “Parameters”](#parameters-4)
###### value
[Section titled “value”](#value)
`unknown`
The value to check
##### Returns
[Section titled “Returns”](#returns-4)
`value is Error`
True if the value is an instance of Error, false otherwise
##### Inherited from
[Section titled “Inherited from”](#inherited-from-8)
`Error.isError`
***
### prepareStackTrace()
[Section titled “prepareStackTrace()”](#preparestacktrace)
> `static` **prepareStackTrace**(`err`, `stackTraces`): `any`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:55
#### Parameters
[Section titled “Parameters”](#parameters-5)
##### err
[Section titled “err”](#err)
`Error`
##### stackTraces
[Section titled “stackTraces”](#stacktraces)
`CallSite`\[]
#### Returns
[Section titled “Returns”](#returns-5)
`any`
#### See
[Section titled “See”](#see)
#### Inherited from
[Section titled “Inherited from”](#inherited-from-9)
`Error.prepareStackTrace`
# StoreRecreationRequiredError
Defined in: packages/pglite-opfs-repacked/src/core/errors.ts:53
The store identifies a different format and must be deleted in full.
## Extends
[Section titled “Extends”](#extends)
* `Error`
## Constructors
[Section titled “Constructors”](#constructors)
### Constructor
[Section titled “Constructor”](#constructor)
> **new StoreRecreationRequiredError**(`message`, `options?`): `StoreRecreationRequiredError`
Defined in: packages/pglite-opfs-repacked/src/core/errors.ts:56
#### Parameters
[Section titled “Parameters”](#parameters)
##### message
[Section titled “message”](#message)
`string`
##### options?
[Section titled “options?”](#options)
`ErrorOptions`
#### Returns
[Section titled “Returns”](#returns)
`StoreRecreationRequiredError`
#### Overrides
[Section titled “Overrides”](#overrides)
`Error.constructor`
## Properties
[Section titled “Properties”](#properties)
### cause?
[Section titled “cause?”](#cause)
> `optional` **cause?**: `unknown`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:24
The cause of the error.
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
`Error.cause`
***
### message
[Section titled “message”](#message-1)
> **message**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1075
#### Inherited from
[Section titled “Inherited from”](#inherited-from-1)
`Error.message`
***
### name
[Section titled “name”](#name)
> **name**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1074
#### Inherited from
[Section titled “Inherited from”](#inherited-from-2)
`Error.name`
***
### stack?
[Section titled “stack?”](#stack)
> `optional` **stack?**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1076
#### Inherited from
[Section titled “Inherited from”](#inherited-from-3)
`Error.stack`
***
### storeCode
[Section titled “storeCode”](#storecode)
> `readonly` **storeCode**: `"STORE_RECREATION_REQUIRED"` = `"STORE_RECREATION_REQUIRED"`
Defined in: packages/pglite-opfs-repacked/src/core/errors.ts:54
***
### stackTraceLimit
[Section titled “stackTraceLimit”](#stacktracelimit)
> `static` **stackTraceLimit**: `number`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:67
The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`).
The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed.
If set to a non-number value, or set to a negative number, stack traces will not capture any frames.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-4)
`Error.stackTraceLimit`
## Methods
[Section titled “Methods”](#methods)
### captureStackTrace()
[Section titled “captureStackTrace()”](#capturestacktrace)
#### Call Signature
[Section titled “Call Signature”](#call-signature)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:51
Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called.
```js
const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack; // Similar to `new Error().stack`
```
The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`.
The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace.
The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance:
```js
function a() {
b();
}
function b() {
c();
}
function c() {
// Create an error without stack trace to avoid calculating the stack trace twice.
const { stackTraceLimit } = Error;
Error.stackTraceLimit = 0;
const error = new Error();
Error.stackTraceLimit = stackTraceLimit;
// Capture the stack trace above function b
Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
throw error;
}
a();
```
##### Parameters
[Section titled “Parameters”](#parameters-1)
###### targetObject
[Section titled “targetObject”](#targetobject)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt)
`Function`
##### Returns
[Section titled “Returns”](#returns-1)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-5)
`Error.captureStackTrace`
#### Call Signature
[Section titled “Call Signature”](#call-signature-1)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1042
Create .stack property on a target object
##### Parameters
[Section titled “Parameters”](#parameters-2)
###### targetObject
[Section titled “targetObject”](#targetobject-1)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt-1)
`Function`
##### Returns
[Section titled “Returns”](#returns-2)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-6)
`Error.captureStackTrace`
***
### isError()
[Section titled “isError()”](#iserror)
#### Call Signature
[Section titled “Call Signature”](#call-signature-2)
> `static` **isError**(`error`): `error is Error`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.esnext.error.d.ts:21
Indicates whether the argument provided is a built-in Error instance or not.
##### Parameters
[Section titled “Parameters”](#parameters-3)
###### error
[Section titled “error”](#error)
`unknown`
##### Returns
[Section titled “Returns”](#returns-3)
`error is Error`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-7)
`Error.isError`
#### Call Signature
[Section titled “Call Signature”](#call-signature-3)
> `static` **isError**(`value`): `value is Error`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1037
Check if a value is an instance of Error
##### Parameters
[Section titled “Parameters”](#parameters-4)
###### value
[Section titled “value”](#value)
`unknown`
The value to check
##### Returns
[Section titled “Returns”](#returns-4)
`value is Error`
True if the value is an instance of Error, false otherwise
##### Inherited from
[Section titled “Inherited from”](#inherited-from-8)
`Error.isError`
***
### prepareStackTrace()
[Section titled “prepareStackTrace()”](#preparestacktrace)
> `static` **prepareStackTrace**(`err`, `stackTraces`): `any`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:55
#### Parameters
[Section titled “Parameters”](#parameters-5)
##### err
[Section titled “err”](#err)
`Error`
##### stackTraces
[Section titled “stackTraces”](#stacktraces)
`CallSite`\[]
#### Returns
[Section titled “Returns”](#returns-5)
`any`
#### See
[Section titled “See”](#see)
#### Inherited from
[Section titled “Inherited from”](#inherited-from-9)
`Error.prepareStackTrace`
# UnexpectedStoreEntryError
Defined in: packages/pglite-opfs-repacked/src/core/errors.ts:83
The dedicated store directory contains an entry outside the exact owned set.
## Extends
[Section titled “Extends”](#extends)
* `Error`
## Constructors
[Section titled “Constructors”](#constructors)
### Constructor
[Section titled “Constructor”](#constructor)
> **new UnexpectedStoreEntryError**(`entryName`): `UnexpectedStoreEntryError`
Defined in: packages/pglite-opfs-repacked/src/core/errors.ts:86
#### Parameters
[Section titled “Parameters”](#parameters)
##### entryName
[Section titled “entryName”](#entryname)
`string`
#### Returns
[Section titled “Returns”](#returns)
`UnexpectedStoreEntryError`
#### Overrides
[Section titled “Overrides”](#overrides)
`Error.constructor`
## Properties
[Section titled “Properties”](#properties)
### cause?
[Section titled “cause?”](#cause)
> `optional` **cause?**: `unknown`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:24
The cause of the error.
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
`Error.cause`
***
### message
[Section titled “message”](#message)
> **message**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1075
#### Inherited from
[Section titled “Inherited from”](#inherited-from-1)
`Error.message`
***
### name
[Section titled “name”](#name)
> **name**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1074
#### Inherited from
[Section titled “Inherited from”](#inherited-from-2)
`Error.name`
***
### stack?
[Section titled “stack?”](#stack)
> `optional` **stack?**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1076
#### Inherited from
[Section titled “Inherited from”](#inherited-from-3)
`Error.stack`
***
### storeCode
[Section titled “storeCode”](#storecode)
> `readonly` **storeCode**: `"UNEXPECTED_STORE_ENTRY"` = `"UNEXPECTED_STORE_ENTRY"`
Defined in: packages/pglite-opfs-repacked/src/core/errors.ts:84
***
### stackTraceLimit
[Section titled “stackTraceLimit”](#stacktracelimit)
> `static` **stackTraceLimit**: `number`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:67
The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`).
The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed.
If set to a non-number value, or set to a negative number, stack traces will not capture any frames.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-4)
`Error.stackTraceLimit`
## Methods
[Section titled “Methods”](#methods)
### captureStackTrace()
[Section titled “captureStackTrace()”](#capturestacktrace)
#### Call Signature
[Section titled “Call Signature”](#call-signature)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:51
Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called.
```js
const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack; // Similar to `new Error().stack`
```
The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`.
The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace.
The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance:
```js
function a() {
b();
}
function b() {
c();
}
function c() {
// Create an error without stack trace to avoid calculating the stack trace twice.
const { stackTraceLimit } = Error;
Error.stackTraceLimit = 0;
const error = new Error();
Error.stackTraceLimit = stackTraceLimit;
// Capture the stack trace above function b
Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
throw error;
}
a();
```
##### Parameters
[Section titled “Parameters”](#parameters-1)
###### targetObject
[Section titled “targetObject”](#targetobject)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt)
`Function`
##### Returns
[Section titled “Returns”](#returns-1)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-5)
`Error.captureStackTrace`
#### Call Signature
[Section titled “Call Signature”](#call-signature-1)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1042
Create .stack property on a target object
##### Parameters
[Section titled “Parameters”](#parameters-2)
###### targetObject
[Section titled “targetObject”](#targetobject-1)
`object`
###### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt-1)
`Function`
##### Returns
[Section titled “Returns”](#returns-2)
`void`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-6)
`Error.captureStackTrace`
***
### isError()
[Section titled “isError()”](#iserror)
#### Call Signature
[Section titled “Call Signature”](#call-signature-2)
> `static` **isError**(`error`): `error is Error`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.esnext.error.d.ts:21
Indicates whether the argument provided is a built-in Error instance or not.
##### Parameters
[Section titled “Parameters”](#parameters-3)
###### error
[Section titled “error”](#error)
`unknown`
##### Returns
[Section titled “Returns”](#returns-3)
`error is Error`
##### Inherited from
[Section titled “Inherited from”](#inherited-from-7)
`Error.isError`
#### Call Signature
[Section titled “Call Signature”](#call-signature-3)
> `static` **isError**(`value`): `value is Error`
Defined in: node\_modules/.bun/bun-types\@1.3.14/node\_modules/bun-types/globals.d.ts:1037
Check if a value is an instance of Error
##### Parameters
[Section titled “Parameters”](#parameters-4)
###### value
[Section titled “value”](#value)
`unknown`
The value to check
##### Returns
[Section titled “Returns”](#returns-4)
`value is Error`
True if the value is an instance of Error, false otherwise
##### Inherited from
[Section titled “Inherited from”](#inherited-from-8)
`Error.isError`
***
### prepareStackTrace()
[Section titled “prepareStackTrace()”](#preparestacktrace)
> `static` **prepareStackTrace**(`err`, `stackTraces`): `any`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:55
#### Parameters
[Section titled “Parameters”](#parameters-5)
##### err
[Section titled “err”](#err)
`Error`
##### stackTraces
[Section titled “stackTraces”](#stacktraces)
`CallSite`\[]
#### Returns
[Section titled “Returns”](#returns-5)
`any`
#### See
[Section titled “See”](#see)
#### Inherited from
[Section titled “Inherited from”](#inherited-from-9)
`Error.prepareStackTrace`
# createOpfsRepackedPGlite
> **createOpfsRepackedPGlite**<`TExtensions`>(`options`): `Promise`<[`OpfsRepackedPGlite`](/api/pglite-opfs-repacked/type-aliases/opfsrepackedpglite/)<`TExtensions`>>
Defined in: packages/pglite-opfs-repacked/src/pglite-factory.ts:43
Construct the only supported OPFS-repacked/PGlite pairing.
PGlite always awaits the filesystem sync. Physical durability is selected once, here, by the VFS option and is never delegated to the host boolean. A strict sync completes successful database initialization before return.
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TExtensions
[Section titled “TExtensions”](#textensions)
`TExtensions` *extends* `Extensions` = `Extensions`
## Parameters
[Section titled “Parameters”](#parameters)
### options
[Section titled “options”](#options)
[`CreateOpfsRepackedPGliteOptions`](/api/pglite-opfs-repacked/interfaces/createopfsrepackedpgliteoptions/)<`TExtensions`>
## Returns
[Section titled “Returns”](#returns)
`Promise`<[`OpfsRepackedPGlite`](/api/pglite-opfs-repacked/type-aliases/opfsrepackedpglite/)<`TExtensions`>>
# CreateOpfsRepackedPGliteOptions
Defined in: packages/pglite-opfs-repacked/src/pglite-factory.ts:14
## Extends
[Section titled “Extends”](#extends)
* `RepackedFilesystemOptions`
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TExtensions
[Section titled “TExtensions”](#textensions)
`TExtensions` *extends* `Extensions` = `Extensions`
## Properties
[Section titled “Properties”](#properties)
### directory
[Section titled “directory”](#directory)
> `readonly` **directory**: `OpfsDirectoryHandle`
Defined in: packages/pglite-opfs-repacked/src/pglite-factory.ts:18
Dedicated directory owned in full by this store.
***
### durability?
[Section titled “durability?”](#durability)
> `readonly` `optional` **durability?**: [`RepackedDurability`](/api/pglite-opfs-repacked/type-aliases/repackeddurability/)
Defined in: packages/pglite-opfs-repacked/src/opfs-repacked-fs.ts:16
Physical durability for this instance. Defaults to `"relaxed"`.
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
`RepackedFilesystemOptions.durability`
***
### extentSize?
[Section titled “extentSize?”](#extentsize)
> `readonly` `optional` **extentSize?**: `number`
Defined in: packages/pglite-opfs-repacked/src/opfs-repacked-fs.ts:14
Creation extent size: 8 KiB–16 MiB in 8 KiB steps. Defaults to 64 KiB.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-1)
`RepackedFilesystemOptions.extentSize`
***
### pglite?
[Section titled “pglite?”](#pglite)
> `readonly` `optional` **pglite?**: `HostOptions`<`TExtensions`>
Defined in: packages/pglite-opfs-repacked/src/pglite-factory.ts:20
PGlite options other than the factory-owned dataDir, fs, and relaxedDurability fields.
# @pgxsinkit/pglite-opfs-repacked
## Classes
[Section titled “Classes”](#classes)
* [CorruptStoreError](/api/pglite-opfs-repacked/classes/corruptstoreerror/)
* [DurabilityModeMismatchError](/api/pglite-opfs-repacked/classes/durabilitymodemismatcherror/)
* [ExtentSizeMismatchError](/api/pglite-opfs-repacked/classes/extentsizemismatcherror/)
* [FsError](/api/pglite-opfs-repacked/classes/fserror/)
* [OpfsRepackedFS](/api/pglite-opfs-repacked/classes/opfsrepackedfs/)
* [StoreClosedError](/api/pglite-opfs-repacked/classes/storeclosederror/)
* [StoreFailedError](/api/pglite-opfs-repacked/classes/storefailederror/)
* [StoreLimitError](/api/pglite-opfs-repacked/classes/storelimiterror/)
* [StoreOwnedError](/api/pglite-opfs-repacked/classes/storeownederror/)
* [StoreRecreationRequiredError](/api/pglite-opfs-repacked/classes/storerecreationrequirederror/)
* [UnexpectedStoreEntryError](/api/pglite-opfs-repacked/classes/unexpectedstoreentryerror/)
## Interfaces
[Section titled “Interfaces”](#interfaces)
* [CreateOpfsRepackedPGliteOptions](/api/pglite-opfs-repacked/interfaces/createopfsrepackedpgliteoptions/)
## Type Aliases
[Section titled “Type Aliases”](#type-aliases)
* [OpfsRepackedPGlite](/api/pglite-opfs-repacked/type-aliases/opfsrepackedpglite/)
* [RepackedDurability](/api/pglite-opfs-repacked/type-aliases/repackeddurability/)
## Functions
[Section titled “Functions”](#functions)
* [createOpfsRepackedPGlite](/api/pglite-opfs-repacked/functions/createopfsrepackedpglite/)
# OpfsRepackedPGlite
> **OpfsRepackedPGlite**<`TExtensions`> = `PGlite` & `PGliteInterfaceExtensions`<`TExtensions`> & `object`
Defined in: packages/pglite-opfs-repacked/src/pglite-factory.ts:27
The factory-owned PGlite instance: a plain PGlite plus the one explicit strict operation reserved for the sync layer above the VFS.
## Type Declaration
[Section titled “Type Declaration”](#type-declaration)
### strictSync()
[Section titled “strictSync()”](#strictsync)
> **strictSync**(): `Promise`<`void`>
Stabilize every preceding data and metadata operation in strict order, serialized against query execution.
#### Returns
[Section titled “Returns”](#returns)
`Promise`<`void`>
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TExtensions
[Section titled “TExtensions”](#textensions)
`TExtensions` *extends* `Extensions` = `Extensions`
# RepackedDurability
> **RepackedDurability** = `"relaxed"` | `"strict"`
Defined in: packages/pglite-opfs-repacked/src/opfs-repacked-fs.ts:10
Physical durability selected once for the lifetime of a factory-owned store.
# createSyncClientHooks
> **createSyncClientHooks**<`TRegistry`>(): `object`
Defined in: create-hooks.tsx:87
Creates a set of React hooks and a context provider bound to a specific `SyncTableRegistry` type. Call this once at the module level in your app:
```ts
export const { SyncClientProvider, useSyncClient, useLiveRows, useLiveDrizzleRows, useLiveQueryRaw } =
createSyncClientHooks();
```
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
## Returns
[Section titled “Returns”](#returns)
### SyncClientProvider
[Section titled “SyncClientProvider”](#syncclientprovider)
> **SyncClientProvider**: (`__namedParameters`) => `Element`
#### Parameters
[Section titled “Parameters”](#parameters)
##### \_\_namedParameters
[Section titled “\_\_namedParameters”](#__namedparameters)
###### children
[Section titled “children”](#children)
`ReactNode`
###### client
[Section titled “client”](#client)
`SyncClient`<`TRegistry`> | `null`
#### Returns
[Section titled “Returns”](#returns-1)
`Element`
### useLiveDrizzleRow
[Section titled “useLiveDrizzleRow”](#uselivedrizzlerow)
> **useLiveDrizzleRow**: <`TRows`>(`buildQuery`, `deps`, `options?`) => `object`
#### Type Parameters
[Section titled “Type Parameters”](#type-parameters-1)
##### TRows
[Section titled “TRows”](#trows)
`TRows` *extends* readonly `unknown`\[]
#### Parameters
[Section titled “Parameters”](#parameters-1)
##### buildQuery
[Section titled “buildQuery”](#buildquery)
(`client`) => `DrizzleSqlBuilder`<`TRows`>
##### deps
[Section titled “deps”](#deps)
`DependencyList`
##### options?
[Section titled “options?”](#options)
###### keepAliveMs?
[Section titled “keepAliveMs?”](#keepalivems)
`number`
###### ready?
[Section titled “ready?”](#ready)
`boolean`
#### Returns
[Section titled “Returns”](#returns-2)
`object`
##### error
[Section titled “error”](#error)
> **error**: `Error` | `null`
##### hydrating
[Section titled “hydrating”](#hydrating)
> **hydrating**: `boolean`
##### loading
[Section titled “loading”](#loading)
> **loading**: `boolean`
##### row
[Section titled “row”](#row)
> **row**: `TRows`\[`number`] | `null`
### useLiveDrizzleRows
[Section titled “useLiveDrizzleRows”](#uselivedrizzlerows)
> **useLiveDrizzleRows**: <`TRows`>(`buildQuery`, `deps`, `options?`) => `LiveRowsState`<`TRows`>
Reactive query using a Drizzle select builder. The builder is re-created whenever `deps` changes (same contract as `useEffect`). pgxsinkit scans the compiled SQL and auto-activates any `lazy` relation the query reads — anywhere it appears (FROM, JOIN, subquery, WHERE) — before subscribing. `use` (see useLiveQueryRaw) is an optional pre-activation hint, not a requirement (ADR-0021).
```ts
const { rows } = useLiveDrizzleRows(
(c) => c.drizzle.select().from(c.views.todos).orderBy(c.views.todos.createdAtUs),
[],
);
// rows is fully typed from the view definition — no casts needed
```
#### Type Parameters
[Section titled “Type Parameters”](#type-parameters-2)
##### TRows
[Section titled “TRows”](#trows-1)
`TRows` *extends* readonly `unknown`\[]
#### Parameters
[Section titled “Parameters”](#parameters-2)
##### buildQuery
[Section titled “buildQuery”](#buildquery-1)
(`client`) => `DrizzleSqlBuilder`<`TRows`>
##### deps
[Section titled “deps”](#deps-1)
`DependencyList`
##### options?
[Section titled “options?”](#options-1)
###### keepAliveMs?
[Section titled “keepAliveMs?”](#keepalivems-1)
`number`
###### ready?
[Section titled “ready?”](#ready-1)
`boolean`
#### Returns
[Section titled “Returns”](#returns-3)
`LiveRowsState`<`TRows`>
### useLiveQueryRaw
[Section titled “useLiveQueryRaw”](#uselivequeryraw)
> **useLiveQueryRaw**: <`TRows`>(`args`) => `LiveRowsState`<`TRows`>
The reactive query for a builder that embeds a raw `sql` fragment (ADR-0021): `use` names the `lazy` relations it reads that the compiled-SQL scan can’t see (a bare identifier inside raw SQL), so they are guaranteed activated before it subscribes. The non-live counterpart of `client.queryRaw({ use, build })`. Pure-Drizzle reads use `useLiveDrizzleRows` / `client.query((c) => …)`, which auto-detect every relation and need no `use`.
```ts
const { rows, hydrating } = useLiveQueryRaw({
use: ["archive"],
build: (c) => c.drizzle.select().from(archiveTable).where(inArray(archiveTable.id, recentIds)),
deps: [recentIds],
});
```
#### Type Parameters
[Section titled “Type Parameters”](#type-parameters-3)
##### TRows
[Section titled “TRows”](#trows-2)
`TRows` *extends* readonly `unknown`\[]
#### Parameters
[Section titled “Parameters”](#parameters-3)
##### args
[Section titled “args”](#args)
###### build
[Section titled “build”](#build)
(`client`) => `DrizzleSqlBuilder`<`TRows`>
###### deps?
[Section titled “deps?”](#deps-2)
`DependencyList`
###### keepAliveMs?
[Section titled “keepAliveMs?”](#keepalivems-2)
`number`
###### ready?
[Section titled “ready?”](#ready-2)
`boolean`
###### use?
[Section titled “use?”](#use)
readonly `SyncTableName`<`TRegistry`>\[]
#### Returns
[Section titled “Returns”](#returns-4)
`LiveRowsState`<`TRows`>
### useLiveQueryRawRow
[Section titled “useLiveQueryRawRow”](#uselivequeryrawrow)
> **useLiveQueryRawRow**: <`TRows`>(`args`) => `object`
#### Type Parameters
[Section titled “Type Parameters”](#type-parameters-4)
##### TRows
[Section titled “TRows”](#trows-3)
`TRows` *extends* readonly `unknown`\[]
#### Parameters
[Section titled “Parameters”](#parameters-4)
##### args
[Section titled “args”](#args-1)
###### build
[Section titled “build”](#build-1)
(`client`) => `DrizzleSqlBuilder`<`TRows`>
###### deps?
[Section titled “deps?”](#deps-3)
`DependencyList`
###### keepAliveMs?
[Section titled “keepAliveMs?”](#keepalivems-3)
`number`
###### ready?
[Section titled “ready?”](#ready-3)
`boolean`
###### use?
[Section titled “use?”](#use-1)
readonly `SyncTableName`<`TRegistry`>\[]
#### Returns
[Section titled “Returns”](#returns-5)
`object`
##### error
[Section titled “error”](#error-1)
> **error**: `Error` | `null`
##### hydrating
[Section titled “hydrating”](#hydrating-1)
> **hydrating**: `boolean`
##### loading
[Section titled “loading”](#loading-1)
> **loading**: `boolean`
##### row
[Section titled “row”](#row-1)
> **row**: `TRows`\[`number`] | `null`
### useLiveRow
[Section titled “useLiveRow”](#useliverow)
> **useLiveRow**: <`TRow`>(`query`, `options?`) => `object`
#### Type Parameters
[Section titled “Type Parameters”](#type-parameters-5)
##### TRow
[Section titled “TRow”](#trow)
`TRow` *extends* `Record`<`string`, `unknown`> = `Record`<`string`, `unknown`>
#### Parameters
[Section titled “Parameters”](#parameters-5)
##### query
[Section titled “query”](#query)
`string`
##### options?
[Section titled “options?”](#options-2)
###### params?
[Section titled “params?”](#params)
readonly `unknown`\[]
###### pglite?
[Section titled “pglite?”](#pglite)
`ClientPGlite`
###### ready?
[Section titled “ready?”](#ready-4)
`boolean`
#### Returns
[Section titled “Returns”](#returns-6)
`object`
##### error
[Section titled “error”](#error-2)
> **error**: `Error` | `null`
##### loading
[Section titled “loading”](#loading-2)
> **loading**: `boolean`
##### row
[Section titled “row”](#row-2)
> **row**: `TRow` | `null`
### useLiveRows
[Section titled “useLiveRows”](#useliverows)
> **useLiveRows**: <`TRow`>(`query`, `options?`) => `object`
Reactive raw-SQL query. This is the **unguarded** escape hatch: it does not participate in the lazy-relation safety net (ADR-0021) — a raw string is not parameterised/quoted predictably, so a `lazy` relation referenced here will read empty/stale unless you `client.ensureSynced([...])` first. Prefer useLiveDrizzleRows / useLiveQueryRaw for anything touching lazy relations.
#### Type Parameters
[Section titled “Type Parameters”](#type-parameters-6)
##### TRow
[Section titled “TRow”](#trow-1)
`TRow` *extends* `Record`<`string`, `unknown`> = `Record`<`string`, `unknown`>
#### Parameters
[Section titled “Parameters”](#parameters-6)
##### query
[Section titled “query”](#query-1)
`string`
##### options?
[Section titled “options?”](#options-3)
###### params?
[Section titled “params?”](#params-1)
readonly `unknown`\[]
###### pglite?
[Section titled “pglite?”](#pglite-1)
`ClientPGlite`
Explicit PGlite instance — overrides the context client. Useful in tests or multi-db scenarios.
###### ready?
[Section titled “ready?”](#ready-5)
`boolean`
#### Returns
[Section titled “Returns”](#returns-7)
`object`
##### error
[Section titled “error”](#error-3)
> **error**: `Error` | `null`
##### loading
[Section titled “loading”](#loading-3)
> **loading**: `boolean`
##### rows
[Section titled “rows”](#rows)
> **rows**: `TRow`\[]
### useMutationList
[Section titled “useMutationList”](#usemutationlist)
> **useMutationList**: (`options?`) => `object`
Reactive filtered mutation detail list (`client.mutations.subscribe`): normalized journal rows across every writable table, filtered by `table` / `entityKey` / `statuses` / `limit`, ordered newest-first. Route/feature-scoped — mount it where a diagnostics view is open, not app-wide (prefer useMutationSummary for a global indicator). No `hydrating` flag (journals are local).
#### Parameters
[Section titled “Parameters”](#parameters-7)
##### options?
[Section titled “options?”](#options-4)
`MutationListOptions`<`TRegistry`> & `object`
#### Returns
[Section titled “Returns”](#returns-8)
`object`
##### error
[Section titled “error”](#error-4)
> **error**: `Error` | `null`
##### loading
[Section titled “loading”](#loading-4)
> **loading**: `boolean`
##### rows
[Section titled “rows”](#rows-1)
> **rows**: `MutationDetail`\[]
### useMutationSummary
[Section titled “useMutationSummary”](#usemutationsummary)
> **useMutationSummary**: (`options?`) => `object`
Reactive registry-wide mutation summary (`client.mutations.subscribeSummary`): per-status counts across EVERY writable journal, folded to one MutationSummary. ONE subscription drives a global sync indicator — no `hydrating` flag, because journals are local and never network-hydrated. Cheap enough to mount permanently (ADR-0040 dedup: one registration regardless of subscriber count).
#### Parameters
[Section titled “Parameters”](#parameters-8)
##### options?
[Section titled “options?”](#options-5)
###### ready?
[Section titled “ready?”](#ready-6)
`boolean`
#### Returns
[Section titled “Returns”](#returns-9)
`object`
##### error
[Section titled “error”](#error-5)
> **error**: `Error` | `null`
##### loading
[Section titled “loading”](#loading-5)
> **loading**: `boolean`
##### summary
[Section titled “summary”](#summary)
> **summary**: `MutationSummary`
### useSyncClient
[Section titled “useSyncClient”](#usesyncclient)
> **useSyncClient**: () => `SyncClient`<`TRegistry`>
#### Returns
[Section titled “Returns”](#returns-10)
`SyncClient`<`TRegistry`>
# @pgxsinkit/react
## Functions
[Section titled “Functions”](#functions)
* [createSyncClientHooks](/api/react/functions/createsyncclienthooks/)
# FetchRouter
Defined in: packages/server/src/router.ts:23
## Constructors
[Section titled “Constructors”](#constructors)
### Constructor
[Section titled “Constructor”](#constructor)
> **new FetchRouter**(): `FetchRouter`
#### Returns
[Section titled “Returns”](#returns)
`FetchRouter`
## Methods
[Section titled “Methods”](#methods)
### fetch()
[Section titled “fetch()”](#fetch)
> `readonly` **fetch**(`request`): `Promise`<`Response`>
Defined in: packages/server/src/router.ts:50
#### Parameters
[Section titled “Parameters”](#parameters)
##### request
[Section titled “request”](#request)
`Request`
#### Returns
[Section titled “Returns”](#returns-1)
`Promise`<`Response`>
***
### get()
[Section titled “get()”](#get)
> **get**(`path`, `handler`): `void`
Defined in: packages/server/src/router.ts:29
#### Parameters
[Section titled “Parameters”](#parameters-1)
##### path
[Section titled “path”](#path)
`string`
##### handler
[Section titled “handler”](#handler)
[`FetchHandler`](/api/server/type-aliases/fetchhandler/)
#### Returns
[Section titled “Returns”](#returns-2)
`void`
***
### post()
[Section titled “post()”](#post)
> **post**(`path`, `handler`): `void`
Defined in: packages/server/src/router.ts:33
#### Parameters
[Section titled “Parameters”](#parameters-2)
##### path
[Section titled “path”](#path-1)
`string`
##### handler
[Section titled “handler”](#handler-1)
[`FetchHandler`](/api/server/type-aliases/fetchhandler/)
#### Returns
[Section titled “Returns”](#returns-3)
`void`
***
### setCors()
[Section titled “setCors()”](#setcors)
> **setCors**(`config`, `scopes`): `void`
Defined in: packages/server/src/router.ts:37
#### Parameters
[Section titled “Parameters”](#parameters-3)
##### config
[Section titled “config”](#config)
[`CorsConfig`](/api/server/interfaces/corsconfig/)
##### scopes
[Section titled “scopes”](#scopes)
[`CorsScope`](/api/server/type-aliases/corsscope/)\[]
#### Returns
[Section titled “Returns”](#returns-4)
`void`
***
### setErrorHandler()
[Section titled “setErrorHandler()”](#seterrorhandler)
> **setErrorHandler**(`handler`): `void`
Defined in: packages/server/src/router.ts:46
#### Parameters
[Section titled “Parameters”](#parameters-4)
##### handler
[Section titled “handler”](#handler-2)
[`RouterErrorHandler`](/api/server/type-aliases/routererrorhandler/)
#### Returns
[Section titled “Returns”](#returns-5)
`void`
# MalformedEventQueueMessageError
Defined in: packages/server/src/events/queue.ts:121
A queue message whose body is not a well-formed EventQueueMessage. The endpoint is the only writer of these queues and it writes contract-shaped bodies, so this means a hand-inserted or corrupted message. It carries the receipt precisely so a consumer runner can dead-letter exactly that message rather than failing the whole read.
## Extends
[Section titled “Extends”](#extends)
* `Error`
## Constructors
[Section titled “Constructors”](#constructors)
### Constructor
[Section titled “Constructor”](#constructor)
> **new MalformedEventQueueMessageError**(`stream`, `receipt`, `detail`): `MalformedEventQueueMessageError`
Defined in: packages/server/src/events/queue.ts:125
#### Parameters
[Section titled “Parameters”](#parameters)
##### stream
[Section titled “stream”](#stream)
`string`
##### receipt
[Section titled “receipt”](#receipt)
`string`
##### detail
[Section titled “detail”](#detail)
`string`
#### Returns
[Section titled “Returns”](#returns)
`MalformedEventQueueMessageError`
#### Overrides
[Section titled “Overrides”](#overrides)
`Error.constructor`
## Properties
[Section titled “Properties”](#properties)
### cause?
[Section titled “cause?”](#cause)
> `optional` **cause?**: `unknown`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:24
#### Inherited from
[Section titled “Inherited from”](#inherited-from)
`Error.cause`
***
### message
[Section titled “message”](#message)
> **message**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1075
#### Inherited from
[Section titled “Inherited from”](#inherited-from-1)
`Error.message`
***
### name
[Section titled “name”](#name)
> **name**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1074
#### Inherited from
[Section titled “Inherited from”](#inherited-from-2)
`Error.name`
***
### receipt
[Section titled “receipt”](#receipt-1)
> `readonly` **receipt**: `string`
Defined in: packages/server/src/events/queue.ts:123
***
### stack?
[Section titled “stack?”](#stack)
> `optional` **stack?**: `string`
Defined in: node\_modules/.bun/typescript\@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1076
#### Inherited from
[Section titled “Inherited from”](#inherited-from-3)
`Error.stack`
***
### stream
[Section titled “stream”](#stream-1)
> `readonly` **stream**: `string`
Defined in: packages/server/src/events/queue.ts:122
***
### stackTraceLimit
[Section titled “stackTraceLimit”](#stacktracelimit)
> `static` **stackTraceLimit**: `number`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:67
The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`).
The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed.
If set to a non-number value, or set to a negative number, stack traces will not capture any frames.
#### Inherited from
[Section titled “Inherited from”](#inherited-from-4)
`Error.stackTraceLimit`
## Methods
[Section titled “Methods”](#methods)
### captureStackTrace()
[Section titled “captureStackTrace()”](#capturestacktrace)
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:51
Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called.
```js
const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack; // Similar to `new Error().stack`
```
The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`.
The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace.
The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance:
```js
function a() {
b();
}
function b() {
c();
}
function c() {
// Create an error without stack trace to avoid calculating the stack trace twice.
const { stackTraceLimit } = Error;
Error.stackTraceLimit = 0;
const error = new Error();
Error.stackTraceLimit = stackTraceLimit;
// Capture the stack trace above function b
Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
throw error;
}
a();
```
#### Parameters
[Section titled “Parameters”](#parameters-1)
##### targetObject
[Section titled “targetObject”](#targetobject)
`object`
##### constructorOpt?
[Section titled “constructorOpt?”](#constructoropt)
`Function`
#### Returns
[Section titled “Returns”](#returns-1)
`void`
#### Inherited from
[Section titled “Inherited from”](#inherited-from-5)
`Error.captureStackTrace`
***
### prepareStackTrace()
[Section titled “prepareStackTrace()”](#preparestacktrace)
> `static` **prepareStackTrace**(`err`, `stackTraces`): `any`
Defined in: node\_modules/.bun/@types+node\@26.1.2/node\_modules/@types/node/globals.d.ts:55
#### Parameters
[Section titled “Parameters”](#parameters-2)
##### err
[Section titled “err”](#err)
`Error`
##### stackTraces
[Section titled “stackTraces”](#stacktraces)
`CallSite`\[]
#### Returns
[Section titled “Returns”](#returns-2)
`any`
#### See
[Section titled “See”](#see)
#### Inherited from
[Section titled “Inherited from”](#inherited-from-6)
`Error.prepareStackTrace`
# assertEventQueueReceipts
> **assertEventQueueReceipts**(`receipts`): `void`
Defined in: packages/server/src/events/queue.ts:153
Receipts are backend-issued decimal ids; anything else never reaches the backend’s SQL.
## Parameters
[Section titled “Parameters”](#parameters)
### receipts
[Section titled “receipts”](#receipts)
readonly `string`\[]
## Returns
[Section titled “Returns”](#returns)
`void`
# buildPlpgsqlBatchFunctionDdl
> **buildPlpgsqlBatchFunctionDdl**(`registry`, `options?`): `string`
Defined in: packages/server/src/mutations/plpgsql-apply.ts:862
## Parameters
[Section titled “Parameters”](#parameters)
### registry
[Section titled “registry”](#registry)
`SyncTableRegistry`
### options?
[Section titled “options?”](#options)
[`ApplyFunctionRenderOptions`](/api/server/interfaces/applyfunctionrenderoptions/) = `{}`
## Returns
[Section titled “Returns”](#returns)
`string`
# buildRegistrySchema
> **buildRegistrySchema**<`TRegistry`>(`registry`): `RegistryTables`<`TRegistry`>
Defined in: packages/server/src/index.ts:365
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
## Parameters
[Section titled “Parameters”](#parameters)
### registry
[Section titled “registry”](#registry)
`TRegistry`
## Returns
[Section titled “Returns”](#returns)
`RegistryTables`<`TRegistry`>
# computeEventPollWaitMs
> **computeEventPollWaitMs**(`idleCount`, `options?`): `number`
Defined in: packages/server/src/events/consumer.ts:303
The adaptive wait for `idleCount` CONSECUTIVE unproductive reads (an empty read or a queue fault), clamped to the ceiling. `idleCount` is 1 for the first one; a productive read resets it to 0 and re-reads immediately.
## Parameters
[Section titled “Parameters”](#parameters)
### idleCount
[Section titled “idleCount”](#idlecount)
`number`
### options?
[Section titled “options?”](#options)
[`EventConsumerPollOptions`](/api/server/interfaces/eventconsumerpolloptions/) = `{}`
## Returns
[Section titled “Returns”](#returns)
`number`
# createEventIngestHandler
> **createEventIngestHandler**<`TRegistry`>(`db`, `registry`, `queue`, `options?`): [`FetchHandler`](/api/server/type-aliases/fetchhandler/)
Defined in: packages/server/src/events/route.ts:194
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
## Parameters
[Section titled “Parameters”](#parameters)
### db
[Section titled “db”](#db)
[`EventIngestDb`](/api/server/interfaces/eventingestdb/)
### registry
[Section titled “registry”](#registry)
`TRegistry`
### queue
[Section titled “queue”](#queue)
[`EventQueue`](/api/server/interfaces/eventqueue/)
### options?
[Section titled “options?”](#options)
[`CreateEventIngestHandlerOptions`](/api/server/interfaces/createeventingesthandleroptions/) = `{}`
## Returns
[Section titled “Returns”](#returns)
[`FetchHandler`](/api/server/type-aliases/fetchhandler/)
# createMutationHandler
> **createMutationHandler**<`TRegistry`>(`db`, `registry`, `operationsLogConfig`, `operationsLogReady`, `resolveAuthClaims?`, `startupVerification?`, `logTimings?`, `applyFunctionGrantExecuteTo?`, `applyFunctionSchema?`): `object`
Defined in: packages/server/src/mutations/route.ts:62
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
## Parameters
[Section titled “Parameters”](#parameters)
### db
[Section titled “db”](#db)
`PgAsyncDatabase`<`PgQueryResultHKT`, `ExtractTablesWithRelations`<{ }, `RegistryTables`<`TRegistry`>>>
### registry
[Section titled “registry”](#registry)
`TRegistry`
### operationsLogConfig
[Section titled “operationsLogConfig”](#operationslogconfig)
`OperationsLogConfig`
### operationsLogReady
[Section titled “operationsLogReady”](#operationslogready)
`Promise`<`void`>
### resolveAuthClaims?
[Section titled “resolveAuthClaims?”](#resolveauthclaims)
(`request`) => {\[`key`: `string`]: `unknown`; `app_metadata?`: {\[`key`: `string`]: `unknown`; `roles?`: `string`\[]; }; `sub?`: `string`; } | `Promise`<{\[`key`: `string`]: `unknown`; `app_metadata?`: {\[`key`: `string`]: `unknown`; `roles?`: `string`\[]; }; `sub?`: `string`; } | `null`> | `null`
### startupVerification?
[Section titled “startupVerification?”](#startupverification)
[`StartupVerificationMode`](/api/server/type-aliases/startupverificationmode/) = `"in-process"`
### logTimings?
[Section titled “logTimings?”](#logtimings)
`boolean` = `false`
### applyFunctionGrantExecuteTo?
[Section titled “applyFunctionGrantExecuteTo?”](#applyfunctiongrantexecuteto)
readonly `string`\[] = `[]`
### applyFunctionSchema?
[Section titled “applyFunctionSchema?”](#applyfunctionschema)
`string`
## Returns
[Section titled “Returns”](#returns)
`object`
### authoritative
[Section titled “authoritative”](#authoritative)
> **authoritative**: [`FetchHandler`](/api/server/type-aliases/fetchhandler/)
### batch
[Section titled “batch”](#batch)
> **batch**: [`FetchHandler`](/api/server/type-aliases/fetchhandler/)
# createPgmqEventQueue
> **createPgmqEventQueue**(`options`): [`EventQueue`](/api/server/interfaces/eventqueue/)
Defined in: packages/server/src/events/pgmq-queue.ts:100
## Parameters
[Section titled “Parameters”](#parameters)
### options
[Section titled “options”](#options)
[`CreatePgmqEventQueueOptions`](/api/server/interfaces/createpgmqeventqueueoptions/)
## Returns
[Section titled “Returns”](#returns)
[`EventQueue`](/api/server/interfaces/eventqueue/)
# createSyncServer
> **createSyncServer**<`TRegistry`, `TDb`>(`options`): [`SyncServer`](/api/server/interfaces/syncserver/)<`TRegistry`, `TDb`>
Defined in: packages/server/src/index.ts:181
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
### TDb
[Section titled “TDb”](#tdb)
`TDb` *extends* `PgAsyncDatabase`<`PgQueryResultHKT`, `ExtractTablesWithRelations`<{ }, `RegistryTables`<`TRegistry`>>> = `PgAsyncDatabase`<`PgQueryResultHKT`, `ExtractTablesWithRelations`<{ }, `RegistryTables`<`TRegistry`>>>
## Parameters
[Section titled “Parameters”](#parameters)
### options
[Section titled “options”](#options)
[`CreateSyncServerOptions`](/api/server/interfaces/createsyncserveroptions/)<`TRegistry`, `TDb`>
## Returns
[Section titled “Returns”](#returns)
[`SyncServer`](/api/server/interfaces/syncserver/)<`TRegistry`, `TDb`>
# defineEventConsumer
> **defineEventConsumer**<`TRegistry`>(`options`): [`EventConsumer`](/api/server/interfaces/eventconsumer/)
Defined in: packages/server/src/events/consumer.ts:339
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
## Parameters
[Section titled “Parameters”](#parameters)
### options
[Section titled “options”](#options)
[`DefineEventConsumerOptions`](/api/server/interfaces/defineeventconsumeroptions/)<`TRegistry`>
## Returns
[Section titled “Returns”](#returns)
[`EventConsumer`](/api/server/interfaces/eventconsumer/)
# ensureOperationsLogSchema
> **ensureOperationsLogSchema**<`TRegistry`>(`db`, `config`): `Promise`<`boolean`>
Defined in: packages/server/src/operations-log/ddl.ts:29
Ensures the operations\_log table exists in the database.
Returns `true` if the table is present, `false` if missing. Consumers should include `operationsLogTable` from `@pgxsinkit/server` in their Drizzle schema so `drizzle-kit generate`/`push` creates it.
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
## Parameters
[Section titled “Parameters”](#parameters)
### db
[Section titled “db”](#db)
`PgAsyncDatabase`<`PgQueryResultHKT`, `ExtractTablesWithRelations`<{ }, `RegistryTables`<`TRegistry`>>>
### config
[Section titled “config”](#config)
`OperationsLogConfig`
## Returns
[Section titled “Returns”](#returns)
`Promise`<`boolean`>
# eventIngestRequiresClaims
> **eventIngestRequiresClaims**(`streams`): `boolean`
Defined in: packages/server/src/events/route.ts:190
Whether ANY registered Event stream declares an identity field — i.e. whether ingest NEEDS verified claims. The mutation path’s posture, mirrored: it demands claims when the registry actually requires them (an RLS policy or an `authClaim` managed field) rather than unconditionally. A registry whose streams declare no identity at all stamps nothing from claims, so it does not force an auth adapter into existence.
## Parameters
[Section titled “Parameters”](#parameters)
### streams
[Section titled “streams”](#streams)
`EventStreamRegistry`
## Returns
[Section titled “Returns”](#returns)
`boolean`
# eventLaneDdlFingerprint
> **eventLaneDdlFingerprint**(`registry`): `string`
Defined in: packages/server/src/events/ddl.ts:67
The fingerprint the event-lane artifact carries for this registry. Derived from the emitted DDL body, so it moves when a stream is added or removed (and when this generator’s output changes) — and does NOT move for a payload-schema change, which provisions nothing.
## Parameters
[Section titled “Parameters”](#parameters)
### registry
[Section titled “registry”](#registry)
`SyncTableRegistry`
## Returns
[Section titled “Returns”](#returns)
`string`
# eventLaneStreamNames
> **eventLaneStreamNames**(`registry`): `string`\[]
Defined in: packages/server/src/events/ddl.ts:32
The registered Event-stream names, SORTED — so the artifact and its fingerprint are registry-order-independent.
## Parameters
[Section titled “Parameters”](#parameters)
### registry
[Section titled “registry”](#registry)
`SyncTableRegistry`
## Returns
[Section titled “Returns”](#returns)
`string`\[]
# eventStreamQueueName
> **eventStreamQueueName**(`stream`): `string`
Defined in: packages/server/src/events/queue.ts:142
The queue name an Event stream is provisioned under: `pgxsinkit_events_` (ADR-0053 decision 5), the SAME derivation the deploy-time DDL emits — one definition, so a runtime call can never address a queue the migration did not create.
The name is re-validated here even though `defineSyncRegistry` already validated it at module eval: the queue name is interpolated into backend SQL, and a value arriving from a non-registry caller must not be able to smuggle anything past that.
## Parameters
[Section titled “Parameters”](#parameters)
### stream
[Section titled “stream”](#stream)
`string`
## Returns
[Section titled “Returns”](#returns)
`string`
# expectedApplyFingerprint
> **expectedApplyFingerprint**(`registry`, `options?`): `string`
Defined in: packages/server/src/mutations/plpgsql-apply.ts:855
The fingerprint the installed apply function should carry for this registry + applier codegen (ADR-0018). It is a hash of the exact generated DDL body, so it shifts on any registry-shape change AND on any change to how the applier emits SQL (e.g. a @pgxsinkit/server upgrade) — the two drift classes a bare signature check cannot see. It does NOT depend on TS-side row-filter / customWhere logic, which never enters the apply function (that shapes the read proxy, not writes).
## Parameters
[Section titled “Parameters”](#parameters)
### registry
[Section titled “registry”](#registry)
`SyncTableRegistry`
### options?
[Section titled “options?”](#options)
[`ApplyFunctionRenderOptions`](/api/server/interfaces/applyfunctionrenderoptions/) = `{}`
## Returns
[Section titled “Returns”](#returns)
`string`
# operationsLogRegclassTarget
> **operationsLogRegclassTarget**(): `string`
Defined in: packages/server/src/operations-log/ddl.ts:18
The `to_regclass` presence-probe target, derived from the real pgTable so the probe (and the warning below) track a rename of `operationsLogTable`. Exported for the integration suite that asserts the degraded no-log path against the same identity.
## Returns
[Section titled “Returns”](#returns)
`string`
# proxyElectricShapeRequest
> **proxyElectricShapeRequest**(`request`, `claims`, `options`): `Promise`<`Response`>
Defined in: packages/server/src/electric-proxy.ts:73
Proxies an Electric shape request, applying registry-driven row filters and stripping omitted columns from JSON shape-log payloads. Optionally answers CORS preflights and adds CORS headers ([ElectricProxyOptions.cors](/api/server/interfaces/electricproxyoptions/#cors)) for gateway-less browser deployments.
The caller is responsible for resolving auth claims from the request. Pass `claims` as `null` for unauthenticated requests (all rows blocked).
## Parameters
[Section titled “Parameters”](#parameters)
### request
[Section titled “request”](#request)
`Request`
### claims
[Section titled “claims”](#claims)
{\[`key`: `string`]: `unknown`; `app_metadata?`: {\[`key`: `string`]: `unknown`; `roles?`: `string`\[]; }; `sub?`: `string`; } | `null`
### options
[Section titled “options”](#options)
[`ElectricProxyOptions`](/api/server/interfaces/electricproxyoptions/)
## Returns
[Section titled “Returns”](#returns)
`Promise`<`Response`>
# readSqlState
> **readSqlState**(`error`): `string` | `undefined`
Defined in: packages/server/src/sql-state.ts:22
Read the Postgres `SQLSTATE` (the five-character error class, e.g. `"23505"` unique\_violation) off a thrown database error.
**Why this helper exists — the bun-sql gotcha.** Bun’s built-in `SQL`/`sql` driver (what pgxsinkit and its consumers run against Postgres) surfaces the server’s SQLSTATE on the error’s **`errno`** property. Its **`code`** property is bun’s own generic string (`"ERR_POSTGRES_SERVER_ERROR"`), NOT the SQLSTATE — so the intuitive `error.code` read (which worked under `postgres.js` / `pg`, where `code` carried the SQLSTATE) silently returns the wrong thing under bun-sql. Consumers reaching for a stable, driver-agnostic SQLSTATE (to map `23505`→“already exists”, `P0001`→a raised app rule, etc.) kept re-deriving this by hand; this is the one canonical extraction.
Resolution order, returning the first well-formed SQLSTATE found:
1. `errno` on the error (bun-sql),
2. `code` on the error (postgres.js / node-postgres, and any driver that follows that convention),
3. the same two properties walked down the `cause` chain (a wrapped/re-thrown error).
A “well-formed” SQLSTATE is exactly five characters of `[0-9A-Z]` — this is what rejects bun’s generic `code` string, an `errno` that is a numeric OS errno, and any other non-SQLSTATE noise. A non-object error (string, number, `null`, `undefined`) yields `undefined`.
## Parameters
[Section titled “Parameters”](#parameters)
### error
[Section titled “error”](#error)
`unknown`
## Returns
[Section titled “Returns”](#returns)
`string` | `undefined`
# renderEventLaneMigration
> **renderEventLaneMigration**(`registry`): `string`
Defined in: packages/server/src/events/ddl.ts:76
The full migration body: the DDL plus its fingerprint as a SQL comment. The comment is what `pgxsinkit-generate --events --check` scans committed migrations for — the same pre-deploy drift detection the apply-function artifact gets, minus the in-database stamp (there is no function here to comment on).
## Parameters
[Section titled “Parameters”](#parameters)
### registry
[Section titled “registry”](#registry)
`SyncTableRegistry`
## Returns
[Section titled “Returns”](#returns)
`string`
# renderPgxsinkitUtilitiesMigration
> **renderPgxsinkitUtilitiesMigration**(): `string`
Defined in: packages/server/src/migrations/utilities.ts:24
The canonical microsecond-clock DB function `public.pgxsinkit_clock_us()` — the ONE home for the `clock_timestamp()`-epoch-microseconds semantics. Every surface (column DEFAULTs, the generated apply function’s managed `nowMicroseconds` fields, the operations-log server stamp) CALLS this function; nothing inlines the expression. This migration installs it, and — because the generated apply function calls it — it MUST be the FIRST folder in any consumer’s migration chain (before the schema and the sync-artifact folder).
The body is composed from the single source NOW\_MICROSECONDS\_SQL\_TEXT in @pgxsinkit/contracts (CREATE FUNCTION is the sanctioned raw-SQL case — Drizzle cannot express a PL/pgSQL/SQL function body).
## Returns
[Section titled “Returns”](#returns)
`string`
# resolveEventIdentity
> **resolveEventIdentity**(`claims`, `identityFields`): [`IdentityResolution`](/api/server/type-aliases/identityresolution/)
Defined in: packages/server/src/events/identity.ts:64
Resolve every declared identity field, or report the FIRST unresolvable one. Fail-closed by construction: there is no partial stamp and no empty-string fallback — an event whose identity cannot be resolved gets a `rejected` verdict naming the field and its claim path.
## Parameters
[Section titled “Parameters”](#parameters)
### claims
[Section titled “claims”](#claims)
#### app\_metadata?
[Section titled “app\_metadata?”](#app_metadata)
{\[`key`: `string`]: `unknown`; `roles?`: `string`\[]; } = `...`
#### app\_metadata.roles?
[Section titled “app\_metadata.roles?”](#app_metadataroles)
`string`\[] = `...`
#### sub?
[Section titled “sub?”](#sub)
`string` = `...`
### identityFields
[Section titled “identityFields”](#identityfields)
`Record`<`string`, `EventStreamIdentityField`>
## Returns
[Section titled “Returns”](#returns)
[`IdentityResolution`](/api/server/type-aliases/identityresolution/)
# ApplyFunctionRenderOptions
Defined in: packages/server/src/mutations/plpgsql-apply.ts:457
How the generated apply function is rendered. Both fields are part of the artifact’s IDENTITY: they change the fingerprinted body, so a change to either forces the regenerate-and-commit flow, and the SERVER must be configured with the same values (it recomputes the expected fingerprint at runtime).
## Properties
[Section titled “Properties”](#properties)
### functionSchema?
[Section titled “functionSchema?”](#functionschema)
> `optional` **functionSchema?**: `string`
Defined in: packages/server/src/mutations/plpgsql-apply.ts:459
Schema to qualify the function with. Default: unqualified (resolved through `search_path`).
***
### grantExecuteTo?
[Section titled “grantExecuteTo?”](#grantexecuteto)
> `optional` **grantExecuteTo?**: readonly `string`\[]
Defined in: packages/server/src/mutations/plpgsql-apply.ts:466
Roles that receive `GRANT EXECUTE` on the apply function (ADR-0054 decision 2). Default `[]` — **owner-only**, which is right whenever the server connects as the function’s owner or a superuser. Name ONLY server roles: the function trusts the `p_user_claims` it is handed, so a granted role can pass any claims it likes. The grant list IS the write path’s trust boundary.
# CorsConfig
Defined in: packages/server/src/router.ts:11
## Properties
[Section titled “Properties”](#properties)
### allowHeaders
[Section titled “allowHeaders”](#allowheaders)
> **allowHeaders**: `string`\[]
Defined in: packages/server/src/router.ts:15
***
### allowMethods
[Section titled “allowMethods”](#allowmethods)
> **allowMethods**: `string`\[]
Defined in: packages/server/src/router.ts:14
***
### origins
[Section titled “origins”](#origins)
> **origins**: `string`\[]
Defined in: packages/server/src/router.ts:13
Exact origins, or a `"*"` entry to allow every origin by reflection (see resolveCorsOrigin).
# CreateEventIngestHandlerOptions
Defined in: packages/server/src/events/route.ts:100
## Properties
[Section titled “Properties”](#properties)
### eventGate?
[Section titled “eventGate?”](#eventgate)
> `optional` **eventGate?**: [`EventGate`](/api/server/type-aliases/eventgate/)
Defined in: packages/server/src/events/route.ts:102
***
### logTimings?
[Section titled “logTimings?”](#logtimings)
> `optional` **logTimings?**: `boolean`
Defined in: packages/server/src/events/route.ts:105
***
### onEventsEnqueued?
[Section titled “onEventsEnqueued?”](#oneventsenqueued)
> `optional` **onEventsEnqueued?**: [`EventsEnqueuedHook`](/api/server/type-aliases/eventsenqueuedhook/)
Defined in: packages/server/src/events/route.ts:104
See [EventsEnqueuedHook](/api/server/type-aliases/eventsenqueuedhook/). Fired only when something was actually enqueued.
***
### resolveAuthClaims?
[Section titled “resolveAuthClaims?”](#resolveauthclaims)
> `optional` **resolveAuthClaims?**: (`request`) => {\[`key`: `string`]: `unknown`; `app_metadata?`: {\[`key`: `string`]: `unknown`; `roles?`: `string`\[]; }; `sub?`: `string`; } | `Promise`<{\[`key`: `string`]: `unknown`; `app_metadata?`: {\[`key`: `string`]: `unknown`; `roles?`: `string`\[]; }; `sub?`: `string`; } | `null`> | `null`
Defined in: packages/server/src/events/route.ts:101
#### Parameters
[Section titled “Parameters”](#parameters)
##### request
[Section titled “request”](#request)
`Request`
#### Returns
[Section titled “Returns”](#returns)
{\[`key`: `string`]: `unknown`; `app_metadata?`: {\[`key`: `string`]: `unknown`; `roles?`: `string`\[]; }; `sub?`: `string`; } | `Promise`<{\[`key`: `string`]: `unknown`; `app_metadata?`: {\[`key`: `string`]: `unknown`; `roles?`: `string`\[]; }; `sub?`: `string`; } | `null`> | `null`
# CreatePgmqEventQueueOptions
Defined in: packages/server/src/events/pgmq-queue.ts:53
## Properties
[Section titled “Properties”](#properties)
### db
[Section titled “db”](#db)
> **db**: [`EventQueueExecutor`](/api/server/interfaces/eventqueueexecutor/)
Defined in: packages/server/src/events/pgmq-queue.ts:55
The default executor — the server’s drizzle handle. Per-call executors (a transaction) take precedence.
# CreateSyncServerOptions
Defined in: packages/server/src/index.ts:69
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
### TDb
[Section titled “TDb”](#tdb)
`TDb` *extends* `PgAsyncDatabase`<`PgQueryResultHKT`, `RegistryRelations`<`TRegistry`>> = `PgAsyncDatabase`<`PgQueryResultHKT`, `RegistryRelations`<`TRegistry`>>
## Properties
[Section titled “Properties”](#properties)
### allowedOrigins?
[Section titled “allowedOrigins?”](#allowedorigins)
> `optional` **allowedOrigins?**: `string`\[]
Defined in: packages/server/src/index.ts:97
***
### applyFunctionGrantExecuteTo?
[Section titled “applyFunctionGrantExecuteTo?”](#applyfunctiongrantexecuteto)
> `optional` **applyFunctionGrantExecuteTo?**: readonly `string`\[]
Defined in: packages/server/src/index.ts:143
The roles the INSTALLED apply function was generated with (`pgxsinkit-generate --grant-execute-to `, ADR-0054). Default `[]` — owner-only, the default the CLI generates.
It is not a grant this server performs; it is how the server reproduces the artifact’s ADR-0018 fingerprint, which hashes the ACL along with the rest of the body. Generate with a grant and leave this unset and every write fails `PXS01` (stale artifact) — so the two lists must stay identical.
***
### applyFunctionSchema?
[Section titled “applyFunctionSchema?”](#applyfunctionschema)
> `optional` **applyFunctionSchema?**: `string`
Defined in: packages/server/src/index.ts:156
The schema the INSTALLED apply function lives in (`pgxsinkit-generate --function-schema `). Default: unset — the artifact is generated unqualified and resolved through the connection’s `search_path`.
It does two things at once, which is why one option drives both: the schema is part of the fingerprinted body (the function names itself in its own self-check), AND it is how this server QUALIFIES the call. Generate with `--function-schema` and leave this unset and the call goes out unqualified — which either finds nothing (`42883`) or, worse, finds a same-named function elsewhere on the `search_path` that then fails `PXS01` against a fingerprint it does not carry. The two must name the same schema.
***
### db
[Section titled “db”](#db)
> **db**: `TDb`
Defined in: packages/server/src/index.ts:77
***
### deployment?
[Section titled “deployment?”](#deployment)
> `optional` **deployment?**: [`DeploymentProfile`](/api/server/interfaces/deploymentprofile/)
Defined in: packages/server/src/index.ts:105
The startup query posture (ADR-0030). The apply function now verifies its own ADR-0018 fingerprint in-body on every call (SQLSTATE `PXS01` on drift), so there is no startup drift check to configure; this governs only the RLS auth-helper verify and the operations-log presence resolution. Defaults are the safe degradation posture (ADR-0030). See [DeploymentProfile](/api/server/interfaces/deploymentprofile/).
***
### electricUrl?
[Section titled “electricUrl?”](#electricurl)
> `optional` **electricUrl?**: `string`
Defined in: packages/server/src/index.ts:84
When set, the server serves a read-path Electric shape proxy that shares the single `resolveAuthClaims` adapter with the write path (ADR-0003). Without it, no shape proxy is registered.
***
### eventGate?
[Section titled “eventGate?”](#eventgate)
> `optional` **eventGate?**: [`EventGate`](/api/server/type-aliases/eventgate/)
Defined in: packages/server/src/index.ts:112
The **Event lane**’s consent/entitlement gate (ADR-0053 decision 1): the lane’s one function, and so an option here rather than a registry field (the registry stays declarative data only). Called once per event, after its payload validated and its identity resolved, and before anything is enqueued; a refusal is a per-event `refused` verdict. Absent → every well-formed event is allowed. See [EventGate](/api/server/type-aliases/eventgate/).
***
### eventQueue?
[Section titled “eventQueue?”](#eventqueue)
> `optional` **eventQueue?**: [`EventQueue`](/api/server/interfaces/eventqueue/)
Defined in: packages/server/src/index.ts:127
The Event lane’s queue backend. Defaults to the shipped pgmq backend over this server’s own `db` (`createPgmqEventQueue`), which is what makes an enqueue join the endpoint’s transaction. Override it to run the lane on another backend, or to substitute a fake in tests.
***
### healthCheck?
[Section titled “healthCheck?”](#healthcheck)
> `optional` **healthCheck?**: `boolean` | { `path`: `string`; }
Defined in: packages/server/src/index.ts:93
Health check endpoint. Enabled by default at `/health`; `false` disables it, `{ path }` relocates it.
***
### host?
[Section titled “host?”](#host)
> `optional` **host?**: `string`
Defined in: packages/server/src/index.ts:95
***
### idleTimeoutSeconds?
[Section titled “idleTimeoutSeconds?”](#idletimeoutseconds)
> `optional` **idleTimeoutSeconds?**: `number`
Defined in: packages/server/src/index.ts:96
***
### logTimings?
[Section titled “logTimings?”](#logtimings)
> `optional` **logTimings?**: `boolean`
Defined in: packages/server/src/index.ts:134
Opt-in per-request timing log (default false). When on, each mutation and shape-proxy request emits one compact `[pgxsinkit-timing]` line with an ISO-8601(ms, UTC) timestamp and phase durations, for attributing wall-clock latency against the client’s `syncDebug` lines. Off by default — a pure diagnostic surface that adds no standing query or latency when unset.
***
### onEventsEnqueued?
[Section titled “onEventsEnqueued?”](#oneventsenqueued)
> `optional` **onEventsEnqueued?**: [`EventsEnqueuedHook`](/api/server/type-aliases/eventsenqueuedhook/)
Defined in: packages/server/src/index.ts:121
Fired after an ingest request ENQUEUED at least one sub-batch (ADR-0053 amendment, 2026-08-02): the deployment-agnostic seam a SERVERLESS deployment uses to nudge whatever endpoint runs the consumer’s `drainOnce()`, so an interactive append drains in milliseconds instead of waiting for the next scheduled tick. Fire-and-forget — it is called after the commit, its throw is caught and warn-logged, and the scheduled sweep (not the nudge) is the delivery guarantee. Absent → nothing is nudged, which is right for a deployment hosting the long-lived runner. See [EventsEnqueuedHook](/api/server/type-aliases/eventsenqueuedhook/).
***
### onStatusChange?
[Section titled “onStatusChange?”](#onstatuschange)
> `optional` **onStatusChange?**: (`status`) => `void`
Defined in: packages/server/src/index.ts:98
#### Parameters
[Section titled “Parameters”](#parameters)
##### status
[Section titled “status”](#status)
`SyncRuntimeStatus`
#### Returns
[Section titled “Returns”](#returns)
`void`
***
### operationsLog?
[Section titled “operationsLog?”](#operationslog)
> `optional` **operationsLog?**: `object`
Defined in: packages/server/src/index.ts:89
#### enabled?
[Section titled “enabled?”](#enabled)
> `optional` **enabled?**: `boolean`
***
### port?
[Section titled “port?”](#port)
> `optional` **port?**: `number`
Defined in: packages/server/src/index.ts:94
***
### registry
[Section titled “registry”](#registry)
> **registry**: `TRegistry`
Defined in: packages/server/src/index.ts:76
***
### resolveAuthClaims?
[Section titled “resolveAuthClaims?”](#resolveauthclaims)
> `optional` **resolveAuthClaims?**: (`request`) => {\[`key`: `string`]: `unknown`; `app_metadata?`: {\[`key`: `string`]: `unknown`; `roles?`: `string`\[]; }; `sub?`: `string`; } | `Promise`<{\[`key`: `string`]: `unknown`; `app_metadata?`: {\[`key`: `string`]: `unknown`; `roles?`: `string`\[]; }; `sub?`: `string`; } | `null`> | `null`
Defined in: packages/server/src/index.ts:78
#### Parameters
[Section titled “Parameters”](#parameters-1)
##### request
[Section titled “request”](#request)
`Request`
#### Returns
[Section titled “Returns”](#returns-1)
{\[`key`: `string`]: `unknown`; `app_metadata?`: {\[`key`: `string`]: `unknown`; `roles?`: `string`\[]; }; `sub?`: `string`; } | `Promise`<{\[`key`: `string`]: `unknown`; `app_metadata?`: {\[`key`: `string`]: `unknown`; `roles?`: `string`\[]; }; `sub?`: `string`; } | `null`> | `null`
***
### resolveShapeParams?
[Section titled “resolveShapeParams?”](#resolveshapeparams)
> `optional` **resolveShapeParams?**: (`request`) => `Record`<`string`, `unknown`> | `undefined`
Defined in: packages/server/src/index.ts:88
Optional per-request extra params passed to customWhere/shared filters.
#### Parameters
[Section titled “Parameters”](#parameters-2)
##### request
[Section titled “request”](#request-1)
`Request`
#### Returns
[Section titled “Returns”](#returns-2)
`Record`<`string`, `unknown`> | `undefined`
***
### shapeProxyPath?
[Section titled “shapeProxyPath?”](#shapeproxypath)
> `optional` **shapeProxyPath?**: `string`
Defined in: packages/server/src/index.ts:86
Path for the shape proxy route. Defaults to `/api/shape`.
# DeadLetteredEventMessage
Defined in: packages/server/src/events/queue.ts:65
One dead-lettered message, as enumerated from the backend’s dead-letter storage.
## Properties
[Section titled “Properties”](#properties)
### deadLetteredAtUs
[Section titled “deadLetteredAtUs”](#deadletteredatus)
> **deadLetteredAtUs**: `string`
Defined in: packages/server/src/events/queue.ts:74
When it was dead-lettered, in unix microseconds (decimal string).
***
### deliveryCount
[Section titled “deliveryCount”](#deliverycount)
> **deliveryCount**: `number`
Defined in: packages/server/src/events/queue.ts:72
***
### id
[Section titled “id”](#id)
> **id**: `string`
Defined in: packages/server/src/events/queue.ts:67
The dead-letter identity, and the handle [EventQueue.requeueDeadLetter](/api/server/interfaces/eventqueue/#requeuedeadletter) takes.
***
### message
[Section titled “message”](#message)
> **message**: `object`
Defined in: packages/server/src/events/queue.ts:69
#### events
[Section titled “events”](#events)
> **events**: `object`\[]
#### stream
[Section titled “stream”](#stream)
> **stream**: `string`
***
### reason?
[Section titled “reason?”](#reason)
> `optional` **reason?**: `string`
Defined in: packages/server/src/events/queue.ts:71
The reason recorded when it was dead-lettered, when one was recorded.
***
### stream
[Section titled “stream”](#stream-1)
> **stream**: `string`
Defined in: packages/server/src/events/queue.ts:68
# DefineEventConsumerOptions
Defined in: packages/server/src/events/consumer.ts:193
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
## Properties
[Section titled “Properties”](#properties)
### batchSize?
[Section titled “batchSize?”](#batchsize)
> `optional` **batchSize?**: `number`
Defined in: packages/server/src/events/consumer.ts:210
Max messages per read. Defaults to [DEFAULT\_EVENT\_CONSUMER\_BATCH\_SIZE](/api/server/variables/default_event_consumer_batch_size/).
***
### callback
[Section titled “callback”](#callback)
> **callback**: [`EventConsumerCallback`](/api/server/type-aliases/eventconsumercallback/)
Defined in: packages/server/src/events/consumer.ts:202
***
### concurrency?
[Section titled “concurrency?”](#concurrency)
> `optional` **concurrency?**: `number`
Defined in: packages/server/src/events/consumer.ts:223
Max CONCURRENT callback invocations per Event stream. Defaults to `1` (strictly sequential). Values above 1 are safe for the idempotent callback the lane already requires: ADR-0053 decision 6 disclaims any ordering across sub-batches precisely so concurrency is available. Reads stay serial — only the callbacks of one delivered read run in parallel.
***
### maxAttempts?
[Section titled “maxAttempts?”](#maxattempts)
> `optional` **maxAttempts?**: `number`
Defined in: packages/server/src/events/consumer.ts:228
Deliveries a sub-batch gets before a further callback failure dead-letters it. Defaults to [DEFAULT\_EVENT\_MAX\_ATTEMPTS](/api/server/variables/default_event_max_attempts/). A malformed body never gets attempts — it dead-letters on sight.
***
### onDeadLetter?
[Section titled “onDeadLetter?”](#ondeadletter)
> `optional` **onDeadLetter?**: (`report`) => `void`
Defined in: packages/server/src/events/consumer.ts:235
Notified for every dead-lettered sub-batch. The runner ALSO warn-logs each one unconditionally: loudness is the ADR’s requirement and a hook that swallows (or throws) must not be able to make a dead letter silent.
#### Parameters
[Section titled “Parameters”](#parameters)
##### report
[Section titled “report”](#report)
[`EventDeadLetterReport`](/api/server/interfaces/eventdeadletterreport/)
#### Returns
[Section titled “Returns”](#returns)
`void`
***
### poll?
[Section titled “poll?”](#poll)
> `optional` **poll?**: [`EventConsumerPollOptions`](/api/server/interfaces/eventconsumerpolloptions/)
Defined in: packages/server/src/events/consumer.ts:230
Adaptive-poll tuning. See [EventConsumerPollOptions](/api/server/interfaces/eventconsumerpolloptions/).
***
### queue
[Section titled “queue”](#queue)
> **queue**: [`EventQueue`](/api/server/interfaces/eventqueue/)
Defined in: packages/server/src/events/consumer.ts:201
The queue to consume from — REQUIRED, and the reason the runner is backend-agnostic by construction. The ordinary wiring is `createPgmqEventQueue({ db })` with the app’s own drizzle handle; constructing it here from a `db` option would bake the shipped backend into the runner for one line of ergonomics.
***
### registry
[Section titled “registry”](#registry)
> **registry**: `TRegistry`
Defined in: packages/server/src/events/consumer.ts:195
The sync registry whose `streams` this runner consumes.
***
### streams?
[Section titled “streams?”](#streams)
> `optional` **streams?**: readonly `string`\[]
Defined in: packages/server/src/events/consumer.ts:208
Narrow to a subset of the registered Event streams — the knob that splits streams across processes. Defaults to ALL registered streams. An unknown name is a definition-time throw (fail-closed): a runner that silently consumed nothing is the failure mode this forbids.
***
### visibilityTimeoutSeconds?
[Section titled “visibilityTimeoutSeconds?”](#visibilitytimeoutseconds)
> `optional` **visibilityTimeoutSeconds?**: `number`
Defined in: packages/server/src/events/consumer.ts:216
The delivered-message invisibility window, renewed at half of it while the runner is still working through a read. Defaults to [DEFAULT\_EVENT\_VISIBILITY\_TIMEOUT\_SECONDS](/api/server/variables/default_event_visibility_timeout_seconds/). Size it above ONE callback’s worst case (renewal covers the rest of the batch), not above the whole batch’s.
# DeliveredEventMessage
Defined in: packages/server/src/events/queue.ts:50
One message delivered by [EventQueue.readBatch](/api/server/interfaces/eventqueue/#readbatch): a single-stream sub-batch plus its delivery metadata.
## Properties
[Section titled “Properties”](#properties)
### deliveryCount
[Section titled “deliveryCount”](#deliverycount)
> **deliveryCount**: `number`
Defined in: packages/server/src/events/queue.ts:59
How many times this message has been DELIVERED (pgmq’s `read_ct`), including the current delivery — the attempt counter a runner’s dead-letter-after-N policy reads. Never a client-supplied value.
***
### enqueuedAtUs
[Section titled “enqueuedAtUs”](#enqueuedatus)
> **enqueuedAtUs**: `string`
Defined in: packages/server/src/events/queue.ts:61
When the message was enqueued, in unix microseconds (decimal string).
***
### message
[Section titled “message”](#message)
> **message**: `object`
Defined in: packages/server/src/events/queue.ts:54
#### events
[Section titled “events”](#events)
> **events**: `object`\[]
#### stream
[Section titled “stream”](#stream)
> **stream**: `string`
***
### receipt
[Section titled “receipt”](#receipt)
> **receipt**: `string`
Defined in: packages/server/src/events/queue.ts:51
***
### stream
[Section titled “stream”](#stream-1)
> **stream**: `string`
Defined in: packages/server/src/events/queue.ts:53
The Event-stream name this message was read from (also carried inside `message`).
# DeploymentProfile
Defined in: packages/server/src/index.ts:62
The `deployment` profile owns the server’s startup query posture (ADR-0030 decision 3). Its defaults are the safe degradation posture — probe-and-verify at startup — so a long-lived host that never sets it still verifies and degrades gracefully; serverless / per-request workers set `{ startupVerification: "deploy-time", operationsLog: "enabled" | "disabled" }` for a zero-startup-query first write.
## Properties
[Section titled “Properties”](#properties)
### operationsLog?
[Section titled “operationsLog?”](#operationslog)
> `optional` **operationsLog?**: [`OperationsLogStartupMode`](/api/server/type-aliases/operationslogstartupmode/)
Defined in: packages/server/src/index.ts:66
How the operations-log table presence is resolved. Default `"probe"`.
***
### startupVerification?
[Section titled “startupVerification?”](#startupverification)
> `optional` **startupVerification?**: [`StartupVerificationMode`](/api/server/type-aliases/startupverificationmode/)
Defined in: packages/server/src/index.ts:64
Governs ONLY the RLS auth-helper verify now (apply-fn drift is self-verifying). Default `"in-process"`.
# ElectricProxyOptions
Defined in: packages/server/src/electric-proxy.ts:21
## Properties
[Section titled “Properties”](#properties)
### bustLiveUpstreamCache?
[Section titled “bustLiveUpstreamCache?”](#bustliveupstreamcache)
> `optional` **bustLiveUpstreamCache?**: `boolean`
Defined in: packages/server/src/electric-proxy.ts:49
Append a unique `cache-buster` to every `live=true` request forwarded upstream (default ON; set `false` to restore upstream CDN collapse of live long-polls). Stopgap for Electric Cloud serving live long-polls from a layer blind to fresh commits: consecutive full-hold (\~41s) `up-to-date` responses at an unmoved offset despite an advancing `cursor`, measured cross-client propagation of 40–89s (backlog 0001, 2026-07-04; upstream report alongside it). Only the live tail is busted — catch-up responses keep their CDN cold-fanout sharing, and per-user-filtered live polls share \~nothing across clients anyway. Remove when Electric fixes the live path (backlog 0001’s close).
***
### cors?
[Section titled “cors?”](#cors)
> `optional` **cors?**: `ElectricProxyCors`
Defined in: packages/server/src/electric-proxy.ts:32
CORS for a browser-facing deployment with **no CORS-adding gateway in front** — e.g. a Supabase Cloud edge function, which the platform routes to directly. When set, OPTIONS preflights are answered here and the response carries the allowed origin plus the Electric headers the client must read off each shape. Omit it where a gateway already handles CORS (the local stack’s Envoy).
***
### electricUrl
[Section titled “electricUrl”](#electricurl)
> **electricUrl**: `string`
Defined in: packages/server/src/electric-proxy.ts:23
***
### extraParams?
[Section titled “extraParams?”](#extraparams)
> `optional` **extraParams?**: `Record`<`string`, `unknown`>
Defined in: packages/server/src/electric-proxy.ts:25
Extra params passed to customWhere functions (e.g. fromLang, toLang).
***
### logTimings?
[Section titled “logTimings?”](#logtimings)
> `optional` **logTimings?**: `boolean`
Defined in: packages/server/src/electric-proxy.ts:39
Opt-in per-request timing log (default off). When on, each forwarded shape request emits one compact `[pgxsinkit-timing]` line (route `"shape"`) with the request’s table/live/offset and the upstream Electric fetch duration + status, for attributing read-path latency. Off by default — a pure diagnostic surface that adds no standing query or latency when unset.
***
### registry
[Section titled “registry”](#registry)
> **registry**: `SyncTableRegistry`
Defined in: packages/server/src/electric-proxy.ts:22
# EventConsumer
Defined in: packages/server/src/events/consumer.ts:248
## Properties
[Section titled “Properties”](#properties)
### drainOnce
[Section titled “drainOnce”](#drainonce)
> **drainOnce**: (`options?`) => `Promise`<[`EventDrainSummary`](/api/server/interfaces/eventdrainsummary/)>
Defined in: packages/server/src/events/consumer.ts:296
One bounded drain pass — the pacing mode for a host that cannot hold a process (ADR-0053 amendment, 2026-08-02).
It walks every configured Event stream, read → deliver → ack, and keeps going until EITHER every stream has returned an empty read OR the wall-clock budget is spent. Same internals as the loop mode throughout: the same read/deliver/ack path, the same lease renewal while a callback runs, the same per-sub-batch retry-by-lapsing-lease, the same dead-lettering after `maxAttempts` with the same `onDeadLetter` hook and unconditional warn log. It never sleeps: a stream that reads empty is finished for this pass, and there is no adaptive interval to wait out.
**The budget is checked BETWEEN sub-batches, never inside one.** A callback already running is awaited and acked exactly as the runner would (its lease stays renewed throughout), and no new read starts once the budget is gone — so a pass can overrun its budget by one callback, and `budgetMs` must leave head-room for that under the platform’s invocation cap. A sub-batch whose callback THROWS near the budget edge is not special-cased: it is left unacked and dropped from renewal, its lease lapses, and the queue redelivers it on a later pass with an incremented delivery count. That is at-least-once working as designed, not a lost event — and it is why the callback must be idempotent.
**Hosting it.** Wire a SCHEDULED invocation (a platform cron, e.g. every 10 s) that calls `drainOnce` and, optionally, an ingest-side nudge (`createSyncServer({ onEventsEnqueued })` firing a fetch-and-forget at the same endpoint) so an interactive append is drained in milliseconds instead of waiting for the next tick. The **schedule is the delivery guarantee; the nudge is only latency** — a lost nudge costs nothing but time. Overlapping invocations are SAFE: two processes reading the same queue are arbitrated by the visibility timeout, exactly as two long-lived runners would be. (Two passes on ONE handle are not — that is a bug, and throws; see below.)
**One handle, one pacing mode.** Throws if `start()` is live, if another `drainOnce` is already in flight on this handle, or if the handle has been stopped — the lifecycle is one-way, so the next drain builds a fresh `defineEventConsumer` (construction is query-free, so that costs nothing).
#### Parameters
[Section titled “Parameters”](#parameters)
##### options?
[Section titled “options?”](#options)
[`EventDrainOptions`](/api/server/interfaces/eventdrainoptions/)
#### Returns
[Section titled “Returns”](#returns)
`Promise`<[`EventDrainSummary`](/api/server/interfaces/eventdrainsummary/)>
***
### start
[Section titled “start”](#start)
> **start**: () => `void`
Defined in: packages/server/src/events/consumer.ts:253
Begin every stream’s loop. Idempotent; a no-op after [EventConsumer.stop](/api/server/interfaces/eventconsumer/#stop). Throws while a [EventConsumer.drainOnce](/api/server/interfaces/eventconsumer/#drainonce) pass is in flight — one handle drives one pacing mode at a time.
#### Returns
[Section titled “Returns”](#returns-1)
`void`
***
### stop
[Section titled “stop”](#stop)
> **stop**: () => `Promise`<`void`>
Defined in: packages/server/src/events/consumer.ts:264
Graceful stop: no new reads, no new callbacks, in-flight callbacks are awaited (and their leases KEPT renewed until they ack or dead-letter), and the promise resolves when every loop — and every renewal task it still owned — is quiet. Safe to call twice (the second call awaits the first). Messages of an in-progress read whose callback had not started are released at once: they stop being renewed and are left unacked, so at-least-once redelivers them as soon as their lease lapses.
A `drainOnce` pass in flight is treated the same way: it starts no further read, finishes the callback it is on, and `stop()` resolves once it has.
#### Returns
[Section titled “Returns”](#returns-2)
`Promise`<`void`>
# EventConsumerBatch
Defined in: packages/server/src/events/consumer.ts:122
One callback invocation: exactly ONE delivered queue message, i.e. one single-stream sub-batch.
## Properties
[Section titled “Properties”](#properties)
### events
[Section titled “events”](#events)
> **events**: readonly `object`\[]
Defined in: packages/server/src/events/consumer.ts:129
The stamped envelopes, EXACTLY as the ingestion endpoint enqueued them (ADR-0053 decision 5) — in append order within this sub-batch. Across sub-batches there is no ordering promise (decision 6).
***
### stream
[Section titled “stream”](#stream)
> **stream**: `string`
Defined in: packages/server/src/events/consumer.ts:124
The Event stream these events were appended under.
# EventConsumerPollOptions
Defined in: packages/server/src/events/consumer.ts:112
Adaptive-poll tuning. Defaults are the ADR’s; they are TUNING, not contract — the pacing mechanism itself is internal and may change.
## Properties
[Section titled “Properties”](#properties)
### ceilingMs?
[Section titled “ceilingMs?”](#ceilingms)
> `optional` **ceilingMs?**: `number`
Defined in: packages/server/src/events/consumer.ts:116
The longest wait an idle stream reaches. Defaults to [DEFAULT\_EVENT\_POLL\_CEILING\_MS](/api/server/variables/default_event_poll_ceiling_ms/).
***
### factor?
[Section titled “factor?”](#factor)
> `optional` **factor?**: `number`
Defined in: packages/server/src/events/consumer.ts:118
The growth factor per consecutive empty read. Defaults to [DEFAULT\_EVENT\_POLL\_FACTOR](/api/server/variables/default_event_poll_factor/).
***
### floorMs?
[Section titled “floorMs?”](#floorms)
> `optional` **floorMs?**: `number`
Defined in: packages/server/src/events/consumer.ts:114
The wait after the first empty read. Defaults to [DEFAULT\_EVENT\_POLL\_FLOOR\_MS](/api/server/variables/default_event_poll_floor_ms/).
# EventDeadLetterReport
Defined in: packages/server/src/events/consumer.ts:142
What [DefineEventConsumerOptions.onDeadLetter](/api/server/interfaces/defineeventconsumeroptions/#ondeadletter) is told about a sub-batch the runner gave up on.
## Properties
[Section titled “Properties”](#properties)
### attempts
[Section titled “attempts”](#attempts)
> **attempts**: `number`
Defined in: packages/server/src/events/consumer.ts:147
Deliveries this message had received. `0` when the body was unreadable (nothing was ever delivered from it).
***
### cause?
[Section titled “cause?”](#cause)
> `optional` **cause?**: `unknown`
Defined in: packages/server/src/events/consumer.ts:153
The error behind it: the callback’s throw, or the [MalformedEventQueueMessageError](/api/server/classes/malformedeventqueuemessageerror/).
***
### message?
[Section titled “message?”](#message)
> `optional` **message?**: `object`
Defined in: packages/server/src/events/consumer.ts:151
The message, when it was readable. Absent for a malformed body (that is exactly what could not be parsed).
#### events
[Section titled “events”](#events)
> **events**: `object`\[]
#### stream
[Section titled “stream”](#stream)
> **stream**: `string`
***
### reason
[Section titled “reason”](#reason)
> **reason**: `string`
Defined in: packages/server/src/events/consumer.ts:145
Why it was dead-lettered — the same reason recorded in the backend’s dead-letter storage.
***
### receipt
[Section titled “receipt”](#receipt)
> **receipt**: `string`
Defined in: packages/server/src/events/consumer.ts:149
The receipt it was dead-lettered under — always present, including for an unparseable body.
***
### stream
[Section titled “stream”](#stream-1)
> **stream**: `string`
Defined in: packages/server/src/events/consumer.ts:143
# EventDrainOptions
Defined in: packages/server/src/events/consumer.ts:163
What [EventConsumer.drainOnce](/api/server/interfaces/eventconsumer/#drainonce) is tuned with.
## Properties
[Section titled “Properties”](#properties)
### budgetMs?
[Section titled “budgetMs?”](#budgetms)
> `optional` **budgetMs?**: `number`
Defined in: packages/server/src/events/consumer.ts:169
The pass’s wall-clock budget. Defaults to [DEFAULT\_EVENT\_DRAIN\_BUDGET\_MS](/api/server/variables/default_event_drain_budget_ms/). Checked between sub-batches only, never inside a callback — so size it under the invocation’s cap with head-room for one callback’s worst case.
# EventDrainSummary
Defined in: packages/server/src/events/consumer.ts:174
What one [EventConsumer.drainOnce](/api/server/interfaces/eventconsumer/#drainonce) pass reports back. Counts are SUB-BATCHES (one queue message, one callback invocation), the unit the queue and the dead-letter archive both work in.
## Properties
[Section titled “Properties”](#properties)
### deadLettered
[Section titled “deadLettered”](#deadlettered)
> **deadLettered**: `number`
Defined in: packages/server/src/events/consumer.ts:181
Sub-batches this pass moved to the backend’s dead-letter storage.
***
### delivered
[Section titled “delivered”](#delivered)
> **delivered**: `number`
Defined in: packages/server/src/events/consumer.ts:179
Sub-batches whose callback completed during this pass. An ack that then failed still counts — the work was delivered, and at-least-once means it may be delivered again later (the callback is idempotent).
***
### empty
[Section titled “empty”](#empty)
> **empty**: `boolean`
Defined in: packages/server/src/events/consumer.ts:190
`true` when every configured Event stream read empty before the budget ran out — the queue is drained as far as this pass can see.
`false` means the pass ended with work possibly still queued: the budget cut it short, or a stream’s read faulted. It is the caller’s signal that another tick has something to do — a scheduler that chains passes should invoke again promptly rather than waiting out its full period.
# EventGateInput
Defined in: packages/server/src/events/route.ts:44
What the gating hook is told about one event. `identity` is already resolved from the verified claims.
## Properties
[Section titled “Properties”](#properties)
### claims
[Section titled “claims”](#claims)
> **claims**: `object`
Defined in: packages/server/src/events/route.ts:50
The verified claims of the ingesting request (`{}` when the registry needs none).
#### Index Signature
[Section titled “Index Signature”](#index-signature)
\[`key`: `string`]: `unknown`
#### app\_metadata?
[Section titled “app\_metadata?”](#app_metadata)
> `optional` **app\_metadata?**: `object`
##### Index Signature
[Section titled “Index Signature”](#index-signature-1)
\[`key`: `string`]: `unknown`
##### app\_metadata.roles?
[Section titled “app\_metadata.roles?”](#app_metadataroles)
> `optional` **roles?**: `string`\[]
#### sub?
[Section titled “sub?”](#sub)
> `optional` **sub?**: `string`
***
### event
[Section titled “event”](#event)
> **event**: `object`
Defined in: packages/server/src/events/route.ts:48
The client’s envelope, with its payload already validated against the stream’s registered schema.
#### eventId
[Section titled “eventId”](#eventid)
> **eventId**: `string`
#### occurredAtUs
[Section titled “occurredAtUs”](#occurredatus)
> **occurredAtUs**: `string` = `unixMicrosecondsSchema`
#### payload
[Section titled “payload”](#payload)
> **payload**: `unknown`
#### stream
[Section titled “stream”](#stream)
> **stream**: `string`
***
### identity
[Section titled “identity”](#identity)
> **identity**: `Record`<`string`, `string`>
Defined in: packages/server/src/events/route.ts:52
The server-stamped identity this event would carry onto the queue.
***
### stream
[Section titled “stream”](#stream-1)
> **stream**: `string`
Defined in: packages/server/src/events/route.ts:46
The Event-stream name — the key the hook dispatches on.
# EventIngestDb
Defined in: packages/server/src/events/route.ts:96
The minimal transaction seam the endpoint needs — satisfied by a drizzle `PgAsyncDatabase`.
## Properties
[Section titled “Properties”](#properties)
### transaction
[Section titled “transaction”](#transaction)
> **transaction**: <`TResult`>(`callback`) => `Promise`<`TResult`>
Defined in: packages/server/src/events/route.ts:97
#### Type Parameters
[Section titled “Type Parameters”](#type-parameters)
##### TResult
[Section titled “TResult”](#tresult)
`TResult`
#### Parameters
[Section titled “Parameters”](#parameters)
##### callback
[Section titled “callback”](#callback)
(`tx`) => `Promise`<`TResult`>
#### Returns
[Section titled “Returns”](#returns)
`Promise`<`TResult`>
# EventQueue
Defined in: packages/server/src/events/queue.ts:77
## Properties
[Section titled “Properties”](#properties)
### ack
[Section titled “ack”](#ack)
> **ack**: (`stream`, `receipts`) => `Promise`<`number`>
Defined in: packages/server/src/events/queue.ts:103
Acknowledge (permanently remove) delivered messages. Returns how many were actually removed.
#### Parameters
[Section titled “Parameters”](#parameters)
##### stream
[Section titled “stream”](#stream)
`string`
##### receipts
[Section titled “receipts”](#receipts)
readonly `string`\[]
#### Returns
[Section titled “Returns”](#returns)
`Promise`<`number`>
***
### deadLetter
[Section titled “deadLetter”](#deadletter)
> **deadLetter**: (`stream`, `receipts`, `reason`) => `Promise`<`number`>
Defined in: packages/server/src/events/queue.ts:105
Move delivered messages to dead-letter storage with a recorded reason. Returns how many moved.
#### Parameters
[Section titled “Parameters”](#parameters-1)
##### stream
[Section titled “stream”](#stream-1)
`string`
##### receipts
[Section titled “receipts”](#receipts-1)
readonly `string`\[]
##### reason
[Section titled “reason”](#reason)
`string`
#### Returns
[Section titled “Returns”](#returns-1)
`Promise`<`number`>
***
### enqueueBatch
[Section titled “enqueueBatch”](#enqueuebatch)
> **enqueueBatch**: (`messages`, `executor?`) => `Promise`<`void`>
Defined in: packages/server/src/events/queue.ts:84
Enqueue whole messages. Called by the ingestion endpoint inside its own transaction, so passing `executor` is what makes a multi-stream flush batch atomic: every message lands or none does (ADR-0053 decision 4 — the endpoint never partially enqueues). Without an executor the backend uses its own handle, and a multi-message call is then only as atomic as that handle.
#### Parameters
[Section titled “Parameters”](#parameters-2)
##### messages
[Section titled “messages”](#messages)
readonly `object`\[]
##### executor?
[Section titled “executor?”](#executor)
[`EventQueueExecutor`](/api/server/interfaces/eventqueueexecutor/)
#### Returns
[Section titled “Returns”](#returns-2)
`Promise`<`void`>
***
### extendVisibility
[Section titled “extendVisibility”](#extendvisibility)
> **extendVisibility**: (`stream`, `receipts`, `visibilityTimeoutSeconds`, `executor?`) => `Promise`<`number`>
Defined in: packages/server/src/events/queue.ts:96
Push delivered messages’ invisibility out to `visibilityTimeoutSeconds` FROM NOW — the lease RENEWAL a consumer runner performs while it is still working through a read (one read makes a whole batch invisible at once, so without renewal the later messages of a slow batch would become visible to another runner while the first is still working toward them).
Returns how many were actually extended. A receipt that is no longer queued (already acked or dead-lettered) is simply not counted — a renewal racing a settle is ordinary, never an error.
#### Parameters
[Section titled “Parameters”](#parameters-3)
##### stream
[Section titled “stream”](#stream-2)
`string`
##### receipts
[Section titled “receipts”](#receipts-2)
readonly `string`\[]
##### visibilityTimeoutSeconds
[Section titled “visibilityTimeoutSeconds”](#visibilitytimeoutseconds)
`number`
##### executor?
[Section titled “executor?”](#executor-1)
[`EventQueueExecutor`](/api/server/interfaces/eventqueueexecutor/)
#### Returns
[Section titled “Returns”](#returns-3)
`Promise`<`number`>
***
### listDeadLetters
[Section titled “listDeadLetters”](#listdeadletters)
> **listDeadLetters**: (`stream`, `limit`) => `Promise`<[`DeadLetteredEventMessage`](/api/server/interfaces/deadletteredeventmessage/)\[]>
Defined in: packages/server/src/events/queue.ts:107
Enumerate an Event stream’s dead-lettered messages, most recent first.
#### Parameters
[Section titled “Parameters”](#parameters-4)
##### stream
[Section titled “stream”](#stream-3)
`string`
##### limit
[Section titled “limit”](#limit)
`number`
#### Returns
[Section titled “Returns”](#returns-4)
`Promise`<[`DeadLetteredEventMessage`](/api/server/interfaces/deadletteredeventmessage/)\[]>
***
### readBatch
[Section titled “readBatch”](#readbatch)
> **readBatch**: (`stream`, `options`) => `Promise`<[`DeliveredEventMessage`](/api/server/interfaces/deliveredeventmessage/)\[]>
Defined in: packages/server/src/events/queue.ts:86
Deliver up to `maxMessages` messages of one Event stream, made invisible for the visibility timeout.
#### Parameters
[Section titled “Parameters”](#parameters-5)
##### stream
[Section titled “stream”](#stream-4)
`string`
##### options
[Section titled “options”](#options)
[`EventQueueReadOptions`](/api/server/interfaces/eventqueuereadoptions/)
#### Returns
[Section titled “Returns”](#returns-5)
`Promise`<[`DeliveredEventMessage`](/api/server/interfaces/deliveredeventmessage/)\[]>
***
### requeueDeadLetter
[Section titled “requeueDeadLetter”](#requeuedeadletter)
> **requeueDeadLetter**: (`stream`, `id`) => `Promise`<`string` | `null`>
Defined in: packages/server/src/events/queue.ts:112
Put one dead-lettered message back on its queue — a DELIBERATE act (ADR-0053 decision 7), never automatic. Resolves with the requeued message’s new receipt, or `null` when the id is not dead-lettered.
#### Parameters
[Section titled “Parameters”](#parameters-6)
##### stream
[Section titled “stream”](#stream-5)
`string`
##### id
[Section titled “id”](#id)
`string`
#### Returns
[Section titled “Returns”](#returns-6)
`Promise`<`string` | `null`>
# EventQueueExecutor
Defined in: packages/server/src/events/queue.ts:29
The minimal SQL seam a backend executes through — satisfied by a drizzle `PgAsyncDatabase` AND by the transaction handle its `transaction()` callback receives. The ingestion endpoint enqueues INSIDE its own transaction (a batch is enqueued atomically or the request fails retryably), so every mutating method takes an optional executor rather than owning a connection.
## Properties
[Section titled “Properties”](#properties)
### execute
[Section titled “execute”](#execute)
> **execute**: (`query`) => `Promise`<`unknown`>
Defined in: packages/server/src/events/queue.ts:30
#### Parameters
[Section titled “Parameters”](#parameters)
##### query
[Section titled “query”](#query)
`SQL`
#### Returns
[Section titled “Returns”](#returns)
`Promise`<`unknown`>
# EventQueueReadOptions
Defined in: packages/server/src/events/queue.ts:39
## Properties
[Section titled “Properties”](#properties)
### maxMessages
[Section titled “maxMessages”](#maxmessages)
> **maxMessages**: `number`
Defined in: packages/server/src/events/queue.ts:46
At most this many messages (each message is one single-stream sub-batch).
***
### visibilityTimeoutSeconds
[Section titled “visibilityTimeoutSeconds”](#visibilitytimeoutseconds)
> **visibilityTimeoutSeconds**: `number`
Defined in: packages/server/src/events/queue.ts:44
How long a delivered message stays invisible to other consumers, in seconds. The runner renews or lets it lapse; the queue seam only applies what it is given.
# EventsEnqueuedInfo
Defined in: packages/server/src/events/route.ts:74
What [EventsEnqueuedHook](/api/server/type-aliases/eventsenqueuedhook/) is told about a request that enqueued something.
## Properties
[Section titled “Properties”](#properties)
### streams
[Section titled “streams”](#streams)
> **streams**: `string`\[]
Defined in: packages/server/src/events/route.ts:76
The Event streams this request put a sub-batch onto, deduplicated, in first-appearance order.
# ServerDiagnostics
Defined in: packages/server/src/index.ts:159
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
## Properties
[Section titled “Properties”](#properties)
### modes
[Section titled “modes”](#modes)
> **modes**: `Record`<`string`, `TRegistry`\[keyof `TRegistry`]\[`"mode"`]>
Defined in: packages/server/src/index.ts:161
***
### tables
[Section titled “tables”](#tables)
> **tables**: keyof `TRegistry` & `string`\[]
Defined in: packages/server/src/index.ts:160
# SyncServer
Defined in: packages/server/src/index.ts:164
## Type Parameters
[Section titled “Type Parameters”](#type-parameters)
### TRegistry
[Section titled “TRegistry”](#tregistry)
`TRegistry` *extends* `SyncTableRegistry`
### TDb
[Section titled “TDb”](#tdb)
`TDb` *extends* `PgAsyncDatabase`<`PgQueryResultHKT`, `RegistryRelations`<`TRegistry`>> = `PgAsyncDatabase`<`PgQueryResultHKT`, `RegistryRelations`<`TRegistry`>>
## Properties
[Section titled “Properties”](#properties)
### address
[Section titled “address”](#address)
> **address**: `SyncServerAddress` | `null`
Defined in: packages/server/src/index.ts:177
***
### diagnostics
[Section titled “diagnostics”](#diagnostics)
> **diagnostics**: () => [`ServerDiagnostics`](/api/server/interfaces/serverdiagnostics/)<`TRegistry`>
Defined in: packages/server/src/index.ts:178
#### Returns
[Section titled “Returns”](#returns)
[`ServerDiagnostics`](/api/server/interfaces/serverdiagnostics/)<`TRegistry`>
***
### drizzle
[Section titled “drizzle”](#drizzle)
> **drizzle**: `TDb`
Defined in: packages/server/src/index.ts:171
***
### fetch
[Section titled “fetch”](#fetch)
> **fetch**: (`request`) => `Promise`<`Response`>
Defined in: packages/server/src/index.ts:172
#### Parameters
[Section titled “Parameters”](#parameters)
##### request
[Section titled “request”](#request)
`Request`
#### Returns
[Section titled “Returns”](#returns-1)
`Promise`<`Response`>
***
### request
[Section titled “request”](#request-1)
> **request**: (`path`, `init?`) => `Promise`<`Response`>
Defined in: packages/server/src/index.ts:173
#### Parameters
[Section titled “Parameters”](#parameters-1)
##### path
[Section titled “path”](#path)
`string`
##### init?
[Section titled “init?”](#init)
`RequestInit`
#### Returns
[Section titled “Returns”](#returns-2)
`Promise`<`Response`>
***
### start
[Section titled “start”](#start)
> **start**: () => `Promise`<`void`>
Defined in: packages/server/src/index.ts:174
#### Returns
[Section titled “Returns”](#returns-3)
`Promise`<`void`>
***
### status
[Section titled “status”](#status)
> **status**: `SyncRuntimeStatus`
Defined in: packages/server/src/index.ts:176
***
### stop
[Section titled “stop”](#stop)
> **stop**: () => `Promise`<`void`>
Defined in: packages/server/src/index.ts:175
#### Returns
[Section titled “Returns”](#returns-4)
`Promise`<`void`>
# @pgxsinkit/server
## Classes
[Section titled “Classes”](#classes)
* [FetchRouter](/api/server/classes/fetchrouter/)
* [MalformedEventQueueMessageError](/api/server/classes/malformedeventqueuemessageerror/)
## Interfaces
[Section titled “Interfaces”](#interfaces)
* [ApplyFunctionRenderOptions](/api/server/interfaces/applyfunctionrenderoptions/)
* [CorsConfig](/api/server/interfaces/corsconfig/)
* [CreateEventIngestHandlerOptions](/api/server/interfaces/createeventingesthandleroptions/)
* [CreatePgmqEventQueueOptions](/api/server/interfaces/createpgmqeventqueueoptions/)
* [CreateSyncServerOptions](/api/server/interfaces/createsyncserveroptions/)
* [DeadLetteredEventMessage](/api/server/interfaces/deadletteredeventmessage/)
* [DefineEventConsumerOptions](/api/server/interfaces/defineeventconsumeroptions/)
* [DeliveredEventMessage](/api/server/interfaces/deliveredeventmessage/)
* [DeploymentProfile](/api/server/interfaces/deploymentprofile/)
* [ElectricProxyOptions](/api/server/interfaces/electricproxyoptions/)
* [EventConsumer](/api/server/interfaces/eventconsumer/)
* [EventConsumerBatch](/api/server/interfaces/eventconsumerbatch/)
* [EventConsumerPollOptions](/api/server/interfaces/eventconsumerpolloptions/)
* [EventDeadLetterReport](/api/server/interfaces/eventdeadletterreport/)
* [EventDrainOptions](/api/server/interfaces/eventdrainoptions/)
* [EventDrainSummary](/api/server/interfaces/eventdrainsummary/)
* [EventGateInput](/api/server/interfaces/eventgateinput/)
* [EventIngestDb](/api/server/interfaces/eventingestdb/)
* [EventQueue](/api/server/interfaces/eventqueue/)
* [EventQueueExecutor](/api/server/interfaces/eventqueueexecutor/)
* [EventQueueReadOptions](/api/server/interfaces/eventqueuereadoptions/)
* [EventsEnqueuedInfo](/api/server/interfaces/eventsenqueuedinfo/)
* [ServerDiagnostics](/api/server/interfaces/serverdiagnostics/)
* [SyncServer](/api/server/interfaces/syncserver/)
## Type Aliases
[Section titled “Type Aliases”](#type-aliases)
* [CorsScope](/api/server/type-aliases/corsscope/)
* [EventConsumerCallback](/api/server/type-aliases/eventconsumercallback/)
* [EventConsumerSleep](/api/server/type-aliases/eventconsumersleep/)
* [EventGate](/api/server/type-aliases/eventgate/)
* [EventGateDecision](/api/server/type-aliases/eventgatedecision/)
* [EventQueueReceipt](/api/server/type-aliases/eventqueuereceipt/)
* [EventsEnqueuedHook](/api/server/type-aliases/eventsenqueuedhook/)
* [FetchHandler](/api/server/type-aliases/fetchhandler/)
* [IdentityResolution](/api/server/type-aliases/identityresolution/)
* [OperationsLogStartupMode](/api/server/type-aliases/operationslogstartupmode/)
* [RouterErrorHandler](/api/server/type-aliases/routererrorhandler/)
* [StartupVerificationMode](/api/server/type-aliases/startupverificationmode/)
## Variables
[Section titled “Variables”](#variables)
* [authoritativeMutationPaths](/api/server/variables/authoritativemutationpaths/)
* [batchMutationPaths](/api/server/variables/batchmutationpaths/)
* [DEFAULT\_EVENT\_CONSUMER\_BATCH\_SIZE](/api/server/variables/default_event_consumer_batch_size/)
* [DEFAULT\_EVENT\_DRAIN\_BUDGET\_MS](/api/server/variables/default_event_drain_budget_ms/)
* [DEFAULT\_EVENT\_MAX\_ATTEMPTS](/api/server/variables/default_event_max_attempts/)
* [DEFAULT\_EVENT\_POLL\_CEILING\_MS](/api/server/variables/default_event_poll_ceiling_ms/)
* [DEFAULT\_EVENT\_POLL\_FACTOR](/api/server/variables/default_event_poll_factor/)
* [DEFAULT\_EVENT\_POLL\_FLOOR\_MS](/api/server/variables/default_event_poll_floor_ms/)
* [DEFAULT\_EVENT\_VISIBILITY\_TIMEOUT\_SECONDS](/api/server/variables/default_event_visibility_timeout_seconds/)
* [EVENT\_LANE\_FINGERPRINT\_PREFIX](/api/server/variables/event_lane_fingerprint_prefix/)
* [EVENT\_QUEUE\_UNAVAILABLE\_RETRY\_AFTER\_SECONDS](/api/server/variables/event_queue_unavailable_retry_after_seconds/)
* [operationsLogTable](/api/server/variables/operationslogtable/)
* [PGMQ\_DEAD\_LETTER\_KEY](/api/server/variables/pgmq_dead_letter_key/)
## Functions
[Section titled “Functions”](#functions)
* [assertEventQueueReceipts](/api/server/functions/asserteventqueuereceipts/)
* [buildPlpgsqlBatchFunctionDdl](/api/server/functions/buildplpgsqlbatchfunctionddl/)
* [buildRegistrySchema](/api/server/functions/buildregistryschema/)
* [computeEventPollWaitMs](/api/server/functions/computeeventpollwaitms/)
* [createEventIngestHandler](/api/server/functions/createeventingesthandler/)
* [createMutationHandler](/api/server/functions/createmutationhandler/)
* [createPgmqEventQueue](/api/server/functions/createpgmqeventqueue/)
* [createSyncServer](/api/server/functions/createsyncserver/)
* [defineEventConsumer](/api/server/functions/defineeventconsumer/)
* [ensureOperationsLogSchema](/api/server/functions/ensureoperationslogschema/)
* [eventIngestRequiresClaims](/api/server/functions/eventingestrequiresclaims/)
* [eventLaneDdlFingerprint](/api/server/functions/eventlaneddlfingerprint/)
* [eventLaneStreamNames](/api/server/functions/eventlanestreamnames/)
* [eventStreamQueueName](/api/server/functions/eventstreamqueuename/)
* [expectedApplyFingerprint](/api/server/functions/expectedapplyfingerprint/)
* [operationsLogRegclassTarget](/api/server/functions/operationslogregclasstarget/)
* [proxyElectricShapeRequest](/api/server/functions/proxyelectricshaperequest/)
* [readSqlState](/api/server/functions/readsqlstate/)
* [renderEventLaneMigration](/api/server/functions/rendereventlanemigration/)
* [renderPgxsinkitUtilitiesMigration](/api/server/functions/renderpgxsinkitutilitiesmigration/)
* [resolveEventIdentity](/api/server/functions/resolveeventidentity/)
# CorsScope
> **CorsScope** = { `exact`: `string`; } | { `prefix`: `string`; }
Defined in: packages/server/src/router.ts:19
A CORS scope is matched either by exact pathname or by pathname prefix (e.g. `/api/`).
# EventConsumerCallback
> **EventConsumerCallback** = (`batch`) => `void` | `Promise`<`void`>
Defined in: packages/server/src/events/consumer.ts:139
The consumer callback. Returning (resolving) ACKS the sub-batch; THROWING retries it — the message stays invisible until its visibility timeout lapses and the queue redelivers it.
It MUST be idempotent (ADR-0053 decision 6, at-least-once): the blessed pattern is deduping on `eventId` against the app’s own durable store, which composes to effectively-exactly-once.
## Parameters
[Section titled “Parameters”](#parameters)
### batch
[Section titled “batch”](#batch)
[`EventConsumerBatch`](/api/server/interfaces/eventconsumerbatch/)
## Returns
[Section titled “Returns”](#returns)
`void` | `Promise`<`void`>
# EventConsumerSleep
> **EventConsumerSleep** = (`ms`, `signal`) => `Promise`<`void`>
Defined in: packages/server/src/events/consumer.ts:160
The deterministic seam the runner waits on. `signal` is aborted by `stop()`, so a stopping runner never lingers for a full idle ceiling. Injected only by tests; production uses an abortable `setTimeout`.
## Parameters
[Section titled “Parameters”](#parameters)
### ms
[Section titled “ms”](#ms)
`number`
### signal
[Section titled “signal”](#signal)
`AbortSignal`
## Returns
[Section titled “Returns”](#returns)
`Promise`<`void`>
# EventGate
> **EventGate** = (`input`) => [`EventGateDecision`](/api/server/type-aliases/eventgatedecision/) | `Promise`<[`EventGateDecision`](/api/server/type-aliases/eventgatedecision/)>
Defined in: packages/server/src/events/route.ts:71
The consent/entitlement gate (ADR-0053 decision 1): the one FUNCTION of the Event lane’s contract, and so a `createSyncServer` option rather than a registry field (the registry stays declarative data only).
Called **once per event**, after the payload validated and the identity resolved, and before anything is enqueued. Per-event (rather than per stream × batch) is the simplest correct shape: a consent or entitlement decision may legitimately depend on the payload, and a per-batch hook could only express the subset that does not. The claims are constant across a batch, so a hook that consults a store should memoize on them — a batch can carry up to `MAX_EVENTS_PER_BATCH` events.
A hook that THROWS fails the whole batch retryably (500, nothing enqueued): a gate that cannot decide must never be read as “allow”.
## Parameters
[Section titled “Parameters”](#parameters)
### input
[Section titled “input”](#input)
[`EventGateInput`](/api/server/interfaces/eventgateinput/)
## Returns
[Section titled “Returns”](#returns)
[`EventGateDecision`](/api/server/type-aliases/eventgatedecision/) | `Promise`<[`EventGateDecision`](/api/server/type-aliases/eventgatedecision/)>
# EventGateDecision
> **EventGateDecision** = `boolean` | { `allow`: `boolean`; `reason?`: `string`; }
Defined in: packages/server/src/events/route.ts:56
`true` allows; `false` or `{ allow: false, reason }` refuses (a TERMINAL `refused` verdict).
# EventQueueReceipt
> **EventQueueReceipt** = `string`
Defined in: packages/server/src/events/queue.ts:37
An opaque handle for one delivered message, used to ack or dead-letter it. Backend-defined (pgmq: the `msg_id` as a decimal string) — never parsed or ordered by callers.
# EventsEnqueuedHook
> **EventsEnqueuedHook** = (`info`) => `void`
Defined in: packages/server/src/events/route.ts:93
Fired after a request has ENQUEUED at least one sub-batch (ADR-0053 amendment, 2026-08-02) — the deployment-agnostic seam a serverless deployment uses to NUDGE its drain function.
The library stays out of the transport: this hook hands over “something landed on these streams” and the deployment decides what that means. A long-lived runner deployment wires nothing (its poller is already about to read). A serverless one fires a fetch-and-forget at whatever endpoint calls the consumer’s `drainOnce()`, so an interactive append is drained in milliseconds rather than at the next scheduled tick.
**It is latency, never delivery.** The scheduled sweep is the guarantee; a nudge that is lost, refused or never sent costs nothing but time. So it is fire-and-forget by construction: it is called AFTER the enqueue transaction committed, its return value is ignored (it is `void` — do not await anything through it), and a throw is caught and warn-logged rather than affecting the response the client already earned.
## Parameters
[Section titled “Parameters”](#parameters)
### info
[Section titled “info”](#info)
[`EventsEnqueuedInfo`](/api/server/interfaces/eventsenqueuedinfo/)
## Returns
[Section titled “Returns”](#returns)
`void`
# FetchHandler
> **FetchHandler** = (`request`) => `Response` | `Promise`<`Response`>
Defined in: packages/server/src/router.ts:9
## Parameters
[Section titled “Parameters”](#parameters)
### request
[Section titled “request”](#request)
`Request`
## Returns
[Section titled “Returns”](#returns)
`Response` | `Promise`<`Response`>
# IdentityResolution
> **IdentityResolution** = { `identity`: `Record`<`string`, `string`>; `ok`: `true`; } | { `claimPath`: readonly `string`\[]; `detail`: `string`; `field`: `string`; `ok`: `false`; }
Defined in: packages/server/src/events/identity.ts:13
A resolved stamp, or the first field that could not be resolved (which makes the event `rejected`).
# OperationsLogStartupMode
> **OperationsLogStartupMode** = `"probe"` | `"enabled"` | `"disabled"`
Defined in: packages/server/src/index.ts:53
How the operations-log table’s presence is resolved at startup (ADR-0030 decision 3):
* `"probe"` (default): the safe degradation posture (ADR-0030) — one query ensures/confirms the table, and logging is disabled at runtime (with a warning) if it is absent, so a missing table degrades gracefully instead of failing writes.
* `"enabled"`: assume the table exists — NO query. If it is actually absent, writes then fail loudly.
* `"disabled"`: turn logging off with NO query.
`"enabled" | "disabled"` are the serverless posture: paired with `startupVerification: "deploy-time"`, a fresh worker sends zero queries before the mutation transaction itself.
# RouterErrorHandler
> **RouterErrorHandler** = (`error`, `request`) => `Response` | `Promise`<`Response`>
Defined in: packages/server/src/router.ts:21
## Parameters
[Section titled “Parameters”](#parameters)
### error
[Section titled “error”](#error)
`unknown`
### request
[Section titled “request”](#request)
`Request`
## Returns
[Section titled “Returns”](#returns)
`Response` | `Promise`<`Response`>
# StartupVerificationMode
> **StartupVerificationMode** = `"in-process"` | `"deploy-time"`
Defined in: packages/server/src/mutations/route.ts:48
How the write path handles its remaining startup query class — the RLS auth-helper verify (ADR-0030 decision 3). The apply-function drift guarantee moved into the call itself (self-verifying function), so this now governs ONLY that helper check:
* `"in-process"` (default): keep today’s boot-time `verifyRlsAuthHelpers` and its clear startup error.
* `"deploy-time"`: skip it — the migration pipeline owns that guarantee, so a fresh (serverless) worker sends ZERO queries before the mutation transaction itself.
# authoritativeMutationPaths
> `const` **authoritativeMutationPaths**: readonly \[`"/api/mutations/unit"`]
Defined in: packages/server/src/mutations/route.ts:60
The authoritative (pessimistic) write endpoint (ADR-0022 §3): one atomic write-unit per POST.
# batchMutationPaths
> `const` **batchMutationPaths**: readonly \[`"/api/mutations"`]
Defined in: packages/server/src/mutations/route.ts:58
# DEFAULT_EVENT_CONSUMER_BATCH_SIZE
> `const` **DEFAULT\_EVENT\_CONSUMER\_BATCH\_SIZE**: `10` = `10`
Defined in: packages/server/src/events/consumer.ts:81
Max messages (each ONE single-stream sub-batch) a single read delivers, when the app sets nothing.
# DEFAULT_EVENT_DRAIN_BUDGET_MS
> `const` **DEFAULT\_EVENT\_DRAIN\_BUDGET\_MS**: `25000` = `25_000`
Defined in: packages/server/src/events/consumer.ts:106
The wall-clock budget one [EventConsumer.drainOnce](/api/server/interfaces/eventconsumer/#drainonce) pass gets, when the caller sets nothing.
**Set it under your platform’s invocation wall-clock cap**, with head-room for one callback: the budget is only ever checked BETWEEN sub-batches, so a pass can overrun it by however long the callback that was already running takes. 25 s suits the common serverless caps (Supabase Edge, Vercel, Cloud Run jobs); a platform with a tighter cap wants a tighter budget, and a generous one (a cron container) can raise it.
# DEFAULT_EVENT_MAX_ATTEMPTS
> `const` **DEFAULT\_EVENT\_MAX\_ATTEMPTS**: `5` = `5`
Defined in: packages/server/src/events/consumer.ts:91
Deliveries a sub-batch gets before a further callback failure dead-letters it, when the app sets nothing.
# DEFAULT_EVENT_POLL_CEILING_MS
> `const` **DEFAULT\_EVENT\_POLL\_CEILING\_MS**: `5000` = `5_000`
Defined in: packages/server/src/events/consumer.ts:95
The adaptive poll’s idle ceiling — the longest an idle stream ever waits between reads.
# DEFAULT_EVENT_POLL_FACTOR
> `const` **DEFAULT\_EVENT\_POLL\_FACTOR**: `2` = `2`
Defined in: packages/server/src/events/consumer.ts:97
How fast consecutive empty reads grow the wait from the floor toward the ceiling.
# DEFAULT_EVENT_POLL_FLOOR_MS
> `const` **DEFAULT\_EVENT\_POLL\_FLOOR\_MS**: `250` = `250`
Defined in: packages/server/src/events/consumer.ts:93
The adaptive poll’s floor — the wait after the FIRST empty read (ADR-0053 decision 7: \~250 ms).
# DEFAULT_EVENT_VISIBILITY_TIMEOUT_SECONDS
> `const` **DEFAULT\_EVENT\_VISIBILITY\_TIMEOUT\_SECONDS**: `60` = `60`
Defined in: packages/server/src/events/consumer.ts:89
How long a delivered sub-batch stays invisible, when the app sets nothing — and how long the runner extends it by on each renewal (it renews at half this). Healthy long processing is covered by renewal, so this is the REDELIVERY DELAY of a sub-batch whose callback threw and the crash-recovery bound, not a budget: it wants to sit above a single callback’s worst case (too short redelivers a callback that is merely slower than one renewal interval), never above the whole batch’s.
# EVENT_LANE_FINGERPRINT_PREFIX
> `const` **EVENT\_LANE\_FINGERPRINT\_PREFIX**: `"pgxsinkit:events:fp1:"` = `"pgxsinkit:events:fp1:"`
Defined in: packages/server/src/events/ddl.ts:29
Stamped into the emitted DDL so `--check` can detect drift between the registry and a committed migration.
# EVENT_QUEUE_UNAVAILABLE_RETRY_AFTER_SECONDS
> `const` **EVENT\_QUEUE\_UNAVAILABLE\_RETRY\_AFTER\_SECONDS**: `5` = `5`
Defined in: packages/server/src/events/route.ts:112
What a 503 asks the client to wait, in seconds. The client honours `Retry-After` over its own jittered backoff when it asks for longer, so this is a floor on the retry of a batch the queue could not take.
# operationsLogTable
> `const` **operationsLogTable**: `PgTableWithColumns`<{ `columns`: { `clientTimestampUs`: `PgBuildColumn`<`"operations_log"`, `PgBigInt64Builder`, { `data`: `bigint`; `dataType`: `"bigint int64"`; `driverParam`: `string`; `enumValues`: `undefined`; `generated`: `undefined`; `hasDefault`: `false`; `hasRuntimeDefault`: `false`; `identity`: `undefined`; `isAutoincrement`: `false`; `isPrimaryKey`: `false`; `name`: `string`; `notNull`: `false`; `tableName`: `"operations_log"`; }>; `createdAt`: `PgBuildColumn`<`"operations_log"`, `SetHasDefault`<`SetNotNull`<`PgTimestampBuilder`>>, { `data`: `Date`; `dataType`: `"object date"`; `driverParam`: `string`; `enumValues`: `undefined`; `generated`: `undefined`; `hasDefault`: `true`; `hasRuntimeDefault`: `false`; `identity`: `undefined`; `isAutoincrement`: `false`; `isPrimaryKey`: `false`; `name`: `string`; `notNull`: `true`; `tableName`: `"operations_log"`; }>; `entityKeyJson`: `PgBuildColumn`<`"operations_log"`, `PgJsonbBuilder`, { `data`: `unknown`; `dataType`: `"object json"`; `driverParam`: `unknown`; `enumValues`: `undefined`; `generated`: `undefined`; `hasDefault`: `false`; `hasRuntimeDefault`: `false`; `identity`: `undefined`; `isAutoincrement`: `false`; `isPrimaryKey`: `false`; `name`: `string`; `notNull`: `false`; `tableName`: `"operations_log"`; }>; `errorMessage`: `PgBuildColumn`<`"operations_log"`, `PgTextBuilder`<\[`string`, `...string[]`]>, { `data`: `string`; `dataType`: `"string"`; `driverParam`: `string`; `enumValues`: `undefined`; `generated`: `undefined`; `hasDefault`: `false`; `hasRuntimeDefault`: `false`; `identity`: `undefined`; `isAutoincrement`: `false`; `isPrimaryKey`: `false`; `name`: `string`; `notNull`: `false`; `tableName`: `"operations_log"`; }>; `httpStatus`: `PgBuildColumn`<`"operations_log"`, `PgIntegerBuilder`, { `data`: `number`; `dataType`: `"number int32"`; `driverParam`: `string` | `number`; `enumValues`: `undefined`; `generated`: `undefined`; `hasDefault`: `false`; `hasRuntimeDefault`: `false`; `identity`: `undefined`; `isAutoincrement`: `false`; `isPrimaryKey`: `false`; `name`: `string`; `notNull`: `false`; `tableName`: `"operations_log"`; }>; `id`: `PgBuildColumn`<`"operations_log"`, `SetIsPrimaryKey`<`PgBigSerial53Builder`>, { `data`: `number`; `dataType`: `"number int53"`; `driverParam`: `number`; `enumValues`: `undefined`; `generated`: `undefined`; `hasDefault`: `true`; `hasRuntimeDefault`: `false`; `identity`: `undefined`; `isAutoincrement`: `false`; `isPrimaryKey`: `false`; `name`: `string`; `notNull`: `true`; `tableName`: `"operations_log"`; }>; `mutationId`: `PgBuildColumn`<`"operations_log"`, `PgTextBuilder`<\[`string`, `...string[]`]>, { `data`: `string`; `dataType`: `"string"`; `driverParam`: `string`; `enumValues`: `undefined`; `generated`: `undefined`; `hasDefault`: `false`; `hasRuntimeDefault`: `false`; `identity`: `undefined`; `isAutoincrement`: `false`; `isPrimaryKey`: `false`; `name`: `string`; `notNull`: `false`; `tableName`: `"operations_log"`; }>; `mutationSeq`: `PgBuildColumn`<`"operations_log"`, `PgIntegerBuilder`, { `data`: `number`; `dataType`: `"number int32"`; `driverParam`: `string` | `number`; `enumValues`: `undefined`; `generated`: `undefined`; `hasDefault`: `false`; `hasRuntimeDefault`: `false`; `identity`: `undefined`; `isAutoincrement`: `false`; `isPrimaryKey`: `false`; `name`: `string`; `notNull`: `false`; `tableName`: `"operations_log"`; }>; `operationKind`: `PgBuildColumn`<`"operations_log"`, `PgVarcharBuilder`<\[`string`, `...string[]`]>, { `data`: `string`; `dataType`: `"string"`; `driverParam`: `string`; `enumValues`: `undefined`; `generated`: `undefined`; `hasDefault`: `false`; `hasRuntimeDefault`: `false`; `identity`: `undefined`; `isAutoincrement`: `false`; `isPrimaryKey`: `false`; `name`: `string`; `notNull`: `false`; `tableName`: `"operations_log"`; }>; `payloadJson`: `PgBuildColumn`<`"operations_log"`, `PgJsonbBuilder`, { `data`: `unknown`; `dataType`: `"object json"`; `driverParam`: `unknown`; `enumValues`: `undefined`; `generated`: `undefined`; `hasDefault`: `false`; `hasRuntimeDefault`: `false`; `identity`: `undefined`; `isAutoincrement`: `false`; `isPrimaryKey`: `false`; `name`: `string`; `notNull`: `false`; `tableName`: `"operations_log"`; }>; `requestPath`: `PgBuildColumn`<`"operations_log"`, `PgTextBuilder`<\[`string`, `...string[]`]>, { `data`: `string`; `dataType`: `"string"`; `driverParam`: `string`; `enumValues`: `undefined`; `generated`: `undefined`; `hasDefault`: `false`; `hasRuntimeDefault`: `false`; `identity`: `undefined`; `isAutoincrement`: `false`; `isPrimaryKey`: `false`; `name`: `string`; `notNull`: `false`; `tableName`: `"operations_log"`; }>; `serverTimestampUs`: `PgBuildColumn`<`"operations_log"`, `SetHasDefault`<`SetNotNull`<`PgBigInt64Builder`>>, { `data`: `bigint`; `dataType`: `"bigint int64"`; `driverParam`: `string`; `enumValues`: `undefined`; `generated`: `undefined`; `hasDefault`: `true`; `hasRuntimeDefault`: `false`; `identity`: `undefined`; `isAutoincrement`: `false`; `isPrimaryKey`: `false`; `name`: `string`; `notNull`: `true`; `tableName`: `"operations_log"`; }>; `status`: `PgBuildColumn`<`"operations_log"`, `SetNotNull`<`PgVarcharBuilder`<\[`string`, `...string[]`]>>, { `data`: `string`; `dataType`: `"string"`; `driverParam`: `string`; `enumValues`: `undefined`; `generated`: `undefined`; `hasDefault`: `false`; `hasRuntimeDefault`: `false`; `identity`: `undefined`; `isAutoincrement`: `false`; `isPrimaryKey`: `false`; `name`: `string`; `notNull`: `true`; `tableName`: `"operations_log"`; }>; `tableName`: `PgBuildColumn`<`"operations_log"`, `PgVarcharBuilder`<\[`string`, `...string[]`]>, { `data`: `string`; `dataType`: `"string"`; `driverParam`: `string`; `enumValues`: `undefined`; `generated`: `undefined`; `hasDefault`: `false`; `hasRuntimeDefault`: `false`; `identity`: `undefined`; `isAutoincrement`: `false`; `isPrimaryKey`: `false`; `name`: `string`; `notNull`: `false`; `tableName`: `"operations_log"`; }>; `userId`: `PgBuildColumn`<`"operations_log"`, `PgUUIDBuilder`, { `data`: `string`; `dataType`: `"string uuid"`; `driverParam`: `string`; `enumValues`: `undefined`; `generated`: `undefined`; `hasDefault`: `false`; `hasRuntimeDefault`: `false`; `identity`: `undefined`; `isAutoincrement`: `false`; `isPrimaryKey`: `false`; `name`: `string`; `notNull`: `false`; `tableName`: `"operations_log"`; }>; }; `dialect`: `"pg"`; `name`: `"operations_log"`; `schema`: `undefined`; }>
Defined in: packages/server/src/operations-log/schema.ts:5
# PGMQ_DEAD_LETTER_KEY
> `const` **PGMQ\_DEAD\_LETTER\_KEY**: `"__pgxsinkitDeadLetter"` = `"__pgxsinkitDeadLetter"`
Defined in: packages/server/src/events/pgmq-queue.ts:51
The reserved key the dead-letter reason is stamped under, INSIDE the archived message body.
Least surface, deliberately: pgmq’s archive is the dead-letter storage (no library-owned DLQ table), the archive row has no free column to carry a reason, and its `headers` column only exists on newer pgmq versions. So the reason rides one reserved key on the ARCHIVED body — the live queue row is never mutated, a normally-consumed message is byte-identical to what was enqueued, and the key is stripped again by [EventQueue.listDeadLetters](/api/server/interfaces/eventqueue/#listdeadletters) / [EventQueue.requeueDeadLetter](/api/server/interfaces/eventqueue/#requeuedeadletter) so a requeued message is exactly the message that was enqueued.
# Core concepts
> The pgxsinkit mental model in six short pages.
These six pages are the mental model. Read them in order — each builds on the last, and together they cover everything a fresh reader (human or AI) tends to get wrong.
1. [The two paths](/concepts/two-paths/) — read and write are separate and asymmetric.
2. [The write path](/concepts/write-path/) — stage locally, flush a batch, apply in the database.
3. [The read path](/concepts/read-path/) — shapes stream Postgres → Electric → PGlite, via a proxy.
4. [The Electric subquery requirement](/concepts/electric-subqueries/) — the mandatory flag, and why it fails closed.
5. [Timestamps](/concepts/timestamps/) — microsecond integers carried as decimal strings.
6. [Local schema & DDL parity](/concepts/local-schema-ddl-parity/) — what local PGlite does and does not replicate.
Once you have the model, the [Registry entry options](/concepts/registry-entry-options/) page is the field-by-field reference for configuring a sync table — every option, with a short example, what it achieves, and when to use it. And [Worker mode](/concepts/worker-mode/) covers the browser topology in which tabs attach through a SharedWorker and capability placement chooses a Safari SW-direct or elected Chromium/Firefox engine (`defineSyncWorker` / `attachSyncClient`) instead of the calling thread.
Not everything is sync state. [The event lane](/concepts/event-lane/) is the second lane beside the sync rail, for high-volume append-only facts that nothing ever reads back down: `appendEvent` into a local Outbox, flushed to an ingestion endpoint, delivered through a queue to a consumer callback you write.
The canonical vocabulary for all of these lives in the repository’s `CONTEXT.md`.
# The Electric subquery requirement
> The mandatory ElectricSQL feature flag, fail-closed behaviour, and the enum→text rule.
This is a **hard prerequisite**, not an optimisation — and because it lives in ElectricSQL’s configuration rather than in pgxsinkit’s code, it is easy to stand up a stack without it.
## The flag
[Section titled “The flag”](#the-flag)
pgxsinkit uses cross-table subquery `where` clauses for membership fan-out — a row in a container streams to every member of that container:
```sql
container_id IN (SELECT container_id FROM memberships WHERE member_id = )
```
The shape proxy forwards this verbatim as the ElectricSQL shape `where`, so it depends on a flagged preview capability:
```bash
# ElectricSQL >= 1.7 (subquery where is a flagged preview; the demo and tests pin the version below)
ELECTRIC_FEATURE_FLAGS=allow_subqueries,tagged_subqueries
```
Any deployment consuming pgxsinkit must run Electric with this flag. The repo’s `infra/compose` pins `electricsql/electric:1.7.7` and sets it.
## On managed Electric Cloud
[Section titled “On managed Electric Cloud”](#on-managed-electric-cloud)
The flag is a **server-side ElectricSQL setting**. On a **self-hosted** Electric (the repo’s compose, or your own container) you set `ELECTRIC_FEATURE_FLAGS` directly. On **managed Electric Cloud** (`api.electric-sql.cloud`) subqueries are a preview **activated per source by Electric staff on request** — there is no self-serve toggle yet — so a Cloud source rejects subquery `where`s until you ask Electric to enable it for you (e.g. via their Discord / support). ElectricSQL intends to make subqueries the **default** on Cloud; until then, request activation for your source.
The tell that you are on an **un-activated** Cloud source: subquery-free shapes sync fine, but any membership-filtered shape returns the `{"where":["Subqueries are not supported"]}` 400 below. (In the board demo this looks like: an admin — whose filter is all-rows, no subquery — works, while a normal member’s shapes 400.)
## It fails closed
[Section titled “It fails closed”](#it-fails-closed)
Without the flag, Electric rejects any subquery `where` with HTTP 400:
```json
{ "where": ["Subqueries are not supported"] }
```
The sync then fails **closed** — no rows stream. It never silently falls back to streaming unfiltered data. A blank client is the symptom of a missing flag; a data leak is not a failure mode here.
## Membership changes converge the local store — both ways, even offline
[Section titled “Membership changes converge the local store — both ways, even offline”](#membership-changes-converge-the-local-store--both-ways-even-offline)
The subquery is what makes a membership change *reactive*, in **both** directions, against the subject’s already-running shape — no re-subscribe:
* **Grant** — a new `memberships` row gives a subject access to a container. Electric re-evaluates the dependent shapes (a tagged *move-in*) and the toolkit **materialises** every newly-matched container row into that subject’s local store. This is the “add-member → the whole container appears” moment.
* **Revoke** — deleting the `memberships` row. Electric signals that the rows have left the shape (a tagged *move-out*) and the toolkit **evicts** them. A row reachable through a second, independent membership survives — it leaves only once its **last** grant is gone.
This convergence holds **live and across an offline gap**. A client following the shape applies the change at once; a client that was disconnected when the membership changed converges on **reconnect** — the resume from its persisted offset replays the change. So a revoked member’s container does not linger in their local store while they are offline (a security property, not only a UX one), and a newly-added member’s container appears the moment they are back.
The one thing that does **not** observe the delta is a fresh `offset=-1` snapshot of an existing handle: it is served from the handle’s materialised log and won’t reflect a source-table change that post-dates it. That is a probing artifact, not the running client’s path — observe convergence on the live subscription or a normal resume, never by re-fetching `offset=-1`. A toolkit consistency group ties the container’s tables to a shared LSN frontier, so the rows that move in or out this way commit together, with no broken-join flicker.
## The enum→text rule
[Section titled “The enum→text rule”](#the-enumtext-rule)
A second consequence of Electric’s where-grammar: a PostgreSQL `enum` column referenced in a shape `where` must be **cast to `text`**:
```sql
"role"::text = 'manager' -- supported
"role" = 'manager' -- rejected: invalid syntax for type enum
```
A literal cast to the enum type (`'manager'::role`) is also unsupported. Cast the **column** to text. The enum column itself stays an enum everywhere else — RLS and the write path keep using it natively, so there is no enum→text migration (which would in any case fail while an RLS policy depends on the column).
# The event lane
> A second lane beside the sync rail for high-volume, append-only client facts — appendEvent, the Outbox, the ingestion endpoint, a queue, and your consumer callback.
Some client data is not sync state at all. Token encounters, aid interactions, review grades, “the user opened this item” — thousands per session, append-only, never edited, never conflicting, and **nothing ever reads them back down**. Putting them on a synced table is wrong in three compounding ways: every client re-downloads its own log as shape rows, the conflict/overlay/versioning machinery taxes rows that can never conflict, and you inherit a retention lifecycle (trim windows, forwarder ordering) that exists only because of the transport choice.
Queue-shaped data gets a queue. The **event lane** is the toolkit’s second lane, beside the sync rail:
```plaintext
appendEvent() → Outbox (local) → flush → POST /api/events → queue → your consumer callback
```
pgxsinkit owns everything between the append on the client and the callback on the server. It is **not** a replacement for the write path — a change needing conflict resolution, an optimistic overlay, or an echo back down stays on [the write path](/concepts/write-path/). Nor is it general pub/sub: one registered consumer side per event stream, no fan-out subscriptions.
## Registering an event stream
[Section titled “Registering an event stream”](#registering-an-event-stream)
An **event stream** is a named, registered category of events sharing one payload schema and one consumer-side handling. It is registered on the sync registry you already have — the record key is the stream name:
```ts
import { defineEventStream, defineSyncRegistry } from "@pgxsinkit/contracts";
import { z } from "zod";
export const registry = defineSyncRegistry({
tables: { issue },
streams: {
issue_viewed: defineEventStream({
payload: z.object({ issueId: z.uuid() }).strict(),
identity: { viewerId: { claimPath: ["sub"] } },
}),
},
});
```
Three things to know before you write one:
* **Identity is stamped by the server, from verified claims.** `claimPath` is the same addressing managed fields use (`["sub"]`, `["app_metadata", "person_id"]`). The client’s envelope carries no identity at all, so put a viewer or actor id in `identity`, never in the payload — a payload is client-supplied and can lie.
* **Names are validated when the module evaluates**: lowercase `[a-z][a-z0-9_]*`, at most 30 characters. The bound comes from the queue name the stream is provisioned under, and it fails at definition rather than at deployment.
* **An object payload must be strict, and that is enforced.** `.strict()` (or `z.strictObject()`) on the root — and on every object in a union — or `defineSyncRegistry` throws. A stripping `z.object({…})` would drop a misspelled or newly-added key silently somewhere between your call site and your consumer, with no verdict anywhere, which is exactly the failure the lane’s validation exists to prevent. Any other root (a string, an array, a record, a transform pipeline) is accepted as written and follows ordinary parse semantics: what the schema accepts is what `appendEvent` validates, and the JSON-normalized form of what it produces is what your consumer receives.
### The schema validates at append; the authoritative parse is at ingest
[Section titled “The schema validates at append; the authoritative parse is at ingest”](#the-schema-validates-at-append-the-authoritative-parse-is-at-ingest)
`appendEvent` checks your payload against the registered schema and then stores **exactly what you passed**. That client-side run is a **validation**: its output is discarded, so the Outbox reads back as the fact your app staged. The **authoritative** parse happens server-side, at ingest, and the JSON-normalized form of *its* output is what is enqueued and what your consumer receives. (Storing the client’s parse output instead would hand the server an already-transformed value to re-parse — a terminal rejection for a perfectly valid append.)
The schema itself therefore **executes at both boundaries**, even though only one execution’s output is ever taken. Three consequences for a `.transform()`:
* **Transforms must be pure and deterministic.** Your callback runs once on the client and once on the server; only the server’s result is kept, and nothing reconciles the two. An effectful transform (writing a log, incrementing a counter) or an environment-dependent one (reading `Date.now()`, a locale, a random value) is unsupported.
* **The output must be JSON.** A transform producing something the JSON value domain cannot carry — a `BigInt`, `undefined` — makes that one event a terminal `rejected` verdict at ingest. Its siblings in the same batch are unaffected.
* **Your consumer receives the JSON-normalized output, not the object the transform returned.** Ingest enqueues the JSON round-trip of the parse result, so a `.transform(v => new Date(v))` delivers that date’s **ISO string**, and a nested `undefined` property is **dropped** (an `undefined` array member becomes `null`). Normalizing at the route is deliberate: every backend — the in-memory fake your unit tests use and the real `jsonb`-backed queue in production — then observes exactly the same value. If you want a rich type at the consumer, encode it in the payload and rebuild it there.
Registering a stream touches no synced table, no local schema and no apply function, so it never rebuilds a client’s read cache.
### Payload schemas may only evolve backward-compatibly
[Section titled “Payload schemas may only evolve backward-compatibly”](#payload-schemas-may-only-evolve-backward-compatibly)
A stream’s payload schema may change **only** in ways that keep accepting every previously-valid payload. Events written offline under the old schema are still sitting in someone’s Outbox, possibly for weeks. An incompatible change requires a **new stream name**. In-band payload versioning stays your practice; this rule is what makes it safe rather than customary.
The registry lock hashes each stream so a change shows up as a reviewable `risky` diff — but it hashes the payload as a **JSON Schema**, and JSON Schema cannot express a zod refinement or transform. Two incompatible `.refine()` thresholds therefore hash identically and the review gate never fires. That is what `revision` is for: bump it (any positive integer) whenever you change acceptance logic the hash cannot see.
```ts
issue_viewed: defineEventStream({
payload: z.object({ issueId: z.uuid() }).strict().refine(hasAccess),
identity: { viewerId: { claimPath: ["sub"] } },
revision: 2, // ← bumped with the refinement, so the lock diff surfaces it
});
```
It is the same obligation `rowFilter.revision` carries for a `customWhere` closure, for the same reason: a hash can only see what it can serialize.
The lock diff grades the three stream-level changes differently: **adding** a stream is `compatible` (it provisions a queue and nothing else), **changing** one’s payload or identity is `risky` (the change above), and **removing** one is `breaking` — clients still holding events for that name get `deferred` verdicts that will never clear, so a removal has to outlive every client that could still be appending to it.
## The Outbox
[Section titled “The Outbox”](#the-outbox)
`client.appendEvent(stream, payload)` validates the payload against the registered schema, stamps an `eventId` and `occurredAtUs`, and writes one row to the **Outbox** — a single durable, local-only table shared by every stream. It is never synced, never overlaid, never conflict-resolved.
```ts
await client.appendEvent("issue_viewed", { issueId });
```
The promise resolves on **durable local enqueue, not on delivery**. Appending never waits for the network, and an append made offline survives a reload and drains when connectivity returns. Four failures are possible, and all four are call-site bugs rather than runtime conditions: no streams registered, an unknown stream name, a payload the schema refuses, and an oversized payload. `appendEvent` is `async`, so each one **rejects the returned promise** with a typed error — `await` it (or attach a `.catch`) or the refusal surfaces as an unhandled rejection instead of at your call site.
The Outbox’s shape is public contract, not an internal detail — get the typed table with `getOutboxTable(registry)` — because apps legitimately compose pending events with down-synced aggregates into best-guess views.
### Watching it drain
[Section titled “Watching it drain”](#watching-it-drain)
```ts
const stop = client.onOutboxStatus(({ empty }) => setPending(!empty));
```
`onOutboxStatus` fires on the empty ↔ non-empty **transitions**, delivering the current state on subscribe (`await client.outboxStatus()` is the one-shot pull). It is the invalidation hook for those best-guess views: when the Outbox drains, the down-synced aggregate is authoritative again. It carries no count on purpose — a count that updates only on transitions is stale by construction, and a count that updates per append is a worse live query than the one you can write yourself against the Outbox table.
## Flushing, and what the server says back
[Section titled “Flushing, and what the server says back”](#flushing-and-what-the-server-says-back)
The flush loop assembles batches in append order across every stream and `POST`s them to `/api/events` — the mutation endpoint’s sibling, with the same auth-header and refresh-once behaviour. With an `autoSync` trigger installed it drives itself (an append nudges a pass, an interval catches retries, and boot or reconnect drains what was written offline); `client.flushEvents()` is the manual primitive for a host that drives everything itself.
Every well-formed event comes back with its own verdict:
| Verdict | Terminal? | What it means |
| ---------- | --------- | -------------------------------------------------------------------------------------- |
| `acked` | yes | Enqueued. The Outbox row is deleted. |
| `refused` | yes | Your server-side gate declined it (consent, entitlement). Row deleted. |
| `rejected` | yes | The server refused this event on a **known** stream (three causes below). Row deleted. |
| `deferred` | **no** | The server does not (yet) know this stream. The row stays and retries. |
`deferred` is the one to understand. A client deployed ahead of its server is ordinary rollout skew, not a bug — deleting those events would be data loss on a completely normal path. So they stay in the Outbox, retry with backoff, and drain the moment the server deploy lands. A burst of `deferred` right after a client release is the deploy order; a burst that never clears means the server’s registry is missing that stream.
`rejected` has exactly three causes, and each one names itself in the verdict’s `reason`:
1. **The payload failed the stream’s registered schema** — or the schema’s parse produced something JSON cannot carry (a `BigInt`, `undefined`), which is its own reason rather than a size complaint.
2. **The serialized payload exceeds the 64 KiB per-event limit.**
3. **A declared identity field could not be stamped** from the verified claims — the claim is absent, or `null`, or an object/array, or an empty/whitespace string. Identity is fail-closed: there is no partial stamp and no empty-string fallback, so the whole event is refused, naming the field and its claim path. A burst of these after an auth change means the issuer stopped minting that claim (or started minting it at a different path), not that the client is malformed.
Causes 1 and 2 should be rare enough to treat as a defect — the library validates both at append, so they mean a non-library caller or a broken deployment. Cause 3 the client cannot pre-empt at all: the envelope carries no identity, so only the server can discover it.
```ts
client.onEventLaneReport((report) => {
for (const verdict of report.terminal) log.warn("event dropped", verdict);
});
```
`onEventLaneReport` carries each pass’s terminal verdicts, its `deferred` ones, and the lane’s batch-level backoff transitions. `acked` is deliberately never reported — a high-volume lane would drown you in its own success. The subscription is **ephemeral**: nothing is retained, because a durable verdict table would be retention-bearing state for a debugging need, and with nothing subscribed the library logs each report at warn level rather than dropping it. Subscribe for the app’s lifetime, not per screen.
### Backpressure is honest: the Outbox is the buffer
[Section titled “Backpressure is honest: the Outbox is the buffer”](#backpressure-is-honest-the-outbox-is-the-buffer)
When the queue is unavailable the endpoint returns `503` with `Retry-After` and enqueues **nothing** — a batch is taken atomically or not at all, and the server never buffers on the queue’s behalf. The client has exactly two retry classes: retryable (network, 5xx, 408/425/429 — jittered exponential backoff with a ceiling, honouring `Retry-After`, paused while offline) and auth (refresh once, then retryable).
There is **no attempt cap and no client-side quarantine**. A row leaves the Outbox only on a server-issued verdict — that is what at-least-once means on this edge. The Outbox is designed to hold offline weeks, so a failing lane presents as a growing Outbox backing off observably, never as silently discarded events.
### Tuning the flush (client config, never the registry)
[Section titled “Tuning the flush (client config, never the registry)”](#tuning-the-flush-client-config-never-the-registry)
Cadence and batching live on the client (`createSyncClient` / `defineSyncWorker`), deliberately not on the registry: the registry is the contract, and a batch-size tweak must not surface as a registry diff.
```ts
const client = await createSyncClient({
registry,
// …
events: {
batchSize: 200, // default 200, clamped to the wire limit of 1000
intervalMs: 5_000, // default: the FALLBACK trigger (appends nudge a pass; boot/reconnect run one)
backoff: { baseMs: 1_000, ceilingMs: 300_000 }, // defaults: 1s first retry, 5min ceiling
streams: {
issue_viewed: { batchSize: 50 }, // fairness: this stream may take at most 50 slots of any batch
},
},
});
```
* **`batchSize`** caps events per flush request; the server enforces the toolkit’s 1000-per-batch limit independently, so a higher value is clamped rather than honoured (it would only ever produce `413`s).
* **`intervalMs`** is the fallback trigger, not the primary one. An append nudges a pass, and boot or reconnect runs one, so the interval only has to catch retries and recovery — hence a deliberately slower default than the convergence driver’s.
* **`backoff`** is jittered exponential (equal jitter around the doubling ceiling, so a recovering fleet does not stampede) and applies both to rows the server `deferred` and to batch-level faults.
* **A per-stream `batchSize` is a fairness knob, and it is applied in SQL.** One batch is a mixed slice ordered by append sequence across every stream, so without a cap a chatty stream’s backlog occupies every slot and a quieter stream stays invisible until that backlog drains. The cap ranks each stream’s eligible rows and drops the over-cap ones *before* the global ordering, so the slots it frees genuinely go to other streams instead of just shrinking the request. A per-stream `backoff` is available for the same reason (a stream mid-rollout may want a shorter retry); an unset bound inherits the lane-wide one.
**Nonsense tuning fails at construction, not at runtime.** Every value is validated when the client is built, and every offender is named in one error. That is deliberate: the failure mode of a typo here is silence, not an exception — a per-stream `batchSize` of `0` would hold every row of that stream back forever while appends kept piling up, with no request and no report to show for it. Counts must be integers ≥ 1, and a backoff bound is judged on the **resolved pair**, with defaults (and, per stream, the lane-wide tuning) filled in first: `{ baseMs: 600_000 }` on its own is refused, because the omitted ceiling takes the 300,000 ms default and every delay would be clamped below the base that was asked for. The error names both numbers and where each came from.
## Consuming: the delivery contract
[Section titled “Consuming: the delivery contract”](#consuming-the-delivery-contract)
The endpoint splits a mixed batch by stream (preserving order), stamps each event’s identity from the verified claims, and enqueues each single-stream sub-batch as one queue message. Your callback receives those stamped envelopes:
```ts
const consumer = defineEventConsumer({
registry,
queue: createPgmqEventQueue({ db }),
callback: async ({ stream, events }) => {
await db.transaction(async (tx) => {
await tx.insert(viewArchive).values(events.map(toRow)).onConflictDoNothing({ target: viewArchive.eventId });
});
},
});
consumer.start();
```
Three properties are the whole contract:
1. **At-least-once.** Your callback **must** be idempotent. The blessed pattern is deduping on `eventId` against your own durable store, which composes at-least-once delivery into effectively-exactly-once handling. Returning acks the sub-batch; throwing retries it.
2. **Order is batch-internal only.** Events arrive in append order *within* one delivered sub-batch. Across sub-batches there is no promise: a retried sub-batch is redelivered after its successors, and concurrency processes sub-batches in parallel. Promising inter-batch order would force concurrency of one and block-the-stream-on-retry — the head-of-line blocking at-least-once queues exist to avoid. If you need temporal order, re-sort from your own archive on `occurredAtUs`.
3. **Identity is what the server stamped**, carried through the queue untouched. This is the point where a client-supplied event becomes an attributed fact, which is why its shape is contract-defined.
The runner is a **long-lived process** you host — deliberately separate from the server’s serverless posture, so do not deploy it as a per-request function. Pacing, visibility renewal and retry are internal to it: while it is working through a read it renews the lease on every message of that read (the one in flight and the ones queued behind it), so `visibilityTimeoutSeconds` is not a budget for the whole batch. It is the **redelivery delay of a sub-batch whose callback threw** — a throw stops the renewals immediately and the lapse is the retry pacing — and the bound on how long a crashed runner’s messages stay stuck. Size it above one callback’s worst case, not the batch’s. One runner can host many streams (each an independent loop), so small deployments run one process and large ones split streams across processes. After a configurable number of attempts a sub-batch dead-letters into the backend’s own archive, with a hook plus an unconditional warning so it is never silent. Requeueing from the archive is always a deliberate act.
Where you have no process to host — a managed backend whose only compute is per-request functions — the same handle answers a **bounded drain** instead: `drainOnce({ budgetMs })` makes one pass across every stream until they all read empty or the budget is spent, and reports `{ delivered, deadLettered, empty }`. Nothing about the contract above changes; it is the same internals called differently, which is possible precisely because pacing was never contract. Host it as a **scheduled invocation** (that schedule is the delivery guarantee) and, optionally, nudge it from ingest with `createSyncServer({ onEventsEnqueued })` so an interactive append drains at once rather than at the next tick — a fire-and-forget hook whose loss costs latency only. Overlapping invocations are safe: the visibility timeout arbitrates concurrent drainers the same way it arbitrates concurrent runners. The long-lived runner stays the primary mode wherever one can run.
Operationally there are two prerequisites, both covered in [Deploying the server](/start/deploying-the-server/): the queues are **deploy-time DDL** (the endpoint may enqueue long before any runner first starts), and the ingestion route mounts itself once your registry declares streams.
## Limits
[Section titled “Limits”](#limits)
Toolkit-level constants, enforced by the server independently of any client tuning, and clamped by the client’s own batching:
* **1000 events** per flush batch,
* **64 KiB** serialized payload per event — events are facts, not documents; a payload near this is a modelling smell,
* **4 MiB** per request body.
A batch-count or body violation is a `413`; a single oversized payload is a per-event `rejected` (and `appendEvent` refuses it at the call site, so a library caller fails long before that).
## The Outbox in the store lifecycle
[Section titled “The Outbox in the store lifecycle”](#the-outbox-in-the-store-lifecycle)
The Outbox is durable, library-owned state, so every lifecycle surface takes a position on it:
* **`destroy()` refuses** while the Outbox is non-empty, exactly as it refuses on owed mutations. The refusal names which of the two blocked it; `{ force: true }` remains the escape hatch.
* **`dropReadCache()` never touches it** — it is not read cache.
* **Backups and diagnostic dumps include it**; the portable data export (synced tables only) excludes it.
* **A restore does NOT quarantine restored Outbox rows** — the deliberate asymmetry with the mutation journal, which does. Restored events resume flushing normally, because event delivery is idempotent end-to-end by design, and mutation replay is not.
The full rationale, and the alternatives that were rejected, are in ADR-0053 (see [Design decisions](/decisions/)).
# Export & restore
> Get the local store out — a lossless backup, a support dump, or portable SQL — and boot a fresh client from a backup.
The sync engine holds a real database in the browser: your synced read cache, the optimistic overlay, and the mutation journal. Three purpose-named exports let you get that data **out**, and a restore boots a fresh client back **in** from a backup. They matter most in worker mode, where `client.pglite` is deliberately unreachable — these methods are the only supported door to the store.
Every export resolves to `{ file, report }`: a named `File` you can download or persist, and a structured `report` carrying phase timings and a snapshot of the mutation journal at export time.
## Which export to use
[Section titled “Which export to use”](#which-export-to-use)
Pick by what you need the artefact **for** — the three differ in format, fidelity, and where they load.
### Store backup — `exportStore()`
[Section titled “Store backup — exportStore()”](#store-backup--exportstore)
A full-fidelity, PGlite-restorable tarball of the **whole** local store: synced cache, overlay, and the mutation journal, unflushed writes included.
```ts
const { file, report } = await client.exportStore();
// file: -.pgdata.tar.gz (application/x-gzip)
```
* **Lossless.** The journal travels *inside* the artefact, so nothing staged is dropped.
* **Offline-safe.** It never blocks and needs no network. It is the **only** lossless export a device with unflushed writes can take while offline.
* **PGlite-only.** It restores into a pgxsinkit client via [`restoreFrom`](#restore), not into a general-purpose Postgres.
Use it for device backup and migration — carry a user’s whole local store to a new device without losing work in flight. Pass `{ compression: "none" }` for an uncompressed tar, or `{ fileName }` to override the generated name.
### Diagnostic dump — `exportDiagnostics()`
[Section titled “Diagnostic dump — exportDiagnostics()”](#diagnostic-dump--exportdiagnostics)
Human-readable SQL of **everything** the store holds — synced tables, the overlay and journal, the read-model views, the reconcile machinery, and the engine’s own metadata — exactly as the store holds it.
```ts
const { file } = await client.exportDiagnostics();
// file: --diagnostics.sql (application/sql)
```
Use it as **support evidence**: attach it to a bug report so someone can read a misbehaving store as-is, unflushed writes and all. It is evidence to read, not an artefact to restore from.
### Data export — `exportData()`
[Section titled “Data export — exportData()”](#data-export--exportdata)
The **portable** artefact: the synced tables and the enum types they depend on — schema and data, and nothing of pgxsinkit’s machinery — as SQL that loads into a vanilla Postgres (`psql -f`).
```ts
const { file, report } = await client.exportData();
// file: --data.sql (application/sql)
```
Use it for **data portability** — hand the synced data to a plain Postgres, free of overlay, journal, views, and reconcile functions.
Unlike the other two, `exportData` **guards the journal**. A portable dump reflects only synced rows, so an unflushed write would silently vanish from it — including an acknowledged write whose synced echo has not yet landed (it still lives only in the overlay). To avoid quietly losing work, `exportData` requires a **drained** journal:
* On a **clean** journal it exports immediately.
* On a **dirty** journal it flushes what it can and waits for convergence, bounded by `drainJournal: { timeoutMs }` (drain is on by default).
* A journal in a state that cannot drain — `failed`, `quarantined`, or `conflicted` — **fails fast** with a `DataExportDrainError` carrying the diagnostics, rather than waiting out a timeout it cannot beat.
The escape hatch is explicit:
```ts
// Export the synced state as-is; unflushed local writes are omitted.
const { file, report } = await client.exportData({ drainJournal: false });
// report.escapeHatch === true records that the drain was skipped.
```
An offline device with a clean journal exports strictly and instantly. An offline device with a **dirty** journal cannot produce a strict data export — its lossless option is the store backup.
## One at a time
[Section titled “One at a time”](#one-at-a-time)
The three exports and the destructive lifecycle operations (`destroy`, `discardEphemeral`, `dropReadCache`) all serialise through a single slot, so a wipe or rebuild can never interleave a running export and corrupt the artefact. A second lifecycle operation attempted while one is in flight rejects immediately with a typed `LifecycleBusyError` (naming what it collided with) rather than queueing — a fresh artefact is better served by retrying once the first settles. Exports wait out a boot rather than rejecting during one, so you can call them straight after construction.
## Restore
[Section titled “Restore”](#restore)
Boot a brand-new client on a store backup by passing the backup file to `restoreFrom`:
```ts
const client = await createSyncClient({
registry,
electricUrl,
batchWriteUrl,
storePath: "my-app-store",
restoreFrom: backupFile, // a File or Blob from exportStore()
});
```
Four rules keep a restore safe:
* **Fresh target across both backends.** Restore boots a new store; it never overlays a live one. The target check covers both IndexedDB and the OPFS commitment/store namespace, even if the current engine would choose only one of them. Any existing authority raises `RestoreTargetExistsError`. Destroy the existing store first, then restore into the now-empty path.
* **Online iff the recovered journal is clean.** If journal recovery finds **nothing to quarantine** — an empty recovered journal, e.g. a server-generated bootstrap artifact — the restore boots **online** and resumes sync straight away (honouring `syncEnabled`/`autoSync`, exactly like a normal boot). If the backup carried unflushed writes, the restore boots **offline** so nothing flushes before you have inspected what came back. An explicit `syncEnabled: false` keeps any restore offline.
* **Recovered journal is quarantined.** Every unflushed write recovered from the backup is moved to `quarantined`, never auto-flushed: the write path has no mutation dedupe, so blindly replaying a recovered write is not safe.
* **You decide, then go online.** When there ARE quarantined writes, inspect `client.diagnostics()`, then for each one either discard it (`discardQuarantined`) and re-author the edit, or handle it as your app sees fit. Going back online is the ordinary read path resuming — reconstruct the client without `restoreFrom` on the next boot — not a special catch-up mode. (A clean-journal restore skips this step entirely: it is already online.)
In worker mode, `client.destroy()` is supervised rather than a normal RPC: it refuses peer tabs with `StoreDestroyRefusedError`, refuses owed mutations unless `{ force: true }`, closes the engine, and deletes both backend namespaces through a resumable `deleting` phase. A crash resumes that deletion on the next boot.
## A note on ephemeral data
[Section titled “A note on ephemeral data”](#a-note-on-ephemeral-data)
Tables you declare with `retention: "ephemeral"` live as temporary (`pg_temp`) objects. **`pg_dump` ignores temporary objects**, so ephemeral rows never appear in a diagnostic dump or a data export — the two SQL artefacts.
The store backup is a datadir tarball rather than a `pg_dump`, so it is worth being precise about it: measured against a live backup, ephemeral **row data does not spill into the tarball**, and an ephemeral table is not visible in a store restored from it (a temporary relation is session-scoped and cannot be resolved by a fresh session). If you use ephemeral retention to keep sensitive rows off durable storage, that intent holds across all three exports.
For the exact contracts these methods honour, see the [design decisions](/decisions/) (ADR-0035 for the exports and restore; ADR-0036 for the store path).
# Local schema & DDL parity
> What the generated local PGlite schema replicates from Postgres, what it never will, and what it might.
The client runs a local PGlite database whose schema is **generated** from your sync registry. It is a **read cache plus write-staging buffer** — not a mirror of your Postgres schema. This page is precise about what it does and does not replicate.
The governing fact behind everything below: **the client only ever holds a filtered subset of rows** (whatever the shapes stream down), and **the server is always the integrity and security authority**.
## What the local schema generates today
[Section titled “What the local schema generates today”](#what-the-local-schema-generates-today)
From the registry, `generateLocalSchemaSql` emits:
* **Enum types** — `CREATE TYPE … AS ENUM` for every enum on a projected column. (You do **not** hand-provide enums; they are automatic. The `prepareLocalDbBeforeSchema` hook is only for *non-enum* prerequisite objects.)
* **The synced table** — its projected columns, their types (including arrays), `NOT NULL`, and the primary key (single or composite).
* **For writable tables:** the [overlay](/concepts/write-path/) table, the mutation journal + its sequence and indexes, a **reconcile trigger + function** that clears overlay/journal rows when the sync echo arrives, and a **read-model view** that unions the overlay over the synced row.
That is the whole of it. In particular, the synced table carries **no defaults, no constraints beyond the primary key, and no foreign keys** today.
## Never local — server authority, by nature
[Section titled “Never local — server authority, by nature”](#never-local--server-authority-by-nature)
These belong to the server and will not be replicated locally; doing so would be redundant at best and divergent at worst:
* **Row-level security, policies, and governance enforcement.** Security is asserted when a write reaches Postgres — see [The write path](/concepts/write-path/).
* **Triggers, functions, and materialized views** (other than the client’s own reconcile trigger and read-model view).
* **Managed-field values** (e.g. owner via `authClaim` at claimPath `["sub"]`, `created_at_us`/`updated_at_us` via `nowMicroseconds`). These are deliberately assigned by the database, not defaulted locally — their server-side DEFAULTs call `public.pgxsinkit_clock_us()`, a server-only function never rendered into the local PGlite DDL (registry defaults aren’t emitted locally at all).
## Not yet local — gaps we intend to narrow
[Section titled “Not yet local — gaps we intend to narrow”](#not-yet-local--gaps-we-intend-to-narrow)
These are currently omitted but are **fidelity gaps**, not principles. The intent is to narrow them over time on a **best-effort basis against the synced subset** — catching obvious violations before a flush, while the server stays authoritative:
* **Static (non-managed) column defaults** — could prefill a staged row to match what the server would produce.
* **CHECK constraints** and **generated columns** — single-row and locally computable, so they could validate or derive before flush.
* **FOREIGN KEY** and **UNIQUE** — only ever enforceable against the rows the client holds (the parent may be unsynced; uniqueness is unknowable across a partial dataset), so any local form is explicitly best-effort and never a substitute for the server check.
Until then, validate user input on the client (e.g. with Zod) and rely on the server to reject what PGlite would not.
## Practical implications
[Section titled “Practical implications”](#practical-implications)
* Don’t rely on a local default, CHECK, FK, or UNIQUE firing **today** — they aren’t emitted yet.
* Because the local schema has **no foreign keys**, you never need `deferrableConstraints` for the *local* apply — even when a child and its parent sync in one consistency group. `deferrableConstraints` governs only the **server** apply, where the FKs are real and a batch may stage a parent and child together.
* Enums *are* created locally; other prerequisite objects go through `prepareLocalDbBeforeSchema`.
* Treat the server as the only place integrity and security are guaranteed; the local schema exists to serve fast offline reads and to stage optimistic writes.
# Local store lifecycle
> Reset or delete local stores deliberately — drop the read cache, destroy a running store supervised, or destroy a dead store's artifacts by path.
The sync engine keeps a real database in the browser. Three purpose-named levers reset or delete it, and they are **not interchangeable** — each answers a different question. Pick by what you want to keep and whether the store is running.
## Which lever to use
[Section titled “Which lever to use”](#which-lever-to-use)
| You want to… | Store state | Use |
| -------------------------------------------------------- | --------------- | ----------------------------- |
| Keep the store, drop synced rows, and resync in place | running | `client.dropReadCache()` |
| Delete this store entirely, from a client attached to it | running | `client.destroy()` |
| Delete a store nobody is attached to, by path | **not** running | `destroyStoreArtifacts(path)` |
### `client.dropReadCache()` — resync in place
[Section titled “client.dropReadCache() — resync in place”](#clientdropreadcache--resync-in-place)
Clears the synced read cache and re-fills it from the server; the store, its schema, the optimistic overlay, and the mutation journal survive. This is the “my synced data looks wrong, start the read path over” lever — nothing local-only is lost. It serialises through the same lifecycle slot as exports, so it can never interleave a running backup.
### `client.destroy()` — supervised destruction of a running store
[Section titled “client.destroy() — supervised destruction of a running store”](#clientdestroy--supervised-destruction-of-a-running-store)
The attached client’s own destroy: it first asks the worker for the attached-tab count and **refuses with `StoreDestroyRefusedError` while peer tabs hold the store**, checks the journal for owed mutations (refused unless `force`), quiesces the engine (SW-direct teardown is acknowledged before anything is deleted), then removes every artifact. Use it for “delete my data” flows initiated from a signed-in, attached client.
### `destroyStoreArtifacts(storePath)` — a dead store, by path
[Section titled “destroyStoreArtifacts(storePath) — a dead store, by path”](#destroystoreartifactsstorepath--a-dead-store-by-path)
The path-addressed companion for stores **nobody is attached to**: obsolete paths a preference change left behind, wipe flows enumerating known paths, cleanup of stores whose client is long gone. It removes the full artifact set — the OPFS store directory, the commitment sentinel, the meta record, **and** the IndexedDB database — backend-agnostic (delete-if-present on both), with a bounded retry around the OPFS delete for VFS ownership-lock lag.
Its precondition is documented, not probed: called on a path a live engine still holds, the delete throws the ownership error after the bounded retry — loud, and **safely re-runnable** (the sequence is idempotent and phase-recorded; a store marked `deleting` is refused for boot, so a re-run completes the destruction). Keep failed paths on your own retry list and try again next boot. An idb-only sweep of `indexedDB.databases()` is **not** a substitute — it leaks the OPFS arena, which is where the bulk of an opfs-backed store lives.
### `quiesceStoreWorker(worker)` — by-path teardown, the destroy companion
[Section titled “quiesceStoreWorker(worker) — by-path teardown, the destroy companion”](#quiescestoreworkerworker--by-path-teardown-the-destroy-companion)
`destroyStoreArtifacts`’ “not running” precondition is documented, not probed — so on a backend that keeps its connection held while its worker lives, you must MAKE the store not-running first. `quiesceStoreWorker(worker, opts?)` is that lever: give it a worker factory of the same shape `attachSyncClient` takes (`() => new SharedWorker(url, { name: storePath })` — the library stays DOM-free), and it reaches the store’s SharedWorker by name, posts the storage declaration, queries placement, and tears the engine home down **by path**. For an SW-direct home (idbfs, real-Safari opfs) it sends `engine-teardown` and **awaits** the reserved ack the host posts only after it has stopped the engine and released the backend connection — resolving `{ engineHome, toreDown: true }`. For an elected home it resolves `{ engineHome, toreDown: false }` and sends nothing: the elected dedicated engine dies with its owning tab, so its store is already released.
Why it matters is a backend split. OPFS releases its sync-access handles when the engine goes idle, so an obsolete opfs path is deletable soon after its last document leaves. **idbfs does not** — PGlite holds its IndexedDB connection for the engine’s whole life, and the board’s workers are `extendedLifetime` (they outlive their spawning document), so an idbfs predecessor keeps `deleteDatabase` `blocked` across a reload until the browser reaps the worker. Quiescing it first releases the connection so the very next destroy wins.
Compose the two, best-effort:
```ts
await quiesceStoreWorker(() => new SharedWorker(url, { name: storePath })).catch(() => {});
await destroyStoreArtifacts(storePath);
```
The `.catch` is deliberate: a quiesce timeout is **not** proof of teardown (the promise rejects on a `timeoutMs` deadline, default 6s), and it must not abort the destroy — `destroyStoreArtifacts`’ own ownership-lag retry then reports honestly, leaving a still-held path on your retry list for next boot. Omit `storage` (no opinion) so a worker bound to an older declaration is never refused. It is idempotent and safe on an already-dead store (a fresh spawn boots no engine; its teardown closes an empty host), so call it unconditionally on every obsolete path — see ADR-0050.
## The preference-change pattern: fresh path + background destruction
[Section titled “The preference-change pattern: fresh path + background destruction”](#the-preference-change-pattern-fresh-path--background-destruction)
A store’s [storage declaration](/concepts/worker-mode/#the-storage-declaration-on-the-wire-adr-0050) (backend, durability) is **immutable** — bound at first contact, refused on conflict (ADR-0050). So a runtime storage toggle never re-homes an existing store. The pattern, as the board demo implements it:
1. **Obsolete first, atomically.** Under your cross-tab lock, drop every store binding and record the dropped **exact store paths** on an obsolete list. This runs *before* the new preference is written, so an interruption leaves dropped bindings under the old preference — never old paths bound under the new one.
2. **Write the preference and reload.** The fresh boot mints new stores under fresh random paths; they bind the new declaration on first contact.
3. **Quiesce-then-destroy obsolete paths in the background.** At each boot, walk the obsolete list and, per path, **tear the store’s worker down** with `quiesceStoreWorker` (best-effort) before `destroyStoreArtifacts` — fire-and-forget, never awaited on the sign-in path. The teardown step is what makes idbfs converge: an `extendedLifetime` idbfs predecessor holds its IndexedDB connection across the reload, so a bare destroy would sit `blocked` forever; quiescing releases the connection so the destroy wins immediately. A path still held after a failed quiesce simply **stays listed** for the next boot’s retry; the list itself is the resume state. No retirement barrier — the only thing said to the old worker is the by-path `engine-teardown`, and even that is best-effort.
Back up before you purge: [`exportStore()`](/concepts/export-and-restore/) produces the lossless tarball a later `restoreFrom` boot can seed a fresh store from — including a store you are about to obsolete.
# Mutation status
> Render a global sync indicator with one subscription — client.mutations and the React hooks.
Every local write lands in a per-table **mutation journal** on its way to the server (see [the write path](/concepts/write-path/)). To show a global “syncing…” indicator, a pending-changes badge, or a diagnostics screen, you rarely want to reach into each table’s journal by hand. pgxsinkit gives you **one registry-wide surface** over every writable journal: `client.mutations`.
## One subscription, not one per table
[Section titled “One subscription, not one per table”](#one-subscription-not-one-per-table)
A naive sync indicator opens one live query per writable table. On a registry with a dozen writable tables that is a dozen registrations competing for the same database thread at startup — the exact fan-out this API removes. Instead, mount **one** summary subscription:
```ts
const handle = await client.mutations.subscribeSummary((summary) => {
// Fires on every change. `summary` carries per-status counts plus derived totals.
setUnsettled(summary.unsettledCount);
});
setUnsettled(handle.initial.unsettledCount); // the current value, delivered on the handle
// later:
handle.unsubscribe();
```
The summary is cheap enough to mount **permanently** for the life of the app. Detail lists are the route- or feature-scoped counterpart — mount them where a diagnostics panel is open.
## The summary shape
[Section titled “The summary shape”](#the-summary-shape)
`summary()` (one-shot) and `subscribeSummary()` (live) both fold to a `MutationSummary`:
| Field | Meaning |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `pendingCount` | staged locally, not yet sent |
| `sendingCount` | in flight to the server |
| `ackedCount` | acknowledged; awaiting the synced echo to reconcile away |
| `failedCount` | transient failure; will be retried |
| `conflictedCount` | a stale write the server declined — your optimistic edit is kept, resolve it as a new write |
| `rejectedCount` | a business rejection; the optimistic edit was auto-discarded |
| `quarantinedCount` | parked from automatic processing because replay is unsafe (a restored journal) or the server permanently rejected it — your optimistic edit is KEPT; inspect and `discardQuarantined` (then re-author) |
| `unsettledCount` | `pending + sending + failed + conflicted + quarantined` — every write still needing work or user action |
| `settledCount` | `acked + rejected` — the truly-done complement |
`unsettledCount` is the number to drive a “you have unsynced changes” indicator, and it **includes `quarantined` and `conflicted`**. Both are terminal in the journal’s automatic state machine, but from your (and your user’s) standpoint they are NOT done: the optimistic edit is kept, later writes for that entity stay blocked, `destroy()` refuses them without `force`, and the user must explicitly resolve them (`discardConflict` / `discardQuarantined`, then re-author). This matters especially after a **restore**, where pgxsinkit deliberately quarantines recovered writes for the user to resolve — a global indicator must surface them, not hide them. The field is `settledCount` (not “terminalCount”) precisely because “terminal” is the state-machine word and quarantine is terminal there while unsettled here.
## Detail lists
[Section titled “Detail lists”](#detail-lists)
For a table of individual writes — a diagnostics drawer, a per-entity status — use `list()` (one-shot) or `subscribe()` (live). Both return normalized rows carrying the table key and the parsed entity key, filtered and ordered newest-first:
```ts
const handle = await client.mutations.subscribe(
{ table: "todos", statuses: ["pending", "failed"], limit: 50 },
(rows) => setRows(rows),
);
setRows(handle.initial);
```
Filters are all optional: `table`, `entityKey`, `statuses`, and `limit`. Rows are ordered by when they were enqueued (newest first).
## React hooks
[Section titled “React hooks”](#react-hooks)
`createSyncClientHooks` returns two hooks for these surfaces. There is no `hydrating` flag — the journal is local and never network-hydrated, so results are available as soon as the store is open.
```tsx
const { useMutationSummary, useMutationList } = createSyncClientHooks();
function SyncIndicator() {
const { summary } = useMutationSummary();
if (summary.unsettledCount === 0) return null;
return {summary.unsettledCount} unsynced;
}
function PendingWrites() {
const { rows, loading } = useMutationList({ statuses: ["pending", "failed"] });
if (loading) return ;
return (
{rows.map((r) => (
-
{r.tableName} · {r.status}
))}
);
}
```
`useMutationSummary` is the one to reach for by default: mount it once, high in the tree, for a global indicator. `useMutationList` is for the scoped, detailed view.
## Worker mode
[Section titled “Worker mode”](#worker-mode)
Both surfaces behave identically on the worker-attached client — the queries run in the selected engine home and stream back over the same bridge the read hooks use. You never touch the generated journal relation names in either mode.
# The read path
> Shapes stream Postgres → Electric → PGlite, through an ownership-enforcing proxy.
The read path streams rows from Postgres **through ElectricSQL** to the client and keeps local PGlite up to date — nothing goes from Postgres to the client directly. The app reads exclusively from PGlite; it never queries Postgres or Electric directly at read time.
## The flow
[Section titled “The flow”](#the-flow)
```plaintext
PostgreSQL → ElectricSQL → shape proxy → PGlite (local)
```
1. **Shapes** define what a client may see — a table plus a `where` filter. Filters can be cross-table subqueries, e.g. membership fan-out where a container row streams to every member:
```sql
container_id IN (SELECT container_id FROM memberships WHERE member_id = )
```
You do not hand-write that. Membership is one of the shipped policy families, and every family ships a read-path mirror that **generates exactly the predicate above** from the very same columns object you hand the policy builder — one declaration, both engines, so a rename or a typo can never leave a row writable but unreadable:
```ts
import { buildMembershipShapeWhere } from "@pgxsinkit/contracts";
// The same object `buildSupabaseMembershipNativePolicies(…)` takes; write-only fields are ignored.
const membership = {
containerColumn: widgets.containerId,
membershipTable: memberships,
membershipContainerColumn: memberships.containerId,
membershipSubjectColumn: memberships.memberId,
};
const widgetsReadFilter = (claims) => buildMembershipShapeWhere(membership, claims);
```
It renders the `IN (subquery)` form above with the subject bound as a param, and denies with `DENY_ALL` when the claims carry no subject.
Reach for a hand-built predicate only past the shipped families — and then use the typed Drizzle helpers, never a string: `c()` for each (bare) column, the table object for the `FROM`, and the subject as a **bound param** (so a quote in the value can’t inject the predicate).
```ts
import { c, DENY_ALL } from "@pgxsinkit/contracts";
import { sql, type SQL } from "drizzle-orm";
const memberContainers = (subject: string): SQL =>
sql`select ${c(memberships.containerId)} from ${memberships} where ${c(memberships.memberId)} = ${subject}`;
const widgetsReadFilter = (claims) =>
claims.sub ? sql`${c(widgets.containerId)} in (${memberContainers(claims.sub)})` : DENY_ALL;
```
The subquery must be **self-contained** (not correlated). See [Authoring a registry → cross-table filters](/start/getting-started/) for the full pattern and the `null` (no filter) vs `DENY_ALL` (no rows) trap.
2. **ElectricSQL** turns each shape into a live stream from Postgres.
3. **The shape proxy** (`proxyElectricShapeRequest`, served by the pgxsinkit server — `createSyncServer` mounts it at `/api/shape` by default, but the path is yours to choose) forwards shape requests to Electric and **enforces owner filtering** for protected tables unless the caller is an admin. In the real path, clients talk to the proxy, not to Electric directly.
4. **PGlite** subscribes through `@pgxsinkit/client`’s internal Electric ingest engine (`src/sync/`, ADR-0009) and applies the stream into local tables. The app reads from there.
## The proxy is the gateway
[Section titled “The proxy is the gateway”](#the-proxy-is-the-gateway)
Reads do not hit Electric directly in a deployed system — they go through the shape proxy, which is where ownership is enforced. Treat synced tables in PGlite as **replication targets**: they are written by this path and must never be mutated by application code (writes go through [the write path](/concepts/write-path/)).
## Reading from the local store
[Section titled “Reading from the local store”](#reading-from-the-local-store)
The app reads through the client’s guarded query — never hand-written SQL. For a **pure-Drizzle** read, pass the builder callback directly to `client.query((c) => …)`: pgxsinkit scans the compiled [Drizzle](https://orm.drizzle.team) SQL and activates + awaits every registry relation the query touches (FROM, JOIN, subquery, WHERE) before it runs — there is nothing to declare. The call resolves to the **rows array** directly. Inside the callback, reach a relation through a directly-imported synced table/view object, `c.drizzle`, or `c.views`.
If the builder embeds a raw ``sql`…` ``fragment — which can name a relation as a bare identifier the scan cannot see — use `client.queryRaw({ use, build })` instead and list those relations in `use`, so they are activated before the query runs. Pure Drizzle never needs `use`. (The reactive equivalents follow the same split: `useLiveDrizzleRows` for pure reads, `useLiveQueryRaw({ use, build })` for raw fragments.)
Lint the split
`@pgxsinkit/client` ships an oxlint rule that enforces this at authoring time. Enable it in your `.oxlintrc.jsonc` and it flags a raw ``sql`…` ``fragment on the pure path (use `queryRaw`) and a redundant `use` on a pure builder (autofixable) — the two facts the type system can’t see:
```jsonc
{
"jsPlugins": ["@pgxsinkit/client/oxlint"],
"rules": { "pgxsinkit/guarded-query-purity": "error" },
}
```
The rule versions with the `@pgxsinkit/client` you have installed. (oxlint `jsPlugins` is currently alpha.)
Which relation you select **from** depends on the entry’s mode:
* A **readonly** entry syncs only its base table — read it from the entry’s `.table`.
* A **readwrite** entry also has a `_read_model` **overlay view** that merges your own optimistic (not-yet-synced) writes over the synced base rows. Read it from the entry’s `.view`, **not** its `.table`. Selecting the base table of a readwrite entry omits your own pending writes, so a just-issued create / edit / delete does not appear locally until it round-trips through Postgres and streams back.
```ts
// readonly entry → base table
client.query((c) => c.drizzle.select({ id: catalogResource.table.id }).from(catalogResource.table));
// readwrite entry → overlay view, so your own optimistic writes are included
const reportView = registry.report.view!; // `.view` is populated only for readwrite entries
client.query((c) => c.drizzle.select({ id: reportView.id }).from(reportView));
```
This is the read-side twin of optimistic writes returning through Electric: the write is visible immediately only because you read the overlay view; the base table catches up when the committed row streams back.
## Reaching the generated relations directly (factories)
[Section titled “Reaching the generated relations directly (factories)”](#reaching-the-generated-relations-directly-factories)
`entry.table` / `entry.view` are the handles app code reads through. Underneath, a writable table generates a small cluster of relations — the synced read cache, the `_overlay` optimistic table, the `_mutations` journal, the `_sync_state` convergence view, and the `_read_model` overlay view — plus the `pgxsinkit_local_meta` key/value table. `@pgxsinkit/client` exports a typed factory per relation so **diagnostics, tests, and tooling** can author queries against them as tier-① Drizzle objects instead of hand-written SQL:
```ts
import { getOverlayTable, getSyncStateView, getJournalTable } from "@pgxsinkit/client";
// Typed by property key when the registry is concretely typed:
const overlay = getOverlayTable(registry, "report");
db.select({ id: overlay.id, kind: overlay.overlayKind }).from(overlay);
// Convergence state for a table (pending count, conflict/quarantine state):
const syncState = getSyncStateView(registry, "report");
db.select({ pending: syncState.pendingCount, conflict: syncState.conflictState }).from(syncState);
```
The full family is `getSyncedLocalTable`, `getOverlayTable`, `getJournalTable`, `getSyncStateView`, `getReadModelView`, and `getLocalMetaTable`.
Two things set these apart from the entry handles:
* **They fill the gaps the entry handles leave.** `entry.table` / `entry.localTable` are already schema-qualified (built with the registry’s schema, and enforced to match it), so for the synced read cache the factory only earns its keep by tracking a `clientProjection.syncedTable` rename. But `entry.view` (the `_read_model` view) is built **unqualified**, so a store in a non-public local schema must author it through `getReadModelView`; and the `_overlay`, `_mutations`, and `_sync_state` relations have **no entry handle at all** — these factories are the only Drizzle objects for them. Each factory memoizes per `(registry, tableKey)` (`getLocalMetaTable` per local schema), so repeated calls return the same object.
* **Typing follows the registry you pass.** With a concretely-typed registry the synced / overlay / read-model objects carry the entry’s real per-column types (`overlay.col`, `$inferInsert`, `.values()` all typecheck by property key); with a bare `SyncTableRegistry` they degrade to an index-signature shape reached by bracket access (`overlay["col"]`). `getJournalTable` and `getSyncStateView` are **always** conservatively indexed for their entity/PK columns, because the PK name set is not recoverable at the type level — but they key those columns differently: the journal keys PK columns by **DB column name** (`journal["author_id"]`), the sync-state view by the entry’s **drizzle property key** (`syncState["authorId"]`). The fixed runtime/state columns stay typed on both.
**When not to use them.** In app code, prefer the guarded `client.query((c) => …)` read path above: it activates lazy relations for you and reads through `entry.table` / `entry.view`. The factories are for reading the generated relations *directly* (a test asserting overlay/journal state, a perf harness, a diagnostic that inspects `_sync_state`) — they complement the guarded read path, they do not replace it.
## Live queries: dedup, keep-alive, and diagnostics
[Section titled “Live queries: dedup, keep-alive, and diagnostics”](#live-queries-dedup-keep-alive-and-diagnostics)
A reactive read (`useLiveDrizzleRows`, `useLiveQueryRaw`, or `client.subscribeLiveRows`) opens a **local SQL live query** over PGlite: it materialises the query once and then re-runs and diffs it on every write that touches its tables, pushing changed rows to your component. That is one of three independent lifetimes, and keeping them apart is what makes the behaviour predictable:
* **Shape lifetime** — what a table *syncs* from the server (the registry’s `subscription`/`retention`). This is the network stream, unrelated to any query you run locally.
* **Local SQL live-query lifetime** — the PGlite registration + diff for one live query. This is what the query manager below owns.
* **Domain projection lifetime** — the models your app builds *from* live rows. That is yours to hold; the library never sees it.
**Dedup is automatic and free.** Identical live queries share a single PGlite registration. Ten components — or ten browser tabs on a shared worker — mounting the same query cost **one** materialisation and **one** re-run + diff per relevant write, fanned out to every subscriber. You do not opt in and nothing changes in your code; it is keyed on the executed SQL + bound params, so two reads that differ only in a `where` value stay separate, as they must.
**Keep-alive** trades re-materialisation for a bounded idle cost. By default a live query is torn down the instant its last consumer unmounts, so re-mounting it (navigating away and back) re-materialises it — which for a heavy aggregate can cost hundreds of milliseconds. Opt a hot query into a grace period and a re-mount within the window reuses the warm registration instantly:
```ts
// Per-subscription hint — retain THIS query for 30s after its last consumer leaves.
const { rows } = useLiveDrizzleRows(
(c) => c.drizzle.select().from(c.views.offering).orderBy(c.views.offering.createdAtUs),
[],
{ keepAliveMs: 30_000 },
);
```
```ts
// Worker/client-wide policy: a default grace period plus hard budgets (LRU-evicted past them).
defineSyncWorker({
registry,
electricUrl,
batchWriteUrl,
liveQueries: {
defaultKeepAliveMs: 0, // default: no retention — tear down on last unmount
maxRetainedQueries: 16, // most retained (zero-subscriber) queries kept
maxRetainedRows: 50_000, // most rows held across all retained queries
},
});
```
The same `liveQueries` block is accepted by `createSyncClient` and governs the in-process client identically.
**Why the default is 0.** A retained zero-subscriber query is not free: PGlite live queries cannot be paused, so it still pays a full re-run + diff on **every** write to its tables for as long as it is held. Retention is a win only for a query that is genuinely hot (frequently re-mounted) and write-cold; for a write-hot query it can cost more over its idle life than the one re-materialisation it saves. So keep-alive is opt-in per hot query rather than on globally.
**Permanence is a mounted subscriber, not a setting.** There is deliberately no “retain forever” knob. For a fixed hot set — the handful of queries your whole app leans on — mount them in a root provider that never unmounts. That keeps exactly one live registration alive for the app’s lifetime, and every route that reads the same query dedups onto it for free. Keep-alive covers the transient case (a route you leave and return to); a mounted subscriber covers the permanent case.
**Observe it** with `client.liveQueryDiagnostics()`: a snapshot of the manager’s live entries — an opaque fingerprint digest, subscriber and row counts, setup and refresh timings, and retention state per entry. It carries **no** SQL text, bound values, or row data, so it is safe to log or surface in support tooling.
```ts
for (const q of await client.liveQueryDiagnostics()) {
console.log(q.digest, "subscribers:", q.subscriberCount, "rows:", q.rowCount, "retained:", q.retained);
}
```
## Hard prerequisite
[Section titled “Hard prerequisite”](#hard-prerequisite)
Subquery `where` (used for fan-out) is a flagged ElectricSQL preview feature. The proxy forwards the `where` verbatim, so Electric must run with `allow_subqueries,tagged_subqueries`. Without the flag Electric rejects the shape with HTTP 400 and the sync fails **closed** — no rows stream, never an unfiltered fan-out. See [The Electric subquery requirement](/concepts/electric-subqueries/).
# Registry entry options
> Every field of a sync table entry — what it does, a short example, and when to reach for it.
A **sync table entry** is the unit of configuration in pgxsinkit. You author one with [`defineSyncTable`](/api/contracts/functions/defineSyncTable/), collect entries into a registry with [`defineSyncRegistry`](/api/contracts/functions/defineSyncRegistry/), and that registry is the single contract the server proxy, the write API, and every client consume.
This page is the field-by-field reference. The [six core-concept pages](/concepts/) are the mental model; read those first. Here, each option has **what it achieves**, a **short example**, **when to use it**, and its **default / constraints**. The autogenerated [`@pgxsinkit/contracts` API reference](/api/contracts/) has the exact types; [`SyncTableInput`](/api/contracts/type-aliases/SyncTableInput/) is the authoring input and [`SyncTableEntry`](/api/contracts/interfaces/SyncTableEntry/) is the resolved result.
## Anatomy of an entry
[Section titled “Anatomy of an entry”](#anatomy-of-an-entry)
```ts
import { bigint, uuid, varchar } from "drizzle-orm/pg-core";
import { authenticatedRole } from "drizzle-orm/supabase";
import { defineSyncTable } from "@pgxsinkit/contracts";
const messageSyncEntry = defineSyncTable({
tableName: "message", // identity (Postgres + local)
makeColumns: () => ({
// Drizzle columns, built internally
id: uuid("id").primaryKey(),
channelId: uuid("channel_id").notNull(),
authorId: uuid("author_id").notNull(),
body: varchar("body", { length: 4000 }).notNull(),
createdAtUs: bigint("created_at_us", { mode: "bigint" }).notNull(),
updatedAtUs: bigint("updated_at_us", { mode: "bigint" }).notNull(),
}),
policies: buildMessagePolicies(authenticatedRole), // RLS on the Postgres table
mode: "readwrite", // read + write capability
conflictPolicy: "last-write-wins", // required for writable tables
subscription: "lazy", // subscribe on first reference
retention: "ephemeral", // no durable local trace
governance: {
managedFields: [
{ column: "authorId", applyOn: ["create"], strategy: "authClaim", claimPath: ["sub"] },
{ column: "createdAtUs", applyOn: ["create"], strategy: "nowMicroseconds" },
{ column: "updatedAtUs", applyOn: ["create", "update"], strategy: "nowMicroseconds" },
],
},
});
```
You supply `tableName` + `makeColumns`; `defineSyncTable` builds the Drizzle `pgTable`, the projected client `localTable`, and (for `readwrite`) the `_read_model` view for you. Access them on the returned entry as `.table`, `.localTable`, and `.view`.
***
## Identity & columns
[Section titled “Identity & columns”](#identity--columns)
### `tableName` (required)
[Section titled “tableName (required)”](#tablename-required)
**What it achieves.** The Postgres table name, and the default for the synced/local table name, the shape key, and the Electric table. Most other names derive from it.
```ts
defineSyncTable({
tableName: "issue",
makeColumns: () => ({/* … */}),
});
```
**Constraints.** Must be a valid Postgres identifier. Unique within a registry.
### `makeColumns` (required)
[Section titled “makeColumns (required)”](#makecolumns-required)
**What it achieves.** A factory returning the Drizzle column map. It is a *function* (called more than once internally — for the server table, the projected local table, and the read-model view) so each consumer gets its own column instances.
```ts
makeColumns: () => ({
id: uuid("id").primaryKey(),
title: varchar("title", { length: 200 }).notNull(),
status: issueStatusEnum("status").notNull().default("todo"),
}),
```
**When to use.** Always. Define real Drizzle columns — types, `notNull`, `.default(...)`, enums, and references all carry through to the generated DDL and the apply ladder.
### `primaryKey`
[Section titled “primaryKey”](#primarykey)
**What it achieves.** THE primary key of the server table — the single source of truth for its physical `PRIMARY KEY` constraint (used for upserts on the read path and to identify a row for writes). `defineSyncTable` **emits** it as the constraint, named `` `${tableName}_pkey` `` — matching Postgres’s default inline-PK constraint name (which drizzle’s own naming does not produce), so DDL declared through pgxsinkit agrees with plain-Postgres inline-PK DDL and drizzle-kit sees no rename churn against a real Postgres database. This closes the gap where the spec was runtime-only metadata: drizzle-kit, seeing no PK in the generated DDL, could DROP a live composite key.
```ts
primaryKey: ["org_id", "person_id"], // composite key → org_person_pkey (spec order)
primaryKey: { name: "org_person_pk", columns: ["org_id", "person_id"] }, // object form names the constraint
```
**Default.** `["id"]`. **When to use.** For a composite key, a non-`id` primary key, or (object form) a custom constraint name.
**Column-level `.primaryKey()`.** A single-column key MAY equivalently be declared idiomatically on the column itself (`id: uuid("id").primaryKey()`); it must match this spec, and emission is skipped because the column already carries the constraint (a single-`id` table’s DDL then agrees with plain-Postgres inline-PK DDL). A custom constraint name requires letting `defineSyncTable` emit the key, so it cannot be combined with a column-level `.primaryKey()`. A table-level `primaryKey(...)` passed via `extras`/`policies` is **rejected** — declare the key here instead; the spec is the single source of truth.
### `applyMode`
[Section titled “applyMode”](#applymode)
**What it achieves.** Chooses how a server **CDC insert** for this table is applied on the client (ADR-0045). A synced cache table is server-authoritative, so the default deliberately keeps a genuine primary-key collision visible.
```ts
applyMode: "insert", // default — a CDC insert is a plain INSERT; a real PK collision surfaces (ADR-0014)
applyMode: "upsert", // idempotent — server CDC inserts become INSERT … ON CONFLICT (pk) DO UPDATE
```
**Default.** `"insert"`. A CDC insert is a plain INSERT with no conflict clause, so a duplicate insert (a real bug in a server-authoritative table) surfaces instead of being silently swallowed — the ADR-0014 collision-surfacing invariant.
**When to use `"upsert"`.** Only when this table legitimately receives locally-**derived** provisional rows — e.g. a local trigger on another synced table inserts a provisional row here, and the server independently creates the same row, so its CDC insert would otherwise collide (23505) and degrade the engine. With `"upsert"`, server CDC inserts (the initial bulk-snapshot path, the steady-state fold, and the per-message path) are applied idempotently as `INSERT … ON CONFLICT (pk) DO UPDATE` (or a pk-targeted `DO NOTHING` for a pk-only table); the authoritative server row overwrites the provisional local row. Declare the exception here, where it lives — the strict invariant stays the default everywhere else.
### `schema`
[Section titled “schema”](#schema)
**What it achieves.** Places the Postgres table in a specific schema (the entry’s shape and DDL are qualified accordingly).
```ts
import { pgSchema } from "drizzle-orm/pg-core";
const perfLab = pgSchema("perf_lab");
defineSyncTable({
tableName: "event",
schema: perfLab,
makeColumns: () => ({/* … */}),
});
```
**Default.** `public`. **When to use.** Multi-schema servers, or to make a local relation collision-proof for the [lazy-relation guard](/concepts/). Set the registry-wide schema on `defineSyncRegistry({ schema, tables })` instead when every table shares one.
***
## Mode
[Section titled “Mode”](#mode)
### `mode`
[Section titled “mode”](#mode-1)
**What it achieves.** The table’s capability, and the machinery `defineSyncTable` derives from it:
* **`readonly`** — synced down only. No overlay/journal, no `_read_model` view, no write handle.
* **`readwrite`** — full local write cluster: an overlay (the optimistic value the UI reads), a durable mutation journal, and an overlay-merged `_read_model` view. Requires a [`conflictPolicy`](#conflictpolicy) and a `nowMicroseconds`-on-update [server version](#governance).
* **`writeonly`** — a write path with no local read cache (rare; for fire-and-forget writes the client never reads back locally).
```ts
mode: "readwrite",
```
**Default.** `readonly`. **When to use.** `readwrite` for any table a client edits; `readonly` for reference/streamed data. Because mode is a *per-client* capability (a teacher writes what a learner only reads), don’t bake the read-only case into a second table — define the table once at its maximum capability and project it with [`asReadonly`](#asreadonly-readonly-projection).
***
## The Postgres table (server side)
[Section titled “The Postgres table (server side)”](#the-postgres-table-server-side)
### `policies`
[Section titled “policies”](#policies)
**What it achieves.** Row-level security policies attached to the Postgres table. These govern the **write** path (and, with Supabase, mirror the read filter). Use the [`drizzle-orm/supabase`](/api/contracts/) role shims and the `buildSupabase*NativePolicies` helpers.
```ts
policies: buildMessagePolicies(authenticatedRole),
```
**When to use.** Whenever a writable table must restrict who can write which rows. Read-side visibility is the [`shape.rowFilter`](#shape) — keep the two mirror images so a row is never visible-but-unwritable by accident.
### `extras`
[Section titled “extras”](#extras)
**What it achieves.** Extra constraints or indexes on the *server* table — anything `pgTable`’s third argument accepts (unique constraints, indexes, composite checks). Receives the built column map.
```ts
extras: (t) => [unique().on(t.orgId, t.slug), index("idx_issue_team").on(t.teamId)],
```
**When to use.** Server-side integrity and performance. Not applied to the local PGlite table.
***
## Read shape
[Section titled “Read shape”](#read-shape)
### `shape`
[Section titled “shape”](#shape)
**What it achieves.** How rows reach the client: the Electric shape name, an optional column projection, and the **row filter** that scopes which rows each caller syncs. The filter runs in the proxy per request, against the caller’s verified JWT claims.
```ts
shape: {
rowFilter: (columns) => ({
// bare, rename-safe column refs via `c()`; the subject is a bound param, never a literal
customWhere: (claims) =>
claims.sub ? sql`${c(columns.teamId)} in (${memberTeams(claims.sub)})` : DENY_ALL,
revision: "v1",
}),
},
```
Sub-fields:
* **`rowFilter.customWhere`** — returns a Drizzle `SQL` fragment (preferred; values become bound params), a raw string (the escape hatch — *you* must escape interpolated values), or `null` to bypass filtering (e.g. admin). Reference columns through [`c()`](/api/contracts/functions/c/) so the `where` uses the bare identifiers Electric’s grammar requires; cast enums to text (`${c(col)}::text = 'x'`); keep subqueries self-contained. Return [`DENY_ALL`](/api/contracts/variables/DENY_ALL/) to make no rows visible.
* **`rowFilter.columns`** — restrict the synced columns at the shape URL.
* **`rowFilter.revision`** — an opaque version tag for the `customWhere` *body* (a closure can’t be hashed). **Bump it whenever you change the filter logic**, or the fingerprint won’t shift and clients will serve the stale shape.
* **`tableName` / `shapeKey` / `electricTable`** — default to `tableName`; override only to point at a differently-named Electric table or share a shape key.
**When to use.** Almost always — the row filter is your read-side authorization. The [read path](/concepts/read-path/) and [Electric subqueries](/concepts/electric-subqueries/) pages cover fan-out filters in depth.
***
## Client projection
[Section titled “Client projection”](#client-projection)
### `clientProjection`
[Section titled “clientProjection”](#clientprojection)
**What it achieves.** Shapes the *local* (PGlite) table differently from the server table — without a migration, since the local schema is a runtime-derived projection.
```ts
clientProjection: {
omitColumns: ["internal_flag"], // never lands on the client
localPrimaryKey: { columns: ["id"] },// readonly-only: local PK override
},
```
Sub-fields:
* **`omitColumns`** — columns present on the server but absent from the client table and its TypeScript shape. For a writable table you may only omit **create-safe** columns (nullable, defaulted, or managed) and never a primary-key column. **They are invisible to the write path by design:** the apply function reads only a table’s projected columns from a write payload. A non-column payload key splits into two cases:
* a projected-away column (one in `omitColumns`) sent *explicitly* is **400-rejected** by the write route’s projected-field check — it is not silently dropped, and the write does not succeed;
* a genuinely **unknown** non-column key (a typo) is **silently ignored** by the apply function — the write collapses to a bare server-version bump and still acks. The write API surfaces *only this case* with **one `console.warn` per (table, key) per process** (a diagnostic, not a rejection) pointing back here; it does not warn for a projected-away column, which is rejected rather than dropped.
Write a server-only column **outside the sync rail** — a server-side `UPDATE`, a trigger, or a [`managedField`](#governancemanagedfields) — never from a client payload.
* **`syncedTable` / `overlayTable` / `journalTable`** — override the derived local relation names (default `${tableName}`, `${tableName}_overlay`, `${tableName}_mutations`). Rarely needed.
* **`localPrimaryKey`** — a different local primary key (e.g. when a readonly client keys rows by a natural key). **Readonly tables only.**
**When to use.** `omitColumns` to keep server-only control columns (or PII) off the client — pair with [`serverProjection.rowTransform`](#serverprojection) when the decision is per-row, not whole-column.
***
## Server projection
[Section titled “Server projection”](#server-projection)
### `serverProjection`
[Section titled “serverProjection”](#serverprojection)
**What it achieves.** A per-row rewrite applied in the proxy *response* path — server authority, not client shape. It can strip a sub-document of a `jsonb` column or rewrite a value **conditionally on row data**, which whole-column `omitColumns` cannot. It runs before column omission, so it may read a column that `omitColumns` then removes.
```ts
serverProjection: {
rowTransform: (row, { claims }) =>
claims?.sub === row.author_id ? row : { ...row, draft_notes: null },
},
```
**When to use.** Conditional redaction: hide a field from everyone but its owner; trim a large blob for non-privileged callers. It never alters the local schema or the Electric shape URL, so it never pollutes Electric’s shared shape cache.
#### On a read projection: a secure “window” over a keyed table
[Section titled “On a read projection: a secure “window” over a keyed table”](#on-a-read-projection-a-secure-window-over-a-keyed-table)
A read projection (`defineReadProjection`) may carry its own `serverProjection` too — resolved by the projection’s `shapeKey` and run on its egress path only. The recipe: a table stores the item body (a `jsonb payload` with the answer key inside) plus a `keysWithheld` control flag; a projection **streams the body, strips the keys per row**. Because the transform must *read* the control flag — which is not part of the client shape — declare it in `serverOnlyColumns`: those owner keys are added to the Electric fetch allow-list (so the transform can see them) yet stay omitted from the client keep-set, so the same egress omission pass strips them before the wire. Order on egress is **transform first, then omission**.
```ts
export const secureItemWindow = defineReadProjection(secureItem, {
as: "secure_item_window",
columns: ["payload", "metadata"], // the client keep-set (PK always kept)
serverProjection: {
rowTransform: (row) => (row.keys_withheld === true ? { ...row, payload: stripKey(row.payload) } : row),
},
serverOnlyColumns: ["keysWithheld"], // fetched for the transform, never on the client wire
});
```
`serverOnlyColumns` **requires** both `serverProjection.rowTransform` (a fetch no transform reads is dead weight) and `columns` (with `columns` omitted every column is already kept, so “server-only” is a contradiction), and must be disjoint from `columns` and the primary key — each is a loud error.
**No inheritance — enforced at definition time.** A projection does **not** inherit its owner’s `serverProjection`. This is deliberate: an inherited transform whose input column is absent from the projection’s fetch list would read `undefined` and silently fail **open** (serving the un-redacted body) — half-protection worse than none. Because a bare projection over a redacting owner would therefore egress the **raw** owner row, the registry does not merely warn: when the owner declares an egress `rowTransform`, `defineReadProjection` **throws at definition time** unless the projection declares its posture. You satisfy the guard in one of two ways:
* declare your **own** `serverProjection` (typically the same fn) plus `serverOnlyColumns` for its control-flag inputs — the redaction the owner does, done again for this shape; or
* opt out explicitly with the literal `serverProjection: "unredacted"` — **only** after confirming the projection’s kept columns leak nothing. It attaches no transform (the shape streams raw owner rows) but records that as a visible, reviewed decision at the definition site.
`"unredacted"` over an owner that declares **no** egress `rowTransform` is itself rejected — a stale opt-out left in place would silently pre-authorize a leak the day the owner grows a transform, so the opt-out is only accepted exactly where it applies.
***
## Write contract
[Section titled “Write contract”](#write-contract)
### `conflictPolicy`
[Section titled “conflictPolicy”](#conflictpolicy)
**What it achieves.** What happens to a **stale** write — one whose base server version is behind the row’s current version at apply (someone else wrote in between).
* **`last-write-wins`** — apply the stale write anyway (a conscious, named choice, not silent clobbering).
* **`reject-if-stale`** — don’t apply; surface the conflict (`onConflict`), keep the user’s optimistic overlay marked conflicted, and let them resolve it as a new write or roll it back with `discardConflict`. (A structurally-rejected write is instead `quarantined`, with the symmetric `discardQuarantined` rollback — see [the write path](/concepts/write-path/#terminal-dispositions-and-rollback).)
```ts
conflictPolicy: "reject-if-stale",
```
**Default.** None — **required** for every writable table (`defineSyncRegistry` rejects a writable table without one; there is no silent default). **When to use.** `reject-if-stale` for edited records where a clobber is data loss (an issue’s title/description); `last-write-wins` for append-mostly data where each row has its own key and collisions are benign (chat messages).
### `governance.managedFields`
[Section titled “governance.managedFields”](#governancemanagedfields)
**What it achieves.** Columns the **database** stamps on apply, overriding any client-sent value (the client write payload omits them). Two strategies:
* **`nowMicroseconds`** — `clock_timestamp()` microseconds, stamped via the canonical `public.pgxsinkit_clock_us()` database function (one home for the clock semantics), installed by the **utilities migration** — which must be the **first folder** in your migration chain. Use for `created_at_us` and `updated_at_us`. The `updated_at_us`-on-update field is the strictly-monotonic **server version** optimistic convergence keys on — a writable table **must** declare one.
* **`authClaim`** — a value read from the verified JWT claims at a JSON `claimPath`. `["sub"]` is the auth subject (the old `auth.uid()` owner); `["app_metadata","person_id"]` an app-minted identity. An optional `cast` overrides the SQL cast (defaults to the target column’s own type).
```ts
governance: {
managedFields: [
{ column: "ownerId", applyOn: ["create"], strategy: "authClaim", claimPath: ["sub"] },
{ column: "createdAtUs", applyOn: ["create"], strategy: "nowMicroseconds" },
{ column: "updatedAtUs", applyOn: ["create", "update"], strategy: "nowMicroseconds" },
],
},
```
**When to use.** Always, on a writable table — at minimum the `updated_at_us` server version. Managed fields are omitted from [`SyncTableCreateInput`](/api/contracts/type-aliases/SyncTableCreateInput/), so you never pass them (the [write path](/concepts/write-path/) explains the optimistic-overlay fill).
### `governance.deferrableConstraints`
[Section titled “governance.deferrableConstraints”](#governancedeferrableconstraints)
**What it achieves.** Declares a constraint deferrable so **intra-batch** foreign keys resolve — the apply function runs the whole batch under `SET CONSTRAINTS ALL DEFERRED`, letting a parent and child inserted in the same flush land together regardless of order.
```ts
governance: {
deferrableConstraints: [{ constraintName: "issue_team_fk", columns: ["team_id"], initiallyDeferred: true }],
},
```
**When to use.** When a single write batch creates rows that reference each other.
***
## Lifecycle axes
[Section titled “Lifecycle axes”](#lifecycle-axes)
These four axes are orthogonal to read/write mode and to authorization. They are properties of a **consistency group** (see below): every table sharing a `consistencyGroup` must agree on `subscription`, `retention`, and `writeMode`, or `defineSyncRegistry` rejects the registry.
### `consistencyGroup`
[Section titled “consistencyGroup”](#consistencygroup)
**What it achieves.** Binds tables onto one `MultiShapeStream` that commits **atomically** at a shared LSN frontier — a reader never sees one grouped table advanced past another for the same server transaction.
```ts
const TEAM_SCOPE = "team-scope";
// team, channel, issue all set: consistencyGroup: TEAM_SCOPE
```
**Default.** None — each table is its own singleton group (independent frontier). **When to use.** When a join across the group must never flicker mid-update (a member added to a team should see the team, its channel, and its issues appear in one frame). Cost: a group advances only as fast as its slowest shape.
**How to scope a group.** Three rules:
1. **Group the transactionally-joined cluster** — tables written together in one server transaction and rendered joined (FK parent + children). That is exactly what the atomic frontier protects.
2. **Quiet members are affordable.** A rarely-written reference table used to be dangerous in a group (its parked long-poll could hold the whole group for a full \~41s hold cycle on CDN-fronted Electric); the [live-tail sibling nudge (ADR-0033)](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0033-live-tail-sibling-nudge.md) caps that at roughly one catch-up round trip per gated commit. Don’t contort a schema to keep a lookup table out of its natural group.
3. **Don’t group “everything”.** Every gated commit nudges each lagging member, so group scope should be the joined cluster, not the whole registry — unrelated clusters belong in separate groups (or stay singletons).
### `subscription`
[Section titled “subscription”](#subscription)
**What it achieves.** *When* a shape subscribes.
* **`eager`** — in the boot subscription set (today’s default).
* **`lazy`** — excluded from boot; subscribed on first query-reference. With `persistent`, first use is a one-time ignition that promotes it to eager for later sessions; with `ephemeral` it is session-scoped.
```ts
subscription: "lazy",
```
**Default.** `eager`. **When to use.** `lazy` for a rarely-opened view, to keep the boot connection budget for the shapes the first screen needs. A lazy relation auto-activates on any query reference. The live-query result envelope’s `hydrating`/ready state is not lazy-specific: it stays `hydrating` until every consistency group the query reads — eager or lazy — has caught up and its rows have been delivered, so a query over an eager relation still catching up on a cold boot is covered too (cached rows paint immediately meanwhile). A `readwrite` entry declared `lazy` and **never** activated is still fully provisioned locally, so it can serve as a [write-only table](/concepts/write-path/#the-write-only-pattern) — authored through `updateBlind` without ever streaming a row.
**Gate authenticated lazy groups until auth is resolved.** The first query reference starts the relation’s **whole consistency group** using the claims available to that shape request. If the group’s row filters return `DENY_ALL` without a user claim, a query mounted while auth is still loading starts an anonymous, empty subscription. A later auth change can rotate the shape and rehydrate it, but that is a refetch boundary rather than the intended first activation; with `persistent` retention, the premature activation is also remembered for later sessions.
For a group that is meaningful only to signed-in users, defer every query that can first reference it until the session exists. In React, pass `{ ready: session != null }` to the live hook. Referencing one member is enough to activate every member of the group. The same holds for writes: an ordinary optimistic write self-activates its target’s group (see [Lazy read/write groups need an echo](/concepts/write-path/#lazy-readwrite-groups-need-an-echo)), so an authenticated-only group should not be written before the session exists either — the write would activate it against anonymous claims. Activating a claims-denied group with no auth token now emits a `console.warn` naming the group, so an accidental anonymous activation (read- or write-triggered) is visible rather than silent.
### `retention`
[Section titled “retention”](#retention)
**What it achieves.** *Whether* the local copy is durable.
* **`persistent`** — the durable PGlite/OPFS backend with a resumable subscription-state.
* **`ephemeral`** — the table’s whole local cluster (read cache, overlay, journal, sequence, views, reconcile function) is emitted as `TEMP`/`pg_temp`, so reads **and** writes leave no durable trace and re-hydrate fresh each session.
An ephemeral table’s sync bookkeeping — its subscription cursor and its tagged-subquery reason sets — is session-scoped too, held in `pg_temp` alongside the rows it tracks. The guarantee that follows: a returning session always re-streams an ephemeral view **from scratch**. Close the tab, come back, and the view rebuilds cleanly rather than resuming a stale cursor over an empty cluster — so nothing an earlier session synced can linger or reappear stale.
```ts
retention: "ephemeral",
```
**Default.** `persistent`. **When to use.** `ephemeral` for data that must not sit durably on the client — proctored-exam answers, sensitive/PII under data-minimisation — or for cold per-user data not worth persisting. **Composition rule:** an ephemeral table has **no durable offline write queue** (its journal is `TEMP`), so pair a must-not-lose write with a `pessimistic` [`writeMode`](#writemode) or a prompt flush. To make this per-client (durable for one client, ephemeral for another), see [`withRetention` / `asEphemeral`](#withretention--asephemeral-lifecycle-projection).
### `writeMode`
[Section titled “writeMode”](#writemode)
**What it achieves.** *How* a write reaches the server.
* **`optimistic`** — staged locally with an overlay, the UI updates immediately, and the convergence loop flushes the journal as one batch; the canonical row returns via the sync echo.
* **`pessimistic`** — server-authoritative: the write flush-routes to an authoritative endpoint that applies it in an isolated, serialised transaction and returns an accepted/rejected result **before** the UI shows success.
```ts
writeMode: "pessimistic",
```
**Default.** `optimistic`. **When to use.** `pessimistic` for an invariant the client can’t evaluate locally — a capacity/quota/uniqueness gate enforced server-side — or to pair with `ephemeral` so a must-not-lose write reaches the server before the tab closes. Not allowed on a `readonly` table. A dynamic override is the imperative `transaction({ mode })` block.
***
## Row classification
[Section titled “Row classification”](#row-classification)
A privacy or visibility rule is rarely about one entry — it is about a **kind of row** that several entries carry. Classification lets you name those kinds in your own words, make classifying mandatory, and then assert rules against every entry of a class at once. See [ADR-0052](/decisions/).
### `rowClass`
[Section titled “rowClass”](#rowclass)
**What it achieves.** Records what **kind** of rows this entry carries. The vocabulary is entirely yours — pgxsinkit defines no values and attaches no behaviour to any of them. It is documentation-as-code at the definition site, and the key an invariant binds to.
```ts
rowClass: "team-scoped",
```
**Default.** None. **Constraints.** When the registry declares [`rowClasses`](#rowclasses-on-the-registry), this becomes **required** and must be one of the declared values; otherwise it is unconstrained. It is authoring metadata only: it never enters the registry fingerprint or the read-contract fingerprint, so classifying a table never invalidates a local store’s cache. Every projection carries it through — `asReadonly`, `withRetention`/`asEphemeral`, and `defineReadProjection` (which **inherits** the owner’s class unless you pass its own `rowClass`).
### `rowClasses` (on the registry)
[Section titled “rowClasses (on the registry)”](#rowclasses-on-the-registry)
**What it achieves.** Declares the registry’s **closed** classification vocabulary — and by doing so makes classification a fail-closed obligation: every entry must then carry a `rowClass` from that exact set, checked when `defineSyncRegistry` runs (so, at module eval).
```ts
export const registry = defineSyncRegistry({
rowClasses: ["directory", "team-scoped", "channel-scoped"],
tables: { profile, team, team_member, channel, issue, message },
});
```
**When to use.** As soon as any rule spans more than one table. The point is not the labels — it is that the **next** table someone adds cannot join the registry without its author deciding which kind of rows it holds, so it can never inherit an obligation invisibly. The error names every unclassified or wrongly-classified entry at once. **Constraints.** Only available on the definition-object form (the bare registry map has nowhere to declare it, and is unconstrained); an empty or duplicated declaration is rejected.
### `assertRegistryInvariant` (the registry-wide invariant)
[Section titled “assertRegistryInvariant (the registry-wide invariant)”](#assertregistryinvariant-the-registry-wide-invariant)
**What it achieves.** Asserts a rule about the **rendered** authorization artifacts of every entry a class binds, evaluated against named claims personas. Each cell hands your predicate the real read filter for those claims (`renderedWhere` — the same `buildRowFilterShape` call the proxy makes per shape request) and the entry table’s RLS policies rendered to SQL text (`renderedPolicies`), so one predicate can check **both** enforcement surfaces at once.
```ts
import { assertRegistryInvariant } from "@pgxsinkit/contracts";
assertRegistryInvariant(registry, {
name: "team-scoped rows are never visible to an anonymous caller",
appliesTo: ["team-scoped", "channel-scoped"], // or a predicate over the entry
claimsFixtures: { anonymous: {}, member: { sub: memberId }, admin: adminClaims },
holds: ({ fixtureName, renderedWhere }) =>
fixtureName !== "anonymous" || renderedWhere?.where === "false" || "anonymous read is not denied",
});
```
**When to use.** At module eval beside the registry (or in a test), the same way [`assertReadContractPreserved`](#assertreadcontractpreserved-the-projection-invariant) is used. Unlike the fingerprints, it can see the `customWhere` **body** — because it renders it — so it catches a filter-logic change no hash can; the trade is that it sees exactly the personas you enumerate, and nothing else. Complement it with `rowFilter.revision` (which forces the cache/subscription reset a logic change needs).
**Fail-closed behaviour.** Every failing cell is aggregated into one error (`entry (fixture): reason`), never first-failure-only. A class in `appliesTo` that the registry’s declared vocabulary does not contain throws immediately, and an invariant that binds **zero** entries throws — an invariant that checks nothing would otherwise pass vacuously, which is the exact failure this mechanism exists to remove. It is a pure audit: nothing about runtime behaviour changes.
**What it cannot see.** Per-entry declarations only. If a worker or route writes rows into one table as a consequence of rows in another, that composition is invisible here — test it at the composition seam. See [The two paths](/concepts/two-paths/).
***
## Per-client projections
[Section titled “Per-client projections”](#per-client-projections)
One authoritative registry defines each table at its **maximum** capability; each client consumes a *projection* of it. The projection helpers are pure transforms over an entry — readable at the call site, and guarded by an invariant so a projection can never silently diverge the data it syncs.
### `asReadonly` (readonly projection)
[Section titled “asReadonly (readonly projection)”](#asreadonly-readonly-projection)
**What it achieves.** Returns the entry `defineSyncTable` would have produced with `mode: "readonly"`: flips `mode`, drops the `_read_model` view and the overlay/journal client projection, and drops the write-only metadata (`conflictPolicy`, `governance`, `writeMode`). The read/identity contract — table, columns, primary key, synced-table name, column omission, shape/row filter — is preserved, and the lifecycle axes carry through.
```ts
import { asReadonly, defineSyncRegistry } from "@pgxsinkit/contracts";
export const memberRegistry = defineSyncRegistry({
...authoritativeRegistry,
team: asReadonly(authoritativeRegistry.team), // members read teams…
team_member: asReadonly(authoritativeRegistry.team_member), // …but only admins write them
});
```
**When to use.** When the same table is `readwrite` for one role and `readonly` for another. The read-only client then provisions no write cluster and exposes no write handle, so an accidental write is impossible — not merely quarantined. See [ADR-0025](/decisions/).
### `withRetention` / `asEphemeral` (lifecycle projection)
[Section titled “withRetention / asEphemeral (lifecycle projection)”](#withretention--asephemeral-lifecycle-projection)
**What it achieves.** Returns a copy of an entry with [`retention`](#retention) overridden, everything else preserved verbatim. `withRetention(entry, retention)` is the bidirectional primitive; `asEphemeral(entry)` is the named convenience for the common direction. Because retention is a lifecycle axis (not part of the read contract), a projection may legitimately differ on it — the override still passes [`assertReadContractPreserved`](#assertreadcontractpreserved-the-projection-invariant).
```ts
import { asEphemeral, asReadonly, withRetention } from "@pgxsinkit/contracts";
// One client keeps the exam durable; another wants no durable trace:
const examForProctor = withRetention(authoritativeRegistry.exam, "ephemeral");
// Compose with asReadonly — read-only AND no durable trace:
const examForViewer = asEphemeral(asReadonly(authoritativeRegistry.exam));
```
**When to use.** When durability is a per-client decision: persist on a trusted device, go ephemeral on a shared/exam machine, all from one authoritative registry. **Constraints (carried over, not enforced by the helper):** every table in a `consistencyGroup` must agree on retention — override the *whole group*, or `defineSyncRegistry` rejects the mixed group; and an ephemeral writable table has no durable offline write queue (pair with `pessimistic` writeMode). A singleton-group table can be flipped alone.
### `assertReadContractPreserved` (the projection invariant)
[Section titled “assertReadContractPreserved (the projection invariant)”](#assertreadcontractpreserved-the-projection-invariant)
**What it achieves.** Asserts that every table a per-client projection declares preserves the authoritative entry’s **read contract** — synced columns, primary key, column omission, row-filter shape. A projection may differ only in **write capability** and **lifecycle** (mode, the overlay/journal machinery, `conflictPolicy`, `governance`, `writeMode`, `consistencyGroup`, `subscription`, `retention`); diverging the *data* throws. A table only in the authoritative registry is a permitted subset; a table in the projection with no authoritative source is an error.
```ts
import { assertReadContractPreserved } from "@pgxsinkit/contracts";
assertReadContractPreserved(authoritativeRegistry, memberRegistry, { label: "member" });
```
**When to use.** At module-eval (or in a test) where you assemble the client registries, so a drifted projection fails **closed** instead of silently serving different rows to different clients. Like every fingerprint here it can’t see the `customWhere` body — bump `rowFilter.revision` so a logic-only divergence is caught.
***
## See also
[Section titled “See also”](#see-also)
* [The two paths](/concepts/two-paths/), [write path](/concepts/write-path/), [read path](/concepts/read-path/) — the mental model these options configure.
* [`@pgxsinkit/contracts` API reference](/api/contracts/) — exact types for every field above.
* [Design decisions](/decisions/) — ADR-0015 (conflict policy), ADR-0021 (lifecycle axes), ADR-0022 (write modes), ADR-0025 (per-client projection), ADR-0052 (row classification and registry invariants).
# Timestamps
> Microsecond integers, carried across boundaries as decimal strings — the sync truth.
pgxsinkit uses a single, deliberate timestamp model. It can look surprising, but it is intentional and load-bearing for convergence.
## The model
[Section titled “The model”](#the-model)
* `created_at_us` and `updated_at_us` are the **authoritative** time fields.
* They are stored in PostgreSQL as `BIGINT` **microseconds since the Unix epoch**.
* They cross API and sync boundaries as **decimal strings** (e.g. `"1718900000000000"`), to avoid JavaScript number-precision loss on 64-bit integers.
* They are the **sync truth**. Human-readable timestamp projections can be added if operationally useful, but they are never what convergence is decided on.
## Why microseconds, and why strings
[Section titled “Why microseconds, and why strings”](#why-microseconds-and-why-strings)
* **Microseconds** give enough resolution to order rapid successive writes without collisions.
* **Decimal strings** survive the JSON boundary intact. A 64-bit microsecond value exceeds `Number.MAX_SAFE_INTEGER`, so sending it as a JSON number would silently corrupt it. Strings keep it exact from Postgres → server → client and back.
## Where it shows up
[Section titled “Where it shows up”](#where-it-shows-up)
* The write path returns the server `updated_at_us` in each ack.
* The client clears an optimistic overlay row only once the read path echoes a row whose `updated_at_us` is **at least as new** as the acked value — this is how the optimistic write and the synced truth reconcile. See [The write path](/concepts/write-path/).
## What not to do
[Section titled “What not to do”](#what-not-to-do)
* Don’t treat these as millisecond JS timestamps — they are microseconds.
* Don’t parse them into `number` on the wire — keep them as strings until you intentionally convert.
* Don’t introduce a separate “real” timestamp column and sync on that; `*_us` is the truth.
# The two paths
> Read and write are separate, asymmetric paths — not one bidirectional channel.
pgxsinkit moves data in two directions over **two different mechanisms**. They are not symmetric and they are not one channel — Electric carries the read direction only, never writes.
## Read path: server → client
[Section titled “Read path: server → client”](#read-path-server--client)
```plaintext
PostgreSQL → ElectricSQL → PGlite
```
Postgres is the source of truth. ElectricSQL streams **shapes** (filtered row sets, including membership fan-out) to the client, where they land in local PGlite. The app reads from PGlite. This path is live and continuous. See [The read path](/concepts/read-path/).
## Write path: client → server
[Section titled “Write path: client → server”](#write-path-client--server)
```plaintext
client → write route → PostgreSQL
```
Local edits do **not** travel back through Electric. They are staged locally, flushed as a batch to a typed write route on the pgxsinkit server, and applied to Postgres by a single in-database function. See [The write path](/concepts/write-path/).
## Why the asymmetry matters
[Section titled “Why the asymmetry matters”](#why-the-asymmetry-matters)
* **You cannot “write to Electric.”** Electric is read transport only. A mutation that isn’t sent to the write route never reaches Postgres, and therefore never comes back down the read path.
* **The loop closes through Postgres.** A local write becomes durable only once the server applies it; it becomes *visible to other clients* only once Electric streams it back down. The client holds the optimistic value in an overlay until that echo returns (see [The write path](/concepts/write-path/) and [Timestamps](/concepts/timestamps/)).
* **Synced tables are replication targets.** Application code must never mutate a synced table directly — those rows are owned by the read path. All writes go through the mutation runtime.
## Not everything is sync state
[Section titled “Not everything is sync state”](#not-everything-is-sync-state)
The two paths above are the **sync rail**, and they are what this page is about. Beside them sits a third, non-sync lane for data that was never sync state: high-volume, append-only client facts (view logs, interaction events) that are never edited, never conflict, and are never read back down. Those go on [the event lane](/concepts/event-lane/) — an append into a local Outbox, flushed to an ingestion endpoint and handed to a queue and your own consumer callback. It has no overlay, no echo and no conflict resolution, so the asymmetry above simply does not arise for it. Everything that *is* sync state still obeys the one rule.
## The one rule
[Section titled “The one rule”](#the-one-rule)
> Read from PGlite. Write through the write route. Never write to a synced table directly, and never expect Electric to carry a write.
## Composition is yours
[Section titled “Composition is yours”](#composition-is-yours)
The registry keeps **one table’s** read filter and write policy in agreement — that is its job, and it does it from a single declaration. What it cannot see is a rule that spans tables and rails.
An example: an invite table’s RLS legitimately lets an offering-scoped teacher create an invite, and an acceptance worker later mints a membership row from it. Both policies are correct alone; together they can break “this offering only ever has one member”, because the worker’s semantics appear in no per-table declaration.
So when something writes rows as a **consequence** of other rows, the invariants of the **output** table are the ones at stake — re-check them against current state when the worker runs, rather than trusting that the input row’s authorization already settled it. And test at the composition seam, driving the worker or route end to end: per-table policy tests structurally cannot fail on a composition hole.
# Worker mode
> Attach through a SharedWorker while runtime capability decides whether the engine runs there or in one elected dedicated worker.
By default `createSyncClient` runs the whole local-first engine **in the tab that called it** — PGlite, the Local schema, the mutation journal, the Electric shape streams, and the convergence loop all execute on that tab’s thread. **Worker mode** leaves the tab a thin view and uses a native `SharedWorker` as the communication centre. A real OPFS open at boot decides the engine’s home: Safari runs it inside that SharedWorker; Chromium and Firefox elect one tab-spawned dedicated worker. React, live-query results, query building (Drizzle still compiles on the tab), auth ownership, and the app-facing API stay on the main thread in both arrangements.
This is the recommended topology for **browser apps**. `createSyncClient` remains the in-process mode for bun tests, Node harnesses, and the fallback below — same engine, same unit-suite coverage, just on the calling thread.
## The two-file pattern
[Section titled “The two-file pattern”](#the-two-file-pattern)
Worker mode is a facade **pair** with the same client shape as `createSyncClient`:
* **The worker entry** (a file bundled for both worker kinds) calls `defineSyncWorker({ registry, electricUrl, batchWriteUrl, … })` at module top level. It hosts the engine directly or acts as its router. The registry is **code** and must be *imported* by the worker file — never cloned or serialized into it.
* **The tab** calls `attachSyncClient({ worker, registry })`, which returns the same surface as `createSyncClient` (the write API, Drizzle reads, live rows, `localReadReady`/`writeReady`/`ready`/`status`/ `stop`), transparently proxied to the shared engine, plus `notifyAuthChanged` and `setOnline`.
`attachSyncClient` resolves at **local-read readiness** — the worker’s engine has an open store with a compatible schema, so cached rows are queryable immediately (offline included). Writes are safe the moment attach resolves: every write method transparently awaits `writeReady` (the write runtime + boot recovery) in the engine, so a write issued the instant attach resolves simply completes once that stage crosses — you never gate writes yourself. `ready` and per-group `groupReady` keep their catch-up meaning (below).
```ts
// sync.worker.ts — bundled as a worker; imports the registry as code
import { defineSyncWorker } from "@pgxsinkit/client";
import { registry } from "./registry";
defineSyncWorker({
registry,
electricUrl: "/api/shape",
batchWriteUrl: "/api/mutations",
// No placement or durability options here: where the engine runs is a runtime capability
// decision, and storage backend + durability are declared on the registry (registry.storage).
});
```
```ts
// tab code
import { attachSyncClient } from "@pgxsinkit/client";
import { registry } from "./registry";
const storePath = "my-app-store";
// Prefer the FACTORY form over a bare instance or a raw `SharedWorker.port`: a SharedWorker cannot be
// reconstructed from itself, so in ELECTED placement (Chromium/Firefox) the factory is what arms
// router-SharedWorker-death recovery — the election coordinator's keepalive rebuilds the router through it.
// A `port`/instance input still works everywhere (the provision→attach handoff is keyed by storePath, not
// the transport) but forfeits that reconstruction; SW-direct (Safari/idbfs) has no keepalive either way.
const worker = () =>
new SharedWorker(new URL("./sync.worker.ts", import.meta.url), {
type: "module",
name: `pgxsinkit:${storePath}`,
extendedLifetime: true,
} as WorkerOptions & { name: string; extendedLifetime: boolean });
const client = await attachSyncClient({
worker,
storePath,
registry,
// No createEngineWorker here: the elected engine worker is auto-derived from the SharedWorker's
// own script URL. Supply createEngineWorker only for non-module / underivable entries (below).
getToken: async () =>
currentSession && { accessToken: currentSession.access_token, expiresAt: currentSession.expires_at },
});
```
The same worker entry serves a native `SharedWorker` (many ports, `onconnect`) and the elected dedicated `Worker` (one implicit port). Keep the SharedWorker name stable and store-specific so tabs converge on the same communication centre. On a handle-denied browser the elected engine worker needs **no** consumer wiring: the SharedWorker reports its own script URL and the winning tab constructs the engine as `new Worker(reportedUrl, { type: "module" })`. `createEngineWorker` is an **override** for entries that cannot be reconstructed from their URL as a module worker (classic-script workers, `blob:`/`data:` URLs, CSP constraints); with no override and no derivable URL, attach fails with a typed error — never a silent no-engine attach.
The worker entry can also carry the app-level schema prepare hooks — `prepareLocalDbBeforeSchema` and `prepareLocalDbAfterSchema` — with the **same semantics as `createSyncClient`** (they run in the worker against the engine’s local store, before/after the registry schema exec); they are worker-entry options rather than attach options because a hook is a function and functions cannot cross the bridge.
If the worker’s engine **local-read core** fails (the store cannot open, or its schema is incompatible), `attachSyncClient` **rejects** with that boot error rather than hanging silently — and a later attach retries the boot. A failure **after** local-read readiness (the background write/sync tail) does *not* reject the already-resolved attach; instead it rejects `writeReady`/`bootSettled`, so a gated write fails loudly rather than the client hanging.
Behavioural change — attach resolves at local-read readiness (ADR-0041)
`attachSyncClient` (and the in-process `createSyncClient`) now resolve at **`localReadReady`**, not after full boot. Cached rows in the persisted store are queryable — **offline included** — the instant attach resolves, before sync starts or the network is touched. Previously attach resolved only after the whole boot (write runtime, boot recovery, and sync **start**) had run, so a returning consumer that only wanted to paint cached rows still waited on the write runtime and the sync engine.
**What this means for your code:** first paint should gate on **attach** (or `await client.localReadReady`), not `await client.ready`. Per-view loading is driven by `hydrating`/`groupReady` (the live-rows hooks); `ready` is for whole-sync UX (a “fully caught up” badge). Writes need **no** gate — every write method awaits `writeReady` internally, so a write issued the instant attach resolves completes once the write runtime is up. If you were relying on “attach resolved ⇒ the next write flushes immediately”, that first write may now await write-runtime init (a small window; see [ADR-0041](/decisions/0041-staged-boot-readiness/)). This repo’s releases are tag-derived, so this callout and the ADR are the record of the change.
## Capability placement and storage
[Section titled “Capability placement and storage”](#capability-placement-and-storage)
Where the engine runs is a **runtime capability decision**, never a consumer knob. The SharedWorker probes once per worker lifetime — unconditionally, under the default `storage.backend: "opfs"` — by actually creating a scratch sync access handle. The only storage knob is the **registry** declaration (`SyncRegistryDefinition.storage`): `backend: "opfs"` (the default) runs the probe on every platform; `backend: "idbfs"` forces the in-SharedWorker IndexedDB engine and skips the probe entirely — the one way to opt out, and it lives with the DATA contract because forcing idbfs is a storage decision, not a placement or wiring one. The probe outcomes:
| Probe result | Engine home | Storage |
| ---------------------------------------------------------------------- | ---------------- | ---------------- |
| Granted in SharedWorker (real macOS/iOS Safari) | `shared-worker` | `opfs-repacked` |
| Denied in SharedWorker, granted in dedicated Worker (Chromium/Firefox) | `elected-worker` | `opfs-repacked` |
| Dedicated Worker also denied (Playwright WebKitGTK) | `elected-worker` | `idbfs` fallback |
| No SharedWorker | `in-process` | `idbfs` |
The Safari statement is backed by a real-device full boot, persist, and reopen on 2026-07-21. Do not substitute Playwright WebKitGTK for Safari: the test build has a different capability result.
Read the outcome from `await client.bootReport()`, not from user-agent detection:
```ts
const report = await client.bootReport();
report?.storageBackend; // "opfs-repacked" | "idbfs" | "filesystem" | "memory"
report?.engineHome; // "shared-worker" | "elected-worker" | "in-process"
report?.storageFallbackReason; // present only when an OPFS-capable boot actually opened idb
```
The worker is named by its store id, so N tabs attach through one communication centre and ultimately share **one store, one Electric connection set, and one convergence loop**. On Safari the SharedWorker owns that engine directly. On Chromium and Firefox, Web Locks elect one tab’s dedicated engine worker; per-tab pipes connect tabs directly to it, and the OPFS VFS’s exclusive handles remain the hard single-owner guard.
The worker owns that convergence loop: a write flushes **event-driven** the moment it is enqueued (the RPC requests a pass), and tabs forward their `online`/`visibilitychange` events as wake signals, so the worker’s own interval — `defineSyncWorker`’s `convergenceIntervalMs`, default **15s** — is purely the retry/recovery fallback sweep. Keep it long; see [Convergence cadence](/start/operating-in-production/#convergence-cadence-event-driven-with-the-interval-as-a-fallback).
Browsers without `SharedWorker` fall back to the plain **in-process** main-thread client — a main thread can never hold sync-access handles — never to a bespoke election layer. Because `attachSyncClient` and `createSyncClient` share a client shape, the fallback is a construction choice, not an app-code fork.
### Multiple stores and identity switching
[Section titled “Multiple stores and identity switching”](#multiple-stores-and-identity-switching)
Scope each worker identity by `storePath`, rather than sharing one worker across a browser profile or application. Give every store its own stable SharedWorker name (normally derived from that path). Distinct stores may be alive concurrently, so an application switching identities should detach/stop the old client and immediately attach the new identity’s worker/store. It must not wait for the old SharedWorker, elected engine, provision claim, or `extendedLifetime` grace period to expire. `stop()` is the client lifecycle boundary: worker mode detaches that tab while peers and the store-specific engine may remain alive; in-process mode closes that client’s engine and store after disposing its live queries.
The board demo exercises this contract by retaining a `userId → storeId` map and switching identities inside one page realm. A returning identity reattaches its mapped store; a first-time identity claims a separately provisioned spare. Neither path reuses the previous identity’s store.
### `extendedLifetime` is a grace period, not placement
[Section titled “extendedLifetime is a grace period, not placement”](#extendedlifetime-is-a-grace-period-not-placement)
Pass `extendedLifetime: true` on every SharedWorker construction. Chromium 148+ may retain it briefly after the last client leaves, which can let a pending relaxed IndexedDB snapshot land and can warm-start a quickly reopened tab. Firefox and Safari ignore the unknown option safely. It does not retain Chromium’s elected engine worker and is not part of the OPFS durability guarantee.
### The storage declaration on the wire (ADR-0050)
[Section titled “The storage declaration on the wire (ADR-0050)”](#the-storage-declaration-on-the-wire-adr-0050)
The worker **name carries the store path and nothing else** — never configuration. The store’s storage declaration (`SyncStorageDeclaration`: `backend`, `durability`) normally lives statically on the registry (`attachSyncRegistryStorage`), and that remains authoritative. For a consumer whose declaration is **dynamic** (a runtime storage toggle, like the board demo’s), the declaration travels on the wire instead: pass `storage` to `attachSyncClient`/`provisionSyncWorker`, and the library posts a **declaration message** on every worker port *before* its placement query. A registry-silent worker defers its placement decision until the first declaration arrives — `backend: "idbfs"` must skip the OPFS probe, so the declaration has to precede the decision — and the first arrival binds for the worker’s lifetime. The same declaration rides the provision/attach payloads so the engine binds the mint’s durability wherever it runs.
The rules are strict, per field, on **explicit values only**: an unset field is “no opinion” and never conflicts; an explicit field disagreeing with the registry’s declaration or the already-bound one — or any provision/attach arriving on a port that has not declared — is a typed `StorageDeclarationRefusedError`, never a silent fallback. A store’s declaration is **immutable**: to change a preference, mint a fresh store under a fresh path, point users at it, and destroy the old path’s artifacts in the background with [`destroyStoreArtifacts`](/concepts/local-store-lifecycle/) — never delete-and-recreate the same path while an `extendedLifetime` predecessor may still hold it. Each obsolete (or wiped) path is first **quiesced** — `quiesceStoreWorker` tears the store’s SharedWorker host down by path so an `extendedLifetime` idbfs predecessor releases the IndexedDB connection it holds across the reload (else `deleteDatabase` blocks forever); OPFS releases on idle and needs no teardown (ADR-0050).
## Relocation and the execution limit
[Section titled “Relocation and the execution limit”](#relocation-and-the-execution-limit)
Elected placement can move the engine when its leader leaves, enters BFCache, reports a worker error, or is deliberately terminated. New calls wait in a bounded handoff queue. Work whose response is lost is reported honestly through `EngineRelocatedError`:
* `outcome === "not-dispatched"` means the operation never left the tab and is safe to retry;
* a dispatched read is safe to repeat after reattach;
* `outcome === "unknown"` means a dispatched mutation may already have updated the journal. Inspect and reconcile; never retry it blindly.
The optional `executionLimit: { maxDispatchMs }` converts an unresponsive elected worker into a deliberate termination and respawn. It is disabled by default, applies only to elected placement, and every tab plus the worker entry must carry the same value. A mismatch raises `ExecutionLimitMismatchError`; enabling it on SW-direct Safari is rejected because a page cannot terminate that in-scope SharedWorker engine.
### Selecting a role per attach
[Section titled “Selecting a role per attach”](#selecting-a-role-per-attach)
A single worker file can bake **more than one registry variant** (e.g. the board’s admin and member registries — same TS shape, different write capability) and pick per attach. Pass `resolveRegistry: (role) => …` to `defineSyncWorker` and `role` to `attachSyncClient`; the attach’s `config.role` selects the registry the engine boots with (falling back to the default `registry` when the role is absent or unknown). The spare-store flow needs this: the spare is provisioned before the user — and therefore the role — is known, and the role is settled only at claim/attach.
## The tab stays the single auth owner
[Section titled “The tab stays the single auth owner”](#the-tab-stays-the-single-auth-owner)
Auth ownership does **not** move into the worker (ADR-0013 unchanged). The tab pushes `{accessToken, expiresAt}` to the worker at attach and again on every app auth-state change (call `client.notifyAuthChanged()`); the worker uses the cached token for shape requests and write flushes, and sends a **pull request** only when a request finds the token near expiry — any attached tab answers via its `getToken`, first response wins. The worker **never runs its own refresh loop**, so exactly one refresher exists and GoTrue refresh-token reuse detection can never be tripped by a second client.
## What crosses the bridge — and what does not
[Section titled “What crosses the bridge — and what does not”](#what-crosses-the-bridge--and-what-does-not)
`attachSyncClient` proxies the full mandated attach surface (ADR-0032 decision 4): the write API (RPC-backed), per-group readiness, the live-rows seam, `ready`/`status`/`stop` — and the one-shot Drizzle reads (`query`/`queryRow`/`queryRaw`/`queryRawRow`). Query building happens on the tab (`drizzle` and `views` are the same handles `createSyncClient` exposes); awaiting a builder sends the compiled SQL over the bridge as **one guarded round trip** — the worker runs the read gate (ADR-0041) and the lazy-group guard (ADR-0021), executes, and returns the raw rows — and Drizzle’s own result mapping (relational/nested included) runs back on the tab, so a one-shot read returns exactly what its in-process twin would. `ensureSynced` is proxied too (activation is engine-wide but additive and idempotent — nothing like `desync`’s blast radius below). Two deliberate mode differences: a bare awaited `client.drizzle.select()…` — the in-process **unguarded escape hatch** (ADR-0021) — is *also* guarded here, since every bridge read routes through the guarded seam (attach is strictly more protected, never less); and `client.drizzle.transaction()` throws — a read transaction needs a local store the tab does not have.
What remains unproxied is structural, not a slice gap: `pglite` (the tab holds no local store), `dropReadCache` (an engine-wide cache rebuild), and `isSynced` (a **synchronous** activation-started peek — it cannot be an RPC, and the tab’s cached per-group state is catch-up readiness, which reads an activated-but-still-catching-up lazy group as not-ready, the very case `isSynced` distinguishes; use `groupReady` for catch-up and `ensureSynced` to activate).
`destroy()` **is** proxied under a supervisor that survives engine shutdown. It refuses with `StoreDestroyRefusedError` while another tab is attached and refuses while journal mutations are owed unless you pass `{ force: true }`. On success it closes the engine, records a resumable `deleting` phase, deletes the commitment and both possible backend stores, and removes the phase record. A crash resumes the same lifecycle on the next boot; a successful SW-direct destroy ends that SharedWorker lifetime so a later attach cannot inherit a closed host.
A store’s **storage backend is fixed at its first mint**, for the store’s whole life. An existing IndexedDB store is opened in place by a newly capable OPFS home — nothing is copied, no OPFS candidate or commitment sentinel appears beside it, and no local data is deleted (the boot report shows `storageBackend: "idbfs"` plus a `storageFallbackReason`). The mirror holds too: a home with no grant refuses a store already committed to OPFS with `CommittedStoreUnreachableError` rather than open an empty `idb://` sibling. Moving a store to another backend is a deliberate destroy — `client.destroy()`, or `destroyStoreArtifacts(storePath)` for a store nobody holds — followed by a fresh boot that re-syncs from the server and mints on whatever the probe then grants. There is no automatic migration and no in-place conversion.
The lazy-relation lifecycle methods **are** proxied — but read the multi-tab semantics before you call `desync`. The engine is **shared**, so a `desync(tableKey)` issued from one tab tears the consistency group down for **every** attached tab: that is inherent to `desync`’s group-wide revert, and under a shared engine “the group” is engine-wide. When the group is an **ephemeral** delivery window, reach for `client.discardEphemeral(tableKey)` instead — the scoped, **multi-tab-safe** finalize. It drops that ephemeral relation’s local rows and reverts it to dormant, refuses a group with any persistent member (naming the offender), and is safe under a shared engine because an ephemeral window is per-delivery-session and inherently single-consumer: nothing durable, and no other tab, depends on it. The local drop is lifecycle-only — post-finalize non-redelivery is the server gate’s guarantee (e.g. a consumed server-owned cursor), not this method’s.
Boot observability crosses too (ADR-0034). `attachSyncClient` takes the `onBootReport` option — fired once with the worker engine’s finalized `BootReport`, but **only if this tab is attached when the boot finalizes** (the one-shot broadcast). Every attached client also exposes `client.bootReport()`, which **pulls** the engine’s most recent completed report over the bridge. Pull is the primitive because a tab that attaches **after** the boot never receives the push: it reads the boot it never witnessed via `bootReport()`, which returns the engine’s stored report regardless of when the tab attached.
The one exception is the **inspection read surface** — `client.rawQuery(sql, params)` and `client.rawExec(sql)` — which *is* proxied: the statement is executed in the worker (where PGlite lives) and the `Results` cross back. It is identical to the in-process client, and it is for **inspection only** (debug pages, REPLs, ad-hoc counts): statements run raw against the local store, bypassing the mutation journal and optimistic overlay, and any write stays local and never converges — for app data reads use the live-rows hooks. `client.pglite` itself stays unavailable. `replAdapter(client)` shapes this surface into the `{ query, exec }` duck `@electric-sql/pglite-repl`’s `` expects, so a SQL REPL works unchanged in worker mode (each statement routed through the bridge).
Everything the engine emits crosses on **one broadcast event channel**: status, per-group readiness, conflict, quarantine, reject, schema-change, and the debug rail — re-exposed by `attachSyncClient` as the same `onStatusChange`/`onConflict`/… callbacks the in-process client takes. The bridge serializes through a `BridgeCodec` seam; the shipped default is the v1 `identityCodec`, and a columnar/transferable codec is a documented future swap (a non-goal today).
### Live queries cross as diffs, not resends
[Section titled “Live queries cross as diffs, not resends”](#live-queries-cross-as-diffs-not-resends)
Live-query results cross the bridge **diff-shaped** — `{order, added, changed, removed}` — computed in the worker with PGlite’s `live.incrementalQuery` for single-PK queries (a keyless query falls back to remove-all + add-all, never a silent full resend). The tab-side materializer **preserves row identity**: an unchanged row keeps the same object reference (`===`), so a memoized React row skips re-rendering even though the update crossed a thread boundary.
## Boot stages: `localReadReady` → `writeReady` → `ready`
[Section titled “Boot stages: localReadReady → writeReady → ready”](#boot-stages-localreadready--writeready--ready)
The client exposes the boot as monotonic, idempotent stage promises, each of which a late attach resolves off its `attach-ack` fold (the engine crosses each stage once; every tab observes the same sequence):
* **`localReadReady`** — the store is open and its schema is compatible; **cached reads are safe, with zero network**. `attachSyncClient` resolves here. Offline boots resolve this stage and stop.
* **`writeReady`** — the write runtime + boot recovery have completed; enqueue is safe. Write methods await it internally, so you never gate writes yourself.
* **`ready`** — **every eager group is caught up** (a fully-consistent whole-sync paint). Unchanged: `auth-needed` and `degraded` do **not** resolve it, and a tab attaching **after** the engine first became ready gets an immediately-resolved `ready`.
In worker mode `writeReady`/`bootSettled` cross in the engine’s background tail after the ack, announced to attached tabs as one-shot **milestone** messages (and folded into a late attach’s ack); a tail failure crosses as a **milestone-error** so the matching stage rejects rather than hanging. Worker mode additionally exposes **per-group readiness** so an app can drive progressive paint: `await client.groupReady(tableKey)` for one group, or read `status.groups` for the whole set. See [Initial catch-up and the alignment trade](/start/operating-in-production/) for how a group reaches its floor.
## The spare store is a pre-spawned worker
[Section titled “The spare store is a pre-spawned worker”](#the-spare-store-is-a-pre-spawned-worker)
The boot optimizations from [Operating in production](/start/operating-in-production/) translate directly, and the prefetch overlap becomes **internal** to the worker:
* The userId→storeId registry stays **tab-side** in `localStorage` — binding resolves *before* attach, which the SharedWorker naming needs anyway.
* The **spare store** becomes a pre-spawned **schemaless worker** at login-screen mount: create + initdb run inside it, off every thread that matters. Claiming it = bind the id, attach, push config + token.
* On the claim, the tab sets the `freshStore` hint (`attachSyncClient({ freshStore: true })`) **only** when it knows the store is a claimed schemaless spare — never for a mapped or returning store. The worker then overlaps the **shape catch-up** with its local boot phases: shape streams start (memory-buffered inbox) the moment config + token arrive, in parallel with schema apply / journal recovery / store-version reconcile, and the buffered commits are gated on `dbReady` and drained in one train to the [ADR-0031](/start/operating-in-production/) catch-up floor. Boot for a far-from-database user is then bounded by `max(create+schema, catch-up)` instead of their sum. The same seam works in in-process mode.
The boot rail stamps this sequence: `boot spare store ensured`, `boot mapped store prewarm`, `boot store claimed`, `boot shape prefetch start`, and `boot commits opened`.
### Pre-opening a warm store, not just a fresh spare
[Section titled “Pre-opening a warm store, not just a fresh spare”](#pre-opening-a-warm-store-not-just-a-fresh-spare)
`provisionSyncWorker({ worker, storePath })` is the pre-open primitive behind that spare — it runs PGlite `create`/initdb inside the worker and holds the raw store idle for the first `attachSyncClient` to adopt — but it is **not only for fresh spares**. Adoption is keyed purely on the **storePath**, not on whether the store has ever been written: a **returning** user whose store is already populated adopts a pre-opened store exactly as a first-time user does. So call `provisionSyncWorker` the moment the store identity is known — at login-screen mount for a returning user, say — to overlap the WASM/PGlite open with auth and UI startup on that **warm persisted store**. (You still omit the `freshStore` hint for a returning store: that hint governs the shape-catch-up overlap above, not the pre-open, and is only ever true for a claimed schemaless spare.)
One qualifier follows from the fixed-backend rule above: pre-opening accelerates a store whose backend **matches what the provisioner would mint**. The common returning user — a store committed to OPFS — keeps the whole head start. A **granted** provision over a store that lives on **IndexedDB** deliberately declines instead: it runs the same non-creating idb existence check the boot classifier keys on, finds that store, and mints nothing, because the backend was fixed at the store’s first mint. Nothing breaks — the ordinary attach opens that store in place on idbfs, just without the pre-open head start.
Adoption is **exact-match**: the boot claims the pre-opened engine only when the `attachSyncClient` `storePath` equals the provisioned one. A mismatch is not an error — the attach falls back to a fresh create and the pre-open is simply discarded, so a wrong guess is wasted work, never a crossed or corrupted store. That safety is also the technique’s limit. Pre-opening **overlaps** the open; it does not remove it, and it cannot start before you know which store to open. Do not manufacture an identity early by parsing another library’s private storage — an auth provider’s `localStorage` layout, for instance — to pre-open sooner. Resolve the store id from your own userId→storeId registry (the same tab-side binding the attach uses) and provision only once it is genuinely known.
Pass the same `worker` input as attach — the factory form, so elected-mode recovery is armed for provisioning too. On Chromium/Firefox, provisioning participates in the same election coordinator; the elected engine is auto-derived from the SharedWorker’s own script URL just as in attach (supply `createEngineWorker` only for non-module/underivable entries). On Safari the engine runs in the SharedWorker directly; there is no keepalive there, so the factory only becomes a recovery seam if you also set `bridgeSilenceMs` (otherwise a dead SharedWorker is recovered by reload, not automatically).
The provision is bounded by one deadline, `provisionExpiryMs` (default 60000): it retires an abandoned warmed provision’s claim in elected mode, **and** — in both modes — settles the returned promise with the typed `ProvisionExpiredError` if nothing acked in that window. So a provision behind a dead SharedWorker connection fails loudly instead of hanging forever. The deadline bounds **your promise**; what becomes of the worker’s create attempt follows the placement. Where the engine runs in the SharedWorker itself (Safari, or a declared-idbfs store) the attempt is left running — an in-flight store open cannot be safely abandoned — so the attach that follows adopts that create if it completed and waits on it if it is genuinely stuck, and a retry re-acks the same attempt rather than starting a second open. In elected placement the same deadline releases the provision’s claim, and when that is the last claim the coordinator retires the elected engine (teardown, then terminate, which releases the VFS handle), so the attach that follows elects a fresh engine and opens the store again. An attach that adopted the coordinator before the deadline holds a claim of its own, which keeps that engine — and its attempt — alive.
The overlap is measurable: an adopted store reports its pre-open in the `BootReport` `provision` block — `provision.initdbMs` is the create cost that ran off-thread before this boot, and `provision.provisionedMsBeforeBoot` is how long the store sat ready before the attach claimed it. See [The structured BootReport](/start/operating-in-production/#the-structured-bootreport--measure-before-you-optimize).
## Debugging a worker: the forwarded rail
[Section titled “Debugging a worker: the forwarded rail”](#debugging-a-worker-the-forwarded-rail)
A `SharedWorker`’s own `console` is invisible to the page — you can only see it under `chrome://inspect`. So the worker **forwards** its debug rail to every attached tab over the event channel, stamped with the **worker’s** monotonic clock and origin-tagged: each tab re-prints the lines as `[pgxsinkit·w ms] …`, gated by that tab’s own `globalThis.__pgxsinkitDebug`. Without the forwarding the entire operability story goes dark; with it, the full write/read/boot rail from [Operating in production](/start/operating-in-production/) reads the same in worker mode, just origin-tagged.
The front half of boot (provision, schema exec) runs on the **first** attach, before any debug-enabled tab is listening — so those opening rail lines used to vanish. `defineSyncWorker` now buffers pre-attach lines in a bounded ring (last 500, worker-clock stamped) and replays them, `[replay]`-marked, to the first attaching tab (ADR-0034). The back half already streams live over the bridge, so together the whole boot — its front half included — reaches the first attached tab.
The same invisibility applies to **network traffic**: the worker owns every shape request and token refresh, and browsers do not show a `SharedWorker`’s requests in the page’s Network panel. If the rail shows `shape request start` lines but the tab’s Network panel shows nothing, that is worker mode working as designed — not “no network calls”. Open the worker’s **own** DevTools (`chrome://inspect/#workers` → the store-named worker → inspect): its Network and Console panels carry the real requests, status codes, and any unforwarded errors (a CORS rejection, for example, is only visible there).
# The write path
> Stage locally, flush a batch, apply in a single in-database function. One path, no backends.
There is exactly **one** write path, and it is deliberately not configurable. Earlier versions of pgxsinkit experimented with several write strategies; the experiments converged on one clear winner — **push the apply logic into the database and consume mutations in bulk** — and the alternatives were deleted. There is no selectable backend, no strategy enum, and no per-table CRUD. (See [ADR-0002](/decisions/) for the full rationale.)
## The flow
[Section titled “The flow”](#the-flow)
1. **Stage locally.** A client write is recorded into a local **overlay** table (the optimistic value the UI reads) and a durable **mutation journal** in PGlite. The app never mutates a synced table directly.
2. **Flush a batch.** The client sends one or more journaled mutations to the server’s write route as a batch: `POST /api/mutations`.
3. **Validate.** The server validates every mutation against the registry’s Zod schema, rejecting client-supplied server-managed or projected-away fields.
4. **Apply in one call.** Inside a transaction, the API calls the single in-database function `pgxsinkit_apply_mutations(...)`, which applies the whole batch. Constraints are deferred for the batch (`SET CONSTRAINTS ALL DEFERRED`) so intra-batch foreign keys resolve.
5. **Acknowledge.** The API returns an ack per mutation, including the server `updated_at_us`.
6. **Clear the overlay on echo.** The optimistic overlay row is cleared only once the read path streams the row back with a server `updated_at_us` at least as new as the acked value — so the UI never flickers back to a stale value.
### Bigint values across JSON
[Section titled “Bigint values across JSON”](#bigint-values-across-json)
Drizzle columns declared with `{ mode: "bigint" }` remain normal `bigint` values in typed client code. When a mutation crosses the JSON boundary, pgxsinkit serializes those values as exact decimal strings; the server’s registry-derived validator coerces the strings back to `bigint` for validation before the original JSON payload reaches the PostgreSQL apply path. This applies to application-owned bigint fields as well as pgxsinkit’s managed microsecond fields. Consumers should not convert them to JavaScript `number` or add a custom transport encoding.
### Array columns
[Section titled “Array columns”](#array-columns)
A **one-dimensional** array column (`uuid("source_ids").array()`) is written like any other: the client sends a JSON array, and the apply function expands it element-wise and applies the array cast. `[]` stores an empty array, JSON `null` stores `NULL`, omitting the key leaves the column untouched, and element order is preserved. Multi-dimensional arrays, an array primary-key column, and a managed field targeting an array column are all refused when you generate the migration, naming the table and column — never silently mis-written.
## Why everything is in the database
[Section titled “Why everything is in the database”](#why-everything-is-in-the-database)
Putting the apply logic in PL/pgSQL was the toolkit’s central finding: it minimises round-trips, keeps the batch atomic, and lets row-level security and managed-field logic run where the data lives. The function is the **mutation applier**; provisioning it is a migration step you generate from your registry with the `pgxsinkit-generate` CLI (`bunx pgxsinkit-generate …`, see [Getting started](/start/getting-started/)).
### Who may execute it: deny-by-default
[Section titled “Who may execute it: deny-by-default”](#who-may-execute-it-deny-by-default)
The applier takes the request’s claims as an argument and **trusts them** — correct for your server, which verified them, and catastrophic for any other caller, who would simply choose its own. So the generated migration revokes `EXECUTE` from `PUBLIC` and from the Supabase roles (`anon`, `authenticated`, `service_role`) immediately after creating the function — then enumerates the installed function’s actual grantees and revokes every one that is neither its owner nor a role you named, so a grant inherited from your own `ALTER DEFAULT PRIVILEGES` cannot survive an install either — and grants it only to the roles you name with `--grant-execute-to`; the default is owner-only. Those statements live **inside the fingerprinted body**, so a stale, still-PUBLIC install cannot pass the self-verification, and the grant list has to match in three places (generate, CI `--check`, and `applyFunctionGrantExecuteTo`). See [ADR-0054](/decisions/) for the rationale, and [Deploying the server](/start/deploying-the-server/#the-apply-function-is-deny-by-default--name-your-servers-database-role) for the naming and the `42501` failure mode.
The applier **verifies itself on every call** (ADR-0030). The migration stamps it with a fingerprint of its own DDL (a `COMMENT ON FUNCTION`); on each apply the server passes the fingerprint it expects for its registry + codegen, and the function compares that against its own stamped comment **before it touches any table** — raising `PXS01` and applying nothing if they disagree (a stale, or hand-installed unfingerprinted, function is refused). Because the check rides the existing call it costs no extra round trip, needs no startup query, and has no read-then-call race. Provisioning-order and per-worker startup cost are tuned with the `deployment` profile on `createSyncServer` (`startupVerification`, `operationsLog`); its defaults preserve long-lived-host behavior, and the serverless posture (`startupVerification: "deploy-time"`, `operationsLog: "enabled" | "disabled"`) sends zero queries before the mutation transaction itself. Deploy-time drift is still caught in CI with `pgxsinkit-generate --check`.
## Row-level security
[Section titled “Row-level security”](#row-level-security)
When any synced table has RLS policies (or governance managed fields that need the actor), the write API resolves JWT claims via `resolveAuthClaims` and passes them into the apply function, which sets the Supabase-style auth context (`role`, `request.jwt.claims`) for the duration of the batch and restores the caller’s prior context afterwards. Missing claims for an RLS-enabled table fail the batch with 401.
## Managed fields
[Section titled “Managed fields”](#managed-fields)
Governance “managed fields” (e.g. `ownerId` via `authClaim` at claimPath `["sub"]`, `createdAtUs`/`updatedAtUs` via `nowMicroseconds`) are written **by the database**, not the client. The server strips any client-supplied values for these fields before applying. (`authClaim` is the single claim-stamping strategy: a value read from the verified JWT claims at a JSON path — `["sub"]` is the auth subject, `["app_metadata","person_id"]` an app-minted identity; the old `auth.uid()` owner is just `["sub"]`.)
This shapes what you pass to a `create`. `SyncTableCreateInput` **omits** every managed-on-create field, so you supply only the non-managed columns — for a chat message that is just `{ id, channelId, body }`; `authorId`, `createdAt`, `updatedAt` are stamped server-side. Including a managed field in the payload is an error (the API rejects it), and the create-validation schema does **not** require them — a `NOT NULL` managed column (an owner/author with no SQL default) is still a valid create with the field absent.
The optimistic overlay does not wait for the server, though. When you call `.create(...)`, the runtime fills the overlay row’s managed fields locally so the UI renders a complete, attributed row this frame: `nowMicroseconds` fields take the client clock, and an `authClaim` field takes the decoded claim at its path (for `["sub"]`, the current session’s subject — the same value the server stamps). Because both sides resolve to the same identity, the value never flips when the server’s row echoes back. The flushed payload still omits these fields (it is built from your original input, not the overlay), so the server remains authoritative.
## Terminal dispositions and rollback
[Section titled “Terminal dispositions and rollback”](#terminal-dispositions-and-rollback)
Most writes ack and clear on echo. Two outcomes are **terminal** — the server will not accept the write as-is — and each keeps the optimistic overlay so the edit is never silently lost, surfaces a callback, and now has a **symmetric discard** that rolls the overlay back:
* **`conflicted`** (ADR-0015, the `reject-if-stale` policy) — a stale edit the server declined because the row moved on. Fires `onConflict`; the app shows a resolve/diff UI and either resolves it as a new write or rolls it back with **`discardConflict(table, entityKey)`**.
* **`quarantined`** (ADR-0006) — a structurally-rejected write (a 4xx the server will never accept: a validation failure, or a permanent policy denial such as an RLS `42501`). Fires `onQuarantine`; the app surfaces it and either re-authors + resubmits or rolls it back with **`discardQuarantined(table, entityKey)`**.
Both discards do the same thing for their status: delete the entity’s terminal journal rows and clear its kept overlay row, so the read model falls back to the synced (server) value and the entity **accepts new mutations again** (a lingering terminal row otherwise blocks a re-create and chains a later update onto a dead head). The overlay is cleared only when no *other* journal row still owes the entity, so a discard never strips an overlay a still-pending write depends on.
```ts
const client = await createSyncClient({
registry,
// …
onQuarantine: async (details) => {
// surface to the user, then either resubmit a corrected write…
// …or roll the optimistic edit back:
await client.discardQuarantined(details[0].tableName, details[0].entityKey);
},
});
```
Because quarantine now has a real rollback, route a **permanent policy denial (e.g. RLS `42501`) to `quarantined`** — there is no longer any reason to mis-route it to `conflicted` just to borrow a discard affordance. Reserve `conflicted` for genuine stale-write conflicts under `reject-if-stale`.
#### `42501` is two different failures
[Section titled “42501 is two different failures”](#42501-is-two-different-failures)
The same SQLSTATE covers a row-level denial and a function-level one, and they are diagnosed in opposite directions:
* **Row-level (RLS): a policy declined this write, for *specific* rows and this actor** (the message names the table — a row-level-security violation). Other writes succeed. This is the per-mutation denial routed to `quarantined` above.
* **Function-level (ACL): `permission denied for function pgxsinkit_apply_mutations`, on *every* write.** Nothing about the row or the actor’s claims is involved — the database role your **server** connects as simply may not execute the applier. Fix it by regenerating the migration with `--grant-execute-to ` (see [Deploying the server](/start/deploying-the-server/)), never by granting it by hand.
“Some writes are denied” versus “all writes are denied” is the whole discriminator.
## Blind pessimistic update
[Section titled “Blind pessimistic update”](#blind-pessimistic-update)
A **pessimistic write unit** (`client.transaction({ mode: "pessimistic" }, …)`, ADR-0022) flush-routes to the authoritative endpoint and resolves only once the server has decided — the block returns each member’s `acked` / `conflicted` / `rejected` outcome. Its table handles carry `create` / `update` / `delete`, and one more: **`updateBlind`**.
Ordinary `update` requires the entity to be present in the actor’s **local read model** — it seeds the optimistic overlay from the local row and captures the base server version to detect a stale write. But some legitimate writers target a row their own read shape **excludes**. The classic case is anonymity-scoped moderation: a moderator flags a report, but the report’s row streams only to the reporter’s projection — the moderator holds an identity-free projection with no matching row. The write target simply never appears locally, so there is no base row to update.
The old way to satisfy `update` here was to **seed a phantom base row** just to pass the presence check. That row then lingered forever: no Electric echo ever arrives for a row you can’t see, so the acked journal entry and its overlay never clear (the acked-row cleanup is gated on a synced echo reaching the acked version), and the phantom row stays in your read model.
`updateBlind(entityKey, patch)` is the fix. It:
* **plans a journal row only** — no optimistic overlay, so nothing enters the read model and nothing can linger there;
* **skips the local-presence check** (there is no base to capture; the server-side `/unit` expander is authoritative for the result);
* is **pessimistic-only** — it is meaningful solely inside a `transaction({ mode: "pessimistic" })` block (or over a statically-pessimistic table). An optimistic-routed blind write has nothing to show optimistically and no base to converge, so it **throws at enqueue**;
* **retires without an echo** — once the authoritative unit acks the row, reconcile drops the journal entry directly (no visible row ever converges for it), so it is crash-safe: any later convergence tick clears it.
```ts
await client.transaction({ mode: "pessimistic" }, (tx) => {
// `reportId` is not in this moderator's read shape — no local row, no overlay.
tx.tables.reports.updateBlind({ id: reportId }, { status: "hidden" });
});
```
A `conflicted` blind write stays dischargeable via `discardConflict`; a `rejected` one is surfaced via `onReject` — both with no overlay to clean up.
### The write-only pattern
[Section titled “The write-only pattern”](#the-write-only-pattern)
Because the local journal / overlay / synced tables are provisioned for **every** registered `readwrite` entry — `subscription` only gates Electric streaming, not the local DDL — a `readwrite` entry declared `subscription: "lazy"` and **never activated** still flushes, acks, and retires blind updates cleanly, with its consistency group never opened. That combination is a **write-only table**: you author to it (through the authoritative endpoint) without ever streaming a row of it into the client. Nothing reads locally, nothing shows optimistically, and no acked row lingers.
### Lazy read/write groups need an echo
[Section titled “Lazy read/write groups need an echo”](#lazy-readwrite-groups-need-an-echo)
Ordinary optimistic `create`, `update`, and `delete` operations maintain an overlay, and an acknowledged journal row retires only after the committed server version returns through Electric. That echo can only arrive over an open shape — so the target’s `subscription: "lazy"` consistency group has to be active by the time the server commits.
You do not have to arrange that yourself. **An ordinary write activates its target’s lazy group automatically.** A write is a reference to its target, and referencing a lazy relation activates its whole consistency group — exactly as a read does. The client fires this activation at enqueue (fire-and-forget, so the write never blocks on the network); the group only has to be open by the time the echo returns, and a start that briefly fails self-heals on the group’s next activation. The manual “mount an activator live query before first write” step is no longer needed.
`updateBlind` stays the deliberate exception: it plans a journal row with no overlay and no echo barrier, retires on the authoritative ack, and does **not** activate its group — that is the whole point of the [write-only pattern](#the-write-only-pattern) (a fully provisioned local table that never streams a row).
The auth angle still holds. A write-triggered activation uses the claims available at that moment, so a group whose row filters deny anonymous callers should not be written before the session exists — otherwise it activates against unauthenticated claims. Activating a claims-denied group with no token now logs a console warning naming the group; see [Gate authenticated lazy groups until auth is resolved](/concepts/registry-entry-options/#subscription) for the gating pattern (still the right approach for authenticated reads, and for authenticated-only writes).
## Pausing convergence (an offline toggle)
[Section titled “Pausing convergence (an offline toggle)”](#pausing-convergence-an-offline-toggle)
The convergence driver decides *when* to run flush/reconcile by asking its `ConvergenceTrigger`’s `shouldConverge()`. That is the seam for an app-built “offline” mode: wrap your trigger so `shouldConverge()` returns `false` while the app is offline. Writes still stage into the local journal (the optimistic overlay updates as usual) — they simply are not sent — so the journal fills visibly while offline. Flip back online and fire one signal, and the queued writes flush and reconcile. No teardown, no lost edits.
This pauses the **outbound** half. The **inbound** read path (the Electric shape subscriptions) has no pause/resume today — `client.stop()` halts it but also closes the local store — so an app offline toggle built this way still *receives* remote changes. Suspending both directions without tearing down the store is a planned capability.
## What this means for you
[Section titled “What this means for you”](#what-this-means-for-you)
* Don’t write to synced tables from app code — stage through the mutation runtime; all writes flush to the one write route (`POST /api/mutations`).
# Design decisions
> Architecture Decision Records for pgxsinkit.
pgxsinkit records significant, hard-to-reverse choices as Architecture Decision Records (ADRs). The canonical copies live in [`docs/adr/`](https://github.com/pgxsinkit/pgxsinkit/tree/main/docs/adr) in the repository; the list below is generated from them (`bun run docs:adr`) and verified on every docs build, so it stays complete as ADRs are added.
* [ADR-0001 — Unified TypeScript release, versioning, and tooling standard (emergent / conform-ed / pgxsinkit)](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0001-unified-ts-release-versioning-tooling-standard.md)
* [ADR-0002 — Single in-database write path; retire the strategy/backend/artifact seam](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0002-single-in-database-write-path.md)
* [ADR-0003 — Secured sync ingress: fail closed, one verified-claims adapter](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0003-secured-sync-ingress.md)
* [ADR-0004 — One registry interpreter: shared resolvers and a registry fingerprint](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0004-one-registry-interpreter.md)
* [ADR-0005 — Mutation convergence: mechanism primitives plus an opt-in driver](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0005-mutation-convergence.md)
* [ADR-0006 — Local schema evolution and mutation compatibility](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0006-local-schema-evolution.md)
* [ADR-0007 — Absorb sync-engine into the client](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0007-absorb-sync-engine.md)
* [ADR-0008 — Documentation proves the product interface](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0008-docs-prove-interface.md)
* [ADR-0009 — Internalize the read-path sync (break with pglite-sync)](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0009-internalize-read-path-sync.md)
* [ADR-0010 — Convergence barrier: resolve optimistic state by Server version, not key-match](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0010-convergence-barrier.md)
* [ADR-0011 — The Convergence model: one owner of local convergence, derived not stored](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0011-convergence-model.md)
* [ADR-0012 — Canonical entity identity and a composite-PK-correct applier](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0012-canonical-entity-identity.md)
* [ADR-0013 — Read-path identity: refresh the token, never freeze it at boot](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0013-read-path-identity-refresh.md)
* [ADR-0014 — Bulk apply on both paths, without the set-based ordering hazard](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0014-bulk-apply-ordering-safety.md)
* [ADR-0015 — Stale-write conflict policy: detect by Server version, choose per table](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0015-stale-write-conflict-policy.md)
* [ADR-0016 — Deferred read-path optimisations and their triggers to revisit](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0016-deferred-read-path-optimisations.md)
* [ADR-0017 — Framework-neutral server: drop the Hono dependency](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0017-framework-neutral-server-drop-hono.md)
* [ADR-0018 — Apply-function drift detection via an embedded fingerprint](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0018-apply-function-drift-detection.md)
* [ADR-0019 — Row filters as type-safe Drizzle fragments → parameterized Electric `where`](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0019-row-filters-as-drizzle-fragments.md)
* [ADR-0020 — Index-friendly RLS: `= ANY(ARRAY(subquery))` for runtime-resolved id-sets](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0020-index-friendly-rls-any-array.md)
* [ADR-0021 — Sync lifecycle: subscription-timing and retention as orthogonal axes](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0021-lazy-ephemeral-sync-lifecycle.md)
* [ADR-0022 — Pessimistic write-units: server-authoritative writes via flush-routing](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0022-pessimistic-write-units.md)
* [ADR-0023 — Subquery move-out: applying Electric’s tagged-subquery eviction in the local store](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0023-subquery-move-out-tagged-reconciliation.md)
* [ADR-0024 — Subquery move-in: applying Electric’s live snapshot rows in the local store](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0024-subquery-move-in-snapshot-rows.md)
* [ADR-0025 — Per-client mode projection: one authoritative registry, readonly projections per client](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0025-per-client-mode-projection.md)
* [ADR-0026 — One claim-stamping managed-field strategy: `authClaim`](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0026-authclaim-managed-field-strategy.md)
* [ADR-0027 — Read projections: a derived second client shape over an owned table](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0027-read-projections.md)
* [ADR-0028 — Own the sync engine outright (upstream compatibility is an anti-goal)](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0028-own-the-sync-engine-outright.md)
* [ADR-0029 — The registry item is the ingest engine’s spec](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0029-registry-item-driven-ingest-engine.md)
* [ADR-0030 — Self-verifying apply function and the serverless deployment profile](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0030-self-verifying-apply-function-deployment-profile.md)
* [ADR-0031 — Catch-up commit-floor alignment for CDN-cached shape watermarks](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0031-catchup-watermark-alignment.md)
* [ADR-0032 — The whole sync engine moves into a SharedWorker](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0032-sync-engine-in-shared-worker.md)
* [ADR-0033 — Live-tail sibling nudge: refresh quiet-shape watermarks instead of waiting out their long-polls](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0033-live-tail-sibling-nudge.md)
* [ADR-0034 — Boot observability: a structured, versioned BootReport for every client boot](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0034-boot-observability-bootreport.md)
* [ADR-0035 — Local store export: store backup, diagnostic dump, and data export](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0035-local-store-export.md)
* [ADR-0036 — Store path contract: derived storage backend, no client-visible memory stores](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0036-store-path-contract.md)
* [ADR-0037 — Vite library build for the React package](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0037-vite-library-build-for-react-package.md)
* [ADR-0038 — Manifest-derived externals for the public package bundles](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0038-manifest-derived-externals-for-public-bundles.md)
* [ADR-0039 — Ordinary writes activate their lazy group; claims-dependent groups warn on anonymous activation](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0039-write-activation-and-anonymous-activation-diagnostic.md)
* [ADR-0040 — A worker-owned live-query manager: awaited teardown, deduplication, and bounded keep-alive](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0040-shared-live-query-manager.md)
* [ADR-0041 — Staged boot readiness: local-read before write and network](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0041-staged-boot-readiness.md)
* [ADR-0042 — Session-scoped sync metadata for ephemeral groups](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0042-session-scoped-sync-metadata-for-ephemeral-groups.md)
* [ADR-0043 — Adopted stores whose persistence cannot be introspected need a named acknowledgment](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0043-adopted-store-persistence-acknowledgment.md)
* [ADR-0044 — The attach client proxies one-shot reads; isSynced stays a refusal](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0044-attach-client-one-shot-reads.md)
* [ADR-0045 — Per-table `applyMode` for locally-derived rows](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0045-per-table-apply-mode-for-locally-derived-rows.md)
* [ADR-0046 — Restore boots online when the recovered journal is clean](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0046-restore-boots-online-when-journal-clean.md)
* [ADR-0047 — Relaxed durability is the default for the local store, declared on the registry](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0047-relaxed-durability-default.md)
* [ADR-0048 — `opfs-repacked` — a packed, recreate-only OPFS VFS for PGlite](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0048-opfs-repacked-vfs.md)
* [ADR-0049 — Capability-driven engine placement: opfs-repacked on every platform](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0049-capability-driven-engine-placement.md)
* [ADR-0050 — Storage declaration transport and path-addressed store teardown](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0050-storage-declaration-transport.md)
* [ADR-0051 — Content-addressed validate caching and per-file unit-test selection](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0051-content-addressed-test-selection.md)
* [ADR-0052 — Row classification and registry invariants](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0052-row-classification-and-registry-invariants.md)
* [ADR-0053 — Queue-shaped event ingestion as a first-class lane](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0053-queue-shaped-event-ingestion.md)
* [ADR-0054 — The apply function is deny-by-default](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0054-apply-function-deny-by-default.md)
# Demo & harness
> The demo app and verification suites exist to prove and harden the toolkit — they are not the product.
The repository contains a demo app and a verification harness. Neither is the product — the [`@pgxsinkit/*` packages](/packages/) are. These exist to make the toolkit demonstrable and to keep it honest against real infrastructure.
## The demo app (`apps/board`)
[Section titled “The demo app (apps/board)”](#the-demo-app-appsboard)
`apps/board` is a Linear-style issue board with realtime chat — the **substantial** demo. It drives the full read and write paths against a trimmed, but version-matched, self-hosted **Supabase + Electric** stack: GoTrue auth, an Envoy gateway, the two toolkit edge functions (`board-write` for the governed mutation ingress, `board-sync` for the registry-filtered Electric shape proxy), Postgres, and Electric. Its job is twofold:
* **Example code** — a working reference for wiring `createSyncClient`, staging and flushing optimistic writes, reading reactively from PGlite, and surfacing convergence/conflict state.
* **A hands-on view of the behaviour** — somewhere to watch offline-first sync, membership fan-out, optimistic writes, and conflict convergence working end-to-end.
It uses a Linear-style domain (Teams, Issues, Channels, Messages). It is one *consumer* of pgxsinkit — not pgxsinkit itself, and not any downstream product’s data layer. Run it:
```bash
mise install && bun install
bun run infra:up # the board stack (Supabase + Electric) + the board's migrations
bun run seed:board # GoTrue identities + deterministic fixtures
bun run dev:board # the Vite client
```
The same board code runs against **managed BaaS — Supabase Cloud + Electric Cloud** — via a documented bring-your-own-credentials path: the endpoints are fully env-driven (`SUPABASE_URL`/keys/ DB URL/`ELECTRIC_SHAPE_URL`), and the local compose is just a dev mirror. The board uses Supabase’s **new asymmetric auth** (ES256 session tokens verified via JWKS, `sb_publishable_`/`sb_secret_` keys — no legacy HS256). See board [ADR-0007 — Supabase asymmetric auth only](https://github.com/pgxsinkit/pgxsinkit/blob/main/apps/board/docs/adr/0007-supabase-asymmetric-auth-only.md) and [ADR-0008 — Run the board on managed BaaS](https://github.com/pgxsinkit/pgxsinkit/blob/main/apps/board/docs/adr/0008-board-on-managed-baas.md). The live cloud run is supported and documented, not CI-gated (it needs real Supabase + Electric Cloud credentials). A public, always-on instance is hosted at [pgxsinkit.github.io/demo](https://pgxsinkit.github.io/demo/) and reset nightly — board [ADR-0009](https://github.com/pgxsinkit/pgxsinkit/blob/main/apps/board/docs/adr/0009-hosted-public-demo.md), [The hosted board /demo](/demo-and-harness/hosted-demo/).
The **minimal** reference (the `apps/write-api` Bun server) runs against the toolkit harness stack instead — the smallest possible `@pgxsinkit/server` deployment:
```bash
cp .env.example .env
bun run infra:harness:up # PostgreSQL + Electric reference stack (allow_subqueries,tagged_subqueries)
bun run dev:api # the @pgxsinkit/server reference server
```
## How the toolkit is verified
[Section titled “How the toolkit is verified”](#how-the-toolkit-is-verified)
The toolkit is proven against **real** services in Podman compose stacks — never mocks. Three verification lanes back it:
* **Integration suites** (`tests/integration`) stand up an isolated, ephemeral PostgreSQL + Electric stack and assert the topology end-to-end: write validation, the in-database apply, membership fan-out, RLS auth context, and eventual convergence in local PGlite.
* **Board demo smoke** drives the demo’s full deployment topology — GoTrue → Envoy → the bundled edge functions → Electric — proving the governed path the unit and integration suites stub out (auth, the proxy’s claim-driven read filter, and the apply’s RLS actor switch).
* **Performance lab** (`apps/perf-lab`, `tests/performance`) measures the write/sync cycle under load.
Each lane provisions its own services, applies the current schema, runs, and tears everything down — so a green suite means the whole topology, not a mocked slice of it, actually converged.
# Run the board on managed BaaS
> Run the board demo against real Supabase Cloud + Electric Cloud with your own credentials.
The board demo (`apps/board`) runs against **managed BaaS — Supabase Cloud + Electric Cloud** with the **same code** it runs locally; you supply your own credentials. The local compose stack ([Demo & harness](/demo-and-harness/)) is just a faithful, version-matched mirror of that managed shape.
It is **not** a one-command push. It is: do a little one-time console setup, fill in a credentials file, then run one deploy command — after which `bun run dev:board` drives the cloud backend.
For a public, always-on, browser-ready instance of this same setup — served at [pgxsinkit.github.io/demo](https://pgxsinkit.github.io/demo/) and reset nightly — see [The hosted board /demo](/demo-and-harness/hosted-demo/).
## What it looks like
[Section titled “What it looks like”](#what-it-looks-like)
```bash
# one-time (manual console steps — see the runbook):
# • create a Supabase project • create an Electric Cloud source on its database
cp board.cloud.env.example board.cloud.env # fill in your project + Electric Cloud values
bun run board:cloud:deploy # migrate → secrets → deploy the three edge functions → cron → seed
bun run dev:board # local Vite, pointed at the cloud backend
```
`board:cloud:deploy` is a thin wrapper over the repeatable steps; each is also its own `board:cloud:migrate` / `:secrets` / `:functions` / `:cron` / `:seed` script.
Use `bun run board:cloud:preview` to build the board with the cloud browser configuration and serve the compiled artifact locally at `http://localhost:5173`. `board:cloud:dev` remains the source-mode Vite server. Every Supabase CLI mutation receives the explicit `BOARD_SUPABASE_PROJECT_REF`; the commands do not depend on whichever project another checkout may have linked. CLI authentication similarly comes from `BOARD_SUPABASE_ACCESS_TOKEN`, not global profile state, so separate Supabase accounts stay separate.
## How it fits together
[Section titled “How it fits together”](#how-it-fits-together)
* **Auth is Supabase’s new asymmetric model** — ES256 sessions verified against the project JWKS, with the new `sb_publishable_`/`sb_secret_` API keys (no HS256). The board functions are the single auth point; the gateway only translates the opaque keys into role JWTs. Board [ADR-0007](https://github.com/pgxsinkit/pgxsinkit/blob/main/apps/board/docs/adr/0007-supabase-asymmetric-auth-only.md).
* **The read path needs no toolkit change** — `board-sync` forwards to `https://api.electric-sql.cloud/v1/shape?source_id=…&secret=…`; the proxy only rewrites `where`/`columns`, so the Cloud source credentials ride through, server-side only.
* **The edge functions deploy as pre-built bundles** (`supabase/config.toml` entrypoints, `verify_jwt = false`), because the demo registry `@pgxsinkit/board-schema` is unpublished. Board [ADR-0008](https://github.com/pgxsinkit/pgxsinkit/blob/main/apps/board/docs/adr/0008-board-on-managed-baas.md).
* **The client sends its publishable key** via `@pgxsinkit/client`’s `requestHeaders` option, alongside the per-request `Authorization`.
* **The Event lane drains through a third function on this stack.** Locally the board runs the toolkit’s long-lived consumer runner (`bun run dev:board:consumer`); managed Supabase has no process to host one, so the cloud deploy adds `board-events-drain` — an edge function that runs one bounded [`drainOnce()`](/start/deploying-the-server/) pass per invocation. A **Supabase Cron schedule (every 10s) is the delivery guarantee**, and `board-write` **nudges** the function on enqueue so a click archives immediately; a lost nudge costs latency only. Its callers are machines with no session, so the gate is a shared secret (`BOARD_EVENTS_DRAIN_SECRET`) compared in constant time — set it in `board.cloud.env`.
Activate subqueries on your Electric Cloud source
The board’s membership-scoped shapes use a cross-table `where` subquery — a flagged Electric preview. On managed Electric Cloud it is **activated per source by Electric staff on request** (no self-serve toggle yet; default-on intended), so **ask Electric to enable subqueries for your source**. Until then a normal member’s shapes return `{"where":["Subqueries are not supported"]}` (an admin, all-rows, works). Or self-host Electric with the flags. See [The Electric subquery requirement](/concepts/electric-subqueries/).
## What’s verified, and what’s yours to verify
[Section titled “What’s verified, and what’s yours to verify”](#whats-verified-and-whats-yours-to-verify)
The **local** stack mirrors the cloud shape exactly and is covered by the board smoke (`bun run test:integration:board`, 8/8): the new-API-key flow, ES256/JWKS verification, and the full read/write topology. The **managed endpoints themselves** are operator-verified — they need your Supabase + Electric Cloud accounts, so the cloud run is supported and documented, not CI-gated.
## The full runbook
[Section titled “The full runbook”](#the-full-runbook)
Step-by-step (project creation, the Electric source, connection strings, the credentials file, and troubleshooting) is in [**docs/runbooks/board-on-cloud.md**](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/runbooks/board-on-cloud.md).
# The hosted board /demo
> How the public board demo is built into the docs deploy and reset nightly to stay clean.
The board runs as a public, always-on demo at **[pgxsinkit.github.io/demo](https://pgxsinkit.github.io/demo/)** — sign in as a seeded identity and try offline-first sync, membership fan-out, optimistic writes, and conflict convergence in the browser, with zero setup. It is the same `apps/board` code as the local and [cloud](/demo-and-harness/board-on-cloud/) runs, backed by a managed Supabase Cloud + Electric Cloud project. Board [ADR-0009](https://github.com/pgxsinkit/pgxsinkit/blob/main/apps/board/docs/adr/0009-hosted-public-demo.md).
## How it is published
[Section titled “How it is published”](#how-it-is-published)
The board is built into `apps/docs/dist/demo/` as a step in the **docs deploy** (`.github/workflows/docs.yml`), so the docs site and the demo deploy as **one artifact** to the `pgxsinkit.github.io` repo. The docs deploy replaces the whole publish (`force_orphan`), so co-publishing — not a second workflow — is what keeps the demo from being clobbered.
The board runs in [worker mode](/concepts/worker-mode/), so the static build also ships a **SharedWorker chunk** (the sync engine — `board-sync.worker.ts`) alongside the app bundle; Vite emits and fingerprints it under `/demo/` like any other asset. A visitor on a browser without `SharedWorker` transparently falls back to the in-process engine (correct, just on the main thread), so the demo works everywhere — it only loses the off-thread isolation on that browser.
Two things make the static build work under a subpath:
* **Subpath assets** — `bun run demo:build` sets the Vite base to `/demo/` and outputs into the docs `dist/`.
* **Hash routing** — the build sets `VITE_BOARD_HASH_ROUTING=1`, flipping the router to hash history (`/demo/#/login`). GitHub Pages serves the **root** `/404.html` for any unknown path, and that 404 belongs to this docs site — so a path-based deep-link into `/demo/login` would render the docs 404. Hash routing keeps every route under `/demo/index.html`, so deep-links and refreshes always boot the SPA. Local dev and `board:cloud:dev` keep clean path URLs.
## Offline return
[Section titled “Offline return”](#offline-return)
A signed-in visitor who closes the demo and reopens it without connectivity boots to a usable board. A small runtime-capture **service worker** (no precache — it caches only what that visitor’s own boots already fetched) replays the app shell and the PGlite engine assets; the data is whatever each table’s declared retention kept in the local store — every eager table, plus the Admin’s chat once activated. The Member’s chat is ephemeral by design and instead shows an explicit connection-needed state, as does sign-in itself — the capability is offline *return*, not first-visit offline. Board [ADR-0010](https://github.com/pgxsinkit/pgxsinkit/blob/main/apps/board/docs/adr/0010-offline-return.md).
## Reset nightly (purge → migrate → reseed)
[Section titled “Reset nightly (purge → migrate → reseed)”](#reset-nightly-purge--migrate--reseed)
The demo is **public and writable** — anyone can create and move issues and post chat. A separate workflow, `.github/workflows/demo-reset.yml`, rebuilds the backend on a nightly cron (`0 3 * * *`) plus `workflow_dispatch`: `purge:board` **drops every migration-created board object** (model-derived drop list plus the `drizzle` bookkeeping schema), `db:board:migrate` **re-applies the latest committed history from scratch**, and `seed:board` **recreates the seeded fixtures**. Any vandalism (offensive issue titles, chat spam) is gone by morning, and a manual run resets it on demand.
Because the schema is rebuilt, not just the rows, the cloud database is **effectively ephemeral** — the same posture as every other database these migrations target. A rewritten or collapsed migration history (`docs/runbooks/regenerate-migrations.md`) ships by simply dispatching this workflow; the function bundles are the separate `bun run board:cloud:functions` step, explicitly targeted by `BOARD_SUPABASE_PROJECT_REF`.
All three steps are the same scripts used locally and by `board:cloud:*`, pointed at the cloud project via env — no Postgres/Electric containers, no Pages deploy, just the GoTrue admin API + the project’s database via the Supavisor **session** pooler (role privileges, not the connection path, are what the DDL needs).
## Operator setup
[Section titled “Operator setup”](#operator-setup)
The public demo points at a real project provisioned per the [board-on-cloud runbook](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/runbooks/board-on-cloud.md). On top of that one-time setup, the hosted demo needs:
| GitHub setting | Kind | Purpose |
| ----------------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `DEMO_BOARD_SUPABASE_URL` | variable (public) | Project URL — baked into the build; the reset’s GoTrue admin gateway. |
| `DEMO_BOARD_PUBLISHABLE_KEY` | variable (public) | `sb_publishable_…` key — baked into the build as the `apikey`. |
| `DEMO_BOARD_FUNCTIONS_REGION` | variable (public) | The project’s region (e.g. `eu-central-1`) — sent as `x-region` on the **write** function (board-write) only, so its DB-bound worker executes **next to the database** instead of next to each visitor. Without it, every write function→DB statement pays a cross-region round trip. The read proxy (board-sync) is left unpinned — its upstream is Electric Cloud’s global CDN, so it should follow the caller. |
| `DEMO_BOARD_SECRET_KEY` | secret | `sb_secret_…` key — the reset’s admin API auth. |
| `DEMO_BOARD_DATABASE_URL` | secret | **Session pooler** connection (pooler host, port 5432, user `postgres.[`) — the reset drops, migrates, and inserts as `postgres`. Not the direct connection: it is IPv6-only, and GitHub-hosted runners have no IPv6. |
| `PGXSINKIT_PAGES_DEPLOY_KEY` | secret | Already required by the docs deploy. |
The build values are **variables, not secrets** on purpose: the project URL and publishable key are public (they ship in client JS), and gating the demo build on a variable lets a fork get a clean docs deploy with the demo step skipped.
Then, on the Supabase project:
* **Set `BOARD_ALLOWED_ORIGINS`** to include `https://pgxsinkit.github.io` (a CORS origin is scheme + host — the `/demo` path is irrelevant) alongside your localhost dev origins, and redeploy secrets (`bun run board:cloud:secrets`). Without this the functions reject the github.io origin’s requests.
* **Disable open email signups** (Auth settings). The reset truncates all board **rows** regardless of author (so vandal content is always wiped) but only deletes the **fixture** auth identities — disabling signups keeps the user set to exactly the seeded fixtures.
* **Activate the Electric subquery preview on your source** — see [The Electric subquery requirement](/concepts/electric-subqueries/). Without it, ordinary members’ membership-scoped shapes 400 while admin works.
## What’s verified
[Section titled “What’s verified”](#whats-verified)
The static build is exercised by `bun run demo:build`; the **live page is operator-verified**, like the rest of the cloud path (it needs the managed backend). The local stack remains the CI-gated proof of the topology (`bun run test:integration:board`, 8/8).
# Storage benchmarks
> A wa-sqlite-style, in-browser benchmark suite comparing PGlite storage backends across timed SQL batteries.
pgxsinkit runs on PGlite. The benchmark suite compares three storage backend slots:
* **`idb`** — IndexedDB, via the `@pgxsinkit/pglite` fork’s IndexedDB VFS (the universal fallback; works in every browser and context). Capability-enabled worker mode prefers `opfs-repacked`; fixed worker mode and the no-SharedWorker main-thread fallback remain on IndexedDB.
* **`opfs-ahp`** — upstream PGlite’s native OPFS VFS, one sync access handle per file (Chromium / Firefox). Kept in the bench for comparison; it is **known broken on WebKit and Linux Chrome**. Default-ticked where it actually runs — **Firefox everywhere** and **Chrome on Windows/macOS** — and default-unticked (but still selectable, with a warning) on **Chrome/Linux** and **WebKit**. On Chrome/Linux a live store needs \~1070 open file descriptors, but Chrome’s profile-wide storage service inherits the session’s 1024 FD soft limit and hangs non-recoverably at exhaustion (raise your session `DefaultLimitNOFILE` to opt in); on WebKit it needs \~1070 sync-access handles against a \~252 cap (the reason `opfs-repacked` exists). All `opfs-ahp` cells run **last** (below), so a wedge can only affect other ahp cells.
* **`opfs-repacked`** — `@pgxsinkit/pglite-opfs-repacked`, which packs the virtual database into a constant four OPFS handles. It is default-ticked and supports both 8 KiB and 64 KiB extent profiles.
The timed backend cells run in dedicated workers so each cell is isolated. Separately, phase 0 runs a full OPFS-repacked boot, persist, and reopen inside a SharedWorker and records `sharedWorkerProof` in the downloaded results envelope. That proof returned `granted-and-persisted` on real macOS and iOS Safari on 2026-07-21. Playwright WebKitGTK denies synchronous handles in both worker kinds; it is useful fallback coverage, not a substitute for the real-Safari proof.
To make the storage choice evidence-based, the perf lab ships a **live, in-browser benchmark suite**, modelled on [rhashimoto/wa-sqlite’s benchmarks page](https://rhashimoto.github.io/wa-sqlite/demo/benchmarks.html): a grid of timed SQL batteries (rows) across the storage backends (columns), with nothing but inline code — no network, no framework.
Each **cell** (one battery × one backend) runs isolated in its **own short-lived dedicated worker**, spawned fresh and terminated when the cell finishes, behind an **inactivity watchdog**: if a worker emits no progress for 90 seconds it is assumed wedged, terminated, recorded in the grid as `hung`, and the suite moves on to the next cell. On top of that, **all `opfs-ahp` cells run last** (after every other backend’s cells): the Chrome/Linux FD-limit wedge is profile-wide and non-recoverable, so scheduling ahp last means a wedged ahp cell can only ever take out other ahp cells, never the `idb`/`opfs-repacked` columns. Column order in the grid stays fixed (`idb`, `opfs-ahp`, `opfs-repacked`) regardless of that run order.
## Run it
[Section titled “Run it”](#run-it)
**[Open the live storage benchmarks →](/bench/)**
Tick the batteries and backends you want and press **Run selected**. `opfs-ahp` is default-ticked on Firefox (everywhere) and Chrome on Windows/macOS, and default-unticked on Chrome/Linux (session FD limit — wedges at exhaustion; raise `DefaultLimitNOFILE` to opt in) and WebKit (handle cap); tick it explicitly to include it — it runs last and the watchdog recovers a hung cell. `opfs-repacked` is default-ticked. Durability is *relaxed* by default; the strict toggle measures the per-commit fsync/flush cost. The OPFS-repacked backend lets you select either extent profile. The page is deliberately dependency-free so it boots on a phone, including iPhone/Safari, where WebKit’s OPFS behavior can differ from desktop engines.
Results **survive a page reload**: after every cell the current envelope is mirrored to `sessionStorage`, and on the next load — unless the page is auto-running — it is restored into the grid behind a labelled notice. This matters on iOS Safari, which can hard-reload the page under memory pressure once the suite has churned enough stores, wiping the DOM before you read the numbers; a fresh **Run selected** overwrites the saved envelope so stale results never masquerade as current. Add `?debug=1` to turn on PGlite/VFS tracing in the browser console (`console.log('[opfs-ahp]', …)`, phase-by-phase filesystem init) — the diagnostic channel for the opfs-ahp store-open hang, viewable via devtools or the Safari remote inspector.
### The batteries
[Section titled “The batteries”](#the-batteries)
| Battery | What it times |
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Flush cost — per-op inserts × durability** | 200 sequential single-row INSERTs, once relaxed and once strict, per backend. Isolates flush cost with a mean / p50 / p95 / max envelope. |
| **Bulk writes** | The classic wa-sqlite pair on a \~6-column table: N rows in ONE transaction vs N rows each in its own autocommit statement. |
| **Big-table reads** | Builds a \~50k-row indexed table, a \~30-column wide table and a \~100KB-text TOAST table once per backend, then times indexed point lookups, an index range scan, a full-table aggregate, an unindexed scan, a join and ORDER BY + LIMIT. |
| **Updates & deletes** | An indexed batch update, a wide-row update, and a bulk delete + reinsert. |
## Desktop findings
[Section titled “Desktop findings”](#desktop-findings)
These are the numbers this machine produces under headless Chromium and Firefox. **They are a baseline, not the target device** — the numbers that decide the storage choice come from a real iPhone/Safari run of the live page.
**Flush cost (the headline).** `idb` under *strict* durability is **\~100–160× slower** than `idb` *relaxed* (≈ 86.8 ms vs 0.80 ms per insert on Chromium; ≈ 158 ms vs 0.95 ms on Firefox), because every autocommit pays an IndexedDB round trip. Both OPFS backends sit near 1–2 ms regardless of durability on desktop.
| Backend | relaxed | strict |
| ------------------------ | ---------- | ---------- |
| `idb` | \~0.40 ms | \~84.7 ms |
| `opfs-ahp` | \~0.91 ms | \~1.06 ms |
| `opfs-repacked` (8 KiB) | 1.4 ms p95 | 1.0 ms p95 |
| `opfs-repacked` (64 KiB) | 1.1 ms p95 | 0.8 ms p95 |
*(Chromium; strict `idb` is the \~100× cliff. Firefox shows the same shape, larger absolute strict cost.)*
**Bulk writes** show the same lesson at scale: batching 10k rows into one transaction runs at tens-to-hundreds of thousands of rows/sec on every backend, while the per-statement autocommit path drops one to two orders of magnitude — the single strongest argument for staging writes in a transaction.
**Big-table reads** are comfortably fast on all backends once the data is resident: indexed point lookups sit around 0.4–2 ms each, and full 50k-row aggregates, unindexed scans, joins and ORDER BY + LIMIT all complete in tens of milliseconds. On desktop the backends differ mainly in **fixture build time** (writing the 50k + wide + TOAST rows) — a write-path difference, not a read-path one.
# Packages
> What each @pgxsinkit/* package is and when you need it.
pgxsinkit ships as a set of focused packages. Most apps install `client`, `server`, and `contracts`, plus `react` for React bindings. The OPFS-repacked package is an optional low-level PGlite storage backend for browser workers.
## Published packages (the product)
[Section titled “Published packages (the product)”](#published-packages-the-product)
| Package | Install when you… | Runtime |
| ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- |
| **`@pgxsinkit/contracts`** | always — shared Zod schemas, the sync registry types (tables **and** event-stream registration), and the transport DTOs every lane uses. | shared |
| **`@pgxsinkit/server`** | you run the server — `createSyncServer`, the apply-function builder, the Electric shape proxy, and the event lane’s ingest route + consumer runner. | any `fetch` runtime |
| **`@pgxsinkit/client`** | you build the client — local overlay + mutation journal, batch flush, read wiring over PGlite, and the event Outbox + its flush loop. | browser / PGlite |
| **`@pgxsinkit/react`** | you want React hooks/bindings over the client. | React |
| **`@pgxsinkit/pglite-opfs-repacked`** | you need a constant-handle OPFS filesystem for a PGlite database in a capability-proven worker. | browser worker |
## Internal packages (not published)
[Section titled “Internal packages (not published)”](#internal-packages-not-published)
| Package | What it is |
| ----------------------- | ------------------------------------------------------------------------------------------------------------ |
| `@pgxsinkit/schema` | the harness/reference sync registry — a membership fixture. Example code; your app defines its own registry. |
| `@pgxsinkit/test-utils` | shared helpers for the integration and unit suites. |
## How they fit the two paths (and the event lane)
[Section titled “How they fit the two paths (and the event lane)”](#how-they-fit-the-two-paths-and-the-event-lane)
* **Write path:** your app uses `@pgxsinkit/client` to stage + flush; `@pgxsinkit/server` validates against `@pgxsinkit/contracts` and applies via the in-database function. See [The write path](/concepts/write-path/).
* **Read path:** `@pgxsinkit/client` (over its internal Electric ingest engine, `src/sync/`) subscribes to shapes served through the server’s proxy. See [The read path](/concepts/read-path/).
* **Event lane** (only if your registry declares `streams`): `@pgxsinkit/contracts` registers the streams and defines the wire contracts; `@pgxsinkit/client` stages appends in the local Outbox and flushes them; `@pgxsinkit/server` mounts the ingest route, provisions the queues, and hosts the consumer runner. It is not a sync path at all — no overlay, no echo, no conflict. See [The event lane](/concepts/event-lane/).
API-level details will live in the [API reference](/reference/) (generated from the package sources). For the storage package’s construction, durability, and recreation contract, see [OPFS-repacked PGlite storage](/packages/pglite-opfs-repacked/).
# OPFS-repacked PGlite storage
> Construct and operate a constant-four-handle OPFS filesystem for a browser-worker PGlite database.
`@pgxsinkit/pglite-opfs-repacked` stores a complete PGlite virtual database directory inside four exclusively owned OPFS files. Its handle count stays four as the database creates virtual files. Use it when the native one-sync-handle-per-file OPFS layout would approach a browser or process limit.
## Requirements
[Section titled “Requirements”](#requirements)
* Run PGlite in a worker scope where an actual `createSyncAccessHandle()` open succeeds. Chromium and Firefox grant it in dedicated workers and deny it in SharedWorkers. Real macOS and iOS Safari grant it in SharedWorkers (full boot/persist/reopen verified 2026-07-21). Method presence is not proof.
* Do not run the database on the window main thread. Playwright WebKitGTK denies synchronous handles in both worker kinds and exercises the IndexedDB fallback; that is not evidence against real Safari.
* Give each database its own otherwise-empty OPFS directory. The package owns the directory in full.
* Use a PGlite build containing the upstream initdb filesystem-cleanup fix until that fix is available in a release. The storage package does not depend on fork-only durability state.
The package itself accepts an OPFS directory handle and does not choose a worker topology. For a cross-browser pgxsinkit app, prefer `@pgxsinkit/client`’s [capability-driven worker mode](/concepts/worker-mode/): it probes the SharedWorker, runs directly there on Safari, and elects a dedicated engine worker on Chromium and Firefox.
## Create and close
[Section titled “Create and close”](#create-and-close)
`createOpfsRepackedPGlite` is the only supported construction path:
```ts
import { createOpfsRepackedPGlite } from "@pgxsinkit/pglite-opfs-repacked";
const root = await navigator.storage.getDirectory();
const directory = await root.getDirectoryHandle("my-database", { create: true });
const pg = await createOpfsRepackedPGlite({
directory,
durability: "relaxed",
extentSize: 64 * 1024,
pglite: {
// Normal PGlite options, including extensions.
},
});
try {
await pg.exec("SELECT 1");
} finally {
await pg.close();
}
```
The factory owns PGlite’s `dataDir`, `fs`, and `relaxedDurability` fields and rejects them inside `pglite`. It retains the filesystem adapter, performs a strict sync after successful initialization, and closes every acquired handle after failed initialization or shutdown.
## One durability authority
[Section titled “One durability authority”](#one-durability-authority)
The VFS option is the only physical-durability choice:
* `"relaxed"` is the default. Ordinary awaited host syncs assert health and perform any due deferred repack without physically flushing routine work. Termination may lose an unflushed suffix, while recovery keeps the longest valid metadata-log prefix and never exposes bytes from an earlier extent owner.
* `"strict"` flushes arena data before metadata on every awaited host sync. Successful query return is a strict boundary.
PGlite always uses its awaited host path. A non-awaited host argument raises `DurabilityModeMismatchError` and poisons the live instance; it is a construction error, not an override. Successful initialization, repack activation, and close from an open instance always use strict ordering in either mode.
## Extent and directory identity
[Section titled “Extent and directory identity”](#extent-and-directory-identity)
For a new store, `extentSize` accepts 8 KiB–16 MiB in 8 KiB increments and defaults to 64 KiB. An existing store’s identity is authoritative. Supplying another valid value raises `ExtentSizeMismatchError` without changing it.
The directory contains exactly `arena.bin`, `metadata-a.bin`, `metadata-b.bin`, and `activation.bin`. An extra entry or wrong entry kind raises `UnexpectedStoreEntryError` before owned content changes. A second live owner raises `StoreOwnedError`.
## Recreate after a format change
[Section titled “Recreate after a format change”](#recreate-after-a-format-change)
Each package build accepts one exact format identity. On `StoreRecreationRequiredError`, close every owner, remove the complete dedicated directory, and create a fresh one:
```ts
await pg.close();
await root.removeEntry("my-database", { recursive: true });
```
Do not copy individual owned files into the fresh directory. `CorruptStoreError` means the activated authority is invalid; restore an external backup or recreate. The VFS fails closed rather than guessing another authority.
## Failure boundaries
[Section titled “Failure boundaries”](#failure-boundaries)
The guaranteed model covers worker, tab, process, and browser termination; unflushed writes may be absent, partial, or independently present; completed flushes remain stable. It does not promise recovery after power loss, media failure, arbitrary external edits, or mysteriously missing activated files.
All storage errors expose stable classes. Store-level errors carry a string `storeCode`; wrapped errors retain `cause`. `StoreFailedError` means the live instance is poisoned: close and reopen, then inspect its cause. See the [generated API reference](/api/pglite-opfs-repacked/readme/) for the complete error surface.
# Project
> Versions pgxsinkit is built and tested against, and how it is released.
## Support matrix
[Section titled “Support matrix”](#support-matrix)
pgxsinkit sits between several systems and is pinned to specific versions of each. The table below is what it is **built and tested against** — not a claim that nothing else can work.
| System | Version | Notes |
| -------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| PostgreSQL | 17+ | Supabase-compatible; CI tests against Supabase Postgres 17.x. Auth claims drive the RLS context. |
| ElectricSQL | floor ≥ 1.7; this release pins + is tested against **1.7.7** | **Must** run with `ELECTRIC_FEATURE_FLAGS=allow_subqueries,tagged_subqueries`. |
| PGlite | 0.5.3 | local client database (peer dependency). |
| Drizzle ORM | 1.0.0-rc.4+ | authoritative server schema + migrations. |
| Server runtime | Bun / Deno / Supabase Edge | the server is a web-standard `fetch` handler — the board demo runs it on the **Supabase Edge (Deno)** runtime, the minimal reference on **Bun**. |
| Zod | v4+ | transport validation. |
### What “tested against” means
[Section titled “What “tested against” means”](#what-tested-against-means)
CI exercises pgxsinkit against a **self-hosted Supabase + ElectricSQL** stack (Podman compose, at the versions pinned above), across both server runtimes: the minimal reference server on **Bun** and the board demo’s two edge functions on the **Supabase Edge (Deno)** runtime. Because every endpoint is env-driven, the same code is expected to run unchanged against the **hosted** services — Supabase Cloud and Electric Cloud — but those are **not yet validated in CI**. Treat them as supported by design, not yet certified. One Cloud caveat: subquery `where`s (membership fan-out) are a flagged Electric preview that **managed Electric Cloud activates per source on request** — see [The Electric subquery requirement](/concepts/electric-subqueries/).
## Releasing
[Section titled “Releasing”](#releasing)
pgxsinkit follows the unified release standard (see [Design decisions](/decisions/) → ADR-0001): versions are derived from the most recent semver tag, publishable `package.json` files carry a `0.0.0` placeholder, and publishing is gated on validation. A push to `main` publishes a `@dev` build to GitHub Packages; a semver tag publishes a release to npm + GitHub Packages.
Full mechanics are in [`RELEASING.md`](https://github.com/pgxsinkit/pgxsinkit/blob/main/RELEASING.md).
## License & source
[Section titled “License & source”](#license--source)
pgxsinkit is open source under the [**MIT License**](https://github.com/pgxsinkit/pgxsinkit/blob/main/LICENSE). Source, issues, and ADRs live at [github.com/pgxsinkit/pgxsinkit](https://github.com/pgxsinkit/pgxsinkit).
# API reference
> Generated type-level reference for the published @pgxsinkit/* packages.
The pages under this section are generated with `starlight-typedoc` directly from each package’s source, so the API reference always matches the code. They cover the five packages you install and use directly:
* **[@pgxsinkit/contracts](/api/contracts/readme/)** — shared Zod schemas, sync registry types, and transport DTOs.
* **[@pgxsinkit/pglite-opfs-repacked](/api/pglite-opfs-repacked/readme/)** — constant-handle OPFS adapter, PGlite factory, validated options, and stable storage errors.
* **[@pgxsinkit/client](/api/client/readme/)** — local overlay + journal, batch flush, and read wiring.
* **[@pgxsinkit/server](/api/server/readme/)** — `createSyncServer`, the apply-function builder, and the Electric shape proxy.
* **[@pgxsinkit/react](/api/react/readme/)** — React bindings over the client.
The Electric read-path ingest engine lives inside `@pgxsinkit/client` (`src/sync/`, ADR-0009) rather than a separate package, so it is not documented as its own entry — see [Packages](/packages/) for where it fits.
New to the library? Start with [Core concepts](/concepts/) for the model, then [Packages](/packages/) for what to install.
# Use these docs with your AI assistant
> Point your coding assistant at pgxsinkit's llms.txt and the Agent Skills shipped in the @pgxsinkit/* packages so it loads the right model fast.
pgxsinkit is easy to misunderstand from the source alone — the read and write paths are asymmetric, the write path is deliberately a single in-database function, and local PGlite schema is not a full mirror of Postgres. These docs publish machine-readable summaries so an assistant can load the correct model without re-deriving it from the whole repository.
## The llms.txt files
[Section titled “The llms.txt files”](#the-llmstxt-files)
| File | What it is |
| --------------------------------------------------------------- | ------------------------------------------------- |
| [`/llms.txt`](https://pgxsinkit.github.io/llms.txt) | Index of the docs with short descriptions. |
| [`/llms-full.txt`](https://pgxsinkit.github.io/llms-full.txt) | The entire documentation as one file. |
| [`/llms-small.txt`](https://pgxsinkit.github.io/llms-small.txt) | A compressed variant for tighter context windows. |
## How to use them
[Section titled “How to use them”](#how-to-use-them)
* **Working in a consuming codebase:** fetch `https://pgxsinkit.github.io/llms-full.txt` into your assistant’s context before asking it to wire sync, or link it from your own agent guide.
* **Contributing to pgxsinkit itself:** the canonical vocabulary lives in the repository’s `CONTEXT.md`, and the agent guide is `AGENTS.md` — read those first.
## Agent Skills shipped in the packages
[Section titled “Agent Skills shipped in the packages”](#agent-skills-shipped-in-the-packages)
The `@pgxsinkit/*` packages also ship **[TanStack Intent](https://tanstack.com/intent) Agent Skills** — task-scoped `SKILL.md` guidance bundled **inside the npm package**, so it is pinned to the exact version you installed. They complement `llms.txt` rather than replace it: `llms.txt` is the broad model you pull by URL; a skill is a focused checklist your assistant loads at the moment it reaches for that task, and it travels with the dependency.
| Skill | Package | Load it before… |
| ------------------------ | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`core`** | `@pgxsinkit/client` | wiring sync at all — the two asymmetric paths, the single in-database write path, the mandatory fail-closed subquery flag, and why local PGlite is not full DDL parity. |
| **`registry-authoring`** | `@pgxsinkit/contracts` | defining a registry — the writable-table rules (a server-version field **and** a conflict policy), server-managed fields, `enum::text` in shape filters, and deriving the read filter and RLS from one predicate. |
| **`operating`** | `@pgxsinkit/client` | shipping to production — runtime latency, capability-driven worker placement (Safari SW-direct; Chromium/Firefox elected), OPFS-vs-idb durability, relocation outcomes, backend permanence and destruction, diagnostics, and the forwarded debug rail. |
| **`deploying`** | `@pgxsinkit/server` | deploying the server + shape proxy on Bun / Deno / Supabase Edge / Workers — bundling for Deno, the function-name path rewrite, and resolving claims from the platform JWT. |
| **`react`** | `@pgxsinkit/react` | building React components — `createSyncClientHooks`, the live read hooks, the snake\_case→field-key remap, and that writes go through `client.tables.`, not the hooks. |
| **`operating`** | `@pgxsinkit/pglite-opfs-repacked` | constructing the constant-handle OPFS backend in a capability-proven worker scope — dedicated workers on Chromium/Firefox, SharedWorkers on real Safari — plus factory-owned durability, extent identity, complete-directory recreation, and stable error remedies. |
Discover and load them with the [TanStack Intent](https://tanstack.com/intent) CLI, from a project that has `@pgxsinkit/*` installed:
```bash
bunx @tanstack/intent@latest list # every skill the installed packages ship
bunx @tanstack/intent@latest load @pgxsinkit/client#core # print one skill
bunx @tanstack/intent@latest install # add "load a matching skill first" guidance to AGENTS.md / CLAUDE.md
```
(Use the `@latest` form: `@electric-sql/client` also installs an `intent` binary, so a bare `intent` in `node_modules/.bin` can resolve to the wrong CLI.)
## The six things assistants get wrong
[Section titled “The six things assistants get wrong”](#the-six-things-assistants-get-wrong)
1. **It is a toolkit, not a demo or a data layer.** The `@pgxsinkit/*` packages are the product.
2. **The two paths are separate and asymmetric.** Writes do not travel back through Electric.
3. **There is one write path.** No selectable backend; one in-database apply function.
4. **The Electric subquery flag is mandatory** and fails closed without it.
5. **Local PGlite schema is not full DDL parity** with Postgres.
6. **Browser storage is capability-selected, not browser-named.** Capability worker mode prefers OPFS-repacked: real Safari runs SW-direct, Chromium/Firefox elect a dedicated worker, and idb is the fallback. Read `BootReport.storageBackend`/`engineHome`; do not infer from WebKitGTK or user-agent text.
Each is covered in [Core concepts](/concepts/).
## Operational gotchas that aren’t visible in the code
[Section titled “Operational gotchas that aren’t visible in the code”](#operational-gotchas-that-arent-visible-in-the-code)
These do not show up when reading the toolkit source — they are properties of the runtime around it, and each silently makes a live app feel slow or flaky. An assistant wiring a real deployment should load [Operating in production](/start/operating-in-production/) and apply them up front:
* **Writes flush on enqueue, not on the interval.** The convergence interval is a *fallback*; keep it long (idle CPU), do not shorten it to chase write latency.
* **A same-origin Electric shape proxy must force `cache-control: no-store`**, or a rotated shape handle serves stale and loops on 409s.
* **A browser opens one long-poll connection per shape.** With several shapes the HTTP/1.1 \~6-per-origin cap starves writes — serve the gateway over **HTTP/2**.
* **Serverless edges cold-start.** The first write after idle lags; warm the worker and set its wall-clock timeout above Electric’s \~25s long-poll.
* **Debug latency with `globalThis.__pgxsinkitDebug`**, and measure at the network boundary — polling PGlite in a loop inflates the number it reports.
* **In a browser, attach through a SharedWorker** (`defineSyncWorker` + `attachSyncClient`) to take PGlite off the main thread. Capability placement is automatic — there is no placement option; pass the SharedWorker as a factory (`worker: () => SharedWorker`) and the elected engine is auto-derived from the SharedWorker’s own script URL (supply `createEngineWorker` only for non-module/underivable entries). Storage is declared on the registry (`storage.backend`/`storage.durability`, defaulting to opfs/relaxed); force idb with `storage.backend: "idbfs"`. Safari runs in that SharedWorker; Chromium/Firefox elect a dedicated engine behind it. Always pass `extendedLifetime: true`, and branch on `EngineRelocatedError.outcome` rather than blindly retrying a mutation. See [Worker mode](/concepts/worker-mode/).
# Deploying the server
> Run the pgxsinkit server on Bun, Deno, or Supabase Edge Functions — path rewrites, resolving claims from a platform JWT, and the HTTP/2 gateway requirement.
The server is a web-standard `fetch` handler, so it runs unmodified anywhere that speaks `Request → Response`. “Unmodified” is true at the **API** level — the few concrete steps below are about the runtime around it, not the toolkit code.
The utilities migration must be first in your chain
Whatever runtime you deploy onto, the generated `pgxsinkit_apply_mutations` and the `clockMicrosecondsSql` column DEFAULTs both call `public.pgxsinkit_clock_us()`, installed by the **utilities migration** (`--utilities`, the first folder in the chain — see [Getting started](/start/getting-started/)). A chain that omits or mis-orders it fails at **migrate** time with an undefined-function error, before the server ever starts.
## Bun
[Section titled “Bun”](#bun)
The trivial case. Either export the handler, or use the bundled `start()` helper:
```ts
const server = createSyncServer({ registry, db, electricUrl, resolveAuthClaims });
export default { fetch: server.fetch }; // Bun.serve picks this up
// or: await server.start(); // the only Bun-specific helper
```
## Deno / Supabase Edge Functions
[Section titled “Deno / Supabase Edge Functions”](#deno--supabase-edge-functions)
Deno runs TypeScript natively and resolves `npm:` specifiers, so the common case needs **no build step**: import the published `npm:@pgxsinkit/server`, import your own registry as local TypeScript, and deploy.
What you *do* hit on Deno and the Edge platform are two runtime concerns — a path prefix and claims resolution. Neither is a toolkit limitation; they are how the platform routes and authenticates, and they apply however you deploy. Here they are with their fixes.
Consuming the toolkit as unbuilt source in a monorepo? Bundle ahead of time
If a function imports the toolkit (or your registry) as **unbuilt source** rather than as a published package, Deno’s strict resolver rejects the extensionless relative imports — bundle each function into one self-contained ESM file ahead of time (keeping `node:*` builtins external). This repository’s own board demo does exactly that; see its `scripts/build-edge-functions.ts`.
1. **Strip the function-name prefix before `server.fetch`.** Edge Functions route by the first path segment, so a POST to `/functions/v1/write/api/mutations` arrives at your worker as `/write/api/mutations`. Strip only the `/write` prefix so the server receives its canonical `/api/mutations` path:
```ts
const server = createSyncServer({ registry, db, resolveAuthClaims });
Deno.serve((request) => {
const url = new URL(request.url);
url.pathname = url.pathname.replace(/^\/write(?=\/|$)/, "") || "/";
return server.fetch(new Request(url, request));
});
```
The **read** path needs no rewrite: `proxyElectricShapeRequest` keys off the query string, not the path, so a shape-proxy function can hand it the request as-is.
2. **Resolve claims from the platform JWT in `resolveAuthClaims`.** `verify_jwt` is a gateway concept; the portable move is to verify the token yourself and return its claims. A GoTrue access token is already `JwtClaims`-shaped — `sub`, a top-level `role` (the Postgres role), and `app_metadata` — so once verified you return it directly:
```ts
async function resolveAuthClaims(request: Request): Promise {
const token = request.headers.get("authorization")?.replace(/^Bearer\s+/i, "");
if (!token) return null; // fail closed: the proxy blocks all rows, the write route rejects
return await verifyHs256(token, Deno.env.get("JWT_SECRET")!); // your HS256 verify → claims | null
}
```
The applier reads `role` to switch the RLS actor and `app_metadata.roles` for any admin predicate; the read proxy reads `sub` + `app_metadata.roles` for the row filter. Both paths share this one adapter, so read and write authorization can never drift.
### Read vs write as two functions
[Section titled “Read vs write as two functions”](#read-vs-write-as-two-functions)
Splitting the write route and the shape proxy into two deployments (e.g. a `write` function and a `sync` function) is a deployment choice, not a toolkit one:
* **write** — `createSyncServer({ registry, db, resolveAuthClaims })` **without** `electricUrl` registers only the mutation route. Wrap with the path rewrite from step 1.
* **sync** — call `proxyElectricShapeRequest(request, claims, { registry, electricUrl })` directly. No rewrite needed. Set the function’s idle/wall-clock timeout **above** Electric’s bounded long-poll (\~25s) so live updates are not cut off mid-cycle. If it is a **same-origin proxy with no CDN**, also force `cache-control: no-store` on the response so the browser never serves a rotated shape handle stale (the 409-loop fix in [Operating in production](/start/operating-in-production/)).
Both import the same registry and share the same `resolveAuthClaims`, which is what keeps the two ingress points honest.
### Auth is bearer-token; the built-in CORS is credentialless
[Section titled “Auth is bearer-token; the built-in CORS is credentialless”](#auth-is-bearer-token-the-built-in-cors-is-credentialless)
`resolveAuthClaims` reads the **`Authorization: Bearer …` token** — pgxsinkit does not authenticate from cookies. To match that, the server’s built-in CORS reflects your configured `allowedOrigins` and the request methods/headers but **does not send `Access-Control-Allow-Credentials`**, so browsers will not attach cookies on cross-origin calls. That is the right default for a token API. If you intend to drive the server from a **Supabase cookie session** (rather than a bearer token), that is a deliberate extension: terminate credentialled CORS at your own gateway and turn the cookie into a bearer token (or into `JwtClaims`) inside `resolveAuthClaims`.
### The apply function is deny-by-default — name your server’s database role
[Section titled “The apply function is deny-by-default — name your server’s database role”](#the-apply-function-is-deny-by-default--name-your-servers-database-role)
The generated `pgxsinkit_apply_mutations` takes the request’s claims as an argument and **trusts them**: it copies them into `request.jwt.claims` and switches `role` before running your RLS-governed DML. That is correct for your server, which passes claims it verified, and catastrophic for anyone else — a caller who can invoke the function directly picks its own claims. So the generated migration is deny-by-default: right after `CREATE`, it revokes `EXECUTE` from `PUBLIC` and (guarded on role existence) from `anon`, `authenticated` and `service_role`, then grants only the roles you name:
```bash
bun run pgxsinkit-generate --registry ./sync-registry.ts --export registry \
--project-dir ./db --config drizzle.config.ts --name sync_artifact \
--grant-execute-to app_writer # repeatable, or comma-separated
```
* **The default is owner-only**, and that is usually right: if your server connects as the role that applies the migrations (the function’s owner) or as a superuser, you need no grant at all.
* **Name only server roles.** A granted role can pass any claims it likes — by design — so the grant list is the write path’s entire trust boundary. Never grant a client-facing role.
* **The grant list is part of the artifact fingerprint**, so the same roles must appear in three places: the generate command, the CI `--check` command, and `createSyncServer({ applyFunctionGrantExecuteTo: ["app_writer"] })`. Otherwise every write fails with `PXS01` (stale artifact).
* **Everyone regenerates once** on upgrade — the ACL moved the fingerprint. Re-run the `--utilities` migration too: it hardens `public.pgxsinkit_clock_us()` the same way, keeping its grants to `anon`/`authenticated`/`service_role` (a column DEFAULT calling the clock runs as the writing role).
* **Roles you never named are revoked too.** After those named revokes, the migration enumerates the installed function’s actual grantees (`aclexplode(pg_proc.proacl)`) and revokes `EXECUTE` from every one that is neither the function’s owner nor on your `--grant-execute-to` list. This matters if your own cluster carries `ALTER DEFAULT PRIVILEGES … GRANT ALL ON FUNCTIONS TO `: like Supabase’s, it re-grants at the `CREATE` inside every install, and only an enumeration can close a name the toolkit cannot know. The end state is exactly **owner + the roles you named**, on every install.
The failure mode when the grant is missing is loud and total, by design. A server connecting as a role that is **neither the function’s owner nor named in `--grant-execute-to`** fails **every** write with SQLSTATE `42501`, `permission denied for function pgxsinkit_apply_mutations` — not some writes, not some rows, and no degraded mode. The fix is never a `GRANT` typed into a psql session — the next regenerate’s `DROP FUNCTION` resets it. Regenerate the migration with `--grant-execute-to` naming the role your **server** connects as, and add that role to the CI `--check` command and to `applyFunctionGrantExecuteTo` in the same change.
pgxsinkit itself needs **no PostgREST**, and leaving it out is good practice — it removes the `/rest/v1/rpc/*` surface that would otherwise expose every `public` function over HTTP. Treat that as defence in depth, though: the ACL is the control, and it holds whatever your topology looks like.
### Installing into a schema of your own: `--function-schema`
[Section titled “Installing into a schema of your own: --function-schema”](#installing-into-a-schema-of-your-own---function-schema)
By default the apply function is generated **unqualified** and resolved through the connection’s `search_path`. `pgxsinkit-generate --function-schema app_fns` installs it into a schema you choose instead — and, exactly like the grant list, **the schema is part of the artifact’s fingerprint**, because the function names itself in its own self-check. So it too must appear in three places, or writes fail:
```bash
bun run pgxsinkit-generate --registry ./sync-registry.ts --export registry \
--project-dir ./db --config drizzle.config.ts --name sync_artifact \
--function-schema app_fns # …and the same flag on the CI `--check` command
```
```ts
createSyncServer({ registry, db, resolveAuthClaims, applyFunctionSchema: "app_fns" });
```
`applyFunctionSchema` does both halves of the job: it makes the server compute the fingerprint the schema-qualified artifact was stamped with, **and** it qualifies the call the server actually makes. Generate with `--function-schema` but leave the server option unset and the call goes out unqualified — it either finds nothing (`42883`, `function pgxsinkit_apply_mutations(…) does not exist`) or finds a same-named function elsewhere on the `search_path` and fails `PXS01` against a fingerprint that install does not carry. Set both to the same schema, or neither.
### Deploying the event lane
[Section titled “Deploying the event lane”](#deploying-the-event-lane)
If your registry declares `streams`, you get a second lane to deploy alongside the two above (the whole model is in [The event lane](/concepts/event-lane/)). Three concrete obligations:
**1. The ingestion route mounts itself.** `createSyncServer` registers `POST /api/events` only when the registry registers at least one event stream — with no streams the path stays a 404. Nothing is probed at startup, so the zero-startup-query posture is unchanged, and because the route is a sibling of `/api/mutations` under `/api/`, the same function-name path rewrite (step 1 above) serves it. Two options are yours:
```ts
createSyncServer({
registry,
db,
resolveAuthClaims,
// Consent / entitlement refusal, keyed by stream name. ABSENT = every well-formed event is allowed.
// Called per event, after its payload validated and its identity resolved, before anything is enqueued;
// a refusal is a per-event `refused` verdict. A gate that THROWS fails the whole batch retryably —
// a gate that cannot decide is never read as "allow".
eventGate: ({ stream, identity }) => allowed(stream, identity["viewerId"]),
// Defaults to the shipped pgmq backend over this server's own `db`, which is what lets an enqueue join
// the endpoint's transaction. Override for another backend, or a fake in tests.
eventQueue: createPgmqEventQueue({ db }),
});
```
**2. pgmq is a cluster prerequisite, and the queues are deploy-time DDL.** The generated migration begins with `CREATE EXTENSION IF NOT EXISTS pgmq`, so the extension must be **available in the PostgreSQL image you deploy** (Supabase’s images ship it; a stock `postgres` image does not). Generate the migration and apply it through your normal flow — never at runtime, because the endpoint may enqueue long before any consumer runner first starts:
```bash
bun run pgxsinkit-generate --events --registry ./sync-registry.ts --export registry \
--project-dir ./db --config drizzle.config.ts --name event_lane_artifact
```
It lands in its own migration folder and carries a fingerprint of the registry’s stream set. Add `--events --check` to CI beside the apply-function check: adding or removing a stream without regenerating then fails before deploy, instead of at the first enqueue onto a queue that does not exist.
**3. The consumer runner is a long-lived process, not a function.** `defineEventConsumer` returns a `start()`/`stop()` handle your app runs in its own Bun process — deliberately apart from the serverless posture the routes above run under, because it polls. There is no CLI entrypoint and no signal handling inside it; wire `stop()` from wherever you already handle shutdown.
```ts
const consumer = defineEventConsumer({
registry,
queue: createPgmqEventQueue({ db }), // required — the runner is backend-agnostic by construction
streams: ["issue_viewed"], // optional: the knob that splits streams across processes (unknown name → throws)
callback: async ({ stream, events }) => archive(stream, events),
onDeadLetter: (report) => alert(report), // the runner ALSO warn-logs every one, unconditionally
});
consumer.start();
process.on("SIGTERM", () => void consumer.stop());
```
Its defaults are sized for a small deployment, and each is a knob:
* **`batchSize` (default 10)** — messages one read delivers, each message being one single-stream sub-batch.
* **`maxAttempts` (default 5)** — deliveries a sub-batch gets before a further callback failure dead-letters it into the queue backend’s own archive (requeueing from there is always a deliberate act).
* **Polling is adaptive and internal**: a non-empty read is followed immediately by another, and consecutive empty reads grow the wait from \~250 ms toward a \~5 s idle ceiling. The floor/ceiling/factor are tuning (`poll`), never contract — the mechanism itself may change.
* **`concurrency` (default 1, strictly sequential)** — the max callbacks in flight per stream. Values above 1 are safe precisely because delivery is at-least-once with no promised order across sub-batches, which is what your idempotent callback already assumes; reads stay serial, only one read’s callbacks run in parallel.
* **`visibilityTimeoutSeconds` (default 60)** is **not** a processing budget. While the runner is working through a read it renews the lease on every unsettled message of that read — the one in flight and the ones queued behind it — at half the window, so a long callback cannot let its own siblings resurface. What the timeout actually sets is the **redelivery delay of a sub-batch whose callback threw** (a throw drops it from renewal at once, and that lapse *is* the retry pacing) and the **crash-recovery bound** — how long a dead runner’s messages stay stuck. So size it above **one** callback’s worst case, never the whole batch’s; too short redelivers a callback that is merely slower than one renewal interval.
A graceful `stop()` starts no new read and no new callback, but the callbacks already in flight are awaited **and keep their leases renewed** until they settle — otherwise every ordinary rollout would hand a second runner the very message this one is finishing. The rest of that read (messages no callback had started) is released immediately, so those redeliver promptly rather than waiting out the window.
#### No process to host it? Drain on a schedule instead
[Section titled “No process to host it? Drain on a schedule instead”](#no-process-to-host-it-drain-on-a-schedule-instead)
Some platforms give you no long-lived compute at all — a managed backend whose only server-side unit is a per-request function. There the runner has nowhere to live, and the queue would simply never drain. The **same handle** answers a second, bounded mode for exactly that:
```ts
// Inside your scheduled (or nudged) function. Construction is query-free, so building the consumer
// per invocation costs nothing — and the handle is one-way, so each invocation builds its own.
const consumer = defineEventConsumer({ registry, queue: createPgmqEventQueue({ db }), callback });
const { delivered, deadLettered, empty } = await consumer.drainOnce({ budgetMs: 20_000 });
```
One pass reads → delivers → acks across every configured stream until they all read **empty** or the **wall-clock budget** runs out. Everything else is identical to the loop mode — same delivery path, same lease renewal, same retry, same dead-lettering — because pacing was always internal.
* **Set `budgetMs` under your platform’s invocation cap, with head-room for one callback.** The budget is checked *between* sub-batches and never inside one: a callback already running is awaited and acked as normal, so a pass can overrun by that much.
* **`empty: false` means “there is more”** — the budget cut the pass short, or a read faulted. A scheduler reading that can invoke again immediately instead of waiting out its period.
* **Overlapping invocations are safe.** Two functions draining the same queue are arbitrated by the visibility timeout, exactly as two long-lived runners would be. (Two passes on *one* handle are not: a `drainOnce` beside a live `start()`, beside another pass, or after `stop()`, throws.)
* **A sub-batch whose callback throws near the budget edge is not lost.** It is left unacked, its lease lapses, and the next pass redelivers it — at-least-once, working as designed.
Pair the schedule with the ingest-side nudge so an interactive append does not wait for the next tick:
```ts
createSyncServer({
registry,
db,
// Fired after a request ENQUEUED something, with its deduplicated stream names. Fire-and-forget: the
// library owns no transport, the throw is caught and warn-logged, and the response is never affected.
onEventsEnqueued: ({ streams }) => void fetch(drainUrl, { method: "POST", headers: drainAuth(streams) }),
});
```
The schedule is the guarantee; the nudge is only latency
Wire both, and lean on the schedule. A nudge can be lost, refused, or fired at a cold function — all of which cost nothing but time, because the next sweep drains the queue anyway. Do not build a design where a missed nudge means a missed event. Keep the long-lived runner wherever you *can* run one: it remains the primary mode.
The consumer callback must be idempotent
Delivery is at-least-once. Dedupe on the library-stamped `eventId` against your own durable store — an `INSERT … ON CONFLICT (event_id) DO NOTHING` composes at-least-once delivery into effectively-exactly-once handling. Events arrive in append order *within* one delivered sub-batch and in no promised order across sub-batches, so re-sort from your archive on `occurredAtUs` if you need temporal order.
### The gateway must speak HTTP/2 — one long-poll connection per shape
[Section titled “The gateway must speak HTTP/2 — one long-poll connection per shape”](#the-gateway-must-speak-http2--one-long-poll-connection-per-shape)
Electric’s client holds **one live long-poll connection open per synced shape**. A client that subscribes to six shapes therefore keeps six connections continuously busy. Browsers cap **HTTP/1.1 at \~6 connections per origin**, so on a plain-HTTP gateway those long-polls consume every slot and the **write** request (which shares the origin) gets **Stalled in the browser’s connection queue** — it is not even dispatched until a long-poll cycle frees a slot, adding seconds of latency that look like a slow server but are entirely client-side queuing.
The fix is to serve the gateway over **HTTP/2** (or HTTP/3), which multiplexes every request over a single connection, so the per-origin cap never binds regardless of shape count. Any production-grade ingress already does this — Cloud Supabase, Electric Cloud, an istio/Envoy gateway, or any TLS reverse proxy. The one place it bites is a **local self-hosted stack served over plain `http://`**: browsers only negotiate HTTP/2 over TLS, so a plain-HTTP gateway is stuck on HTTP/1.1. The board demo fronts its kong gateway with a small TLS-terminating Caddy sidecar (h2 + h3) for exactly this reason.
This only shows up with ≥\~6 shapes over plain HTTP/1.1
A deployment with one or two shapes will not hit it; the symptom appears once concurrent shape long-polls reach the browser’s per-origin connection limit. Node and `curl` never reproduce it — they have no per-host cap — so it is invisible to server-side benchmarks and only a real browser surfaces it. If writes feel slow only in the browser and only once several shapes are live, check whether the gateway is HTTP/2: in DevTools → Network, a stuck `write` request shows a long **Stalled** time.
Edge cold starts are a platform property, not a toolkit cost
On a serverless Edge platform a worker is suspended when idle and evicted after longer idle, so the first request after a quiet period pays a cold start — re-importing the bundle and re-establishing the Postgres connection. The convergence machinery is fast (the write applies in a few ms, the echo streams back in well under a second), so this shows up as the first write *after idle* lagging while steady-state writes are instant. Two mitigations, both platform-level: keep the worker warm with a periodic cheap request, and set the worker’s wall-clock budget **above** your busiest held-open shape long-poll so a live subscription is not recycled mid-cycle (forcing a read-path reconnect). A long-lived **Bun** (or Deno) deployment has neither characteristic — one warm process, a pooled connection, no per-request worker lifecycle — which is the simplest answer if first-write latency matters more than serverless scale-to-zero.
# Getting started
> Install the @pgxsinkit/* packages and stand up the read and write paths.
This page gets you from zero to a working read + write path. For what each package does, see [Packages](/packages/); for the model behind it, see [Core concepts](/concepts/).
## Prerequisites
[Section titled “Prerequisites”](#prerequisites)
* **PostgreSQL 17+** (Supabase-compatible; the write path uses Supabase-style auth claims for RLS).
* **ElectricSQL ≥ 1.7**, run with the subquery feature flag — this is **mandatory**:
```bash
ELECTRIC_FEATURE_FLAGS=allow_subqueries,tagged_subqueries
```
Without it, sync fails closed (no rows stream). See [The Electric subquery requirement](/concepts/electric-subqueries/).
* **The `pgmq` extension**, but only if you use the [event lane](/concepts/event-lane/). Its generated migration runs `CREATE EXTENSION IF NOT EXISTS pgmq`, so the extension has to be present in the PostgreSQL image you deploy — Supabase’s images ship it; a stock `postgres` image does not. Registries with no `streams` need nothing.
* **A runtime that serves a web `fetch` handler** for the pgxsinkit server — Bun, Deno, **Supabase Edge Functions**, Cloudflare Workers, etc. The server core is runtime-neutral and framework-free (web-standard `Request`/`Response`, with the DB client injected); only the optional `server.start()` helper is Bun-specific. And **Drizzle** (`drizzle-orm@1.0.0-rc.4`+) for schema.
Enum columns in shape filters
A PostgreSQL `enum` referenced in a shape `where` must be cast to `text` — `"role"::text = 'manager'`, not `"role" = 'manager'`. The column stays an enum everywhere else.
## Install
[Section titled “Install”](#install)
* bun
```bash
bun add @pgxsinkit/client @pgxsinkit/server @pgxsinkit/contracts
# React bindings (optional)
bun add @pgxsinkit/react
```
* pnpm
```bash
pnpm add @pgxsinkit/client @pgxsinkit/server @pgxsinkit/contracts
# React bindings (optional)
pnpm add @pgxsinkit/react
```
* npm
```bash
npm install @pgxsinkit/client @pgxsinkit/server @pgxsinkit/contracts
# React bindings (optional)
npm install @pgxsinkit/react
```
* yarn
```bash
yarn add @pgxsinkit/client @pgxsinkit/server @pgxsinkit/contracts
# React bindings (optional)
yarn add @pgxsinkit/react
```
The packages are published to public npm; install them with whichever package manager your app uses — pgxsinkit mandates none. Peer dependencies include `drizzle-orm`, `@electric-sql/pglite`, and `zod`.
## Stand up the write path
[Section titled “Stand up the write path”](#stand-up-the-write-path)
1. **Define your sync registry** — the tables, their sync mode, and governance (managed fields like owner/timestamps). This is the single source of truth both paths read from.
sync-registry.ts
```ts
import { clockMicrosecondsSql, defineSyncRegistry, defineSyncTable } from "@pgxsinkit/contracts";
import { bigint, uuid, varchar } from "drizzle-orm/pg-core";
export const registry = defineSyncRegistry({
widgets: defineSyncTable({
tableName: "widgets",
mode: "readwrite",
makeColumns: () => ({
id: uuid("id").primaryKey(),
label: varchar("label", { length: 120 }).notNull(),
ownerId: uuid("owner_id"),
// Server version: a strictly-monotonic per-row token convergence keys on.
updatedAtUs: bigint("updated_at_us", { mode: "bigint" }).notNull().default(clockMicrosecondsSql),
}),
// Both are REQUIRED for a writable table — no silent default:
conflictPolicy: "reject-if-stale", // or "last-write-wins"
governance: {
managedFields: [
{ column: "updatedAtUs", applyOn: ["create", "update"], strategy: "nowMicroseconds" },
// Stamp the owner from the verified `sub` claim. (`auth.uid()` is just claimPath: ["sub"];
// use ["app_metadata","person_id"] etc. for an app-minted identity.)
{ column: "ownerId", applyOn: ["create"], strategy: "authClaim", claimPath: ["sub"] },
],
},
// Add a shape.rowFilter for row-level read filtering (see the security note below).
}),
});
```
Writable tables have two hard requirements
`defineSyncRegistry` throws unless every `readwrite` table declares **both**: a **Server version** (a `nowMicroseconds`-on-`update` managed field, conventionally `updated_at_us`) that optimistic convergence keys on, and a **`conflictPolicy`** (`reject-if-stale` | `last-write-wins`) — there is no silent default, because a silent last-write-wins is exactly the data loss the choice exists to surface. Fields stamped by `authClaim` (a verified claim at a JSON path — `["sub"]` is the old `auth.uid()` owner) / `nowMicroseconds` are server-assigned; the server rejects them if present in a client write payload.
2. **Provision the apply function.** The write path applies batches through one in-database PL/pgSQL function, `pgxsinkit_apply_mutations`. Generate a drizzle-kit migration that installs it from your registry with the published `pgxsinkit-generate` CLI (a `bin` of `@pgxsinkit/server`), run from your own project. The CLI **requires [Bun](https://bun.sh) on PATH** — it imports your TypeScript registry and runs `drizzle-kit` — but the bin itself launches under any runner; `bunx`, or `npx` / `pnpm exec` / `yarn` once Bun is installed (it prints a clear message if Bun is missing).
```bash
bunx pgxsinkit-generate \
--registry ./sync-registry.ts \
--export registry \
--project-dir ./db \
--config drizzle.config.ts \
--name sync_artifact
```
This writes a standard drizzle-kit migration you commit and apply through your normal migration flow. The migration lands wherever your drizzle config’s `out` points — pass `--config` and the generator reads `out` from it (or set `--out` explicitly); with neither it probes `drizzle/` and `infra/drizzle/`.
The generated apply function and the audit/version column DEFAULTs (`clockMicrosecondsSql` above) both **call** the canonical `public.pgxsinkit_clock_us()` microsecond clock, so its **utilities migration must be the first folder in the chain** — generate it once with `--utilities`, before the drizzle-kit schema baseline, passing an early-sorting folder name:
```bash
bunx pgxsinkit-generate --utilities \
--project-dir ./db \
--config drizzle.config.ts \
--name 20200101000000_pgxsinkit_utilities
```
The generated function carries a fingerprint of itself (a `COMMENT ON FUNCTION`). It **verifies itself on every call** (ADR-0030): the server passes the fingerprint it expects for its registry + codegen, and the function compares that against its own stamped comment before touching any table — raising `PXS01` and applying nothing on a mismatch. So a registry change or a `@pgxsinkit/server` upgrade you forgot to regenerate + apply fails loud (a stale — or hand-installed, unfingerprinted — function is refused) instead of writing against the wrong applier. The check rides the existing call: no extra round trip, no startup query, no read-then-call race. To catch the same drift in CI before it ships, run the generator in `--check` mode — read-only, writes nothing, non-zero exit on drift:
```bash
bunx pgxsinkit-generate --check \
--registry ./sync-registry.ts --export registry \
--project-dir ./db --config drizzle.config.ts
```
3. **Create the server** and serve its `fetch`. All writes go through `POST /api/mutations`; the ownership-enforcing shape proxy is served from the same app.
```ts
import { createSyncServer } from "@pgxsinkit/server";
import { registry } from "./sync-registry";
const server = createSyncServer({
registry,
db, // your Drizzle database
electricUrl: process.env.ELECTRIC_URL!, // e.g. http://localhost:3000/v1/shape
resolveAuthClaims: async (_request) => {
// verify the request's JWT and return its claims, or null to block all rows
return null;
},
});
export default { fetch: server.fetch };
// `server.fetch` is a web-standard handler — deploy it on Bun, Deno, Supabase Edge
// Functions, or Cloudflare Workers. Only the optional `server.start()` helper needs Bun.
```
Deploying onto a non-Bun runtime (Deno / Supabase Edge Functions) has a couple of concrete steps — a path rewrite and resolving claims from the platform’s JWT. See [Deploying the server](/start/deploying-the-server/).
Two execution contexts enforce the same authorization
Authorization runs in two different engines, so the subject is referenced two ways — derive both from one predicate so a row is never readable-but-unwritable (or the reverse) by accident:
* **Write path — RLS in Postgres:** the applier sets the auth context (`role`, `current_setting('request.jwt.claims')`) before applying a batch, and RLS policies read it (`auth.uid()` is just `request.jwt.claims ->> 'sub'`). The owner/author column itself is stamped from those same claims by an `authClaim` managed field at a claim path — `["sub"]` is the old `auth.uid()` owner, but it can be any verified claim (e.g. `["app_metadata","person_id"]`).
* **Read path — the shape `rowFilter`:** the proxy builds the Electric shape `where` and **Electric** runs it, not Postgres — so a `customWhere` returns a Drizzle `SQL` fragment built from the table’s columns: reference each column through `c()` (a bare identifier, as Electric requires) and bind the authorizing claim (`claims.sub` by default) as a `$n` param rather than hand-escaping it, with any enum cast to `text`. Return `DENY_ALL` to block all rows, or `null` to bypass filtering — note `null`/`""` means *no filter, all rows visible*, so an owner filter must return `DENY_ALL` (not `null`) when the claim is absent. When you define the table and its filter **together in one `defineSyncTable`**, write `shape.rowFilter` as a **function of the built columns** — `rowFilter: (columns) => ({ customWhere: (claims) => … c(columns.ownerId) … })` — so `c()` references the real typed columns this call is creating (the same columns `extras` receives), with no separately-built table needed.
For a **cross-table membership fan-out** (sync a row only if the subject belongs to its container), the `customWhere` predicate is a subquery — author it the same typed way, never a string: `c()` for each column, the **table object** for the `FROM`, the subject as a **bound param**, factored into a helper so read filter and any narrower variant share it:
```ts
const memberContainers = (subject: string): SQL =>
sql`select ${c(memberships.containerId)} from ${memberships} where ${c(memberships.memberId)} = ${subject}`;
const widgetsReadFilter = (claims: JwtClaims) =>
claims.sub ? sql`${c(widgets.containerId)} in (${memberContainers(claims.sub)})` : DENY_ALL;
```
Subqueries **nest** by interpolation (wrap one `sql` fragment in another to narrow a fan-out) and must be **self-contained** (not correlated). The subquery `where` is the flagged Electric preview — run with `allow_subqueries,tagged_subqueries` or it fails closed. `apps/board`’s `packages/board-schema/src/registry.ts` is a worked end-to-end example.
For RLS, `@pgxsinkit/contracts` ships `buildSupabaseOwnerOrAdminNativePolicies` and `buildSupabaseMembershipNativePolicies` for the owner and membership shapes — they take **Drizzle columns** and derive the table from them, so call them inside `defineSyncTable`’s `extras: (t) => …` callback. The read filter and the write policy build from the same Drizzle columns, so they cannot silently drift. For anything beyond them (e.g. collaborative any-member writes), compose your own from `pgPolicy` + Drizzle operators (`and`/`or`/`eq`).
Each family also ships the **read half** of that one declaration, so you never hand-write the mirror: return `buildOwnerOrAdminShapeWhere(ownerColumn, claims)` (an admin gets `null` — no filter — mirroring the policy’s bypass; everyone else gets `buildOwnershipShapeWhere`), `buildMembershipShapeWhere(columns, claims)`, or `buildGrantScopeAccessShapeWhere(scopeColumn, claims, options)` for a JWT-resident grant set (a bypass grant → `null` too; the bare `resolveGrantScopeIds` + `buildGrantScopeShapeWhere` pair cannot see one) — hand each the *same* options object you gave the policy builder — straight from `customWhere`. Each denies with `DENY_ALL` when the claims resolve to no access: the ownership, owner-or-admin and membership mirrors when there is no subject, the grant-scope mirror when the resolved grant set is empty. They mirror the **SELECT** policy only (a shape `where` filters a read stream), and they render containment as Electric’s plain `IN (subquery)` where the policy renders `= ANY(ARRAY(select …))` for Postgres’ index-scan discipline — same columns, two dialects.
## Stand up the read path
[Section titled “Stand up the read path”](#stand-up-the-read-path)
The client writes locally into an overlay + a durable journal, flushes the journal to the server’s write route, and subscribes to Electric shapes that land in local PGlite. Reads are served from PGlite; the server’s **shape proxy** forwards shape requests to Electric and enforces ownership.
```ts
import { createSyncClient } from "@pgxsinkit/client";
import { registry } from "./sync-registry";
const client = await createSyncClient({
registry,
electricUrl: "/api/shape", // your shape-proxy path (createSyncServer's default route)
batchWriteUrl: "/api/mutations", // the exact pgxsinkit batch endpoint
getAuthToken: async () => currentJwt(),
});
// `createSyncClient` resolves at LOCAL-READ readiness (ADR-0041): cached rows are queryable immediately —
// offline included — and writes transparently await the write runtime, so you can read and write straight
// away. `await client.ready` below is optional: it waits for WHOLE-SYNC catch-up (every eager group), which
// this demo does so the first read reflects the server. For a UI, paint at creation and drive per-view
// loading with `hydrating`/`groupReady` instead of blocking on `ready`.
await client.ready;
// Optimistic local write — staged in the overlay + journal, flushed on the next pass.
await client.tables.widgets.create({ id: crypto.randomUUID(), label: "Hello" });
// Reads come from the local read model (the overlay unioned over synced rows).
const widgets = await client.drizzle.select().from(client.views.widgets);
```
These snippets compile against the published packages, so they stay in step with the shipped API. See [The read path](/concepts/read-path/) and [The write path](/concepts/write-path/) for the full flow, and [Packages](/packages/) for the client entry points.
### Naming the local store
[Section titled “Naming the local store”](#naming-the-local-store)
Pass a `storePath` to name the local store — a plain name, **never** a storage URL:
```ts
const client = await createSyncClient({
registry,
electricUrl: "/api/shape",
batchWriteUrl: "/api/mutations",
storePath: "my-app-store", // a plain name; omit it to use the built-in default
});
```
The storage backend is **derived** from where the code runs — IndexedDB in the browser, the filesystem on Node/Bun — so you never write `idb://…` or `file://…` yourself. A scheme-bearing string is rejected at boot with a clear error. A memory-backed store is deliberately not a production option; pgxsinkit’s durability guarantees (persistent retention, the optimistic write journal) assume a persisted store.
### Testing
[Section titled “Testing”](#testing)
For unit tests that want a fast, throwaway in-memory store, import `memoryStoreForTests` from `@pgxsinkit/client/testing` and spread it into the options — a named, deliberate opt-in whose durability caveats are documented on the helper:
```ts
import { memoryStoreForTests } from "@pgxsinkit/client/testing";
const client = await createSyncClient({
registry,
electricUrl: "/api/shape",
batchWriteUrl: "/api/mutations",
...memoryStoreForTests("my-test"),
});
```
`createSyncClient` runs the engine on the calling thread — right for Node, tests, and as a fallback. In a **browser app** you will usually attach through a SharedWorker instead: `defineSyncWorker` in a worker entry and `attachSyncClient` in the tab return the same client shape off the main thread. Capability placement can host the OPFS engine in that SharedWorker on Safari or behind it in an elected worker on Chromium/Firefox. See [Worker mode](/concepts/worker-mode/).
## Project a writable table read-only per client
[Section titled “Project a writable table read-only per client”](#project-a-writable-table-read-only-per-client)
When two clients consume the same table at different capabilities — a teacher writes a `posting_restriction` a learner only reads, or a learner writes a `report` a teacher only moderates — `mode` is **per-client**, not a property of the table. Define the table **once** in an authoritative registry at its writable capability, generate the server from that, and give each client a **projection** of it.
`mode` is resolved at `defineSyncTable` time and drives the local write machinery (overlay + journal) and the overlay-merged read-model view, so you cannot hand-spread `{ ...entry, mode: "readonly" }` — the entry would keep a view over overlay state the readonly client never creates. Use `asReadonly`, which re-derives a true readonly entry: it drops the overlay/journal projection, the read-model view, and `conflictPolicy`/`governance`/`writeMode`, and keeps the read contract (columns, primary key, synced table, the shape/row filter) intact.
```ts
import { asReadonly, assertReadContractPreserved, defineSyncRegistry } from "@pgxsinkit/contracts";
import { postingRestriction } from "./tables"; // defineSyncTable(…, { mode: "readwrite", conflictPolicy, governance })
// One authoritative registry — the server's apply function + shape proxy are generated from this.
export const authoritativeRegistry = defineSyncRegistry({ posting_restriction: postingRestriction });
// Per-client projections: the teacher writes the table; the learner only reads it.
export const teacherRegistry = defineSyncRegistry({ posting_restriction: postingRestriction });
export const learnerRegistry = defineSyncRegistry({ posting_restriction: asReadonly(postingRestriction) });
// Fail closed if a projection ever diverges the data it syncs (columns / pk / row-filter shape):
assertReadContractPreserved(authoritativeRegistry, teacherRegistry, { label: "teacher" });
assertReadContractPreserved(authoritativeRegistry, learnerRegistry, { label: "learner" });
```
A projection that must append events has to re-declare its streams
The bare-registry-map overload used above (`defineSyncRegistry({ posting_restriction: … })`) has nowhere to declare event streams, and a registry’s streams ride a **non-enumerable symbol** — so spreading a table map (or a registry) into a projection carries the tables and silently leaves the streams behind, and that client’s `appendEvent` throws “no Event streams registered”. If a projection is meant to serve the event lane, build it with the definition-object form and re-declare them: `defineSyncRegistry({ tables: { … }, streams })`. Re-declaring is cheap and safe — a stream provisions a queue and touches no synced table, no local schema and no apply function.
Generate the server from the authoritative registry
The apply function emits a write branch for **every** table and stamps managed fields / reject-if-stale from the entry’s write contract, so it must be generated from the writable entry — generate from the authoritative registry, not a readonly projection. A claims-branching `customWhere` then serves every client from one shape definition (each request carries its own claims). A projection may differ **only** in write capability and lifecycle (`subscription`/`retention`/group), never in the read contract — that is what `assertReadContractPreserved` enforces. It cannot see the `customWhere` body, so bump `rowFilter.revision` on a logic change. The full registry fingerprint differs between the writable and readonly variants — expected and fine: it is client-local, guarding each client’s own store, and the server never sees it. See [ADR-0025](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0025-per-client-mode-projection.md).
## Register an event stream (queue-shaped data)
[Section titled “Register an event stream (queue-shaped data)”](#register-an-event-stream-queue-shaped-data)
Not everything belongs on a synced table. High-volume, append-only client facts — “the user viewed this”, interaction logs, review grades — are never edited, never conflict, and are never read back down, so putting them on the sync rail makes every client re-download its own log. They go on the **event lane** instead, registered on the same registry under a `streams` key whose record key is the stream name:
```ts
import { defineEventStream, defineSyncRegistry } from "@pgxsinkit/contracts";
import { z } from "zod";
export const registry = defineSyncRegistry({
tables: { issue },
streams: {
issue_viewed: defineEventStream({
// Strict is REQUIRED for an object payload, and validated at `appendEvent` as well as at ingest.
payload: z.object({ issueId: z.uuid() }).strict(),
// Stamped SERVER-side from the verified claims — the same claimPath addressing managed fields use.
identity: { viewerId: { claimPath: ["sub"] } },
}),
},
});
```
Then `await client.appendEvent("issue_viewed", { issueId })` stages the event in a durable local Outbox and resolves — delivery happens in the background, and an append made offline drains on reconnect. Three rules worth knowing at authoring time:
* **Never put a viewer/actor id in the payload.** A payload is client-supplied and can lie; `identity` is read from verified claims, and the client’s envelope carries no identity at all.
* **Stream names are validated when the module evaluates** — lowercase `[a-z][a-z0-9_]*`, at most 30 characters — so a name that could not be provisioned fails at definition rather than at deployment.
* **An object payload must be strict** — `.strict()` or `z.strictObject()`, on the root and on every object in a union — and the registry throws if it is not, so an unknown key can never be stripped in silence.
* **A payload schema may only evolve backward-compatibly.** Events written offline under the old schema are still in flight; an incompatible change needs a **new stream name**. Bump `revision` whenever you change acceptance logic the lock’s JSON-Schema hash cannot see (a `.refine`, a transform), or the review gate never fires.
Registering a stream touches no synced table and no apply function, so it never rebuilds a client’s read cache — but it does add a deploy step (per-stream queues) and a consumer process. Full model: [The event lane](/concepts/event-lane/); deployment: [Deploying the server](/start/deploying-the-server/).
## Try the demo
[Section titled “Try the demo”](#try-the-demo)
The repository’s `apps/board` (a Linear-style board + chat) drives all of the above end-to-end against a partial Supabase + Electric stack:
```bash
mise install && bun install
mkcert -install # one-time: trust the local CA so the browser accepts the gateway's TLS cert
cp .env.example .env
bun run infra:up # the full board stack (Supabase + Electric) via Podman compose
bun run seed:board # GoTrue identities + fixtures
bun run dev:board
```
The board's gateway is served over HTTPS (HTTP/2) — mkcert is a prerequisite
The browser holds one Electric long-poll connection **per synced shape**, and the board syncs six. Over plain HTTP/1.1 those long-polls exhaust the browser’s \~6-connections-per-origin cap and writes starve behind them (see [Deploying the server → the gateway must speak HTTP/2](/start/deploying-the-server/)). So the demo fronts its gateway with a TLS-terminating **HTTP/2 + HTTP/3** Caddy sidecar, and the board’s browser origin is `https://localhost:54343`. `infra:up` issues the cert with [`mkcert`](https://github.com/FiloSottile/mkcert), but the local CA must be **trusted** first — `mkcert -install` (one-time) — because a browser `fetch()` to an untrusted cert fails outright (no click-through), and HTTP/3’s QUIC refuses an untrusted cert entirely. Without mkcert the stack still comes up on plain `http://localhost:54331` (used by the integration tests and seed scripts); only the fast browser path is unavailable.
For the minimal `@pgxsinkit/server` reference instead, use `bun run infra:harness:up` (PostgreSQL + Electric) + `bun run dev:api`. See [Demo & harness](/demo-and-harness/) for what each is for.
# Operating in production
> The runtime gotchas a live pgxsinkit app hits — convergence cadence, edge cold starts, proxy caching, the browser connection budget, and the built-in latency instrumentation.
The read and write primitives are fast, but a few **runtime and deployment** properties around them decide whether a live app *feels* fast. None are toolkit bugs — they are how serverless edges, browser HTTP, and CDN-shaped caching behave — but every one of them was hit dogfooding the demo, so they are collected here with their fixes. If writes or sync feel slow in a real browser but your server benchmarks are fast, the cause is almost always on this page.
## Convergence cadence: event-driven, with the interval as a fallback
[Section titled “Convergence cadence: event-driven, with the interval as a fallback”](#convergence-cadence-event-driven-with-the-interval-as-a-fallback)
When you pass an `autoSync` trigger to `createSyncClient`, the client drives a `flush → reconcile` pass. The pass is **event-driven**: the client calls `requestPass()` the moment a mutation is enqueued, so a local write flushes to the server immediately — it does **not** wait for the trigger’s next interval tick. The interval (`createBrowserConvergenceTrigger({ intervalMs })`, default **1.5s**) is therefore only a **fallback** for retries, recovery, and cross-tab wake-ups.
Because the happy path is event-driven, you should make that fallback interval **long**. A short interval is the dominant idle cost: every PGlite query carries \~50ms of WASM overhead and serializes on the one worker thread, and an unconditional reconcile each tick fires PGlite’s live-query notifications, re-running every mounted query. The toolkit already idle-skips an empty reconcile, but the cheapest idle board is still a rare interval — the board demo runs `intervalMs: 15_000`, taking idle CPU from \~70% of a core to \~2% with **no change to convergence latency** (latency is bounded by the Electric echo, not the interval).
In [worker mode](/concepts/worker-mode/) the **worker** owns this loop: `defineSyncWorker`’s `convergenceIntervalMs` is the same fallback interval and already defaults to **15s**. Writes still flush on enqueue (the write RPC requests a pass) and tabs forward `online`/`visibilitychange` as wake signals, so the same rule holds — the interval is a retry/recovery sweep, not the write path.
Don't tie latency to the interval
A common mistake is to shorten the interval to “make writes faster.” It does nothing — writes already flush on enqueue. Shortening it only burns idle CPU. Leave it long.
## Local write latency: the durability preference (relaxed by default)
[Section titled “Local write latency: the durability preference (relaxed by default)”](#local-write-latency-the-durability-preference-relaxed-by-default)
`durability` is declared **once, on the registry** — `SyncRegistryDefinition.storage.durability` (`"relaxed" | "strict"`, default `"relaxed"`). It is not a minting-surface, worker-entry, or attach-site option: whether losing the last not-yet-flushed action is acceptable is decided by what the data IS, so one declaration binds every open of every store minted from that registry — no tab can ever disagree with another. The physical behavior depends on the capability-selected backend:
* **IndexedDB:** strict flushes the whole datadir synchronously at the end of every query, setting a \~100–200ms optimistic-write floor. Relaxed returns before that snapshot and schedules it asynchronously.
* **OPFS-repacked:** PGlite always awaits the host sync. Relaxed asserts VFS health and runs any due deferred repack without an ordinary physical flush; strict flushes arena data before metadata. Initialization, repack activation, and open-state close keep strict ordering in both modes.
The resolved mode is stamped on the `boot pglite.create` rail line. A capability fallback from opfs to idbfs keeps the registry-declared durability unchanged.
**What you trade.** On idb, writes since the last completed snapshot are at risk only if the browser terminates before both their journal rows reach the write API and the scheduled snapshot lands. On OPFS-repacked, relaxed recovery returns the longest valid stable metadata-log prefix, so an unflushed suffix may be absent; a returned strict boundary is stable under the browser-termination model. Synced tables are server-recoverable by construction. Your own local-only tables have no such copy.
Opt out only if you need to
If you keep local-only data you cannot re-derive and cannot accept the backend’s relaxed termination window, declare `storage: { durability: "strict" }` on the registry. Everyone else should leave the default on.
## Edge serverless cold starts
[Section titled “Edge serverless cold starts”](#edge-serverless-cold-starts)
On a serverless Edge platform a worker is suspended when idle and evicted after longer idle, so the **first write after a quiet period** pays a cold start while steady-state writes are instant. Measured on the self-hosted Supabase edge-runtime: a warm write applies in **\~20ms**; a write to a worker suspended \~15s pays **\~0.45s** (a Postgres reconnect on resume); a write to a worker whose module cache is cold pays **\~5.8s** (a fresh isolate re-imports the whole bundle). Drag the first card after the board sits idle and that cold worker is the entire delay — not the sync rail.
This is a property of the **serverless deployment target**, not pgxsinkit: the same functions on a long-lived Bun or Deno process (one warm process, a pooled connection) or on a managed warm pool have no cold start. Two mitigations if you stay serverless:
* **Keep the worker warm** with a periodic cheap request. The cheapest request that still reaches the worker is a no-op write — an empty `{"mutations":[]}` POST, rejected at request validation *before* any DB work. A small sidecar pinging it every \~8s keeps writes at \~20ms after idle.
* **Set the worker’s wall-clock timeout above your longest held-open shape long-poll** (Electric’s is \~25s) so a live read subscription is not recycled mid-cycle, forcing a read-path reconnect. See [Deploying the server](/start/deploying-the-server/).
## Proxying Electric: force `cache-control: no-store`
[Section titled “Proxying Electric: force cache-control: no-store”](#proxying-electric-force-cache-control-no-store)
Electric tags shape responses with a long, CDN-oriented `cache-control` (`max-age=…, stale-while-revalidate=…`) that assumes a CDN keying on the full request URL. Behind a **same-origin proxy with no CDN**, the browser’s HTTP cache instead serves those responses **stale** the moment a shape handle rotates server-side (a re-seed, a re-login, a restart). The client then loops on “expired shape handle” **409s** until it self-heals — a confusing, intermittent stall.
The fix is one line in your shape-proxy function: force `cache-control: no-store` on the response so the browser never reuses a rotated shape. Resumption stays cheap because Electric’s own offset/handle bookkeeping (persisted in the local store) is what makes it cheap — not the HTTP cache.
```ts
const response = await proxyElectricShapeRequest(request, claims, { registry, electricUrl });
const headers = new Headers(response.headers);
headers.set("cache-control", "no-store");
return new Response(response.body, { status: response.status, statusText: response.statusText, headers });
```
The **upstream** direction has a caching hazard too, on hosted Electric behind a CDN: live long-polls can be answered by a layer blind to fresh commits for consecutive full-hold cycles, turning “live” into \~40–90s cross-client propagation even though every request URL is unique (the `cursor` advances). The proxy therefore appends a unique `cache-buster` to every `live=true` request it forwards (`bustLiveUpstreamCache`, default on) — catch-up requests stay unbusted so their CDN cold-fanout sharing keeps working. If your Electric is self-hosted with no CDN in front, the extra param is harmless; you can set `bustLiveUpstreamCache: false` to restore untouched forwarding.
**Know which of these two mechanisms is which.** The live-bust is a *temporary mitigation for an upstream defect* — a healthy CDN should complete coalesced live polls the moment data arrives, and busting defeats Electric’s sanctioned live-poll coalescing (every client’s poll reaches origin individually, which is the property their “millions of concurrent clients” scaling story rests on). At large client counts on hosted Electric that trade matters: you are buying sub-second liveness with per-client origin fan-out. Flip it off once the upstream live path wakes reliably through the CDN. The sibling **nudge** (next paragraph) is different in kind: it is *permanent protocol behavior*, correct and required under CDN-fronted Electric no matter how healthy the CDN is.
The proxy-side bust restores wake-on-commit for the shape that **changed** — but a consistency group commits atomically at its *slowest* shape’s watermark, and a **quiet** sibling’s parked long-poll returns nothing until its hold expires (\~41s on Electric Cloud), which would still delay the commit by that whole hold. The client closes this half itself (no configuration): when a live change batch is gated behind quiet siblings, it **nudges** them — aborting their parked polls and forcing an immediate non-live catch-up (with a one-shot `cache-buster`, so a CDN HIT cannot echo the stale watermark) that returns a fresh watermark in \~sub-second. Bounded rounds, single-flight per group, atomicity untouched ([ADR-0033](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0033-live-tail-sibling-nudge.md)).
## Fronting Electric with your own CDN: configure it properly
[Section titled “Fronting Electric with your own CDN: configure it properly”](#fronting-electric-with-your-own-cdn-configure-it-properly)
CDN-fronted Electric is the sanctioned scaling paradigm — catch-ups served from cache, live long-polls **coalesced** at the CDN so Electric holds one origin connection instead of one per client — and pgxsinkit is built to be correct under it. But “correct” degrades to “correct and slow” behind a misconfigured CDN, so if you put your own Cloudflare/Fastly/CloudFront in front of a self-hosted Electric, configure it to these rules:
**Cache key.**
* Key on the **full URL including every query parameter**. `handle`, `offset`, `live`, `cursor`, `cache-buster`, `expired_handle`, and the shape-defining params (`table`, `where`, `params`, `columns`) are all load-bearing; a key that drops any of them serves one shape’s (or one moment’s) body to another. This is also Electric’s own troubleshooting requirement — their client logs `[Electric] Received stale cached response…` when it detects the violation.
* Never strip, rewrite, or “normalize away” query params in a transform rule. `cursor` is how Electric phases live polling through a CDN; `cache-buster` is how retries and the ADR-0033 nudge punch through deliberately.
**Cache policy.**
* **Respect origin `cache-control` verbatim** — don’t override TTLs in either direction. Electric marks catch-up responses cacheable (that’s the cold-fanout win) and live responses short-lived.
* Keep any `stale-while-revalidate` serving window **modest** (seconds–minutes). Electric Cloud ships a \~1-month SWR window on catch-ups, which guarantees the first visitor after a quiet hour paints hours-stale and then reconciles; pgxsinkit tolerates that (ADR-0031), but there is no reason to serve it.
**Live long-polls (`live=true`).**
* **Coalescing identical in-flight live requests is good** — it is the design. The requirement is that when the origin completes a held poll (data arrived), the CDN must **complete every coalesced client immediately** with that response — and must never serve an already-completed “nothing new” live response from cache to a later poll (a correct full-URL key prevents this: the `cursor` differs).
* Set proxy/CDN **read timeouts above the long-poll hold** (Electric holds \~20s; allow 60s+), and disable response buffering that would sit on a completed poll.
**Transport.** HTTP/2 (or HTTP/3) on every hop — see the connection-budget section below.
**Verify it, don’t assume it.** With `bustLiveUpstreamCache` **off**, write from one client and time another client’s render: **≲3s means the CDN is healthy**; \~a full hold cycle (20–40s) means live polls are being served blind — fix the cache key/coalescing config (and enable `bustLiveUpstreamCache` as a stopgap while you do). On the client rail, a repeating `sync change batch held by group frontier` → `live-tail nudge exhausted` pattern means even busted catch-ups aren’t reaching origin — at that point the CDN is rewriting URLs, and the config (not pgxsinkit) is the bug.
## The browser connection budget (serve the gateway over HTTP/2)
[Section titled “The browser connection budget (serve the gateway over HTTP/2)”](#the-browser-connection-budget-serve-the-gateway-over-http2)
Electric’s client holds **one live long-poll connection open per synced shape**. A client subscribing to six shapes keeps six connections continuously busy, and browsers cap **HTTP/1.1 at \~6 connections per origin** — so over plain HTTP those long-polls consume every slot and the **write** request (same origin) gets **Stalled in the browser’s connection queue** for a whole long-poll cycle before it is even dispatched. Serve the gateway over **HTTP/2** (or HTTP/3), which multiplexes every request over one connection, so the cap never binds. This only bites a local stack served over plain `http://`; any production ingress (Cloud Supabase, Electric Cloud, an istio/Envoy gateway, a TLS reverse proxy) already speaks HTTP/2. Full detail and the symptom check are in [Deploying the server](/start/deploying-the-server/).
## Read-path apply failures hold, they don’t diverge
[Section titled “Read-path apply failures hold, they don’t diverge”](#read-path-apply-failures-hold-they-dont-diverge)
If a local commit into PGlite keeps failing — a bad local migration, a storage/quota error, a corrupt local store — the read path retries with backoff and then **latches into a `degraded` phase** instead of advancing past the change it could not apply. It holds the read cache at the **last good commit**, so the client never silently diverges from the server. The failure is surfaced through the `onSyncError` callback you pass to `createSyncClient`, and the runtime’s `status` reports the `degraded` phase.
How it recovers depends on *why* it degraded:
* A **read-stream** error (a dropped shape connection) clears automatically on the next successful batch. A read path that cannot reach the server **at all** — the client went offline — reports the same `degraded` phase: the fetch is retried forever inside Electric’s backoff, so the runtime detects it from the failed attempts rather than from an error, and it clears the same way the moment a batch lands. A connection that dies by **hanging** rather than failing (a pulled cable mid-long-poll) produces neither a failed attempt nor a batch, so a runtime claiming `ready` is additionally held to a **read-silence window** (`readSilenceMs`, default 45s — a healthy stream’s long-poll always cycles well inside it): silence past the window drops `ready` to the same self-recovering `degraded`. That is the phase to key an offline/“connection needed” surface off; `navigator.onLine` is not a substitute.
* A **commit** failure is **sticky** — fetching can keep succeeding while applies fail — so it clears only on the next commit that succeeds, *after* you fix the underlying cause, or on a **client restart**. There is no separate reset call for this; `recoverSending` rebuilds the *write* journal, not the read frontier.
## Watching the event lane’s Outbox
[Section titled “Watching the event lane’s Outbox”](#watching-the-event-lanes-outbox)
If your app appends events (the model is in [The event lane](/concepts/event-lane/)), the client gives you two surfaces to operate it with, and neither is optional reading before you ship one. The knobs on the other side of them — batch caps, the fallback interval, backoff bounds, and the per-stream fairness cap — are the client’s `events` option, documented under [Tuning the flush](/concepts/event-lane/#tuning-the-flush-client-config-never-the-registry).
**The drain signal** is `client.onOutboxStatus(({ empty }) => …)` — the empty ↔ non-empty transitions, with the current state delivered on subscribe (`await client.outboxStatus()` is the one-shot pull). Use it to invalidate a view that composes pending events with down-synced aggregates: when the Outbox drains, the aggregate is authoritative again. It carries no count deliberately — a count that updates only on transitions is stale by construction — so query the Outbox table (`getOutboxTable(registry)`) if you want one.
**The verdicts** are `client.onEventLaneReport(cb)`: per flush pass, the terminal non-`acked` verdicts, the `deferred` ones, and the lane’s batch-level backoff transitions. Subscribe for the **app’s lifetime**, not per screen — the subscription is ephemeral, and once a terminal row is deleted the Outbox cannot answer for it. (With nothing subscribed the library logs each report at warn level rather than dropping it.)
Read them this way:
* `refused` — your `eventGate` declined it. Expected, not an error.
* `rejected` — a schema-invalid or oversized payload for a **known** stream. The library validates at append, so in practice this means a non-library caller or a broken deployment: treat it as a **bug**.
* `deferred` — the server does not (yet) know that stream. This is **not** a failure and **not** terminal: it is ordinary rollout skew, the rows stay in the Outbox, and they drain when the server deploy lands. A burst right after a client release is the deploy order; a burst that never clears means **deployment skew** — the server’s registry does not declare that stream (it is the registry, and only the registry, that decides this verdict). Never “clean up” the Outbox in response to it.
A lane stuck in **batch-level backoff** is a different diagnosis, and the report’s `backoff` transitions (plus a persistent `503` with `Retry-After`) are its signal. The server knows the stream but could not enqueue the batch, and it fails the batch **whole** — nothing is enqueued and no per-event verdicts are issued. The first thing to check is the queue itself: registering a stream requires generating and applying the **`--events` migration**, and without it the endpoint enqueues onto a pgmq queue that does not exist. (Then: the database’s reachability from the ingress, and the server log line the 503 always writes.)
A growing Outbox is the design working, not a leak
There is no attempt cap and no client-side quarantine on this lane: a row leaves the Outbox only on a server-issued per-event verdict, which is what at-least-once means on this edge. So a lane that cannot reach its server presents as a growing Outbox backing off observably (with `Retry-After` honoured), never as silently discarded events — the Outbox is designed to absorb offline weeks. Note also that `destroy()` refuses while it is non-empty, exactly as it refuses on owed mutations.
## `operations_log` records user content
[Section titled “operations\_log records user content”](#operations_log-records-user-content)
The optional write-side audit log (`operationsLog: { enabled: true }`, **off by default**) persists every mutation — table, kind, and **payload** — including the raw body of mutations that **failed validation**. Those payloads contain whatever your users typed. So treat the `operations_log` table as sensitive: restrict access and set a retention policy, and leave it disabled in environments where that content should not sit at rest.
Its `mutation_id` column is **`text`, not `uuid`** — deliberately. This is the server-side tier of a two-tier invariant: pgxsinkit’s public write surface (the HTTP route’s request/ack schemas and the client’s `mutation_id UUID` journal) is **UUID-only by contract**, but the generated apply function and this log accept an **opaque text id** for one narrow case — a direct, server-side caller that derives child envelopes with composite ids (`${parentMutationId}::`) and invokes the apply function itself, never crossing the HTTP route or the client journal. A non-UUID id can never reach the UUID-typed public surface.
## Debugging latency: `globalThis.__pgxsinkitDebug`
[Section titled “Debugging latency: globalThis.\_\_pgxsinkitDebug”](#debugging-latency-globalthis__pgxsinkitdebug)
`@pgxsinkit/client` ships opt-in, timestamped instrumentation that traces a write through every phase — exactly what localises a “writes are slow” problem to a single hop. It is **off by default** and adds nothing to a normal run; enable it from the console or before the client boots:
```js
globalThis.__pgxsinkitDebug = true; // then reproduce; filter the console to "pgxsinkit" + enable Verbose
```
Each line is stamped with a monotonic millisecond clock, so you read the **gaps** between phases directly:
* `mutation staged {mutationId, table}` (the write’s origin — correlate by id with the sent/acked lines)
* `convergence pass requested` → `convergence flush` → `convergence reconcile` (with durations)
* `board-write auth token resolved {ms}` (a stalling per-request `getSession()` shows up here)
* `board-write responded {status, ms}` (a cold edge worker, or a browser connection stall, shows up here)
* `shape request start {shape, offset, live}` → `shape request done {shape, status, ms, upToDate?}` (every Electric HTTP cycle on the read path — catch-up and long-poll alike)
* `must-refetch received {shape}` (the server rotated a shape; the truncate + re-snapshot recovery follows)
* `sync received change batch from Electric` → `sync applied … {ms}` (the receive + local apply; the “applied” line fires only when the batch actually committed — a batch gated behind a quiet sibling’s watermark logs `sync change batch held by group frontier` instead, followed by `live-tail sibling nudge {shape}` lines as the engine refreshes the laggards, ADR-0033)
* `live query updated → re-render` (the final UI hop)
* `boot pglite.create` → `boot client ready` (the boot phases — local store open, schema apply, journal recovery, store-version reconcile, sync start — for attributing a slow first paint to a boot phase)
* `boot pglite assets warm` (only when the host uses the pre-warm below — see next section)
### The structured `BootReport` — measure before you optimize
[Section titled “The structured BootReport — measure before you optimize”](#the-structured-bootreport--measure-before-you-optimize)
The rail lines above are for a **human** mid-debug: you eyeball the gaps as they scroll. For numbers a machine can keep — a dashboard series, a CI budget gate, an honest before/after — every boot **also** builds a structured, **versioned** `BootReport`, independently of the rail, so it exists whether or not `__pgxsinkitDebug` is on. Boot performance regressed repeatedly because it was optimized on guesses (the suspected bottleneck was rarely the real one); the report is the evidence to start from instead (ADR-0034). Read it by push, by pull, or both:
```ts
const client = await createSyncClient({
registry,
electricUrl,
batchWriteUrl,
onBootReport: (report) => {
// fires exactly once, at boot completion — ship it to a dashboard, or assert a CI budget
metrics.timing("boot.total", report.totalMs);
},
});
const report = await client.bootReport(); // the most recent completed boot, or null before the first sync
```
`report.totalMs` is boot start → every eager group caught up; `phases` decomposes the local work (pglite create, schema exec, journal recovery, store-version reconcile, sync start, catch-up) and `groups[]` breaks out the per-consistency-group boot catch-up (`rows`, `requests`, `fetchMs`, `applyMs`, start/ready offsets).
**`localReadReadyMs` and `writeReadyMs` mark the staged-boot crossings** (ADR-0041; additive, `reportVersion` stays `1`). `localReadReadyMs` is boot start → cached reads are safe (store open, schema compatible, reconcile done — zero network); it is the moment `attachSyncClient` / `createSyncClient` resolve. `writeReadyMs` is boot start → the write runtime + boot recovery finished (enqueue is safe). Both are `null` when the boot rejected before reaching that stage. The gap from `localReadReadyMs` to `totalMs` is the whole-sync catch-up a cached paint no longer waits on.
**`storeKind` names how the store presented at boot** — `"restored"` (seeded from a backup via `restoreFrom`), `"fresh"` (a caller-proven schemaless spare — the same signal as the `freshStore` boolean, which stays alongside it), or `"warm"` (an existing persisted store, the common case). The `warmBoot` group carries the two warm-boot fast paths, both live.
**Durable-schema replay.** Boot hashes the registry-generated durable SQL and compares it with the fingerprint stored in the store. On a match the whole durable replay is **skipped** — `schemaSkipped: true`, `schemaFingerprintMatch: true`. On a mismatch or a store with no stored fingerprint (fresh, rebuilt) the durable schema is replayed and the new fingerprint stamped, and both flags read `false`. The **ephemeral** schema is recreated on every boot regardless (TEMP relations die with the old engine), so it is never part of the skip. A boot that adopts a caller-supplied `pgliteInstance` runs no schema stage at all and leaves both flags at their conservative `false`.
**Journal recovery.** The boot-time `sending → pending` recovery pass is driven by a durable recovery marker that a clean settle clears:
* **Marker clear** (the common warm boot — the previous run proved no `sending` row remains): the per-table pass is skipped entirely. `journalRecoverySkipped: true`, `journalRecoveryRequired: false`, `journalTablesVisited: 0`, `journalRowsRecovered: 0`.
* **Marker set** (a crash may have left committed `sending` rows): the per-table updates plus the self-verifying marker clear run in one transaction. `journalRecoverySkipped: false`, `journalRecoveryRequired: true`, `journalTablesVisited` is the registry’s writable-journal count, and `journalRowsRecovered` is the **real count** of rows lifted `sending → pending`.
* **Marker absent** (never initialised), a **caller-supplied `pgliteInstance`** (pgxsinkit never touches the marker table it does not own), or a **restore boot** (which ignores the marker and quarantines what it recovers): one conservative unconditional pass. `journalRecoverySkipped: false`, `journalRecoveryRequired: true`, `journalTablesVisited` is the writable-journal count, and `journalRowsRecovered` is `null` — that pass is uncounted, so `null` means “not measured”, never “zero”.
**Read `fetchMs`/`applyMs` as concurrent segments, not a network bill.** Groups catch up **in parallel** on a single-threaded WASM host, so a group’s `fetchMs` (its settle→next-delivery wall) absorbs the OTHER groups’ apply transactions and main-thread work landing between its deliveries — it is an **upper bound on network wait** (“time this group spent not applying”), **not** pure network cost. `applyMs` likewise includes waiting behind a sibling group’s transaction on the single shared connection, so concurrent groups’ `applyMs` can overlap. Do not sum the per-group segments into a partition of `totalMs`. (A related reading note: `phases.syncStartMs` is structurally `0` when the boot is ready inside the sync-start call itself — zero eager groups, or instant catch-up.)
**The `provision` block is what your login-dwell amortized.** When it is non-null, the store was adopted from a pre-provisioned spare (the worker-mode / eager-create pattern below): `provision.initdbMs` is the PGlite create cost that ran **off-thread before this boot**, and `provision.provisionedMsBeforeBoot` is how long that store sat ready before the boot claimed it — the spare’s amortized `initdb`, made visible. On such a boot `phases.pgliteCreateMs` is `null`, because the create cost is reported in `provision` instead.
The report is `reportVersion: 1` — a contract number a consumer can branch on (additive fields keep it; a breaking reshape bumps it). It is a plain structured-clone-safe object, so in worker mode it crosses the bridge unchanged; see [Worker mode](/concepts/worker-mode/) for the push-at-finalize vs pull-for-late-tabs semantics (`onBootReport` fires only for a tab attached when the boot finalizes; a later tab reads the same boot through `bootReport()`).
### Pre-warming PGlite’s boot assets
[Section titled “Pre-warming PGlite’s boot assets”](#pre-warming-pglites-boot-assets)
A cold `PGlite.create` spends \~2.5s fetching and compiling the Postgres WASM (plus the initdb WASM and the filesystem bundle) before it can open a store — and that cost otherwise lands **after** sign-in, on the critical path to first paint. `createSyncClient` accepts a `pgliteBootAssets` option: a promise of the already-fetched/compiled assets (`{ pgliteWasmModule?, initdbWasmModule?, fsBundle? }`) that it awaits and passes straight into `PGlite.create`, so the create skips its own lazy asset load. Kick the fetch+compile off on an **earlier screen** (a login/identity picker) and hand the still-pending promise in, and the WASM cost hides behind user think-time. It is pure best-effort: a rejected/failed warm is caught to `undefined` and PGlite falls back to loading its own assets — the warm never fails the boot. The `boot pglite assets warm` rail stamp times the warm itself. (The board demo wires this from its login route; see `apps/board/src/board/pglite-warm.ts` for the Vite `?url` asset-resolution pattern.)
In **worker mode the engine loads PGlite’s own assets — deliberately**; do not pre-supply them to the worker. The tab’s warm still serves the engine, by priming the same-origin HTTP cache the worker fetches from. Handing the engine a pre-compiled `WebAssembly.Module` benched **net-negative**: it forces compile-to-completion before instantiate, forfeiting the pipelining PGlite gets from its own streaming load, and the engine realm has no overlap window longer than the placement/handshake gap, so the compile only competes for CPU at worker spawn.
Pre-warming hides only the WASM fetch+compile — `PGlite.create` still spends \~1.9s on `initdb` and opening the store, and that cannot start until the store id is known (typically the signed-in user). To hide that cost too, create the store **eagerly** under a generated id on the first screen and **bind** it at auth. `createClientPGlite(storePath, { bootAssets })` runs the identical create the client does internally (the `electric` + `live` extensions, boot-asset consumption, the `boot pglite.create` stamp) and returns a schemaless instance; hand the still-pending promise to `createSyncClient`’s `precreatedPglite` option. Unlike `pgliteInstance` (which assumes the caller applied the schema), `precreatedPglite` still lets the client run schema exec, prepare hooks, journal recovery, and store-version reconcile — so the eager create buys only `initdb`, and the role/registry-derived schema stays post-auth. A rejected `precreatedPglite` is caught and falls back to the `storePath` create path (also consuming `pgliteBootAssets`), so the pattern is a pure accelerator, never a boot dependency. Bind eager stores to users with a small localStorage registry (userId→storeId plus one unbound “spare”): create a spare on the login screen, claim it at sign-in, and GC any store that is neither mapped nor the spare. (Board demo: `apps/board/src/board/store-registry.ts`.)
The server side has the matching rail: `createSyncServer({ logTimings: true })` (default off) emits one compact `[pgxsinkit-timing]` JSON line per request — the mutation route with `preTxMs`/`txOpenMs`/`authMs`/`applyMs`/`totalMs` (`txOpenMs` is the driver’s lazy connect + BEGIN, where a serverless worker’s connection cost hides), the shape proxy with `upstreamMs`/`totalMs`. Client-observed minus server `totalMs` isolates routing + network. On serverless hosts, mind the **geometry**: workers run near the caller while the database lives in one region, so a chatty **write** pays the cross-region round trip per statement — pin the DB-bound write function to the database’s region (Supabase: the `x-region` header, carried by the client’s `writeRequestHeaders` option) so the long hop is paid once per request instead. Pin **only** DB-bound functions: a read proxy’s upstream is Electric Cloud’s globally-distributed CDN, so pinning reads away from the caller **adds** intercontinental hops per catch-up (\~1.2s vs \~300ms unpinned) — leave read proxies unpinned to follow the caller, and keep the region header out of the shared `requestHeaders` (which reads also send).
Measure at the network boundary, not by polling PGlite
Every PGlite query is \~50ms of WASM work on a single thread, so a tight `setInterval` that reads PGlite to “watch” a value inflates the very latency it reports — a self-inflicted slowdown that repeatedly masqueraded as a sync problem while dogfooding. Trust the instrumentation’s network-boundary timings (and a server-side `curl`) over a polling loop.
## Worker mode: reading the rail off the main thread
[Section titled “Worker mode: reading the rail off the main thread”](#worker-mode-reading-the-rail-off-the-main-thread)
In a browser app you will usually attach through a **SharedWorker** rather than run on the calling thread — `defineSyncWorker` in a worker entry, `attachSyncClient` in the tab. A capability probe at boot decides the engine’s home: real Safari hosts the OPFS engine in that SharedWorker, while Chromium and Firefox elect a dedicated engine worker behind it. See [Worker mode](/concepts/worker-mode/) for the SharedWorker factory, relocation, and storage lifecycle. PGlite, shape streams, and convergence stay off the main thread in either home.
* **The debug rail is forwarded and origin-tagged.** A SharedWorker’s own `console` is invisible to the page (only `chrome://inspect` reaches it), so the worker forwards every rail line to each attached tab, stamped with the **worker’s** monotonic clock and re-printed as `[pgxsinkit·w ms] …` — gated by that tab’s own `globalThis.__pgxsinkitDebug`. Set the flag on the **tab** as usual; the write/read/boot phases read the same, just origin-tagged. Without the forwarding a worker-mode app would go dark, so this is on whenever the tab’s debug flag is. The front half of boot runs on the **first** attach, before any tab is listening, so the worker buffers those pre-attach rail lines in a bounded ring (last 500) and replays them, `[replay]`-marked, to the first attaching tab — so even the boot’s opening phases reach it (ADR-0034). The worker’s **network traffic is invisible the same way**: shape requests never appear in the page’s Network panel, so “rail shows `shape request start`, Network tab shows nothing” is normal — inspect the worker itself (`chrome://inspect/#workers`) for the real requests, status codes, and errors such as CORS rejections.
* **The spare store is a pre-spawned worker, and the prefetch overlaps internally.** The spare-store pattern from [Pre-warming PGlite’s boot assets](#pre-warming-pglites-boot-assets) becomes a schemaless worker spawned at the login screen; claiming it binds the store id (tab-side `localStorage`) and attaches with config + token. On a **provably fresh** claimed store the worker overlaps the shape catch-up with its local boot phases, so a far-from-database boot is bounded by `max(create+schema, catch-up)` instead of their sum. New boot-rail stamps trace it: `boot spare store ensured`, `boot mapped store prewarm`, `boot store claimed`, `boot shape prefetch start`, and `boot commits opened`.
* **`ready` is unchanged; per-group readiness is available.** `client.ready` still gates on every eager group. For progressive paint, `await client.groupReady(tableKey)` or read `status.groups` — no contract change.
SharedWorker support and the in-process fallback
Worker mode needs a native `SharedWorker`. Where it is missing the client falls back to the plain **in-process** engine (correct, just on the main thread) — the same code `createSyncClient` runs and what bun/Node tests use — so an app is never blocked, it just loses off-thread isolation and uses IndexedDB. Pass `extendedLifetime: true` on every SharedWorker construction: Chromium 148+ may keep it alive for a short flush/warm-start grace period, while Firefox and Safari safely ignore the unknown option. It does not keep an elected Chromium/Firefox engine alive.
A home with no OPFS grant refuses a store that is already committed to OPFS
A boot whose engine home holds no OPFS sync-access grant — a tab realm, the in-process fallback above — cannot open a store that a granted home already committed to the OPFS backend. It **fails closed** with `CommittedStoreUnreachableError` rather than opening `idb://`, which would mint an **empty sibling** at the same path: the app looks wiped and any offline writes fork into a store no worker-mode boot ever opens. There is no override flag (the same posture as the storage declaration: never a silently different storage mode). The two exits are in the message — boot the store from a home that holds a grant, or destroy it first with `destroyStoreArtifacts(storePath)` and let the next boot rebuild it. That destroy is the callable remedy here: it is **path-addressed** (your boot just failed, so there is no client to call `destroy()` on), it deletes **both** backends plus the commitment sentinel and the meta record, and it needs no grant — quiesce any live worker for the path first (`quiesceStoreWorker`). The error carries the `storePath` it refused, so a handler can call the remedy without parsing the message. A worker-mode `provision` accelerator in such a home declines for the same reason instead of pre-minting the sibling.
## Initial catch-up, CDN-cached watermarks, and the alignment trade
[Section titled “Initial catch-up, CDN-cached watermarks, and the alignment trade”](#initial-catch-up-cdn-cached-watermarks-and-the-alignment-trade)
A consistency group syncs its shapes as one atomic unit: the client commits at the **slowest shape’s** frontier, so a transaction touching two tables never renders half-applied. Each shape’s frontier advances on Electric’s `up-to-date` message, whose `global_last_seen_lsn` is the replication head that shape has caught up to.
Electric’s **catch-up** (non-live) shape responses are **CDN-cacheable by design** — a cold fanout of clients then shares one origin fetch — and the `up-to-date` watermark rides **inside that cached body**. On a fresh load, a quiet shape can therefore deliver a **stale** cached watermark while a busy sibling delivers real changes at higher LSNs. Held to the slowest frontier verbatim, the group would pin those delivered changes in the buffer until the quiet shape’s first **live** long-poll returned a fresh watermark — a consistent but up-to-**\~41s stale** board on Electric Cloud that then visibly “rearranges itself” (the CDN policy observed: `max-age=604800, s-maxage=3600, stale-while-revalidate=2629746`).
The client instead **aligns the group’s commit floors once** — the moment every shape has reported `up-to-date` at least since load/reset — lifting them to the freshest asserted global head so the busy shape’s changes commit at catch-up completion. The commit floor is kept **separate from the dedup frontier**, so a change a stale cache omitted still arrives, is accepted, and commits on the next poll (never dropped as already-seen). On the **live tail** the floor goes inert once live frontiers pass it and the slowest-shape gate keeps governing steady state — but a gated batch no longer *waits out* a quiet sibling’s long-poll: the engine nudges the laggards’ watermarks fresh instead ([ADR-0033](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0033-live-tail-sibling-nudge.md), the section above).
The honest trade: at a catch-up (or re-snapshot) boundary a multi-table transaction straddling two shapes’ CDN cache generations can render **torn for roughly one shape-request round trip** before it self-heals (a live request from the stale offset returns immediately, because the data exists past it) — a sub-second torn view at load, in place of a consistent-but-seconds-stale one. The alignment moment is visible on the debug rail as a single `catch-up watermark aligned {floor}` line. See [ADR-0031](https://github.com/pgxsinkit/pgxsinkit/blob/main/docs/adr/0031-catchup-watermark-alignment.md) for the full rationale.
# What is pgxsinkit?
> An offline-first sync toolkit for Postgres, ElectricSQL, Drizzle, and PGlite — what you install, and how its two paths fit together.
pgxsinkit is an **offline-first sync toolkit**: the `@pgxsinkit/*` packages you install to give a local-first app a Postgres-backed read path and a write path, with per-row access control on both — Postgres row-level security on the write path, and a matching row filter on the read path.
## A library, not an app
[Section titled “A library, not an app”](#a-library-not-an-app)
pgxsinkit is a standalone open-source **library** — the published `@pgxsinkit/*` packages are what you install and depend on. The repository also carries a demo app and a verification harness, but those exist to show the toolkit working and to keep it honest against real infrastructure; they are not the product, and not any application’s data layer. See [Demo & harness](/demo-and-harness/).
## The two paths
[Section titled “The two paths”](#the-two-paths)
pgxsinkit is built around two **separate, asymmetric** paths — they are not one bidirectional channel. Writes do not travel back through Electric; the read and write sides use different mechanisms.
| | Read path | Write path |
| --------- | ----------------------------------- | ----------------------------------------- |
| Direction | server → client | client → server |
| Route | `PostgreSQL → ElectricSQL → PGlite` | `client → write route → PostgreSQL` |
| Carries | shape streams (live rows) | batches of staged mutations |
| Electric? | yes (the read transport) | **no** — writes never go through Electric |
See [The two paths](/concepts/two-paths/) for why the asymmetry matters, then [The write path](/concepts/write-path/) and [The read path](/concepts/read-path/) for each side.
## Browser storage
[Section titled “Browser storage”](#browser-storage)
For browser apps, capability-driven storage is the default. A real OPFS probe at boot puts the constant-four-handle `opfs-repacked` engine directly in a SharedWorker on macOS/iOS Safari, or in one Web-Locks-elected dedicated worker on Chromium and Firefox. A registry can force IndexedDB (`storage.backend: "idbfs"`), and the no-SharedWorker fallback stays on IndexedDB. The app still attaches through one `attachSyncClient` surface; inspect the BootReport instead of branching on browser names. See [Worker mode](/concepts/worker-mode/).
## A hard prerequisite
[Section titled “A hard prerequisite”](#a-hard-prerequisite)
pgxsinkit relies on ElectricSQL’s subquery `where` support for membership fan-out, which is a **flagged** preview feature. You must run Electric with `ELECTRIC_FEATURE_FLAGS=allow_subqueries,tagged_subqueries`. Without it the sync fails **closed** — no rows stream, never an unfiltered fan-out. This is not optional. See [The Electric subquery requirement](/concepts/electric-subqueries/).
## Where to go next
[Section titled “Where to go next”](#where-to-go-next)
* [Getting started](/start/getting-started/) — install and wire a minimal read + write.
* [Core concepts](/concepts/) — the mental model, in six short pages.
* [Packages](/packages/) — which `@pgxsinkit/*` package does what.]