@tanstack/electric-db-collection 0.4.6 → 0.4.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/electric.cjs +33 -12
- package/dist/cjs/electric.cjs.map +1 -1
- package/dist/esm/electric.js +34 -13
- package/dist/esm/electric.js.map +1 -1
- package/package.json +2 -2
- package/src/electric.ts +35 -11
package/dist/cjs/electric.cjs
CHANGED
|
@@ -149,6 +149,7 @@ function createLoadSubsetDedupe({
|
|
|
149
149
|
}
|
|
150
150
|
const compileOptions = encodeColumnName ? { encodeColumnName } : void 0;
|
|
151
151
|
const logPrefix = collectionId ? `[${collectionId}] ` : ``;
|
|
152
|
+
const abortReason = (abortedSignal) => abortedSignal.reason ?? new db.LoadSubsetOperationAbortedError();
|
|
152
153
|
function handleSnapshotError(error, operation) {
|
|
153
154
|
if (signal.aborted) {
|
|
154
155
|
debug(`${logPrefix}Ignoring ${operation} error during cleanup: %o`, error);
|
|
@@ -159,7 +160,11 @@ function createLoadSubsetDedupe({
|
|
|
159
160
|
}
|
|
160
161
|
const loadSubset = async (opts) => {
|
|
161
162
|
const commitCursor = getCommitCursor();
|
|
162
|
-
|
|
163
|
+
const throwIfAborted = () => {
|
|
164
|
+
if (signal.aborted) throw abortReason(signal);
|
|
165
|
+
if (opts.signal?.aborted) throw abortReason(opts.signal);
|
|
166
|
+
};
|
|
167
|
+
throwIfAborted();
|
|
163
168
|
if (isBufferingInitialSync()) {
|
|
164
169
|
const snapshotParams = sqlCompiler.compileSQL(opts, compileOptions);
|
|
165
170
|
try {
|
|
@@ -195,6 +200,18 @@ function createLoadSubsetDedupe({
|
|
|
195
200
|
const { cursor, where, orderBy, limit } = opts;
|
|
196
201
|
if (stream.isUpToDate) {
|
|
197
202
|
let timeoutId;
|
|
203
|
+
const abortSignals = [signal, opts.signal].filter(
|
|
204
|
+
(candidate) => candidate !== void 0
|
|
205
|
+
);
|
|
206
|
+
let rejectAbort = () => {
|
|
207
|
+
};
|
|
208
|
+
const aborted = new Promise((_resolve, reject) => {
|
|
209
|
+
rejectAbort = reject;
|
|
210
|
+
});
|
|
211
|
+
const abort = (event) => rejectAbort(abortReason(event.currentTarget));
|
|
212
|
+
for (const abortSignal of abortSignals) {
|
|
213
|
+
abortSignal.addEventListener(`abort`, abort, { once: true });
|
|
214
|
+
}
|
|
198
215
|
try {
|
|
199
216
|
await Promise.race([
|
|
200
217
|
stream.forceDisconnectAndRefresh(),
|
|
@@ -203,9 +220,11 @@ function createLoadSubsetDedupe({
|
|
|
203
220
|
resolve,
|
|
204
221
|
FORCE_DISCONNECT_AND_REFRESH_TIMEOUT_MS
|
|
205
222
|
);
|
|
206
|
-
})
|
|
223
|
+
}),
|
|
224
|
+
aborted
|
|
207
225
|
]);
|
|
208
226
|
} catch (error) {
|
|
227
|
+
if (signal.aborted || opts.signal?.aborted) throw error;
|
|
209
228
|
if (handleSnapshotError(error, `forceDisconnectAndRefresh`)) {
|
|
210
229
|
return;
|
|
211
230
|
}
|
|
@@ -215,9 +234,12 @@ function createLoadSubsetDedupe({
|
|
|
215
234
|
);
|
|
216
235
|
} finally {
|
|
217
236
|
clearTimeout(timeoutId);
|
|
237
|
+
for (const abortSignal of abortSignals) {
|
|
238
|
+
abortSignal.removeEventListener(`abort`, abort);
|
|
239
|
+
}
|
|
218
240
|
}
|
|
219
241
|
}
|
|
220
|
-
|
|
242
|
+
throwIfAborted();
|
|
221
243
|
try {
|
|
222
244
|
if (cursor) {
|
|
223
245
|
const whereCurrentOpts = {
|
|
@@ -754,16 +776,11 @@ function createElectricSync(shapeOptions, options) {
|
|
|
754
776
|
}
|
|
755
777
|
};
|
|
756
778
|
const abortController = new AbortController();
|
|
779
|
+
const forwardExternalAbort = () => abortController.abort();
|
|
757
780
|
if (shapeOptions.signal) {
|
|
758
|
-
shapeOptions.signal.addEventListener(
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
abortController.abort();
|
|
762
|
-
},
|
|
763
|
-
{
|
|
764
|
-
once: true
|
|
765
|
-
}
|
|
766
|
-
);
|
|
781
|
+
shapeOptions.signal.addEventListener(`abort`, forwardExternalAbort, {
|
|
782
|
+
once: true
|
|
783
|
+
});
|
|
767
784
|
if (shapeOptions.signal.aborted) {
|
|
768
785
|
abortController.abort();
|
|
769
786
|
}
|
|
@@ -1078,6 +1095,10 @@ function createElectricSync(shapeOptions, options) {
|
|
|
1078
1095
|
return {
|
|
1079
1096
|
loadSubset: loadSubsetDedupe?.loadSubset,
|
|
1080
1097
|
cleanup: () => {
|
|
1098
|
+
shapeOptions.signal?.removeEventListener(
|
|
1099
|
+
`abort`,
|
|
1100
|
+
forwardExternalAbort
|
|
1101
|
+
);
|
|
1081
1102
|
unsubscribeStream();
|
|
1082
1103
|
abortController.abort();
|
|
1083
1104
|
loadSubsetDedupe?.reset();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"electric.cjs","sources":["../../src/electric.ts"],"sourcesContent":["import {\n ShapeStream,\n isChangeMessage,\n isControlMessage,\n isVisibleInSnapshot,\n} from '@electric-sql/client'\nimport { Store } from '@tanstack/store'\nimport DebugModule from 'debug'\nimport {\n DeduplicatedLoadSubset,\n and,\n withCollectionConfigFactory,\n} from '@tanstack/db'\nimport {\n ExpectedNumberInAwaitTxIdError,\n StreamAbortedError,\n TimeoutWaitingForMatchError,\n TimeoutWaitingForTxIdError,\n} from './errors'\nimport { compileSQL } from './sql-compiler'\nimport {\n addTagToIndex,\n deriveDisjunctPositions,\n findRowsMatchingPattern,\n getTagLength,\n isMoveInMessage,\n isMoveOutMessage,\n parseTag as parseTagString,\n removeTagFromIndex,\n rowVisible,\n tagMatchesPattern,\n} from './tag-index'\nimport type { ColumnEncoder } from './sql-compiler'\nimport type {\n ActiveConditions,\n DisjunctPositions,\n MovePattern,\n MoveTag,\n ParsedMoveTag,\n RowId,\n TagIndex,\n} from './tag-index'\nimport type {\n BaseCollectionConfig,\n ChangeMessageOrDeleteKeyMessage,\n CollectionConfig,\n DeleteMutationFnParams,\n InsertMutationFnParams,\n LoadSubsetOptions,\n SyncAppliedReceipt,\n SyncConfig,\n SyncMode,\n UpdateMutationFnParams,\n UtilsRecord,\n} from '@tanstack/db'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type {\n ControlMessage,\n GetExtensions,\n Message,\n Offset,\n PostgresSnapshot,\n Row,\n ShapeStreamOptions,\n} from '@electric-sql/client'\n\n// Re-export for user convenience in custom match functions\nexport { isChangeMessage, isControlMessage } from '@electric-sql/client'\n\nconst debug = DebugModule.debug(`ts/db:electric`)\n\nconst FORCE_DISCONNECT_AND_REFRESH_TIMEOUT_MS = 250\n\n/**\n * Symbol for internal test hooks (hidden from public API)\n */\nexport const ELECTRIC_TEST_HOOKS = Symbol(`electricTestHooks`)\n\n/**\n * Internal test hooks interface (for testing only)\n */\nexport interface ElectricTestHooks {\n /**\n * Called before marking collection ready after first up-to-date in progressive mode\n * Allows tests to pause and validate snapshot phase before atomic swap completes\n */\n beforeMarkingReady?: () => Promise<void>\n}\n\n/**\n * Type representing a transaction ID in ElectricSQL\n */\nexport type Txid = number\n\ntype ElectricResumeState =\n | {\n kind: `resume`\n offset: string\n handle: string\n shapeId: string\n updatedAt: number\n }\n | {\n kind: `reset`\n updatedAt: number\n }\n\ntype ElectricSyncMeta = {\n version: 1\n resume?: ElectricResumeState\n seenTxids: Array<Txid>\n}\n\nfunction parseElectricResumeState(\n value: unknown,\n): ElectricResumeState | undefined {\n if (!value || typeof value !== `object`) {\n return undefined\n }\n\n const record = value as Record<string, unknown>\n if (\n record.kind === `resume` &&\n typeof record.offset === `string` &&\n typeof record.handle === `string` &&\n typeof record.shapeId === `string` &&\n typeof record.updatedAt === `number` &&\n Number.isFinite(record.updatedAt)\n ) {\n return {\n kind: `resume`,\n offset: record.offset,\n handle: record.handle,\n shapeId: record.shapeId,\n updatedAt: record.updatedAt,\n }\n }\n\n if (\n record.kind === `reset` &&\n typeof record.updatedAt === `number` &&\n Number.isFinite(record.updatedAt)\n ) {\n return {\n kind: `reset`,\n updatedAt: record.updatedAt,\n }\n }\n\n return undefined\n}\n\nfunction parseElectricSyncMeta(value: unknown): ElectricSyncMeta | undefined {\n if (!value || typeof value !== `object`) {\n return undefined\n }\n\n const record = value as Record<string, unknown>\n if (\n record.version !== 1 ||\n !Array.isArray(record.seenTxids) ||\n !record.seenTxids.every(\n (txid) => typeof txid === `number` && Number.isFinite(txid),\n )\n ) {\n return undefined\n }\n\n const resume =\n record.resume === undefined\n ? undefined\n : parseElectricResumeState(record.resume)\n if (record.resume !== undefined && resume === undefined) {\n return undefined\n }\n\n return {\n version: 1,\n ...(resume ? { resume } : {}),\n seenTxids: Array.from(new Set(record.seenTxids)).sort((a, b) => a - b),\n }\n}\n\nfunction mergeElectricSyncMeta(\n current: unknown,\n incoming: unknown,\n): ElectricSyncMeta | unknown {\n const currentMeta = parseElectricSyncMeta(current)\n const incomingMeta = parseElectricSyncMeta(incoming)\n\n if (!incomingMeta) {\n return current\n }\n if (!currentMeta) {\n return incomingMeta\n }\n\n const resume = getNewestElectricResumeState(\n currentMeta.resume,\n incomingMeta.resume,\n )\n\n return {\n version: 1,\n ...(resume ? { resume } : {}),\n seenTxids: Array.from(\n new Set([...currentMeta.seenTxids, ...incomingMeta.seenTxids]),\n ).sort((a, b) => a - b),\n }\n}\n\nfunction getNewestElectricResumeState(\n current: ElectricResumeState | undefined,\n incoming: ElectricResumeState | undefined,\n): ElectricResumeState | undefined {\n if (!current) return incoming\n if (!incoming) return current\n return incoming.updatedAt >= current.updatedAt ? incoming : current\n}\n\n/**\n * Custom match function type - receives stream messages and returns boolean\n * indicating if the mutation has been synchronized\n */\nexport type MatchFunction<T extends Row<unknown>> = (\n message: Message<T>,\n) => boolean\n\n/**\n * Matching strategies for Electric synchronization\n * Handlers can return:\n * - Txid strategy: { txid: number | number[], timeout?: number } (recommended)\n * - Void (no return value) - mutation completes without waiting\n *\n * The optional timeout property specifies how long to wait for the txid(s) in milliseconds.\n * If not specified, defaults to 5000ms.\n */\nexport type MatchingStrategy = {\n txid: Txid | Array<Txid>\n timeout?: number\n} | void\n\n/**\n * Type representing a snapshot end message\n */\ntype SnapshotEndMessage = ControlMessage & {\n headers: { control: `snapshot-end` }\n}\n// The `InferSchemaOutput` and `ResolveType` are copied from the `@tanstack/db` package\n// but we modified `InferSchemaOutput` slightly to restrict the schema output to `Row<unknown>`\n// This is needed in order for `GetExtensions` to be able to infer the parser extensions type from the schema\ntype InferSchemaOutput<T> = T extends StandardSchemaV1\n ? StandardSchemaV1.InferOutput<T> extends Row<unknown>\n ? StandardSchemaV1.InferOutput<T>\n : Record<string, unknown>\n : Record<string, unknown>\n\n/**\n * The mode of sync to use for the collection.\n * @default `eager`\n * @description\n * - `eager`:\n * - syncs all data immediately on preload\n * - collection will be marked as ready once the sync is complete\n * - there is no incremental sync\n * - `on-demand`:\n * - syncs data in incremental snapshots when the collection is queried\n * - collection will be marked as ready immediately after the first snapshot is synced\n * - `progressive`:\n * - syncs all data for the collection in the background\n * - uses incremental snapshots during the initial sync to provide a fast path to the data required for queries\n * - collection will be marked as ready once the full sync is complete\n */\nexport type ElectricSyncMode = SyncMode | `progressive`\n\n/**\n * Configuration interface for Electric collection options\n * @template T - The type of items in the collection\n * @template TSchema - The schema type for validation\n */\nexport interface ElectricCollectionConfig<\n T extends Row<unknown> = Row<unknown>,\n TSchema extends StandardSchemaV1 = never,\n> extends Omit<\n BaseCollectionConfig<\n T,\n string | number,\n TSchema,\n ElectricCollectionUtils<T>,\n any\n >,\n `onInsert` | `onUpdate` | `onDelete` | `syncMode`\n> {\n /**\n * Configuration options for the ElectricSQL ShapeStream\n */\n shapeOptions: ShapeStreamOptions<GetExtensions<T>>\n syncMode?: ElectricSyncMode\n\n /**\n * Internal test hooks (for testing only)\n * Hidden via Symbol to prevent accidental usage in production\n */\n [ELECTRIC_TEST_HOOKS]?: ElectricTestHooks\n\n /**\n * Optional asynchronous handler function called before an insert operation\n * @param params Object containing transaction and collection information\n * @returns Promise resolving to { txid, timeout? } or void\n * @example\n * // Basic Electric insert handler with txid (recommended)\n * onInsert: async ({ transaction }) => {\n * const newItem = transaction.mutations[0].modified\n * const result = await api.todos.create({\n * data: newItem\n * })\n * return { txid: result.txid }\n * }\n *\n * @example\n * // Insert handler with custom timeout\n * onInsert: async ({ transaction }) => {\n * const newItem = transaction.mutations[0].modified\n * const result = await api.todos.create({\n * data: newItem\n * })\n * return { txid: result.txid, timeout: 10000 } // Wait up to 10 seconds\n * }\n *\n * @example\n * // Insert handler with multiple items - return array of txids\n * onInsert: async ({ transaction }) => {\n * const items = transaction.mutations.map(m => m.modified)\n * const results = await Promise.all(\n * items.map(item => api.todos.create({ data: item }))\n * )\n * return { txid: results.map(r => r.txid) }\n * }\n *\n * @example\n * // Use awaitMatch utility for custom matching\n * onInsert: async ({ transaction, collection }) => {\n * const newItem = transaction.mutations[0].modified\n * await api.todos.create({ data: newItem })\n * await collection.utils.awaitMatch(\n * (message) => isChangeMessage(message) &&\n * message.headers.operation === 'insert' &&\n * message.value.name === newItem.name\n * )\n * }\n */\n onInsert?: (\n params: InsertMutationFnParams<\n T,\n string | number,\n ElectricCollectionUtils<T>\n >,\n ) => Promise<MatchingStrategy>\n\n /**\n * Optional asynchronous handler function called before an update operation\n * @param params Object containing transaction and collection information\n * @returns Promise resolving to { txid, timeout? } or void\n * @example\n * // Basic Electric update handler with txid (recommended)\n * onUpdate: async ({ transaction }) => {\n * const { original, changes } = transaction.mutations[0]\n * const result = await api.todos.update({\n * where: { id: original.id },\n * data: changes\n * })\n * return { txid: result.txid }\n * }\n *\n * @example\n * // Use awaitMatch utility for custom matching\n * onUpdate: async ({ transaction, collection }) => {\n * const { original, changes } = transaction.mutations[0]\n * await api.todos.update({ where: { id: original.id }, data: changes })\n * await collection.utils.awaitMatch(\n * (message) => isChangeMessage(message) &&\n * message.headers.operation === 'update' &&\n * message.value.id === original.id\n * )\n * }\n */\n onUpdate?: (\n params: UpdateMutationFnParams<\n T,\n string | number,\n ElectricCollectionUtils<T>\n >,\n ) => Promise<MatchingStrategy>\n\n /**\n * Optional asynchronous handler function called before a delete operation\n * @param params Object containing transaction and collection information\n * @returns Promise resolving to { txid, timeout? } or void\n * @example\n * // Basic Electric delete handler with txid (recommended)\n * onDelete: async ({ transaction }) => {\n * const mutation = transaction.mutations[0]\n * const result = await api.todos.delete({\n * id: mutation.original.id\n * })\n * return { txid: result.txid }\n * }\n *\n * @example\n * // Use awaitMatch utility for custom matching\n * onDelete: async ({ transaction, collection }) => {\n * const mutation = transaction.mutations[0]\n * await api.todos.delete({ id: mutation.original.id })\n * await collection.utils.awaitMatch(\n * (message) => isChangeMessage(message) &&\n * message.headers.operation === 'delete' &&\n * message.value.id === mutation.original.id\n * )\n * }\n */\n onDelete?: (\n params: DeleteMutationFnParams<\n T,\n string | number,\n ElectricCollectionUtils<T>\n >,\n ) => Promise<MatchingStrategy>\n}\n\nfunction isUpToDateMessage<T extends Row<unknown>>(\n message: Message<T>,\n): message is ControlMessage & { up_to_date: true } {\n return isControlMessage(message) && message.headers.control === `up-to-date`\n}\n\nfunction isMustRefetchMessage<T extends Row<unknown>>(\n message: Message<T>,\n): message is ControlMessage & { headers: { control: `must-refetch` } } {\n return isControlMessage(message) && message.headers.control === `must-refetch`\n}\n\nfunction isSnapshotEndMessage<T extends Row<unknown>>(\n message: Message<T>,\n): message is SnapshotEndMessage {\n return isControlMessage(message) && message.headers.control === `snapshot-end`\n}\n\nfunction isSubsetEndMessage<T extends Row<unknown>>(\n message: Message<T>,\n): message is ControlMessage & { headers: { control: `subset-end` } } {\n return (\n isControlMessage(message) &&\n (message.headers.control as string) === `subset-end`\n )\n}\n\nfunction parseSnapshotMessage(message: SnapshotEndMessage): PostgresSnapshot {\n return {\n xmin: message.headers.xmin,\n xmax: message.headers.xmax,\n xip_list: message.headers.xip_list,\n }\n}\n\nfunction toStableSerializable(value: unknown): unknown {\n if (value == null) {\n return value\n }\n\n if (Array.isArray(value)) {\n return value.map((entry) => toStableSerializable(entry))\n }\n\n if (value instanceof Date) {\n return value.toISOString()\n }\n\n if (typeof value === `object`) {\n const record = value as Record<string, unknown>\n const stableRecord: Record<string, unknown> = {}\n const keys = Object.keys(record).sort((left, right) =>\n left < right ? -1 : left > right ? 1 : 0,\n )\n for (const key of keys) {\n stableRecord[key] = toStableSerializable(record[key])\n }\n return stableRecord\n }\n\n return value\n}\n\nfunction getStableShapeIdentity(shapeOptions: {\n url: string\n params?: Record<string, unknown>\n}): string {\n return JSON.stringify(\n toStableSerializable({\n url: shapeOptions.url,\n params: shapeOptions.params ?? null,\n }),\n )\n}\n\n// Check if a message contains txids in its headers\nfunction hasTxids<T extends Row<unknown>>(\n message: Message<T>,\n): message is Message<T> & { headers: { txids?: Array<Txid> } } {\n return `txids` in message.headers && Array.isArray(message.headers.txids)\n}\n\n/**\n * Creates a deduplicated loadSubset handler for progressive/on-demand modes\n * Returns null for eager mode, or a DeduplicatedLoadSubset instance for other modes.\n * Handles fetching snapshots in progressive mode during buffering phase,\n * and requesting snapshots in on-demand mode.\n *\n * When cursor expressions are provided (whereFrom/whereCurrent), makes two\n * requestSnapshot calls:\n * - One for whereFrom (rows > cursor) with limit\n * - One for whereCurrent (rows = cursor, for tie-breaking) without limit\n */\nfunction createLoadSubsetDedupe<T extends Row<unknown>>({\n stream,\n syncMode,\n isBufferingInitialSync,\n begin,\n write,\n commit,\n getCommitCursor,\n waitForCommitsAfter,\n collectionId,\n encodeColumnName,\n signal,\n}: {\n stream: ShapeStream<T>\n syncMode: ElectricSyncMode\n isBufferingInitialSync: () => boolean\n begin: () => void\n write: (mutation: {\n type: `insert` | `update` | `delete`\n value: T\n metadata: Record<string, unknown>\n }) => void\n commit: (signal?: AbortSignal) => SyncAppliedReceipt\n getCommitCursor: () => number\n waitForCommitsAfter: (cursor: number) => Promise<void>\n collectionId?: string\n /**\n * Optional function to encode column names (e.g., camelCase to snake_case).\n * This is typically the `encode` function from shapeOptions.columnMapper.\n */\n encodeColumnName?: ColumnEncoder\n /**\n * Abort signal to check if the stream has been aborted during cleanup.\n * When aborted, errors from requestSnapshot are silently ignored.\n */\n signal: AbortSignal\n}): DeduplicatedLoadSubset | null {\n if (syncMode === `eager`) {\n return null\n }\n\n const compileOptions = encodeColumnName ? { encodeColumnName } : undefined\n const logPrefix = collectionId ? `[${collectionId}] ` : ``\n\n /**\n * Handles errors from snapshot operations. Returns true if the error was\n * handled (signal aborted during cleanup), false if it should be re-thrown.\n */\n function handleSnapshotError(error: unknown, operation: string): boolean {\n if (signal.aborted) {\n debug(`${logPrefix}Ignoring ${operation} error during cleanup: %o`, error)\n return true\n }\n debug(`${logPrefix}Error in ${operation}: %o`, error)\n return false\n }\n\n const loadSubset = async (opts: LoadSubsetOptions) => {\n const commitCursor = getCommitCursor()\n if (opts.signal?.aborted) return\n\n if (isBufferingInitialSync()) {\n const snapshotParams = compileSQL<T>(opts, compileOptions)\n try {\n const { data: rows } = await stream.fetchSnapshot(snapshotParams)\n if (opts.signal?.aborted || !isBufferingInitialSync()) {\n debug(`${logPrefix}Ignoring snapshot - sync completed while fetching`)\n return\n }\n\n if (rows.length > 0) {\n begin()\n for (const row of rows) {\n write({\n type: `insert`,\n value: row.value,\n metadata: { ...row.headers },\n })\n }\n await commit(opts.signal)\n debug(`${logPrefix}Applied snapshot with ${rows.length} rows`)\n }\n } catch (error) {\n if (opts.signal?.aborted) return\n if (handleSnapshotError(error, `fetchSnapshot`)) {\n return\n }\n throw error\n }\n return\n }\n\n if (syncMode === `progressive`) {\n return\n }\n\n const { cursor, where, orderBy, limit } = opts\n\n // When the stream is already up-to-date, it may be in a long-poll wait.\n // Forcing a disconnect-and-refresh ensures requestSnapshot gets a response\n // from a fresh server round-trip rather than waiting for the current poll to end.\n // Some native fetch implementations (notably React Native/Expo) may not abort\n // long-poll requests promptly. Bound the wait so on-demand live queries don't\n // remain loading until the long-poll naturally times out.\n // If the refresh fails or times out, we fall through to requestSnapshot which\n // still works.\n if (stream.isUpToDate) {\n let timeoutId: ReturnType<typeof setTimeout> | undefined\n try {\n await Promise.race([\n stream.forceDisconnectAndRefresh(),\n new Promise<void>((resolve) => {\n timeoutId = setTimeout(\n resolve,\n FORCE_DISCONNECT_AND_REFRESH_TIMEOUT_MS,\n )\n }),\n ])\n } catch (error) {\n if (handleSnapshotError(error, `forceDisconnectAndRefresh`)) {\n return\n }\n debug(\n `${logPrefix}forceDisconnectAndRefresh failed, proceeding to requestSnapshot: %o`,\n error,\n )\n } finally {\n clearTimeout(timeoutId)\n }\n }\n\n if (opts.signal?.aborted) return\n\n // Upstream limitation: ShapeStream.requestSnapshot() publishes its rows\n // through the stream callback before its Promise resolves. It accepts no\n // request signal and exposes no request identity on those messages, so an\n // aborted request can already have installed rows before the check below.\n // Full request-scoped cancellation requires support in the Electric client;\n // matching snapshots by parameters is unsafe for overlapping equal requests.\n try {\n if (cursor) {\n const whereCurrentOpts: LoadSubsetOptions = {\n where: where ? and(where, cursor.whereCurrent) : cursor.whereCurrent,\n orderBy,\n }\n const whereCurrentParams = compileSQL<T>(\n whereCurrentOpts,\n compileOptions,\n )\n\n const whereFromOpts: LoadSubsetOptions = {\n where: where ? and(where, cursor.whereFrom) : cursor.whereFrom,\n orderBy,\n limit,\n }\n const whereFromParams = compileSQL<T>(whereFromOpts, compileOptions)\n\n debug(`${logPrefix}Requesting cursor.whereCurrent snapshot (all ties)`)\n debug(\n `${logPrefix}Requesting cursor.whereFrom snapshot (with limit ${limit})`,\n )\n\n await Promise.all([\n stream.requestSnapshot(whereCurrentParams),\n stream.requestSnapshot(whereFromParams),\n ])\n } else {\n const snapshotParams = compileSQL<T>(opts, compileOptions)\n await stream.requestSnapshot(snapshotParams)\n }\n } catch (error) {\n if (opts.signal?.aborted) return\n if (handleSnapshotError(error, `requestSnapshot`)) {\n return\n }\n throw error\n }\n await waitForCommitsAfter(commitCursor)\n }\n\n return new DeduplicatedLoadSubset({ loadSubset })\n}\n\n/**\n * Type for the awaitTxId utility function\n */\nexport type AwaitTxIdFn = (txId: Txid, timeout?: number) => Promise<boolean>\n\n/**\n * Type for the awaitMatch utility function\n */\nexport type AwaitMatchFn<T extends Row<unknown>> = (\n matchFn: MatchFunction<T>,\n timeout?: number,\n) => Promise<boolean>\n\n/**\n * Electric collection utilities type\n */\nexport interface ElectricCollectionUtils<\n T extends Row<unknown> = Row<unknown>,\n> extends UtilsRecord {\n awaitTxId: AwaitTxIdFn\n awaitMatch: AwaitMatchFn<T>\n}\n\n/**\n * Creates Electric collection options for use with a standard Collection\n *\n * @template T - The explicit type of items in the collection (highest priority)\n * @template TSchema - The schema type for validation and type inference (second priority)\n * @template TFallback - The fallback type if no explicit or schema type is provided\n * @param config - Configuration options for the Electric collection\n * @returns Collection options with utilities\n */\n\n// Overload for when schema is provided\nexport function electricCollectionOptions<T extends StandardSchemaV1>(\n config: ElectricCollectionConfig<InferSchemaOutput<T>, T> & {\n schema: T\n },\n): Omit<\n CollectionConfig<InferSchemaOutput<T>, string | number, T>,\n `utils` | `onInsert` | `onUpdate` | `onDelete`\n> &\n Pick<\n ElectricCollectionConfig<InferSchemaOutput<T>, T>,\n `onInsert` | `onUpdate` | `onDelete`\n > & {\n id?: string\n utils: ElectricCollectionUtils<InferSchemaOutput<T>>\n schema: T\n }\n\n// Overload for when no schema is provided\nexport function electricCollectionOptions<T extends Row<unknown>>(\n config: ElectricCollectionConfig<T> & {\n schema?: never // prohibit schema\n },\n): Omit<\n CollectionConfig<T, string | number>,\n `utils` | `onInsert` | `onUpdate` | `onDelete`\n> &\n Pick<ElectricCollectionConfig<T>, `onInsert` | `onUpdate` | `onDelete`> & {\n id?: string\n utils: ElectricCollectionUtils<T>\n schema?: never // no schema in the result\n }\n\nexport function electricCollectionOptions<T extends Row<unknown>>(\n config: ElectricCollectionConfig<T, any>,\n): Omit<\n CollectionConfig<T, string | number, any, ElectricCollectionUtils<T>>,\n `utils`\n> & {\n id?: string\n utils: ElectricCollectionUtils<T>\n schema?: any\n} {\n const seenTxids = new Store<Set<Txid>>(new Set([]))\n const seenSnapshots = new Store<Array<PostgresSnapshot>>([])\n const hydratedResumeState = new Store<ElectricResumeState | undefined>(\n undefined,\n )\n const internalSyncMode = config.syncMode ?? `eager`\n const finalSyncMode =\n internalSyncMode === `progressive` ? `on-demand` : internalSyncMode\n const pendingMatches = new Store<\n Map<\n string,\n {\n matchFn: (message: Message<any>) => boolean\n resolve: (value: boolean) => void\n reject: (error: Error) => void\n timeoutId: ReturnType<typeof setTimeout>\n matched: boolean\n }\n >\n >(new Map())\n\n // Buffer messages since last up-to-date to handle race conditions\n const currentBatchMessages = new Store<Array<Message<any>>>([])\n\n // Track whether the current batch has been committed (up-to-date received)\n // This allows awaitMatch to resolve immediately for messages from committed batches\n const batchCommitted = new Store<boolean>(false)\n\n /**\n * Helper function to remove multiple matches from the pendingMatches store\n */\n const removePendingMatches = (matchIds: Array<string>) => {\n if (matchIds.length > 0) {\n pendingMatches.setState((current) => {\n const newMatches = new Map(current)\n matchIds.forEach((id) => newMatches.delete(id))\n return newMatches\n })\n }\n }\n\n /**\n * Helper function to resolve and cleanup matched pending matches\n */\n const resolveMatchedPendingMatches = () => {\n const matchesToResolve: Array<string> = []\n pendingMatches.state.forEach((match, matchId) => {\n if (match.matched) {\n clearTimeout(match.timeoutId)\n match.resolve(true)\n matchesToResolve.push(matchId)\n debug(\n `${config.id ? `[${config.id}] ` : ``}awaitMatch resolved on up-to-date for match %s`,\n matchId,\n )\n }\n })\n removePendingMatches(matchesToResolve)\n }\n const sync = createElectricSync<T>(config.shapeOptions, {\n seenTxids,\n seenSnapshots,\n hydratedResumeState,\n syncMode: internalSyncMode,\n pendingMatches,\n currentBatchMessages,\n batchCommitted,\n removePendingMatches,\n resolveMatchedPendingMatches,\n collectionId: config.id,\n testHooks: config[ELECTRIC_TEST_HOOKS],\n })\n\n /**\n * Wait for a specific transaction ID to be synced\n * @param txId The transaction ID to wait for as a number\n * @param timeout Optional timeout in milliseconds (defaults to 5000ms)\n * @returns Promise that resolves when the txId is synced\n */\n const awaitTxId: AwaitTxIdFn = async (\n txId: Txid,\n timeout: number = 5000,\n ): Promise<boolean> => {\n debug(\n `${config.id ? `[${config.id}] ` : ``}awaitTxId called with txid %d`,\n txId,\n )\n if (typeof txId !== `number`) {\n throw new ExpectedNumberInAwaitTxIdError(typeof txId, config.id)\n }\n\n // First check if the txid is in the seenTxids store\n const hasTxid = seenTxids.state.has(txId)\n if (hasTxid) return true\n\n // Then check if the txid is in any of the seen snapshots\n const hasSnapshot = seenSnapshots.state.some((snapshot) =>\n isVisibleInSnapshot(txId, snapshot),\n )\n if (hasSnapshot) return true\n\n return new Promise((resolve, reject) => {\n const cleanup = () => {\n clearTimeout(timeoutId)\n subSeenTxids.unsubscribe()\n subSeenSnapshots.unsubscribe()\n }\n\n const timeoutId = setTimeout(() => {\n cleanup()\n reject(new TimeoutWaitingForTxIdError(txId, config.id))\n }, timeout)\n\n const subSeenTxids = seenTxids.subscribe(() => {\n if (seenTxids.state.has(txId)) {\n debug(\n `${config.id ? `[${config.id}] ` : ``}awaitTxId found match for txid %o`,\n txId,\n )\n cleanup()\n resolve(true)\n }\n })\n\n const subSeenSnapshots = seenSnapshots.subscribe(() => {\n const visibleSnapshot = seenSnapshots.state.find((snapshot) =>\n isVisibleInSnapshot(txId, snapshot),\n )\n if (visibleSnapshot) {\n debug(\n `${config.id ? `[${config.id}] ` : ``}awaitTxId found match for txid %o in snapshot %o`,\n txId,\n visibleSnapshot,\n )\n cleanup()\n resolve(true)\n }\n })\n })\n }\n\n /**\n * Wait for a custom match function to find a matching message\n * @param matchFn Function that returns true when a message matches\n * @param timeout Optional timeout in milliseconds (defaults to 5000ms)\n * @returns Promise that resolves when a matching message is found\n */\n const awaitMatch: AwaitMatchFn<any> = async (\n matchFn: MatchFunction<any>,\n timeout: number = 3000,\n ): Promise<boolean> => {\n debug(\n `${config.id ? `[${config.id}] ` : ``}awaitMatch called with custom function`,\n )\n\n return new Promise((resolve, reject) => {\n const matchId = Math.random().toString(36)\n\n const cleanupMatch = () => {\n pendingMatches.setState((current) => {\n const newMatches = new Map(current)\n newMatches.delete(matchId)\n return newMatches\n })\n }\n\n const onTimeout = () => {\n cleanupMatch()\n reject(new TimeoutWaitingForMatchError(config.id))\n }\n\n const timeoutId = setTimeout(onTimeout, timeout)\n\n // We need access to the stream messages to check against the match function\n // This will be handled by the sync configuration\n const checkMatch = (message: Message<any>) => {\n if (matchFn(message)) {\n debug(\n `${config.id ? `[${config.id}] ` : ``}awaitMatch found matching message, waiting for up-to-date`,\n )\n // Mark as matched but don't resolve yet - wait for up-to-date\n pendingMatches.setState((current) => {\n const newMatches = new Map(current)\n const existing = newMatches.get(matchId)\n if (existing) {\n newMatches.set(matchId, { ...existing, matched: true })\n }\n return newMatches\n })\n return true\n }\n return false\n }\n\n // Check against current batch messages first to handle race conditions\n for (const message of currentBatchMessages.state) {\n if (matchFn(message)) {\n // If batch is committed (up-to-date already received), resolve immediately\n // just like awaitTxId does when it finds a txid in seenTxids\n if (batchCommitted.state) {\n debug(\n `${config.id ? `[${config.id}] ` : ``}awaitMatch found immediate match in committed batch, resolving immediately`,\n )\n clearTimeout(timeoutId)\n resolve(true)\n return\n }\n\n // If batch is not yet committed, register match and wait for up-to-date\n debug(\n `${config.id ? `[${config.id}] ` : ``}awaitMatch found immediate match in current batch, waiting for up-to-date`,\n )\n pendingMatches.setState((current) => {\n const newMatches = new Map(current)\n newMatches.set(matchId, {\n matchFn: checkMatch,\n resolve,\n reject,\n timeoutId,\n matched: true, // Already matched, will resolve on up-to-date\n })\n return newMatches\n })\n return\n }\n }\n\n // Store the match function for the sync process to use\n // We'll add this to a pending matches store\n pendingMatches.setState((current) => {\n const newMatches = new Map(current)\n newMatches.set(matchId, {\n matchFn: checkMatch,\n resolve,\n reject,\n timeoutId,\n matched: false,\n })\n return newMatches\n })\n })\n }\n\n /**\n * Process matching strategy and wait for synchronization\n */\n const processMatchingStrategy = async (\n result: MatchingStrategy,\n ): Promise<void> => {\n // Only wait if result contains txid\n if (result && `txid` in result) {\n const timeout = result.timeout\n // Handle both single txid and array of txids\n if (Array.isArray(result.txid)) {\n await Promise.all(result.txid.map((txid) => awaitTxId(txid, timeout)))\n } else {\n await awaitTxId(result.txid, timeout)\n }\n }\n // If result is void/undefined, don't wait - mutation completes immediately\n }\n\n // Create wrapper handlers for direct persistence operations that handle different matching strategies\n const wrappedOnInsert = config.onInsert\n ? async (\n params: InsertMutationFnParams<\n any,\n string | number,\n ElectricCollectionUtils<T>\n >,\n ) => {\n const handlerResult = await config.onInsert!(params)\n await processMatchingStrategy(handlerResult)\n return handlerResult\n }\n : undefined\n\n const wrappedOnUpdate = config.onUpdate\n ? async (\n params: UpdateMutationFnParams<\n any,\n string | number,\n ElectricCollectionUtils<T>\n >,\n ) => {\n const handlerResult = await config.onUpdate!(params)\n await processMatchingStrategy(handlerResult)\n return handlerResult\n }\n : undefined\n\n const wrappedOnDelete = config.onDelete\n ? async (\n params: DeleteMutationFnParams<\n any,\n string | number,\n ElectricCollectionUtils<T>\n >,\n ) => {\n const handlerResult = await config.onDelete!(params)\n await processMatchingStrategy(handlerResult)\n return handlerResult\n }\n : undefined\n\n // Extract standard Collection config properties\n const {\n shapeOptions: _shapeOptions,\n onInsert: _onInsert,\n onUpdate: _onUpdate,\n onDelete: _onDelete,\n ...restConfig\n } = config\n\n const options = {\n ...restConfig,\n syncMode: finalSyncMode,\n sync: {\n ...sync,\n exportSyncMeta: (): ElectricSyncMeta => ({\n version: 1,\n ...(hydratedResumeState.state\n ? { resume: hydratedResumeState.state }\n : {}),\n seenTxids: Array.from(seenTxids.state).sort((a, b) => a - b),\n }),\n importSyncMeta: (meta: unknown): void => {\n const parsed = parseElectricSyncMeta(meta)\n if (!parsed) {\n return\n }\n\n hydratedResumeState.setState(() => parsed.resume)\n seenTxids.setState(() => new Set(parsed.seenTxids))\n },\n mergeSyncMeta: mergeElectricSyncMeta,\n },\n onInsert: wrappedOnInsert,\n onUpdate: wrappedOnUpdate,\n onDelete: wrappedOnDelete,\n utils: {\n awaitTxId,\n awaitMatch,\n },\n }\n\n return withCollectionConfigFactory(options, () =>\n (\n electricCollectionOptions as (\n nextConfig: ElectricCollectionConfig<T, any>,\n ) => typeof options\n )(config),\n )\n}\n\n/**\n * Internal function to create ElectricSQL sync configuration\n */\nfunction createElectricSync<T extends Row<unknown>>(\n shapeOptions: ShapeStreamOptions<GetExtensions<T>>,\n options: {\n syncMode: ElectricSyncMode\n seenTxids: Store<Set<Txid>>\n seenSnapshots: Store<Array<PostgresSnapshot>>\n hydratedResumeState: Store<ElectricResumeState | undefined>\n pendingMatches: Store<\n Map<\n string,\n {\n matchFn: (message: Message<T>) => boolean\n resolve: (value: boolean) => void\n reject: (error: Error) => void\n timeoutId: ReturnType<typeof setTimeout>\n matched: boolean\n }\n >\n >\n currentBatchMessages: Store<Array<Message<T>>>\n batchCommitted: Store<boolean>\n removePendingMatches: (matchIds: Array<string>) => void\n resolveMatchedPendingMatches: () => void\n collectionId?: string\n testHooks?: ElectricTestHooks\n },\n): SyncConfig<T> {\n const {\n seenTxids,\n seenSnapshots,\n hydratedResumeState,\n syncMode,\n pendingMatches,\n currentBatchMessages,\n batchCommitted,\n removePendingMatches,\n resolveMatchedPendingMatches,\n collectionId,\n testHooks,\n } = options\n const MAX_BATCH_MESSAGES = 1000 // Safety limit for message buffer\n\n // Store for the relation schema information\n const relationSchema = new Store<string | undefined>(undefined)\n\n const tagCache = new Map<MoveTag, ParsedMoveTag>()\n\n // Parses a tag string into a ParsedMoveTag.\n // It memoizes the result parsed tag such that future calls\n // for the same tag string return the same ParsedMoveTag array.\n const parseTag = (tag: MoveTag): ParsedMoveTag => {\n const cachedTag = tagCache.get(tag)\n if (cachedTag) {\n return cachedTag\n }\n\n const parsedTag = parseTagString(tag)\n tagCache.set(tag, parsedTag)\n return parsedTag\n }\n\n // Tag tracking state\n const rowTagSets = new Map<RowId, Set<MoveTag>>()\n const tagIndex: TagIndex = []\n let tagLength: number | undefined = undefined\n\n // DNF state: active_conditions are per-row, disjunct_positions are global\n // (fixed by the shape's WHERE clause, derived once from the first tagged message).\n const rowActiveConditions = new Map<RowId, ActiveConditions>()\n let disjunctPositions: DisjunctPositions | undefined = undefined\n\n /**\n * Initialize the tag index with the correct length\n */\n const initializeTagIndex = (length: number): void => {\n if (tagIndex.length < length) {\n // Extend the index array to the required length\n for (let i = tagIndex.length; i < length; i++) {\n tagIndex[i] = new Map()\n }\n }\n }\n\n /**\n * Add tags to a row and update the tag index\n */\n const addTagsToRow = (\n tags: Array<MoveTag>,\n rowId: RowId,\n rowTagSet: Set<MoveTag>,\n ): void => {\n for (const tag of tags) {\n const parsedTag = parseTag(tag)\n\n // Infer tag length from first tag\n if (tagLength === undefined) {\n tagLength = getTagLength(parsedTag)\n initializeTagIndex(tagLength)\n }\n\n // Validate tag length matches\n const currentTagLength = getTagLength(parsedTag)\n if (currentTagLength !== tagLength) {\n debug(\n `${collectionId ? `[${collectionId}] ` : ``}Tag length mismatch: expected ${tagLength}, got ${currentTagLength}`,\n )\n continue\n }\n\n rowTagSet.add(tag)\n addTagToIndex(parsedTag, rowId, tagIndex, tagLength)\n }\n }\n\n /**\n * Remove tags from a row and update the tag index\n */\n const removeTagsFromRow = (\n removedTags: Array<MoveTag>,\n rowId: RowId,\n rowTagSet: Set<MoveTag>,\n ): void => {\n if (tagLength === undefined) {\n return\n }\n\n for (const tag of removedTags) {\n const parsedTag = parseTag(tag)\n rowTagSet.delete(tag)\n removeTagFromIndex(parsedTag, rowId, tagIndex, tagLength)\n // We aggresively evict the tag from the cache\n // if this tag is shared with another row\n // and is not removed from that other row\n // then next time we encounter the tag it will be parsed again\n tagCache.delete(tag)\n }\n }\n\n /**\n * Process tags for a change message (add and remove tags)\n */\n const processTagsForChangeMessage = (\n tags: Array<MoveTag> | undefined,\n removedTags: Array<MoveTag> | undefined,\n rowId: RowId,\n activeConditions?: ActiveConditions,\n ): Set<MoveTag> => {\n // Initialize tag set for this row if it doesn't exist (needed for checking deletion)\n if (!rowTagSets.has(rowId)) {\n rowTagSets.set(rowId, new Set())\n }\n const rowTagSet = rowTagSets.get(rowId)!\n\n // Add new tags\n if (tags) {\n addTagsToRow(tags, rowId, rowTagSet)\n\n // Derive disjunct positions once — they are fixed by the shape's WHERE clause.\n if (disjunctPositions === undefined) {\n const parsedTags = tags.map(parseTag)\n disjunctPositions = deriveDisjunctPositions(parsedTags)\n }\n }\n\n // Remove tags\n if (removedTags) {\n removeTagsFromRow(removedTags, rowId, rowTagSet)\n }\n\n // Store active conditions if provided (overwrite on re-send)\n if (activeConditions && activeConditions.length > 0) {\n rowActiveConditions.set(rowId, [...activeConditions])\n }\n\n return rowTagSet\n }\n\n /**\n * Clear all tag tracking state (used when truncating)\n */\n const clearTagTrackingState = (): void => {\n rowTagSets.clear()\n tagIndex.length = 0\n tagLength = undefined\n rowActiveConditions.clear()\n disjunctPositions = undefined\n }\n\n /**\n * Remove all tags for a row from both the tag set and the index\n * Used when a row is deleted\n */\n const clearTagsForRow = (rowId: RowId): void => {\n if (tagLength === undefined) {\n return\n }\n\n const rowTagSet = rowTagSets.get(rowId)\n if (!rowTagSet) {\n return\n }\n\n // Remove each tag from the index\n for (const tag of rowTagSet) {\n const parsedTag = parseTag(tag)\n const currentTagLength = getTagLength(parsedTag)\n if (currentTagLength === tagLength) {\n removeTagFromIndex(parsedTag, rowId, tagIndex, tagLength)\n }\n tagCache.delete(tag)\n }\n\n // Remove the row from the tag sets map\n rowTagSets.delete(rowId)\n rowActiveConditions.delete(rowId)\n }\n\n /**\n * Remove matching tags from a row based on a pattern\n * Returns true if the row should be deleted (no longer visible)\n */\n const removeMatchingTagsFromRow = (\n rowId: RowId,\n pattern: MovePattern,\n ): boolean => {\n const rowTagSet = rowTagSets.get(rowId)\n if (!rowTagSet) {\n return false\n }\n\n // DNF mode: check visibility using active conditions.\n // Tag index entries are preserved so that move-in can re-activate positions.\n const activeConditions = rowActiveConditions.get(rowId)\n if (activeConditions && disjunctPositions) {\n // Set the condition at this pattern's position to false\n activeConditions[pattern.pos] = false\n\n if (!rowVisible(activeConditions, disjunctPositions)) {\n // Row is no longer visible — clean up all state including tag index\n for (const tag of rowTagSet) {\n const parsedTag = parseTag(tag)\n removeTagFromIndex(parsedTag, rowId, tagIndex, tagLength!)\n tagCache.delete(tag)\n }\n rowTagSets.delete(rowId)\n rowActiveConditions.delete(rowId)\n return true\n }\n return false\n }\n\n // Simple shape (no subquery dependencies — server sends no active_conditions):\n // Remove matching tags and delete if tag set is empty\n for (const tag of rowTagSet) {\n const parsedTag = parseTag(tag)\n if (tagMatchesPattern(parsedTag, pattern)) {\n rowTagSet.delete(tag)\n removeTagFromIndex(parsedTag, rowId, tagIndex, tagLength!)\n }\n }\n\n if (rowTagSet.size === 0) {\n rowTagSets.delete(rowId)\n return true\n }\n\n return false\n }\n\n /**\n * Process move-out event: remove matching tags from rows and delete rows with empty tag sets\n */\n const processMoveOutEvent = (\n patterns: Array<MovePattern>,\n begin: () => void,\n write: (message: ChangeMessageOrDeleteKeyMessage<T>) => void,\n transactionStarted: boolean,\n ): boolean => {\n if (tagLength === undefined) {\n debug(\n `${collectionId ? `[${collectionId}] ` : ``}Received move-out message but no tag length set yet, ignoring`,\n )\n return transactionStarted\n }\n\n let txStarted = transactionStarted\n\n // Process all patterns and collect rows to delete\n for (const pattern of patterns) {\n // Find all rows that match this pattern\n const affectedRowIds = findRowsMatchingPattern(pattern, tagIndex)\n\n for (const rowId of affectedRowIds) {\n if (removeMatchingTagsFromRow(rowId, pattern)) {\n // Delete rows with empty tag sets\n if (!txStarted) {\n begin()\n txStarted = true\n }\n\n write({\n type: `delete`,\n key: rowId,\n })\n }\n }\n }\n\n return txStarted\n }\n\n /**\n * Process move-in event: re-activate conditions for rows matching the patterns.\n * This is a silent operation — no messages are emitted to the collection.\n */\n const processMoveInEvent = (patterns: Array<MovePattern>): void => {\n if (tagLength === undefined) {\n debug(\n `${collectionId ? `[${collectionId}] ` : ``}Received move-in message but no tag length set yet, ignoring`,\n )\n return\n }\n\n for (const pattern of patterns) {\n const affectedRowIds = findRowsMatchingPattern(pattern, tagIndex)\n\n for (const rowId of affectedRowIds) {\n const activeConditions = rowActiveConditions.get(rowId)\n if (activeConditions) {\n activeConditions[pattern.pos] = true\n }\n }\n }\n }\n\n /**\n * Get the sync metadata for insert operations\n * @returns Record containing relation information\n */\n const getSyncMetadata = (): Record<string, unknown> => {\n // Use the stored schema if available, otherwise default to 'public'\n const schema = relationSchema.state || `public`\n\n return {\n relation: shapeOptions.params?.table\n ? [schema, shapeOptions.params.table]\n : undefined,\n }\n }\n\n let unsubscribeStream: () => void\n\n return {\n sync: (params: Parameters<SyncConfig<T>[`sync`]>[0]) => {\n const {\n begin,\n write,\n commit: commitSyncTransaction,\n markReady,\n markError,\n truncate,\n collection,\n metadata,\n } = params\n let commitSequence = 0\n const pendingAppliedReceipts = new Map<number, Promise<void>>()\n const commit = (signal?: AbortSignal): SyncAppliedReceipt => {\n const sequence = ++commitSequence\n const applied = commitSyncTransaction(signal)\n if (applied === true) {\n return true\n }\n pendingAppliedReceipts.set(sequence, applied)\n const removeReceipt = () => pendingAppliedReceipts.delete(sequence)\n void applied.then(removeReceipt, removeReceipt)\n return applied\n }\n const waitForCommitsAfter = async (cursor: number): Promise<void> => {\n await Promise.all(\n Array.from(pendingAppliedReceipts, ([sequence, applied]) =>\n sequence > cursor ? applied : undefined,\n ),\n )\n }\n const readPersistedResumeState = (): ElectricResumeState | undefined => {\n const persistedResumeState = metadata?.collection.get(`electric:resume`)\n return parseElectricResumeState(persistedResumeState)\n }\n\n const persistedResumeState = getNewestElectricResumeState(\n readPersistedResumeState(),\n hydratedResumeState.state,\n )\n const shapeIdentity = getStableShapeIdentity({\n url: shapeOptions.url,\n params: shapeOptions.params as Record<string, unknown> | undefined,\n })\n const hasIncompatiblePersistedResume =\n persistedResumeState?.kind === `resume` &&\n persistedResumeState.shapeId !== shapeIdentity\n const canUsePersistedResume =\n shapeOptions.offset === undefined &&\n shapeOptions.handle === undefined &&\n persistedResumeState?.kind === `resume` &&\n !hasIncompatiblePersistedResume\n\n // Wrap markReady to wait for test hook in progressive mode\n let progressiveReadyGate: Promise<void> | null = null\n let streamErrorVersion = 0\n const wrappedMarkReady = (\n isBuffering: boolean,\n expectedErrorVersion = streamErrorVersion,\n ) => {\n if (streamErrorVersion !== expectedErrorVersion) return\n\n // Only create gate if we're in buffering phase (first up-to-date)\n if (\n isBuffering &&\n syncMode === `progressive` &&\n testHooks?.beforeMarkingReady\n ) {\n // Create a new gate promise for this sync cycle\n progressiveReadyGate = testHooks.beforeMarkingReady()\n progressiveReadyGate.then(() => {\n if (streamErrorVersion === expectedErrorVersion) {\n markReady()\n }\n })\n } else {\n // No hook, not buffering, or already past first up-to-date\n markReady()\n }\n }\n\n // Abort controller for the stream - wraps the signal if provided\n const abortController = new AbortController()\n\n if (shapeOptions.signal) {\n shapeOptions.signal.addEventListener(\n `abort`,\n () => {\n abortController.abort()\n },\n {\n once: true,\n },\n )\n if (shapeOptions.signal.aborted) {\n abortController.abort()\n }\n }\n\n // Cleanup pending matches on abort\n abortController.signal.addEventListener(`abort`, () => {\n pendingMatches.setState((current) => {\n current.forEach((match) => {\n clearTimeout(match.timeoutId)\n match.reject(new StreamAbortedError())\n })\n return new Map() // Clear all pending matches\n })\n })\n\n const stream = new ShapeStream({\n ...shapeOptions,\n // In on-demand mode, we only want to sync changes, so we set the log to `changes_only`\n log: syncMode === `on-demand` ? `changes_only` : undefined,\n // In on-demand mode, we only need the changes from the point of time the collection was created\n // so we default to `now` when there is no saved offset.\n offset:\n shapeOptions.offset ??\n (canUsePersistedResume\n ? (persistedResumeState.offset as Offset)\n : syncMode === `on-demand`\n ? `now`\n : undefined),\n handle:\n shapeOptions.handle ??\n (canUsePersistedResume ? persistedResumeState.handle : undefined),\n signal: abortController.signal,\n onError: (errorParams) => {\n streamErrorVersion++\n // Note that Electric sends a 409 error on a `must-refetch` message, but the\n // ShapeStream handled this and it will not reach this handler, therefor\n // this handler will not run for a `must-refetch`.\n const initialSyncFailed = collection.status === `loading`\n if (initialSyncFailed) {\n markError(errorParams)\n }\n\n if (shapeOptions.onError) {\n return shapeOptions.onError(errorParams)\n } else {\n console.error(\n `An error occurred while syncing collection: ${collection.id}, \\n` +\n (initialSyncFailed\n ? `the initial sync has been marked as failed. \\n`\n : `the last ready snapshot has been preserved. \\n`) +\n `You can provide an 'onError' handler on the shapeOptions to handle this error, and this message will not be logged.`,\n errorParams,\n )\n }\n\n return\n },\n })\n let transactionStarted = false\n const newTxids = new Set<Txid>()\n const newSnapshots: Array<PostgresSnapshot> = []\n // Track if we've completed initial sync in progressive mode. A persisted\n // resume starts from an already-committed stream offset, so the next\n // up-to-date message must not run the initial atomic swap again.\n let hasReceivedUpToDate =\n syncMode === `progressive` && canUsePersistedResume\n\n // Progressive mode state\n // Helper to determine if we're buffering the initial sync\n const isBufferingInitialSync = () =>\n syncMode === `progressive` && !hasReceivedUpToDate\n const bufferedMessages: Array<Message<T>> = [] // Buffer change messages during initial sync\n\n // Track keys that have been synced to handle overlapping subset queries.\n // When multiple subset queries return the same row, the server sends `insert`\n // for each response. We convert subsequent inserts to updates to avoid\n // duplicate key errors when the row's data has changed between requests.\n const syncedKeys = new Set<string | number>()\n\n const stageResumeMetadata = () => {\n const shapeHandle = stream.shapeHandle\n const lastOffset = stream.lastOffset\n if (!shapeHandle || lastOffset === `-1`) {\n return\n }\n\n const resumeState: ElectricResumeState = {\n kind: `resume`,\n offset: lastOffset,\n handle: shapeHandle,\n shapeId: shapeIdentity,\n updatedAt: Date.now(),\n }\n hydratedResumeState.setState(() => resumeState)\n metadata?.collection.set(`electric:resume`, resumeState)\n }\n\n const commitResetResumeMetadataImmediately = () => {\n const resetState: ElectricResumeState = {\n kind: `reset`,\n updatedAt: Date.now(),\n }\n hydratedResumeState.setState(() => resetState)\n\n if (metadata) {\n begin({ immediate: true })\n metadata.collection.set(`electric:resume`, resetState)\n commit()\n }\n }\n\n if (hasIncompatiblePersistedResume) {\n commitResetResumeMetadataImmediately()\n }\n\n /**\n * Process a change message: handle tags and write the mutation\n */\n const processChangeMessage = (changeMessage: Message<T>) => {\n if (!isChangeMessage(changeMessage)) {\n return\n }\n\n // Process tags if present\n const tags = changeMessage.headers.tags\n const removedTags = changeMessage.headers.removed_tags\n const hasTags = tags || removedTags\n\n // Extract active_conditions from headers (DNF support)\n const activeConditions = changeMessage.headers.active_conditions as\n | ActiveConditions\n | undefined\n\n const rowId = collection.getKeyFromItem(changeMessage.value)\n const operation = changeMessage.headers.operation\n\n // Track synced keys and handle overlapping subset queries.\n // When multiple subset queries return the same row, the server sends\n // `insert` for each response. We convert subsequent inserts to updates\n // to avoid duplicate key errors when the row's data has changed.\n const isDelete = operation === `delete`\n const isDuplicateInsert =\n operation === `insert` && syncedKeys.has(rowId)\n\n if (isDelete) {\n syncedKeys.delete(rowId)\n } else {\n syncedKeys.add(rowId)\n }\n\n if (isDelete) {\n clearTagsForRow(rowId)\n } else if (hasTags) {\n processTagsForChangeMessage(\n tags,\n removedTags,\n rowId,\n activeConditions,\n )\n }\n\n write({\n type: isDuplicateInsert ? `update` : operation,\n value: changeMessage.value,\n // Include the primary key and relation info in the metadata\n metadata: {\n ...changeMessage.headers,\n },\n })\n }\n\n // Create deduplicated loadSubset wrapper for non-eager modes\n // This prevents redundant snapshot requests when multiple concurrent\n // live queries request overlapping or subset predicates\n const loadSubsetDedupe = createLoadSubsetDedupe({\n stream,\n syncMode,\n isBufferingInitialSync,\n begin,\n write,\n commit,\n getCommitCursor: () => commitSequence,\n waitForCommitsAfter,\n collectionId,\n // Pass the columnMapper's encode function to transform column names\n // (e.g., camelCase to snake_case) when compiling SQL for subset queries\n encodeColumnName: shapeOptions.columnMapper?.encode,\n // Pass abort signal so requestSnapshot errors can be ignored during cleanup\n signal: abortController.signal,\n })\n\n unsubscribeStream = stream.subscribe((messages: Array<Message<T>>) => {\n // Track commit point type - up-to-date takes precedence as it also triggers progressive mode atomic swap\n let commitPoint: `up-to-date` | `subset-end` | null = null\n\n // Don't clear the buffer between batches - this preserves messages for awaitMatch\n // to find even if multiple batches arrive before awaitMatch is called.\n // The buffer is naturally limited by MAX_BATCH_MESSAGES (oldest messages are dropped).\n // Reset batchCommitted since we're starting a new batch\n batchCommitted.setState(() => false)\n\n for (const message of messages) {\n // Add message to current batch buffer (for race condition handling)\n if (\n isChangeMessage(message) ||\n isMoveOutMessage(message) ||\n isMoveInMessage(message)\n ) {\n currentBatchMessages.setState((currentBuffer) => {\n const newBuffer = [...currentBuffer, message]\n // Limit buffer size for safety\n if (newBuffer.length > MAX_BATCH_MESSAGES) {\n newBuffer.splice(0, newBuffer.length - MAX_BATCH_MESSAGES)\n }\n return newBuffer\n })\n }\n\n // Check for txids in the message and add them to our store\n // Skip during buffered initial sync in progressive mode (txids will be extracted during atomic swap)\n // EXCEPTION: If a transaction is already started (e.g., from must-refetch), track txids\n // to avoid losing them when messages are written to the existing transaction.\n if (\n hasTxids(message) &&\n (!isBufferingInitialSync() || transactionStarted)\n ) {\n message.headers.txids?.forEach((txid) => newTxids.add(txid))\n }\n\n // Check pending matches against this message\n // Note: matchFn will mark matches internally, we don't resolve here\n const matchesToRemove: Array<string> = []\n pendingMatches.state.forEach((match, matchId) => {\n if (!match.matched) {\n try {\n match.matchFn(message)\n } catch (err) {\n // If matchFn throws, clean up and reject the promise\n clearTimeout(match.timeoutId)\n match.reject(\n err instanceof Error ? err : new Error(String(err)),\n )\n matchesToRemove.push(matchId)\n debug(`matchFn error: %o`, err)\n }\n }\n })\n\n // Remove matches that errored\n removePendingMatches(matchesToRemove)\n\n if (isChangeMessage(message)) {\n // Check if the message contains schema information\n const schema = message.headers.schema\n if (schema && typeof schema === `string`) {\n // Store the schema for future use if it's a valid string\n relationSchema.setState(() => schema)\n }\n\n // In buffered initial sync of progressive mode, buffer messages instead of writing\n // EXCEPTION: If a transaction is already started (e.g., from must-refetch), write\n // directly to it instead of buffering. This prevents orphan transactions.\n if (isBufferingInitialSync() && !transactionStarted) {\n bufferedMessages.push(message)\n } else {\n // Normal processing: write changes immediately\n if (!transactionStarted) {\n begin()\n transactionStarted = true\n }\n\n processChangeMessage(message)\n }\n } else if (isSnapshotEndMessage(message)) {\n // Track postgres snapshot metadata for resolving awaiting mutations\n // Skip during buffered initial sync (will be extracted during atomic swap)\n // EXCEPTION: If a transaction is already started (e.g., from must-refetch), track snapshots\n // to avoid losing them when messages are written to the existing transaction.\n if (!isBufferingInitialSync() || transactionStarted) {\n newSnapshots.push(parseSnapshotMessage(message))\n }\n } else if (isUpToDateMessage(message)) {\n // up-to-date takes precedence - also triggers progressive mode atomic swap\n commitPoint = `up-to-date`\n } else if (isSubsetEndMessage(message)) {\n // subset-end triggers commit but not progressive mode atomic swap\n if (commitPoint !== `up-to-date`) {\n commitPoint = `subset-end`\n }\n } else if (isMoveOutMessage(message)) {\n // Handle move-out event: buffer if buffering, otherwise process immediately\n // EXCEPTION: If a transaction is already started (e.g., from must-refetch), process\n // immediately to avoid orphan transactions.\n if (isBufferingInitialSync() && !transactionStarted) {\n bufferedMessages.push(message)\n } else {\n // Normal processing: process move-out immediately\n transactionStarted = processMoveOutEvent(\n message.headers.patterns,\n begin,\n write,\n transactionStarted,\n )\n }\n } else if (isMoveInMessage(message)) {\n // Handle move-in event: re-activate conditions for matching rows.\n // Buffer if buffering, otherwise process immediately.\n if (isBufferingInitialSync() && !transactionStarted) {\n bufferedMessages.push(message)\n } else {\n processMoveInEvent(message.headers.patterns)\n }\n } else if (isMustRefetchMessage(message)) {\n debug(\n `${collectionId ? `[${collectionId}] ` : ``}Received must-refetch message, starting transaction with truncate`,\n )\n\n commitResetResumeMetadataImmediately()\n\n // Start a transaction and truncate the collection\n if (!transactionStarted) {\n begin()\n transactionStarted = true\n }\n\n truncate()\n\n // Clear tag tracking state\n clearTagTrackingState()\n\n // Clear synced keys tracking since we're starting fresh\n syncedKeys.clear()\n\n // Reset the loadSubset deduplication state since we're starting fresh\n // This ensures that previously loaded predicates don't prevent refetching after truncate\n loadSubsetDedupe?.reset()\n\n // Reset flags so we continue accumulating changes until next up-to-date\n commitPoint = null\n hasReceivedUpToDate = false // Reset for progressive mode (isBufferingInitialSync will reflect this)\n bufferedMessages.length = 0 // Clear buffered messages\n }\n }\n\n if (commitPoint !== null) {\n let applied: SyncAppliedReceipt = true\n const wasBufferingInitialSync = isBufferingInitialSync()\n // PROGRESSIVE MODE: Atomic swap on first up-to-date (not subset-end)\n // EXCEPTION: Skip atomic swap if a transaction is already started (e.g., from must-refetch).\n // In that case, do a normal commit to properly close the existing transaction.\n if (\n isBufferingInitialSync() &&\n commitPoint === `up-to-date` &&\n !transactionStarted\n ) {\n debug(\n `${collectionId ? `[${collectionId}] ` : ``}Progressive mode: Performing atomic swap with ${bufferedMessages.length} buffered messages`,\n )\n\n // Start atomic swap transaction\n begin()\n\n // Truncate to clear all snapshot data\n truncate()\n\n // Clear tag tracking state for atomic swap\n clearTagTrackingState()\n\n // Clear synced keys tracking for atomic swap\n syncedKeys.clear()\n\n // Apply all buffered change messages and extract txids/snapshots\n for (const bufferedMsg of bufferedMessages) {\n if (isChangeMessage(bufferedMsg)) {\n processChangeMessage(bufferedMsg)\n\n // Extract txids from buffered messages (will be committed to store after transaction)\n if (hasTxids(bufferedMsg)) {\n bufferedMsg.headers.txids?.forEach((txid) =>\n newTxids.add(txid),\n )\n }\n } else if (isSnapshotEndMessage(bufferedMsg)) {\n // Extract snapshots from buffered messages (will be committed to store after transaction)\n newSnapshots.push(parseSnapshotMessage(bufferedMsg))\n } else if (isMoveOutMessage(bufferedMsg)) {\n // Process buffered move-out messages during atomic swap\n processMoveOutEvent(\n bufferedMsg.headers.patterns,\n begin,\n write,\n transactionStarted,\n )\n } else if (isMoveInMessage(bufferedMsg)) {\n // Process buffered move-in messages during atomic swap\n processMoveInEvent(bufferedMsg.headers.patterns)\n }\n }\n\n // Commit the atomic swap\n stageResumeMetadata()\n applied = commit()\n\n // Exit buffering phase by marking that we've received up-to-date\n // isBufferingInitialSync() will now return false\n bufferedMessages.length = 0\n\n debug(\n `${collectionId ? `[${collectionId}] ` : ``}Progressive mode: Atomic swap complete, now in normal sync mode`,\n )\n } else {\n // Normal mode or on-demand: commit transaction if one was started\n // Both up-to-date and subset-end trigger a commit\n if (transactionStarted) {\n stageResumeMetadata()\n applied = commit()\n transactionStarted = false\n } else if (commitPoint === `up-to-date` && metadata) {\n begin()\n stageResumeMetadata()\n applied = commit()\n }\n }\n const readyErrorVersion = streamErrorVersion\n if (applied === true) {\n wrappedMarkReady(wasBufferingInitialSync, readyErrorVersion)\n } else {\n void applied.then(\n () =>\n wrappedMarkReady(wasBufferingInitialSync, readyErrorVersion),\n () => undefined,\n )\n }\n\n // Track that we've received the first up-to-date for progressive mode\n if (commitPoint === `up-to-date`) {\n hasReceivedUpToDate = true\n }\n\n // Always commit txids when we receive up-to-date, regardless of transaction state\n seenTxids.setState((currentTxids) => {\n const clonedSeen = new Set<Txid>(currentTxids)\n if (newTxids.size > 0) {\n debug(\n `${collectionId ? `[${collectionId}] ` : ``}new txids synced from pg %O`,\n Array.from(newTxids),\n )\n }\n newTxids.forEach((txid) => clonedSeen.add(txid))\n newTxids.clear()\n return clonedSeen\n })\n\n // Always commit snapshots when we receive up-to-date, regardless of transaction state\n seenSnapshots.setState((currentSnapshots) => {\n const seen = [...currentSnapshots, ...newSnapshots]\n newSnapshots.forEach((snapshot) =>\n debug(\n `${collectionId ? `[${collectionId}] ` : ``}new snapshot synced from pg %o`,\n snapshot,\n ),\n )\n newSnapshots.length = 0\n return seen\n })\n\n // Resolve all matched pending matches on up-to-date or subset-end\n // Set batchCommitted BEFORE resolving to avoid timing window where late awaitMatch\n // calls could register as \"matched\" after resolver pass already ran\n batchCommitted.setState(() => true)\n\n resolveMatchedPendingMatches()\n }\n })\n\n // Return the deduplicated loadSubset if available (on-demand or progressive mode)\n // The loadSubset method is auto-bound, so it can be safely returned directly\n return {\n loadSubset: loadSubsetDedupe?.loadSubset,\n cleanup: () => {\n // Unsubscribe from the stream\n unsubscribeStream()\n // Abort the abort controller to stop the stream\n abortController.abort()\n // Reset deduplication tracking so collection can load fresh data if restarted\n loadSubsetDedupe?.reset()\n hydratedResumeState.setState(() => undefined)\n },\n }\n },\n // Expose the getSyncMetadata function\n getSyncMetadata,\n }\n}\n"],"names":["isControlMessage","compileSQL","and","DeduplicatedLoadSubset","Store","ExpectedNumberInAwaitTxIdError","isVisibleInSnapshot","TimeoutWaitingForTxIdError","TimeoutWaitingForMatchError","withCollectionConfigFactory","parseTagString","tagIndex","getTagLength","addTagToIndex","removeTagFromIndex","deriveDisjunctPositions","rowVisible","tagMatchesPattern","findRowsMatchingPattern","persistedResumeState","StreamAbortedError","ShapeStream","isChangeMessage","isMoveOutMessage","isMoveInMessage"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"electric.cjs","sources":["../../src/electric.ts"],"sourcesContent":["import {\n ShapeStream,\n isChangeMessage,\n isControlMessage,\n isVisibleInSnapshot,\n} from '@electric-sql/client'\nimport { Store } from '@tanstack/store'\nimport DebugModule from 'debug'\nimport {\n DeduplicatedLoadSubset,\n LoadSubsetOperationAbortedError,\n and,\n withCollectionConfigFactory,\n} from '@tanstack/db'\nimport {\n ExpectedNumberInAwaitTxIdError,\n StreamAbortedError,\n TimeoutWaitingForMatchError,\n TimeoutWaitingForTxIdError,\n} from './errors'\nimport { compileSQL } from './sql-compiler'\nimport {\n addTagToIndex,\n deriveDisjunctPositions,\n findRowsMatchingPattern,\n getTagLength,\n isMoveInMessage,\n isMoveOutMessage,\n parseTag as parseTagString,\n removeTagFromIndex,\n rowVisible,\n tagMatchesPattern,\n} from './tag-index'\nimport type { ColumnEncoder } from './sql-compiler'\nimport type {\n ActiveConditions,\n DisjunctPositions,\n MovePattern,\n MoveTag,\n ParsedMoveTag,\n RowId,\n TagIndex,\n} from './tag-index'\nimport type {\n BaseCollectionConfig,\n ChangeMessageOrDeleteKeyMessage,\n CollectionConfig,\n DeleteMutationFnParams,\n InsertMutationFnParams,\n LoadSubsetOptions,\n SyncAppliedReceipt,\n SyncConfig,\n SyncMode,\n UpdateMutationFnParams,\n UtilsRecord,\n} from '@tanstack/db'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type {\n ControlMessage,\n GetExtensions,\n Message,\n Offset,\n PostgresSnapshot,\n Row,\n ShapeStreamOptions,\n} from '@electric-sql/client'\n\n// Re-export for user convenience in custom match functions\nexport { isChangeMessage, isControlMessage } from '@electric-sql/client'\n\nconst debug = DebugModule.debug(`ts/db:electric`)\n\nconst FORCE_DISCONNECT_AND_REFRESH_TIMEOUT_MS = 250\n\n/**\n * Symbol for internal test hooks (hidden from public API)\n */\nexport const ELECTRIC_TEST_HOOKS = Symbol(`electricTestHooks`)\n\n/**\n * Internal test hooks interface (for testing only)\n */\nexport interface ElectricTestHooks {\n /**\n * Called before marking collection ready after first up-to-date in progressive mode\n * Allows tests to pause and validate snapshot phase before atomic swap completes\n */\n beforeMarkingReady?: () => Promise<void>\n}\n\n/**\n * Type representing a transaction ID in ElectricSQL\n */\nexport type Txid = number\n\ntype ElectricResumeState =\n | {\n kind: `resume`\n offset: string\n handle: string\n shapeId: string\n updatedAt: number\n }\n | {\n kind: `reset`\n updatedAt: number\n }\n\ntype ElectricSyncMeta = {\n version: 1\n resume?: ElectricResumeState\n seenTxids: Array<Txid>\n}\n\nfunction parseElectricResumeState(\n value: unknown,\n): ElectricResumeState | undefined {\n if (!value || typeof value !== `object`) {\n return undefined\n }\n\n const record = value as Record<string, unknown>\n if (\n record.kind === `resume` &&\n typeof record.offset === `string` &&\n typeof record.handle === `string` &&\n typeof record.shapeId === `string` &&\n typeof record.updatedAt === `number` &&\n Number.isFinite(record.updatedAt)\n ) {\n return {\n kind: `resume`,\n offset: record.offset,\n handle: record.handle,\n shapeId: record.shapeId,\n updatedAt: record.updatedAt,\n }\n }\n\n if (\n record.kind === `reset` &&\n typeof record.updatedAt === `number` &&\n Number.isFinite(record.updatedAt)\n ) {\n return {\n kind: `reset`,\n updatedAt: record.updatedAt,\n }\n }\n\n return undefined\n}\n\nfunction parseElectricSyncMeta(value: unknown): ElectricSyncMeta | undefined {\n if (!value || typeof value !== `object`) {\n return undefined\n }\n\n const record = value as Record<string, unknown>\n if (\n record.version !== 1 ||\n !Array.isArray(record.seenTxids) ||\n !record.seenTxids.every(\n (txid) => typeof txid === `number` && Number.isFinite(txid),\n )\n ) {\n return undefined\n }\n\n const resume =\n record.resume === undefined\n ? undefined\n : parseElectricResumeState(record.resume)\n if (record.resume !== undefined && resume === undefined) {\n return undefined\n }\n\n return {\n version: 1,\n ...(resume ? { resume } : {}),\n seenTxids: Array.from(new Set(record.seenTxids)).sort((a, b) => a - b),\n }\n}\n\nfunction mergeElectricSyncMeta(\n current: unknown,\n incoming: unknown,\n): ElectricSyncMeta | unknown {\n const currentMeta = parseElectricSyncMeta(current)\n const incomingMeta = parseElectricSyncMeta(incoming)\n\n if (!incomingMeta) {\n return current\n }\n if (!currentMeta) {\n return incomingMeta\n }\n\n const resume = getNewestElectricResumeState(\n currentMeta.resume,\n incomingMeta.resume,\n )\n\n return {\n version: 1,\n ...(resume ? { resume } : {}),\n seenTxids: Array.from(\n new Set([...currentMeta.seenTxids, ...incomingMeta.seenTxids]),\n ).sort((a, b) => a - b),\n }\n}\n\nfunction getNewestElectricResumeState(\n current: ElectricResumeState | undefined,\n incoming: ElectricResumeState | undefined,\n): ElectricResumeState | undefined {\n if (!current) return incoming\n if (!incoming) return current\n return incoming.updatedAt >= current.updatedAt ? incoming : current\n}\n\n/**\n * Custom match function type - receives stream messages and returns boolean\n * indicating if the mutation has been synchronized\n */\nexport type MatchFunction<T extends Row<unknown>> = (\n message: Message<T>,\n) => boolean\n\n/**\n * Matching strategies for Electric synchronization\n * Handlers can return:\n * - Txid strategy: { txid: number | number[], timeout?: number } (recommended)\n * - Void (no return value) - mutation completes without waiting\n *\n * The optional timeout property specifies how long to wait for the txid(s) in milliseconds.\n * If not specified, defaults to 5000ms.\n */\nexport type MatchingStrategy = {\n txid: Txid | Array<Txid>\n timeout?: number\n} | void\n\n/**\n * Type representing a snapshot end message\n */\ntype SnapshotEndMessage = ControlMessage & {\n headers: { control: `snapshot-end` }\n}\n// The `InferSchemaOutput` and `ResolveType` are copied from the `@tanstack/db` package\n// but we modified `InferSchemaOutput` slightly to restrict the schema output to `Row<unknown>`\n// This is needed in order for `GetExtensions` to be able to infer the parser extensions type from the schema\ntype InferSchemaOutput<T> = T extends StandardSchemaV1\n ? StandardSchemaV1.InferOutput<T> extends Row<unknown>\n ? StandardSchemaV1.InferOutput<T>\n : Record<string, unknown>\n : Record<string, unknown>\n\n/**\n * The mode of sync to use for the collection.\n * @default `eager`\n * @description\n * - `eager`:\n * - syncs all data immediately on preload\n * - collection will be marked as ready once the sync is complete\n * - there is no incremental sync\n * - `on-demand`:\n * - syncs data in incremental snapshots when the collection is queried\n * - collection will be marked as ready immediately after the first snapshot is synced\n * - `progressive`:\n * - syncs all data for the collection in the background\n * - uses incremental snapshots during the initial sync to provide a fast path to the data required for queries\n * - collection will be marked as ready once the full sync is complete\n */\nexport type ElectricSyncMode = SyncMode | `progressive`\n\n/**\n * Configuration interface for Electric collection options\n * @template T - The type of items in the collection\n * @template TSchema - The schema type for validation\n */\nexport interface ElectricCollectionConfig<\n T extends Row<unknown> = Row<unknown>,\n TSchema extends StandardSchemaV1 = never,\n> extends Omit<\n BaseCollectionConfig<\n T,\n string | number,\n TSchema,\n ElectricCollectionUtils<T>,\n any\n >,\n `onInsert` | `onUpdate` | `onDelete` | `syncMode`\n> {\n /**\n * Configuration options for the ElectricSQL ShapeStream\n */\n shapeOptions: ShapeStreamOptions<GetExtensions<T>>\n syncMode?: ElectricSyncMode\n\n /**\n * Internal test hooks (for testing only)\n * Hidden via Symbol to prevent accidental usage in production\n */\n [ELECTRIC_TEST_HOOKS]?: ElectricTestHooks\n\n /**\n * Optional asynchronous handler function called before an insert operation\n * @param params Object containing transaction and collection information\n * @returns Promise resolving to { txid, timeout? } or void\n * @example\n * // Basic Electric insert handler with txid (recommended)\n * onInsert: async ({ transaction }) => {\n * const newItem = transaction.mutations[0].modified\n * const result = await api.todos.create({\n * data: newItem\n * })\n * return { txid: result.txid }\n * }\n *\n * @example\n * // Insert handler with custom timeout\n * onInsert: async ({ transaction }) => {\n * const newItem = transaction.mutations[0].modified\n * const result = await api.todos.create({\n * data: newItem\n * })\n * return { txid: result.txid, timeout: 10000 } // Wait up to 10 seconds\n * }\n *\n * @example\n * // Insert handler with multiple items - return array of txids\n * onInsert: async ({ transaction }) => {\n * const items = transaction.mutations.map(m => m.modified)\n * const results = await Promise.all(\n * items.map(item => api.todos.create({ data: item }))\n * )\n * return { txid: results.map(r => r.txid) }\n * }\n *\n * @example\n * // Use awaitMatch utility for custom matching\n * onInsert: async ({ transaction, collection }) => {\n * const newItem = transaction.mutations[0].modified\n * await api.todos.create({ data: newItem })\n * await collection.utils.awaitMatch(\n * (message) => isChangeMessage(message) &&\n * message.headers.operation === 'insert' &&\n * message.value.name === newItem.name\n * )\n * }\n */\n onInsert?: (\n params: InsertMutationFnParams<\n T,\n string | number,\n ElectricCollectionUtils<T>\n >,\n ) => Promise<MatchingStrategy>\n\n /**\n * Optional asynchronous handler function called before an update operation\n * @param params Object containing transaction and collection information\n * @returns Promise resolving to { txid, timeout? } or void\n * @example\n * // Basic Electric update handler with txid (recommended)\n * onUpdate: async ({ transaction }) => {\n * const { original, changes } = transaction.mutations[0]\n * const result = await api.todos.update({\n * where: { id: original.id },\n * data: changes\n * })\n * return { txid: result.txid }\n * }\n *\n * @example\n * // Use awaitMatch utility for custom matching\n * onUpdate: async ({ transaction, collection }) => {\n * const { original, changes } = transaction.mutations[0]\n * await api.todos.update({ where: { id: original.id }, data: changes })\n * await collection.utils.awaitMatch(\n * (message) => isChangeMessage(message) &&\n * message.headers.operation === 'update' &&\n * message.value.id === original.id\n * )\n * }\n */\n onUpdate?: (\n params: UpdateMutationFnParams<\n T,\n string | number,\n ElectricCollectionUtils<T>\n >,\n ) => Promise<MatchingStrategy>\n\n /**\n * Optional asynchronous handler function called before a delete operation\n * @param params Object containing transaction and collection information\n * @returns Promise resolving to { txid, timeout? } or void\n * @example\n * // Basic Electric delete handler with txid (recommended)\n * onDelete: async ({ transaction }) => {\n * const mutation = transaction.mutations[0]\n * const result = await api.todos.delete({\n * id: mutation.original.id\n * })\n * return { txid: result.txid }\n * }\n *\n * @example\n * // Use awaitMatch utility for custom matching\n * onDelete: async ({ transaction, collection }) => {\n * const mutation = transaction.mutations[0]\n * await api.todos.delete({ id: mutation.original.id })\n * await collection.utils.awaitMatch(\n * (message) => isChangeMessage(message) &&\n * message.headers.operation === 'delete' &&\n * message.value.id === mutation.original.id\n * )\n * }\n */\n onDelete?: (\n params: DeleteMutationFnParams<\n T,\n string | number,\n ElectricCollectionUtils<T>\n >,\n ) => Promise<MatchingStrategy>\n}\n\nfunction isUpToDateMessage<T extends Row<unknown>>(\n message: Message<T>,\n): message is ControlMessage & { up_to_date: true } {\n return isControlMessage(message) && message.headers.control === `up-to-date`\n}\n\nfunction isMustRefetchMessage<T extends Row<unknown>>(\n message: Message<T>,\n): message is ControlMessage & { headers: { control: `must-refetch` } } {\n return isControlMessage(message) && message.headers.control === `must-refetch`\n}\n\nfunction isSnapshotEndMessage<T extends Row<unknown>>(\n message: Message<T>,\n): message is SnapshotEndMessage {\n return isControlMessage(message) && message.headers.control === `snapshot-end`\n}\n\nfunction isSubsetEndMessage<T extends Row<unknown>>(\n message: Message<T>,\n): message is ControlMessage & { headers: { control: `subset-end` } } {\n return (\n isControlMessage(message) &&\n (message.headers.control as string) === `subset-end`\n )\n}\n\nfunction parseSnapshotMessage(message: SnapshotEndMessage): PostgresSnapshot {\n return {\n xmin: message.headers.xmin,\n xmax: message.headers.xmax,\n xip_list: message.headers.xip_list,\n }\n}\n\nfunction toStableSerializable(value: unknown): unknown {\n if (value == null) {\n return value\n }\n\n if (Array.isArray(value)) {\n return value.map((entry) => toStableSerializable(entry))\n }\n\n if (value instanceof Date) {\n return value.toISOString()\n }\n\n if (typeof value === `object`) {\n const record = value as Record<string, unknown>\n const stableRecord: Record<string, unknown> = {}\n const keys = Object.keys(record).sort((left, right) =>\n left < right ? -1 : left > right ? 1 : 0,\n )\n for (const key of keys) {\n stableRecord[key] = toStableSerializable(record[key])\n }\n return stableRecord\n }\n\n return value\n}\n\nfunction getStableShapeIdentity(shapeOptions: {\n url: string\n params?: Record<string, unknown>\n}): string {\n return JSON.stringify(\n toStableSerializable({\n url: shapeOptions.url,\n params: shapeOptions.params ?? null,\n }),\n )\n}\n\n// Check if a message contains txids in its headers\nfunction hasTxids<T extends Row<unknown>>(\n message: Message<T>,\n): message is Message<T> & { headers: { txids?: Array<Txid> } } {\n return `txids` in message.headers && Array.isArray(message.headers.txids)\n}\n\n/**\n * Creates a deduplicated loadSubset handler for progressive/on-demand modes\n * Returns null for eager mode, or a DeduplicatedLoadSubset instance for other modes.\n * Handles fetching snapshots in progressive mode during buffering phase,\n * and requesting snapshots in on-demand mode.\n *\n * When cursor expressions are provided (whereFrom/whereCurrent), makes two\n * requestSnapshot calls:\n * - One for whereFrom (rows > cursor) with limit\n * - One for whereCurrent (rows = cursor, for tie-breaking) without limit\n */\nfunction createLoadSubsetDedupe<T extends Row<unknown>>({\n stream,\n syncMode,\n isBufferingInitialSync,\n begin,\n write,\n commit,\n getCommitCursor,\n waitForCommitsAfter,\n collectionId,\n encodeColumnName,\n signal,\n}: {\n stream: ShapeStream<T>\n syncMode: ElectricSyncMode\n isBufferingInitialSync: () => boolean\n begin: () => void\n write: (mutation: {\n type: `insert` | `update` | `delete`\n value: T\n metadata: Record<string, unknown>\n }) => void\n commit: (signal?: AbortSignal) => SyncAppliedReceipt\n getCommitCursor: () => number\n waitForCommitsAfter: (cursor: number) => Promise<void>\n collectionId?: string\n /**\n * Optional function to encode column names (e.g., camelCase to snake_case).\n * This is typically the `encode` function from shapeOptions.columnMapper.\n */\n encodeColumnName?: ColumnEncoder\n /**\n * Abort signal to check if the stream has been aborted during cleanup.\n * When aborted, errors from requestSnapshot are silently ignored.\n */\n signal: AbortSignal\n}): DeduplicatedLoadSubset | null {\n if (syncMode === `eager`) {\n return null\n }\n\n const compileOptions = encodeColumnName ? { encodeColumnName } : undefined\n const logPrefix = collectionId ? `[${collectionId}] ` : ``\n\n const abortReason = (abortedSignal: AbortSignal): unknown =>\n abortedSignal.reason ?? new LoadSubsetOperationAbortedError()\n\n /**\n * Handles errors from snapshot operations. Returns true if the error was\n * handled (signal aborted during cleanup), false if it should be re-thrown.\n */\n function handleSnapshotError(error: unknown, operation: string): boolean {\n if (signal.aborted) {\n debug(`${logPrefix}Ignoring ${operation} error during cleanup: %o`, error)\n return true\n }\n debug(`${logPrefix}Error in ${operation}: %o`, error)\n return false\n }\n\n const loadSubset = async (opts: LoadSubsetOptions) => {\n const commitCursor = getCommitCursor()\n const throwIfAborted = () => {\n if (signal.aborted) throw abortReason(signal)\n if (opts.signal?.aborted) throw abortReason(opts.signal)\n }\n throwIfAborted()\n\n if (isBufferingInitialSync()) {\n const snapshotParams = compileSQL<T>(opts, compileOptions)\n try {\n const { data: rows } = await stream.fetchSnapshot(snapshotParams)\n if (opts.signal?.aborted || !isBufferingInitialSync()) {\n debug(`${logPrefix}Ignoring snapshot - sync completed while fetching`)\n return\n }\n\n if (rows.length > 0) {\n begin()\n for (const row of rows) {\n write({\n type: `insert`,\n value: row.value,\n metadata: { ...row.headers },\n })\n }\n await commit(opts.signal)\n debug(`${logPrefix}Applied snapshot with ${rows.length} rows`)\n }\n } catch (error) {\n if (opts.signal?.aborted) return\n if (handleSnapshotError(error, `fetchSnapshot`)) {\n return\n }\n throw error\n }\n return\n }\n\n if (syncMode === `progressive`) {\n return\n }\n\n const { cursor, where, orderBy, limit } = opts\n\n // When the stream is already up-to-date, it may be in a long-poll wait.\n // Forcing a disconnect-and-refresh ensures requestSnapshot gets a response\n // from a fresh server round-trip rather than waiting for the current poll to end.\n // Some native fetch implementations (notably React Native/Expo) may not abort\n // long-poll requests promptly. Bound the wait so on-demand live queries don't\n // remain loading until the long-poll naturally times out.\n // If the refresh fails or times out, we fall through to requestSnapshot which\n // still works.\n if (stream.isUpToDate) {\n let timeoutId: ReturnType<typeof setTimeout> | undefined\n const abortSignals = [signal, opts.signal].filter(\n (candidate): candidate is AbortSignal => candidate !== undefined,\n )\n let rejectAbort: (reason: unknown) => void = () => {}\n const aborted = new Promise<never>((_resolve, reject) => {\n rejectAbort = reject\n })\n const abort = (event: Event) =>\n rejectAbort(abortReason(event.currentTarget as AbortSignal))\n for (const abortSignal of abortSignals) {\n abortSignal.addEventListener(`abort`, abort, { once: true })\n }\n try {\n await Promise.race([\n stream.forceDisconnectAndRefresh(),\n new Promise<void>((resolve) => {\n timeoutId = setTimeout(\n resolve,\n FORCE_DISCONNECT_AND_REFRESH_TIMEOUT_MS,\n )\n }),\n aborted,\n ])\n } catch (error) {\n if (signal.aborted || opts.signal?.aborted) throw error\n if (handleSnapshotError(error, `forceDisconnectAndRefresh`)) {\n return\n }\n debug(\n `${logPrefix}forceDisconnectAndRefresh failed, proceeding to requestSnapshot: %o`,\n error,\n )\n } finally {\n clearTimeout(timeoutId)\n for (const abortSignal of abortSignals) {\n abortSignal.removeEventListener(`abort`, abort)\n }\n }\n }\n\n throwIfAborted()\n\n // Upstream limitation: ShapeStream.requestSnapshot() publishes its rows\n // through the stream callback before its Promise resolves. It accepts no\n // request signal and exposes no request identity on those messages, so an\n // aborted request can already have installed rows before the check below.\n // Full request-scoped cancellation requires support in the Electric client;\n // matching snapshots by parameters is unsafe for overlapping equal requests.\n try {\n if (cursor) {\n const whereCurrentOpts: LoadSubsetOptions = {\n where: where ? and(where, cursor.whereCurrent) : cursor.whereCurrent,\n orderBy,\n }\n const whereCurrentParams = compileSQL<T>(\n whereCurrentOpts,\n compileOptions,\n )\n\n const whereFromOpts: LoadSubsetOptions = {\n where: where ? and(where, cursor.whereFrom) : cursor.whereFrom,\n orderBy,\n limit,\n }\n const whereFromParams = compileSQL<T>(whereFromOpts, compileOptions)\n\n debug(`${logPrefix}Requesting cursor.whereCurrent snapshot (all ties)`)\n debug(\n `${logPrefix}Requesting cursor.whereFrom snapshot (with limit ${limit})`,\n )\n\n await Promise.all([\n stream.requestSnapshot(whereCurrentParams),\n stream.requestSnapshot(whereFromParams),\n ])\n } else {\n const snapshotParams = compileSQL<T>(opts, compileOptions)\n await stream.requestSnapshot(snapshotParams)\n }\n } catch (error) {\n if (opts.signal?.aborted) return\n if (handleSnapshotError(error, `requestSnapshot`)) {\n return\n }\n throw error\n }\n await waitForCommitsAfter(commitCursor)\n }\n\n return new DeduplicatedLoadSubset({ loadSubset })\n}\n\n/**\n * Type for the awaitTxId utility function\n */\nexport type AwaitTxIdFn = (txId: Txid, timeout?: number) => Promise<boolean>\n\n/**\n * Type for the awaitMatch utility function\n */\nexport type AwaitMatchFn<T extends Row<unknown>> = (\n matchFn: MatchFunction<T>,\n timeout?: number,\n) => Promise<boolean>\n\n/**\n * Electric collection utilities type\n */\nexport interface ElectricCollectionUtils<\n T extends Row<unknown> = Row<unknown>,\n> extends UtilsRecord {\n awaitTxId: AwaitTxIdFn\n awaitMatch: AwaitMatchFn<T>\n}\n\n/**\n * Creates Electric collection options for use with a standard Collection\n *\n * @template T - The explicit type of items in the collection (highest priority)\n * @template TSchema - The schema type for validation and type inference (second priority)\n * @template TFallback - The fallback type if no explicit or schema type is provided\n * @param config - Configuration options for the Electric collection\n * @returns Collection options with utilities\n */\n\n// Overload for when schema is provided\nexport function electricCollectionOptions<T extends StandardSchemaV1>(\n config: ElectricCollectionConfig<InferSchemaOutput<T>, T> & {\n schema: T\n },\n): Omit<\n CollectionConfig<InferSchemaOutput<T>, string | number, T>,\n `utils` | `onInsert` | `onUpdate` | `onDelete`\n> &\n Pick<\n ElectricCollectionConfig<InferSchemaOutput<T>, T>,\n `onInsert` | `onUpdate` | `onDelete`\n > & {\n id?: string\n utils: ElectricCollectionUtils<InferSchemaOutput<T>>\n schema: T\n }\n\n// Overload for when no schema is provided\nexport function electricCollectionOptions<T extends Row<unknown>>(\n config: ElectricCollectionConfig<T> & {\n schema?: never // prohibit schema\n },\n): Omit<\n CollectionConfig<T, string | number>,\n `utils` | `onInsert` | `onUpdate` | `onDelete`\n> &\n Pick<ElectricCollectionConfig<T>, `onInsert` | `onUpdate` | `onDelete`> & {\n id?: string\n utils: ElectricCollectionUtils<T>\n schema?: never // no schema in the result\n }\n\nexport function electricCollectionOptions<T extends Row<unknown>>(\n config: ElectricCollectionConfig<T, any>,\n): Omit<\n CollectionConfig<T, string | number, any, ElectricCollectionUtils<T>>,\n `utils`\n> & {\n id?: string\n utils: ElectricCollectionUtils<T>\n schema?: any\n} {\n const seenTxids = new Store<Set<Txid>>(new Set([]))\n const seenSnapshots = new Store<Array<PostgresSnapshot>>([])\n const hydratedResumeState = new Store<ElectricResumeState | undefined>(\n undefined,\n )\n const internalSyncMode = config.syncMode ?? `eager`\n const finalSyncMode =\n internalSyncMode === `progressive` ? `on-demand` : internalSyncMode\n const pendingMatches = new Store<\n Map<\n string,\n {\n matchFn: (message: Message<any>) => boolean\n resolve: (value: boolean) => void\n reject: (error: Error) => void\n timeoutId: ReturnType<typeof setTimeout>\n matched: boolean\n }\n >\n >(new Map())\n\n // Buffer messages since last up-to-date to handle race conditions\n const currentBatchMessages = new Store<Array<Message<any>>>([])\n\n // Track whether the current batch has been committed (up-to-date received)\n // This allows awaitMatch to resolve immediately for messages from committed batches\n const batchCommitted = new Store<boolean>(false)\n\n /**\n * Helper function to remove multiple matches from the pendingMatches store\n */\n const removePendingMatches = (matchIds: Array<string>) => {\n if (matchIds.length > 0) {\n pendingMatches.setState((current) => {\n const newMatches = new Map(current)\n matchIds.forEach((id) => newMatches.delete(id))\n return newMatches\n })\n }\n }\n\n /**\n * Helper function to resolve and cleanup matched pending matches\n */\n const resolveMatchedPendingMatches = () => {\n const matchesToResolve: Array<string> = []\n pendingMatches.state.forEach((match, matchId) => {\n if (match.matched) {\n clearTimeout(match.timeoutId)\n match.resolve(true)\n matchesToResolve.push(matchId)\n debug(\n `${config.id ? `[${config.id}] ` : ``}awaitMatch resolved on up-to-date for match %s`,\n matchId,\n )\n }\n })\n removePendingMatches(matchesToResolve)\n }\n const sync = createElectricSync<T>(config.shapeOptions, {\n seenTxids,\n seenSnapshots,\n hydratedResumeState,\n syncMode: internalSyncMode,\n pendingMatches,\n currentBatchMessages,\n batchCommitted,\n removePendingMatches,\n resolveMatchedPendingMatches,\n collectionId: config.id,\n testHooks: config[ELECTRIC_TEST_HOOKS],\n })\n\n /**\n * Wait for a specific transaction ID to be synced\n * @param txId The transaction ID to wait for as a number\n * @param timeout Optional timeout in milliseconds (defaults to 5000ms)\n * @returns Promise that resolves when the txId is synced\n */\n const awaitTxId: AwaitTxIdFn = async (\n txId: Txid,\n timeout: number = 5000,\n ): Promise<boolean> => {\n debug(\n `${config.id ? `[${config.id}] ` : ``}awaitTxId called with txid %d`,\n txId,\n )\n if (typeof txId !== `number`) {\n throw new ExpectedNumberInAwaitTxIdError(typeof txId, config.id)\n }\n\n // First check if the txid is in the seenTxids store\n const hasTxid = seenTxids.state.has(txId)\n if (hasTxid) return true\n\n // Then check if the txid is in any of the seen snapshots\n const hasSnapshot = seenSnapshots.state.some((snapshot) =>\n isVisibleInSnapshot(txId, snapshot),\n )\n if (hasSnapshot) return true\n\n return new Promise((resolve, reject) => {\n const cleanup = () => {\n clearTimeout(timeoutId)\n subSeenTxids.unsubscribe()\n subSeenSnapshots.unsubscribe()\n }\n\n const timeoutId = setTimeout(() => {\n cleanup()\n reject(new TimeoutWaitingForTxIdError(txId, config.id))\n }, timeout)\n\n const subSeenTxids = seenTxids.subscribe(() => {\n if (seenTxids.state.has(txId)) {\n debug(\n `${config.id ? `[${config.id}] ` : ``}awaitTxId found match for txid %o`,\n txId,\n )\n cleanup()\n resolve(true)\n }\n })\n\n const subSeenSnapshots = seenSnapshots.subscribe(() => {\n const visibleSnapshot = seenSnapshots.state.find((snapshot) =>\n isVisibleInSnapshot(txId, snapshot),\n )\n if (visibleSnapshot) {\n debug(\n `${config.id ? `[${config.id}] ` : ``}awaitTxId found match for txid %o in snapshot %o`,\n txId,\n visibleSnapshot,\n )\n cleanup()\n resolve(true)\n }\n })\n })\n }\n\n /**\n * Wait for a custom match function to find a matching message\n * @param matchFn Function that returns true when a message matches\n * @param timeout Optional timeout in milliseconds (defaults to 5000ms)\n * @returns Promise that resolves when a matching message is found\n */\n const awaitMatch: AwaitMatchFn<any> = async (\n matchFn: MatchFunction<any>,\n timeout: number = 3000,\n ): Promise<boolean> => {\n debug(\n `${config.id ? `[${config.id}] ` : ``}awaitMatch called with custom function`,\n )\n\n return new Promise((resolve, reject) => {\n const matchId = Math.random().toString(36)\n\n const cleanupMatch = () => {\n pendingMatches.setState((current) => {\n const newMatches = new Map(current)\n newMatches.delete(matchId)\n return newMatches\n })\n }\n\n const onTimeout = () => {\n cleanupMatch()\n reject(new TimeoutWaitingForMatchError(config.id))\n }\n\n const timeoutId = setTimeout(onTimeout, timeout)\n\n // We need access to the stream messages to check against the match function\n // This will be handled by the sync configuration\n const checkMatch = (message: Message<any>) => {\n if (matchFn(message)) {\n debug(\n `${config.id ? `[${config.id}] ` : ``}awaitMatch found matching message, waiting for up-to-date`,\n )\n // Mark as matched but don't resolve yet - wait for up-to-date\n pendingMatches.setState((current) => {\n const newMatches = new Map(current)\n const existing = newMatches.get(matchId)\n if (existing) {\n newMatches.set(matchId, { ...existing, matched: true })\n }\n return newMatches\n })\n return true\n }\n return false\n }\n\n // Check against current batch messages first to handle race conditions\n for (const message of currentBatchMessages.state) {\n if (matchFn(message)) {\n // If batch is committed (up-to-date already received), resolve immediately\n // just like awaitTxId does when it finds a txid in seenTxids\n if (batchCommitted.state) {\n debug(\n `${config.id ? `[${config.id}] ` : ``}awaitMatch found immediate match in committed batch, resolving immediately`,\n )\n clearTimeout(timeoutId)\n resolve(true)\n return\n }\n\n // If batch is not yet committed, register match and wait for up-to-date\n debug(\n `${config.id ? `[${config.id}] ` : ``}awaitMatch found immediate match in current batch, waiting for up-to-date`,\n )\n pendingMatches.setState((current) => {\n const newMatches = new Map(current)\n newMatches.set(matchId, {\n matchFn: checkMatch,\n resolve,\n reject,\n timeoutId,\n matched: true, // Already matched, will resolve on up-to-date\n })\n return newMatches\n })\n return\n }\n }\n\n // Store the match function for the sync process to use\n // We'll add this to a pending matches store\n pendingMatches.setState((current) => {\n const newMatches = new Map(current)\n newMatches.set(matchId, {\n matchFn: checkMatch,\n resolve,\n reject,\n timeoutId,\n matched: false,\n })\n return newMatches\n })\n })\n }\n\n /**\n * Process matching strategy and wait for synchronization\n */\n const processMatchingStrategy = async (\n result: MatchingStrategy,\n ): Promise<void> => {\n // Only wait if result contains txid\n if (result && `txid` in result) {\n const timeout = result.timeout\n // Handle both single txid and array of txids\n if (Array.isArray(result.txid)) {\n await Promise.all(result.txid.map((txid) => awaitTxId(txid, timeout)))\n } else {\n await awaitTxId(result.txid, timeout)\n }\n }\n // If result is void/undefined, don't wait - mutation completes immediately\n }\n\n // Create wrapper handlers for direct persistence operations that handle different matching strategies\n const wrappedOnInsert = config.onInsert\n ? async (\n params: InsertMutationFnParams<\n any,\n string | number,\n ElectricCollectionUtils<T>\n >,\n ) => {\n const handlerResult = await config.onInsert!(params)\n await processMatchingStrategy(handlerResult)\n return handlerResult\n }\n : undefined\n\n const wrappedOnUpdate = config.onUpdate\n ? async (\n params: UpdateMutationFnParams<\n any,\n string | number,\n ElectricCollectionUtils<T>\n >,\n ) => {\n const handlerResult = await config.onUpdate!(params)\n await processMatchingStrategy(handlerResult)\n return handlerResult\n }\n : undefined\n\n const wrappedOnDelete = config.onDelete\n ? async (\n params: DeleteMutationFnParams<\n any,\n string | number,\n ElectricCollectionUtils<T>\n >,\n ) => {\n const handlerResult = await config.onDelete!(params)\n await processMatchingStrategy(handlerResult)\n return handlerResult\n }\n : undefined\n\n // Extract standard Collection config properties\n const {\n shapeOptions: _shapeOptions,\n onInsert: _onInsert,\n onUpdate: _onUpdate,\n onDelete: _onDelete,\n ...restConfig\n } = config\n\n const options = {\n ...restConfig,\n syncMode: finalSyncMode,\n sync: {\n ...sync,\n exportSyncMeta: (): ElectricSyncMeta => ({\n version: 1,\n ...(hydratedResumeState.state\n ? { resume: hydratedResumeState.state }\n : {}),\n seenTxids: Array.from(seenTxids.state).sort((a, b) => a - b),\n }),\n importSyncMeta: (meta: unknown): void => {\n const parsed = parseElectricSyncMeta(meta)\n if (!parsed) {\n return\n }\n\n hydratedResumeState.setState(() => parsed.resume)\n seenTxids.setState(() => new Set(parsed.seenTxids))\n },\n mergeSyncMeta: mergeElectricSyncMeta,\n },\n onInsert: wrappedOnInsert,\n onUpdate: wrappedOnUpdate,\n onDelete: wrappedOnDelete,\n utils: {\n awaitTxId,\n awaitMatch,\n },\n }\n\n return withCollectionConfigFactory(options, () =>\n (\n electricCollectionOptions as (\n nextConfig: ElectricCollectionConfig<T, any>,\n ) => typeof options\n )(config),\n )\n}\n\n/**\n * Internal function to create ElectricSQL sync configuration\n */\nfunction createElectricSync<T extends Row<unknown>>(\n shapeOptions: ShapeStreamOptions<GetExtensions<T>>,\n options: {\n syncMode: ElectricSyncMode\n seenTxids: Store<Set<Txid>>\n seenSnapshots: Store<Array<PostgresSnapshot>>\n hydratedResumeState: Store<ElectricResumeState | undefined>\n pendingMatches: Store<\n Map<\n string,\n {\n matchFn: (message: Message<T>) => boolean\n resolve: (value: boolean) => void\n reject: (error: Error) => void\n timeoutId: ReturnType<typeof setTimeout>\n matched: boolean\n }\n >\n >\n currentBatchMessages: Store<Array<Message<T>>>\n batchCommitted: Store<boolean>\n removePendingMatches: (matchIds: Array<string>) => void\n resolveMatchedPendingMatches: () => void\n collectionId?: string\n testHooks?: ElectricTestHooks\n },\n): SyncConfig<T> {\n const {\n seenTxids,\n seenSnapshots,\n hydratedResumeState,\n syncMode,\n pendingMatches,\n currentBatchMessages,\n batchCommitted,\n removePendingMatches,\n resolveMatchedPendingMatches,\n collectionId,\n testHooks,\n } = options\n const MAX_BATCH_MESSAGES = 1000 // Safety limit for message buffer\n\n // Store for the relation schema information\n const relationSchema = new Store<string | undefined>(undefined)\n\n const tagCache = new Map<MoveTag, ParsedMoveTag>()\n\n // Parses a tag string into a ParsedMoveTag.\n // It memoizes the result parsed tag such that future calls\n // for the same tag string return the same ParsedMoveTag array.\n const parseTag = (tag: MoveTag): ParsedMoveTag => {\n const cachedTag = tagCache.get(tag)\n if (cachedTag) {\n return cachedTag\n }\n\n const parsedTag = parseTagString(tag)\n tagCache.set(tag, parsedTag)\n return parsedTag\n }\n\n // Tag tracking state\n const rowTagSets = new Map<RowId, Set<MoveTag>>()\n const tagIndex: TagIndex = []\n let tagLength: number | undefined = undefined\n\n // DNF state: active_conditions are per-row, disjunct_positions are global\n // (fixed by the shape's WHERE clause, derived once from the first tagged message).\n const rowActiveConditions = new Map<RowId, ActiveConditions>()\n let disjunctPositions: DisjunctPositions | undefined = undefined\n\n /**\n * Initialize the tag index with the correct length\n */\n const initializeTagIndex = (length: number): void => {\n if (tagIndex.length < length) {\n // Extend the index array to the required length\n for (let i = tagIndex.length; i < length; i++) {\n tagIndex[i] = new Map()\n }\n }\n }\n\n /**\n * Add tags to a row and update the tag index\n */\n const addTagsToRow = (\n tags: Array<MoveTag>,\n rowId: RowId,\n rowTagSet: Set<MoveTag>,\n ): void => {\n for (const tag of tags) {\n const parsedTag = parseTag(tag)\n\n // Infer tag length from first tag\n if (tagLength === undefined) {\n tagLength = getTagLength(parsedTag)\n initializeTagIndex(tagLength)\n }\n\n // Validate tag length matches\n const currentTagLength = getTagLength(parsedTag)\n if (currentTagLength !== tagLength) {\n debug(\n `${collectionId ? `[${collectionId}] ` : ``}Tag length mismatch: expected ${tagLength}, got ${currentTagLength}`,\n )\n continue\n }\n\n rowTagSet.add(tag)\n addTagToIndex(parsedTag, rowId, tagIndex, tagLength)\n }\n }\n\n /**\n * Remove tags from a row and update the tag index\n */\n const removeTagsFromRow = (\n removedTags: Array<MoveTag>,\n rowId: RowId,\n rowTagSet: Set<MoveTag>,\n ): void => {\n if (tagLength === undefined) {\n return\n }\n\n for (const tag of removedTags) {\n const parsedTag = parseTag(tag)\n rowTagSet.delete(tag)\n removeTagFromIndex(parsedTag, rowId, tagIndex, tagLength)\n // We aggresively evict the tag from the cache\n // if this tag is shared with another row\n // and is not removed from that other row\n // then next time we encounter the tag it will be parsed again\n tagCache.delete(tag)\n }\n }\n\n /**\n * Process tags for a change message (add and remove tags)\n */\n const processTagsForChangeMessage = (\n tags: Array<MoveTag> | undefined,\n removedTags: Array<MoveTag> | undefined,\n rowId: RowId,\n activeConditions?: ActiveConditions,\n ): Set<MoveTag> => {\n // Initialize tag set for this row if it doesn't exist (needed for checking deletion)\n if (!rowTagSets.has(rowId)) {\n rowTagSets.set(rowId, new Set())\n }\n const rowTagSet = rowTagSets.get(rowId)!\n\n // Add new tags\n if (tags) {\n addTagsToRow(tags, rowId, rowTagSet)\n\n // Derive disjunct positions once — they are fixed by the shape's WHERE clause.\n if (disjunctPositions === undefined) {\n const parsedTags = tags.map(parseTag)\n disjunctPositions = deriveDisjunctPositions(parsedTags)\n }\n }\n\n // Remove tags\n if (removedTags) {\n removeTagsFromRow(removedTags, rowId, rowTagSet)\n }\n\n // Store active conditions if provided (overwrite on re-send)\n if (activeConditions && activeConditions.length > 0) {\n rowActiveConditions.set(rowId, [...activeConditions])\n }\n\n return rowTagSet\n }\n\n /**\n * Clear all tag tracking state (used when truncating)\n */\n const clearTagTrackingState = (): void => {\n rowTagSets.clear()\n tagIndex.length = 0\n tagLength = undefined\n rowActiveConditions.clear()\n disjunctPositions = undefined\n }\n\n /**\n * Remove all tags for a row from both the tag set and the index\n * Used when a row is deleted\n */\n const clearTagsForRow = (rowId: RowId): void => {\n if (tagLength === undefined) {\n return\n }\n\n const rowTagSet = rowTagSets.get(rowId)\n if (!rowTagSet) {\n return\n }\n\n // Remove each tag from the index\n for (const tag of rowTagSet) {\n const parsedTag = parseTag(tag)\n const currentTagLength = getTagLength(parsedTag)\n if (currentTagLength === tagLength) {\n removeTagFromIndex(parsedTag, rowId, tagIndex, tagLength)\n }\n tagCache.delete(tag)\n }\n\n // Remove the row from the tag sets map\n rowTagSets.delete(rowId)\n rowActiveConditions.delete(rowId)\n }\n\n /**\n * Remove matching tags from a row based on a pattern\n * Returns true if the row should be deleted (no longer visible)\n */\n const removeMatchingTagsFromRow = (\n rowId: RowId,\n pattern: MovePattern,\n ): boolean => {\n const rowTagSet = rowTagSets.get(rowId)\n if (!rowTagSet) {\n return false\n }\n\n // DNF mode: check visibility using active conditions.\n // Tag index entries are preserved so that move-in can re-activate positions.\n const activeConditions = rowActiveConditions.get(rowId)\n if (activeConditions && disjunctPositions) {\n // Set the condition at this pattern's position to false\n activeConditions[pattern.pos] = false\n\n if (!rowVisible(activeConditions, disjunctPositions)) {\n // Row is no longer visible — clean up all state including tag index\n for (const tag of rowTagSet) {\n const parsedTag = parseTag(tag)\n removeTagFromIndex(parsedTag, rowId, tagIndex, tagLength!)\n tagCache.delete(tag)\n }\n rowTagSets.delete(rowId)\n rowActiveConditions.delete(rowId)\n return true\n }\n return false\n }\n\n // Simple shape (no subquery dependencies — server sends no active_conditions):\n // Remove matching tags and delete if tag set is empty\n for (const tag of rowTagSet) {\n const parsedTag = parseTag(tag)\n if (tagMatchesPattern(parsedTag, pattern)) {\n rowTagSet.delete(tag)\n removeTagFromIndex(parsedTag, rowId, tagIndex, tagLength!)\n }\n }\n\n if (rowTagSet.size === 0) {\n rowTagSets.delete(rowId)\n return true\n }\n\n return false\n }\n\n /**\n * Process move-out event: remove matching tags from rows and delete rows with empty tag sets\n */\n const processMoveOutEvent = (\n patterns: Array<MovePattern>,\n begin: () => void,\n write: (message: ChangeMessageOrDeleteKeyMessage<T>) => void,\n transactionStarted: boolean,\n ): boolean => {\n if (tagLength === undefined) {\n debug(\n `${collectionId ? `[${collectionId}] ` : ``}Received move-out message but no tag length set yet, ignoring`,\n )\n return transactionStarted\n }\n\n let txStarted = transactionStarted\n\n // Process all patterns and collect rows to delete\n for (const pattern of patterns) {\n // Find all rows that match this pattern\n const affectedRowIds = findRowsMatchingPattern(pattern, tagIndex)\n\n for (const rowId of affectedRowIds) {\n if (removeMatchingTagsFromRow(rowId, pattern)) {\n // Delete rows with empty tag sets\n if (!txStarted) {\n begin()\n txStarted = true\n }\n\n write({\n type: `delete`,\n key: rowId,\n })\n }\n }\n }\n\n return txStarted\n }\n\n /**\n * Process move-in event: re-activate conditions for rows matching the patterns.\n * This is a silent operation — no messages are emitted to the collection.\n */\n const processMoveInEvent = (patterns: Array<MovePattern>): void => {\n if (tagLength === undefined) {\n debug(\n `${collectionId ? `[${collectionId}] ` : ``}Received move-in message but no tag length set yet, ignoring`,\n )\n return\n }\n\n for (const pattern of patterns) {\n const affectedRowIds = findRowsMatchingPattern(pattern, tagIndex)\n\n for (const rowId of affectedRowIds) {\n const activeConditions = rowActiveConditions.get(rowId)\n if (activeConditions) {\n activeConditions[pattern.pos] = true\n }\n }\n }\n }\n\n /**\n * Get the sync metadata for insert operations\n * @returns Record containing relation information\n */\n const getSyncMetadata = (): Record<string, unknown> => {\n // Use the stored schema if available, otherwise default to 'public'\n const schema = relationSchema.state || `public`\n\n return {\n relation: shapeOptions.params?.table\n ? [schema, shapeOptions.params.table]\n : undefined,\n }\n }\n\n let unsubscribeStream: () => void\n\n return {\n sync: (params: Parameters<SyncConfig<T>[`sync`]>[0]) => {\n const {\n begin,\n write,\n commit: commitSyncTransaction,\n markReady,\n markError,\n truncate,\n collection,\n metadata,\n } = params\n let commitSequence = 0\n const pendingAppliedReceipts = new Map<number, Promise<void>>()\n const commit = (signal?: AbortSignal): SyncAppliedReceipt => {\n const sequence = ++commitSequence\n const applied = commitSyncTransaction(signal)\n if (applied === true) {\n return true\n }\n pendingAppliedReceipts.set(sequence, applied)\n const removeReceipt = () => pendingAppliedReceipts.delete(sequence)\n void applied.then(removeReceipt, removeReceipt)\n return applied\n }\n const waitForCommitsAfter = async (cursor: number): Promise<void> => {\n await Promise.all(\n Array.from(pendingAppliedReceipts, ([sequence, applied]) =>\n sequence > cursor ? applied : undefined,\n ),\n )\n }\n const readPersistedResumeState = (): ElectricResumeState | undefined => {\n const persistedResumeState = metadata?.collection.get(`electric:resume`)\n return parseElectricResumeState(persistedResumeState)\n }\n\n const persistedResumeState = getNewestElectricResumeState(\n readPersistedResumeState(),\n hydratedResumeState.state,\n )\n const shapeIdentity = getStableShapeIdentity({\n url: shapeOptions.url,\n params: shapeOptions.params as Record<string, unknown> | undefined,\n })\n const hasIncompatiblePersistedResume =\n persistedResumeState?.kind === `resume` &&\n persistedResumeState.shapeId !== shapeIdentity\n const canUsePersistedResume =\n shapeOptions.offset === undefined &&\n shapeOptions.handle === undefined &&\n persistedResumeState?.kind === `resume` &&\n !hasIncompatiblePersistedResume\n\n // Wrap markReady to wait for test hook in progressive mode\n let progressiveReadyGate: Promise<void> | null = null\n let streamErrorVersion = 0\n const wrappedMarkReady = (\n isBuffering: boolean,\n expectedErrorVersion = streamErrorVersion,\n ) => {\n if (streamErrorVersion !== expectedErrorVersion) return\n\n // Only create gate if we're in buffering phase (first up-to-date)\n if (\n isBuffering &&\n syncMode === `progressive` &&\n testHooks?.beforeMarkingReady\n ) {\n // Create a new gate promise for this sync cycle\n progressiveReadyGate = testHooks.beforeMarkingReady()\n progressiveReadyGate.then(() => {\n if (streamErrorVersion === expectedErrorVersion) {\n markReady()\n }\n })\n } else {\n // No hook, not buffering, or already past first up-to-date\n markReady()\n }\n }\n\n // Abort controller for the stream - wraps the signal if provided\n const abortController = new AbortController()\n const forwardExternalAbort = () => abortController.abort()\n\n if (shapeOptions.signal) {\n shapeOptions.signal.addEventListener(`abort`, forwardExternalAbort, {\n once: true,\n })\n if (shapeOptions.signal.aborted) {\n abortController.abort()\n }\n }\n\n // Cleanup pending matches on abort\n abortController.signal.addEventListener(`abort`, () => {\n pendingMatches.setState((current) => {\n current.forEach((match) => {\n clearTimeout(match.timeoutId)\n match.reject(new StreamAbortedError())\n })\n return new Map() // Clear all pending matches\n })\n })\n\n const stream = new ShapeStream({\n ...shapeOptions,\n // In on-demand mode, we only want to sync changes, so we set the log to `changes_only`\n log: syncMode === `on-demand` ? `changes_only` : undefined,\n // In on-demand mode, we only need the changes from the point of time the collection was created\n // so we default to `now` when there is no saved offset.\n offset:\n shapeOptions.offset ??\n (canUsePersistedResume\n ? (persistedResumeState.offset as Offset)\n : syncMode === `on-demand`\n ? `now`\n : undefined),\n handle:\n shapeOptions.handle ??\n (canUsePersistedResume ? persistedResumeState.handle : undefined),\n signal: abortController.signal,\n onError: (errorParams) => {\n streamErrorVersion++\n // Note that Electric sends a 409 error on a `must-refetch` message, but the\n // ShapeStream handled this and it will not reach this handler, therefor\n // this handler will not run for a `must-refetch`.\n const initialSyncFailed = collection.status === `loading`\n if (initialSyncFailed) {\n markError(errorParams)\n }\n\n if (shapeOptions.onError) {\n return shapeOptions.onError(errorParams)\n } else {\n console.error(\n `An error occurred while syncing collection: ${collection.id}, \\n` +\n (initialSyncFailed\n ? `the initial sync has been marked as failed. \\n`\n : `the last ready snapshot has been preserved. \\n`) +\n `You can provide an 'onError' handler on the shapeOptions to handle this error, and this message will not be logged.`,\n errorParams,\n )\n }\n\n return\n },\n })\n let transactionStarted = false\n const newTxids = new Set<Txid>()\n const newSnapshots: Array<PostgresSnapshot> = []\n // Track if we've completed initial sync in progressive mode. A persisted\n // resume starts from an already-committed stream offset, so the next\n // up-to-date message must not run the initial atomic swap again.\n let hasReceivedUpToDate =\n syncMode === `progressive` && canUsePersistedResume\n\n // Progressive mode state\n // Helper to determine if we're buffering the initial sync\n const isBufferingInitialSync = () =>\n syncMode === `progressive` && !hasReceivedUpToDate\n const bufferedMessages: Array<Message<T>> = [] // Buffer change messages during initial sync\n\n // Track keys that have been synced to handle overlapping subset queries.\n // When multiple subset queries return the same row, the server sends `insert`\n // for each response. We convert subsequent inserts to updates to avoid\n // duplicate key errors when the row's data has changed between requests.\n const syncedKeys = new Set<string | number>()\n\n const stageResumeMetadata = () => {\n const shapeHandle = stream.shapeHandle\n const lastOffset = stream.lastOffset\n if (!shapeHandle || lastOffset === `-1`) {\n return\n }\n\n const resumeState: ElectricResumeState = {\n kind: `resume`,\n offset: lastOffset,\n handle: shapeHandle,\n shapeId: shapeIdentity,\n updatedAt: Date.now(),\n }\n hydratedResumeState.setState(() => resumeState)\n metadata?.collection.set(`electric:resume`, resumeState)\n }\n\n const commitResetResumeMetadataImmediately = () => {\n const resetState: ElectricResumeState = {\n kind: `reset`,\n updatedAt: Date.now(),\n }\n hydratedResumeState.setState(() => resetState)\n\n if (metadata) {\n begin({ immediate: true })\n metadata.collection.set(`electric:resume`, resetState)\n commit()\n }\n }\n\n if (hasIncompatiblePersistedResume) {\n commitResetResumeMetadataImmediately()\n }\n\n /**\n * Process a change message: handle tags and write the mutation\n */\n const processChangeMessage = (changeMessage: Message<T>) => {\n if (!isChangeMessage(changeMessage)) {\n return\n }\n\n // Process tags if present\n const tags = changeMessage.headers.tags\n const removedTags = changeMessage.headers.removed_tags\n const hasTags = tags || removedTags\n\n // Extract active_conditions from headers (DNF support)\n const activeConditions = changeMessage.headers.active_conditions as\n | ActiveConditions\n | undefined\n\n const rowId = collection.getKeyFromItem(changeMessage.value)\n const operation = changeMessage.headers.operation\n\n // Track synced keys and handle overlapping subset queries.\n // When multiple subset queries return the same row, the server sends\n // `insert` for each response. We convert subsequent inserts to updates\n // to avoid duplicate key errors when the row's data has changed.\n const isDelete = operation === `delete`\n const isDuplicateInsert =\n operation === `insert` && syncedKeys.has(rowId)\n\n if (isDelete) {\n syncedKeys.delete(rowId)\n } else {\n syncedKeys.add(rowId)\n }\n\n if (isDelete) {\n clearTagsForRow(rowId)\n } else if (hasTags) {\n processTagsForChangeMessage(\n tags,\n removedTags,\n rowId,\n activeConditions,\n )\n }\n\n write({\n type: isDuplicateInsert ? `update` : operation,\n value: changeMessage.value,\n // Include the primary key and relation info in the metadata\n metadata: {\n ...changeMessage.headers,\n },\n })\n }\n\n // Create deduplicated loadSubset wrapper for non-eager modes\n // This prevents redundant snapshot requests when multiple concurrent\n // live queries request overlapping or subset predicates\n const loadSubsetDedupe = createLoadSubsetDedupe({\n stream,\n syncMode,\n isBufferingInitialSync,\n begin,\n write,\n commit,\n getCommitCursor: () => commitSequence,\n waitForCommitsAfter,\n collectionId,\n // Pass the columnMapper's encode function to transform column names\n // (e.g., camelCase to snake_case) when compiling SQL for subset queries\n encodeColumnName: shapeOptions.columnMapper?.encode,\n // Pass abort signal so requestSnapshot errors can be ignored during cleanup\n signal: abortController.signal,\n })\n\n unsubscribeStream = stream.subscribe((messages: Array<Message<T>>) => {\n // Track commit point type - up-to-date takes precedence as it also triggers progressive mode atomic swap\n let commitPoint: `up-to-date` | `subset-end` | null = null\n\n // Don't clear the buffer between batches - this preserves messages for awaitMatch\n // to find even if multiple batches arrive before awaitMatch is called.\n // The buffer is naturally limited by MAX_BATCH_MESSAGES (oldest messages are dropped).\n // Reset batchCommitted since we're starting a new batch\n batchCommitted.setState(() => false)\n\n for (const message of messages) {\n // Add message to current batch buffer (for race condition handling)\n if (\n isChangeMessage(message) ||\n isMoveOutMessage(message) ||\n isMoveInMessage(message)\n ) {\n currentBatchMessages.setState((currentBuffer) => {\n const newBuffer = [...currentBuffer, message]\n // Limit buffer size for safety\n if (newBuffer.length > MAX_BATCH_MESSAGES) {\n newBuffer.splice(0, newBuffer.length - MAX_BATCH_MESSAGES)\n }\n return newBuffer\n })\n }\n\n // Check for txids in the message and add them to our store\n // Skip during buffered initial sync in progressive mode (txids will be extracted during atomic swap)\n // EXCEPTION: If a transaction is already started (e.g., from must-refetch), track txids\n // to avoid losing them when messages are written to the existing transaction.\n if (\n hasTxids(message) &&\n (!isBufferingInitialSync() || transactionStarted)\n ) {\n message.headers.txids?.forEach((txid) => newTxids.add(txid))\n }\n\n // Check pending matches against this message\n // Note: matchFn will mark matches internally, we don't resolve here\n const matchesToRemove: Array<string> = []\n pendingMatches.state.forEach((match, matchId) => {\n if (!match.matched) {\n try {\n match.matchFn(message)\n } catch (err) {\n // If matchFn throws, clean up and reject the promise\n clearTimeout(match.timeoutId)\n match.reject(\n err instanceof Error ? err : new Error(String(err)),\n )\n matchesToRemove.push(matchId)\n debug(`matchFn error: %o`, err)\n }\n }\n })\n\n // Remove matches that errored\n removePendingMatches(matchesToRemove)\n\n if (isChangeMessage(message)) {\n // Check if the message contains schema information\n const schema = message.headers.schema\n if (schema && typeof schema === `string`) {\n // Store the schema for future use if it's a valid string\n relationSchema.setState(() => schema)\n }\n\n // In buffered initial sync of progressive mode, buffer messages instead of writing\n // EXCEPTION: If a transaction is already started (e.g., from must-refetch), write\n // directly to it instead of buffering. This prevents orphan transactions.\n if (isBufferingInitialSync() && !transactionStarted) {\n bufferedMessages.push(message)\n } else {\n // Normal processing: write changes immediately\n if (!transactionStarted) {\n begin()\n transactionStarted = true\n }\n\n processChangeMessage(message)\n }\n } else if (isSnapshotEndMessage(message)) {\n // Track postgres snapshot metadata for resolving awaiting mutations\n // Skip during buffered initial sync (will be extracted during atomic swap)\n // EXCEPTION: If a transaction is already started (e.g., from must-refetch), track snapshots\n // to avoid losing them when messages are written to the existing transaction.\n if (!isBufferingInitialSync() || transactionStarted) {\n newSnapshots.push(parseSnapshotMessage(message))\n }\n } else if (isUpToDateMessage(message)) {\n // up-to-date takes precedence - also triggers progressive mode atomic swap\n commitPoint = `up-to-date`\n } else if (isSubsetEndMessage(message)) {\n // subset-end triggers commit but not progressive mode atomic swap\n if (commitPoint !== `up-to-date`) {\n commitPoint = `subset-end`\n }\n } else if (isMoveOutMessage(message)) {\n // Handle move-out event: buffer if buffering, otherwise process immediately\n // EXCEPTION: If a transaction is already started (e.g., from must-refetch), process\n // immediately to avoid orphan transactions.\n if (isBufferingInitialSync() && !transactionStarted) {\n bufferedMessages.push(message)\n } else {\n // Normal processing: process move-out immediately\n transactionStarted = processMoveOutEvent(\n message.headers.patterns,\n begin,\n write,\n transactionStarted,\n )\n }\n } else if (isMoveInMessage(message)) {\n // Handle move-in event: re-activate conditions for matching rows.\n // Buffer if buffering, otherwise process immediately.\n if (isBufferingInitialSync() && !transactionStarted) {\n bufferedMessages.push(message)\n } else {\n processMoveInEvent(message.headers.patterns)\n }\n } else if (isMustRefetchMessage(message)) {\n debug(\n `${collectionId ? `[${collectionId}] ` : ``}Received must-refetch message, starting transaction with truncate`,\n )\n\n commitResetResumeMetadataImmediately()\n\n // Start a transaction and truncate the collection\n if (!transactionStarted) {\n begin()\n transactionStarted = true\n }\n\n truncate()\n\n // Clear tag tracking state\n clearTagTrackingState()\n\n // Clear synced keys tracking since we're starting fresh\n syncedKeys.clear()\n\n // Reset the loadSubset deduplication state since we're starting fresh\n // This ensures that previously loaded predicates don't prevent refetching after truncate\n loadSubsetDedupe?.reset()\n\n // Reset flags so we continue accumulating changes until next up-to-date\n commitPoint = null\n hasReceivedUpToDate = false // Reset for progressive mode (isBufferingInitialSync will reflect this)\n bufferedMessages.length = 0 // Clear buffered messages\n }\n }\n\n if (commitPoint !== null) {\n let applied: SyncAppliedReceipt = true\n const wasBufferingInitialSync = isBufferingInitialSync()\n // PROGRESSIVE MODE: Atomic swap on first up-to-date (not subset-end)\n // EXCEPTION: Skip atomic swap if a transaction is already started (e.g., from must-refetch).\n // In that case, do a normal commit to properly close the existing transaction.\n if (\n isBufferingInitialSync() &&\n commitPoint === `up-to-date` &&\n !transactionStarted\n ) {\n debug(\n `${collectionId ? `[${collectionId}] ` : ``}Progressive mode: Performing atomic swap with ${bufferedMessages.length} buffered messages`,\n )\n\n // Start atomic swap transaction\n begin()\n\n // Truncate to clear all snapshot data\n truncate()\n\n // Clear tag tracking state for atomic swap\n clearTagTrackingState()\n\n // Clear synced keys tracking for atomic swap\n syncedKeys.clear()\n\n // Apply all buffered change messages and extract txids/snapshots\n for (const bufferedMsg of bufferedMessages) {\n if (isChangeMessage(bufferedMsg)) {\n processChangeMessage(bufferedMsg)\n\n // Extract txids from buffered messages (will be committed to store after transaction)\n if (hasTxids(bufferedMsg)) {\n bufferedMsg.headers.txids?.forEach((txid) =>\n newTxids.add(txid),\n )\n }\n } else if (isSnapshotEndMessage(bufferedMsg)) {\n // Extract snapshots from buffered messages (will be committed to store after transaction)\n newSnapshots.push(parseSnapshotMessage(bufferedMsg))\n } else if (isMoveOutMessage(bufferedMsg)) {\n // Process buffered move-out messages during atomic swap\n processMoveOutEvent(\n bufferedMsg.headers.patterns,\n begin,\n write,\n transactionStarted,\n )\n } else if (isMoveInMessage(bufferedMsg)) {\n // Process buffered move-in messages during atomic swap\n processMoveInEvent(bufferedMsg.headers.patterns)\n }\n }\n\n // Commit the atomic swap\n stageResumeMetadata()\n applied = commit()\n\n // Exit buffering phase by marking that we've received up-to-date\n // isBufferingInitialSync() will now return false\n bufferedMessages.length = 0\n\n debug(\n `${collectionId ? `[${collectionId}] ` : ``}Progressive mode: Atomic swap complete, now in normal sync mode`,\n )\n } else {\n // Normal mode or on-demand: commit transaction if one was started\n // Both up-to-date and subset-end trigger a commit\n if (transactionStarted) {\n stageResumeMetadata()\n applied = commit()\n transactionStarted = false\n } else if (commitPoint === `up-to-date` && metadata) {\n begin()\n stageResumeMetadata()\n applied = commit()\n }\n }\n const readyErrorVersion = streamErrorVersion\n if (applied === true) {\n wrappedMarkReady(wasBufferingInitialSync, readyErrorVersion)\n } else {\n void applied.then(\n () =>\n wrappedMarkReady(wasBufferingInitialSync, readyErrorVersion),\n () => undefined,\n )\n }\n\n // Track that we've received the first up-to-date for progressive mode\n if (commitPoint === `up-to-date`) {\n hasReceivedUpToDate = true\n }\n\n // Always commit txids when we receive up-to-date, regardless of transaction state\n seenTxids.setState((currentTxids) => {\n const clonedSeen = new Set<Txid>(currentTxids)\n if (newTxids.size > 0) {\n debug(\n `${collectionId ? `[${collectionId}] ` : ``}new txids synced from pg %O`,\n Array.from(newTxids),\n )\n }\n newTxids.forEach((txid) => clonedSeen.add(txid))\n newTxids.clear()\n return clonedSeen\n })\n\n // Always commit snapshots when we receive up-to-date, regardless of transaction state\n seenSnapshots.setState((currentSnapshots) => {\n const seen = [...currentSnapshots, ...newSnapshots]\n newSnapshots.forEach((snapshot) =>\n debug(\n `${collectionId ? `[${collectionId}] ` : ``}new snapshot synced from pg %o`,\n snapshot,\n ),\n )\n newSnapshots.length = 0\n return seen\n })\n\n // Resolve all matched pending matches on up-to-date or subset-end\n // Set batchCommitted BEFORE resolving to avoid timing window where late awaitMatch\n // calls could register as \"matched\" after resolver pass already ran\n batchCommitted.setState(() => true)\n\n resolveMatchedPendingMatches()\n }\n })\n\n // Return the deduplicated loadSubset if available (on-demand or progressive mode)\n // The loadSubset method is auto-bound, so it can be safely returned directly\n return {\n loadSubset: loadSubsetDedupe?.loadSubset,\n cleanup: () => {\n shapeOptions.signal?.removeEventListener(\n `abort`,\n forwardExternalAbort,\n )\n // Unsubscribe from the stream\n unsubscribeStream()\n // Abort the abort controller to stop the stream\n abortController.abort()\n // Reset deduplication tracking so collection can load fresh data if restarted\n loadSubsetDedupe?.reset()\n hydratedResumeState.setState(() => undefined)\n },\n }\n },\n // Expose the getSyncMetadata function\n getSyncMetadata,\n }\n}\n"],"names":["isControlMessage","LoadSubsetOperationAbortedError","compileSQL","and","DeduplicatedLoadSubset","Store","ExpectedNumberInAwaitTxIdError","isVisibleInSnapshot","TimeoutWaitingForTxIdError","TimeoutWaitingForMatchError","withCollectionConfigFactory","parseTagString","tagIndex","getTagLength","addTagToIndex","removeTagFromIndex","deriveDisjunctPositions","rowVisible","tagMatchesPattern","findRowsMatchingPattern","persistedResumeState","StreamAbortedError","ShapeStream","isChangeMessage","isMoveOutMessage","isMoveInMessage"],"mappings":"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"}
|
package/dist/esm/electric.js
CHANGED
|
@@ -2,7 +2,7 @@ import { ShapeStream, isChangeMessage, isVisibleInSnapshot, isControlMessage } f
|
|
|
2
2
|
import { isChangeMessage as isChangeMessage2, isControlMessage as isControlMessage2 } from "@electric-sql/client";
|
|
3
3
|
import { Store } from "@tanstack/store";
|
|
4
4
|
import DebugModule from "debug";
|
|
5
|
-
import { withCollectionConfigFactory, DeduplicatedLoadSubset, and } from "@tanstack/db";
|
|
5
|
+
import { withCollectionConfigFactory, DeduplicatedLoadSubset, and, LoadSubsetOperationAbortedError } from "@tanstack/db";
|
|
6
6
|
import { StreamAbortedError, ExpectedNumberInAwaitTxIdError, TimeoutWaitingForTxIdError, TimeoutWaitingForMatchError } from "./errors.js";
|
|
7
7
|
import { compileSQL } from "./sql-compiler.js";
|
|
8
8
|
import { isMoveOutMessage, isMoveInMessage, deriveDisjunctPositions, getTagLength, addTagToIndex, parseTag, removeTagFromIndex, findRowsMatchingPattern, rowVisible, tagMatchesPattern } from "./tag-index.js";
|
|
@@ -148,6 +148,7 @@ function createLoadSubsetDedupe({
|
|
|
148
148
|
}
|
|
149
149
|
const compileOptions = encodeColumnName ? { encodeColumnName } : void 0;
|
|
150
150
|
const logPrefix = collectionId ? `[${collectionId}] ` : ``;
|
|
151
|
+
const abortReason = (abortedSignal) => abortedSignal.reason ?? new LoadSubsetOperationAbortedError();
|
|
151
152
|
function handleSnapshotError(error, operation) {
|
|
152
153
|
if (signal.aborted) {
|
|
153
154
|
debug(`${logPrefix}Ignoring ${operation} error during cleanup: %o`, error);
|
|
@@ -158,7 +159,11 @@ function createLoadSubsetDedupe({
|
|
|
158
159
|
}
|
|
159
160
|
const loadSubset = async (opts) => {
|
|
160
161
|
const commitCursor = getCommitCursor();
|
|
161
|
-
|
|
162
|
+
const throwIfAborted = () => {
|
|
163
|
+
if (signal.aborted) throw abortReason(signal);
|
|
164
|
+
if (opts.signal?.aborted) throw abortReason(opts.signal);
|
|
165
|
+
};
|
|
166
|
+
throwIfAborted();
|
|
162
167
|
if (isBufferingInitialSync()) {
|
|
163
168
|
const snapshotParams = compileSQL(opts, compileOptions);
|
|
164
169
|
try {
|
|
@@ -194,6 +199,18 @@ function createLoadSubsetDedupe({
|
|
|
194
199
|
const { cursor, where, orderBy, limit } = opts;
|
|
195
200
|
if (stream.isUpToDate) {
|
|
196
201
|
let timeoutId;
|
|
202
|
+
const abortSignals = [signal, opts.signal].filter(
|
|
203
|
+
(candidate) => candidate !== void 0
|
|
204
|
+
);
|
|
205
|
+
let rejectAbort = () => {
|
|
206
|
+
};
|
|
207
|
+
const aborted = new Promise((_resolve, reject) => {
|
|
208
|
+
rejectAbort = reject;
|
|
209
|
+
});
|
|
210
|
+
const abort = (event) => rejectAbort(abortReason(event.currentTarget));
|
|
211
|
+
for (const abortSignal of abortSignals) {
|
|
212
|
+
abortSignal.addEventListener(`abort`, abort, { once: true });
|
|
213
|
+
}
|
|
197
214
|
try {
|
|
198
215
|
await Promise.race([
|
|
199
216
|
stream.forceDisconnectAndRefresh(),
|
|
@@ -202,9 +219,11 @@ function createLoadSubsetDedupe({
|
|
|
202
219
|
resolve,
|
|
203
220
|
FORCE_DISCONNECT_AND_REFRESH_TIMEOUT_MS
|
|
204
221
|
);
|
|
205
|
-
})
|
|
222
|
+
}),
|
|
223
|
+
aborted
|
|
206
224
|
]);
|
|
207
225
|
} catch (error) {
|
|
226
|
+
if (signal.aborted || opts.signal?.aborted) throw error;
|
|
208
227
|
if (handleSnapshotError(error, `forceDisconnectAndRefresh`)) {
|
|
209
228
|
return;
|
|
210
229
|
}
|
|
@@ -214,9 +233,12 @@ function createLoadSubsetDedupe({
|
|
|
214
233
|
);
|
|
215
234
|
} finally {
|
|
216
235
|
clearTimeout(timeoutId);
|
|
236
|
+
for (const abortSignal of abortSignals) {
|
|
237
|
+
abortSignal.removeEventListener(`abort`, abort);
|
|
238
|
+
}
|
|
217
239
|
}
|
|
218
240
|
}
|
|
219
|
-
|
|
241
|
+
throwIfAborted();
|
|
220
242
|
try {
|
|
221
243
|
if (cursor) {
|
|
222
244
|
const whereCurrentOpts = {
|
|
@@ -753,16 +775,11 @@ function createElectricSync(shapeOptions, options) {
|
|
|
753
775
|
}
|
|
754
776
|
};
|
|
755
777
|
const abortController = new AbortController();
|
|
778
|
+
const forwardExternalAbort = () => abortController.abort();
|
|
756
779
|
if (shapeOptions.signal) {
|
|
757
|
-
shapeOptions.signal.addEventListener(
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
abortController.abort();
|
|
761
|
-
},
|
|
762
|
-
{
|
|
763
|
-
once: true
|
|
764
|
-
}
|
|
765
|
-
);
|
|
780
|
+
shapeOptions.signal.addEventListener(`abort`, forwardExternalAbort, {
|
|
781
|
+
once: true
|
|
782
|
+
});
|
|
766
783
|
if (shapeOptions.signal.aborted) {
|
|
767
784
|
abortController.abort();
|
|
768
785
|
}
|
|
@@ -1077,6 +1094,10 @@ function createElectricSync(shapeOptions, options) {
|
|
|
1077
1094
|
return {
|
|
1078
1095
|
loadSubset: loadSubsetDedupe?.loadSubset,
|
|
1079
1096
|
cleanup: () => {
|
|
1097
|
+
shapeOptions.signal?.removeEventListener(
|
|
1098
|
+
`abort`,
|
|
1099
|
+
forwardExternalAbort
|
|
1100
|
+
);
|
|
1080
1101
|
unsubscribeStream();
|
|
1081
1102
|
abortController.abort();
|
|
1082
1103
|
loadSubsetDedupe?.reset();
|
package/dist/esm/electric.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"electric.js","sources":["../../src/electric.ts"],"sourcesContent":["import {\n ShapeStream,\n isChangeMessage,\n isControlMessage,\n isVisibleInSnapshot,\n} from '@electric-sql/client'\nimport { Store } from '@tanstack/store'\nimport DebugModule from 'debug'\nimport {\n DeduplicatedLoadSubset,\n and,\n withCollectionConfigFactory,\n} from '@tanstack/db'\nimport {\n ExpectedNumberInAwaitTxIdError,\n StreamAbortedError,\n TimeoutWaitingForMatchError,\n TimeoutWaitingForTxIdError,\n} from './errors'\nimport { compileSQL } from './sql-compiler'\nimport {\n addTagToIndex,\n deriveDisjunctPositions,\n findRowsMatchingPattern,\n getTagLength,\n isMoveInMessage,\n isMoveOutMessage,\n parseTag as parseTagString,\n removeTagFromIndex,\n rowVisible,\n tagMatchesPattern,\n} from './tag-index'\nimport type { ColumnEncoder } from './sql-compiler'\nimport type {\n ActiveConditions,\n DisjunctPositions,\n MovePattern,\n MoveTag,\n ParsedMoveTag,\n RowId,\n TagIndex,\n} from './tag-index'\nimport type {\n BaseCollectionConfig,\n ChangeMessageOrDeleteKeyMessage,\n CollectionConfig,\n DeleteMutationFnParams,\n InsertMutationFnParams,\n LoadSubsetOptions,\n SyncAppliedReceipt,\n SyncConfig,\n SyncMode,\n UpdateMutationFnParams,\n UtilsRecord,\n} from '@tanstack/db'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type {\n ControlMessage,\n GetExtensions,\n Message,\n Offset,\n PostgresSnapshot,\n Row,\n ShapeStreamOptions,\n} from '@electric-sql/client'\n\n// Re-export for user convenience in custom match functions\nexport { isChangeMessage, isControlMessage } from '@electric-sql/client'\n\nconst debug = DebugModule.debug(`ts/db:electric`)\n\nconst FORCE_DISCONNECT_AND_REFRESH_TIMEOUT_MS = 250\n\n/**\n * Symbol for internal test hooks (hidden from public API)\n */\nexport const ELECTRIC_TEST_HOOKS = Symbol(`electricTestHooks`)\n\n/**\n * Internal test hooks interface (for testing only)\n */\nexport interface ElectricTestHooks {\n /**\n * Called before marking collection ready after first up-to-date in progressive mode\n * Allows tests to pause and validate snapshot phase before atomic swap completes\n */\n beforeMarkingReady?: () => Promise<void>\n}\n\n/**\n * Type representing a transaction ID in ElectricSQL\n */\nexport type Txid = number\n\ntype ElectricResumeState =\n | {\n kind: `resume`\n offset: string\n handle: string\n shapeId: string\n updatedAt: number\n }\n | {\n kind: `reset`\n updatedAt: number\n }\n\ntype ElectricSyncMeta = {\n version: 1\n resume?: ElectricResumeState\n seenTxids: Array<Txid>\n}\n\nfunction parseElectricResumeState(\n value: unknown,\n): ElectricResumeState | undefined {\n if (!value || typeof value !== `object`) {\n return undefined\n }\n\n const record = value as Record<string, unknown>\n if (\n record.kind === `resume` &&\n typeof record.offset === `string` &&\n typeof record.handle === `string` &&\n typeof record.shapeId === `string` &&\n typeof record.updatedAt === `number` &&\n Number.isFinite(record.updatedAt)\n ) {\n return {\n kind: `resume`,\n offset: record.offset,\n handle: record.handle,\n shapeId: record.shapeId,\n updatedAt: record.updatedAt,\n }\n }\n\n if (\n record.kind === `reset` &&\n typeof record.updatedAt === `number` &&\n Number.isFinite(record.updatedAt)\n ) {\n return {\n kind: `reset`,\n updatedAt: record.updatedAt,\n }\n }\n\n return undefined\n}\n\nfunction parseElectricSyncMeta(value: unknown): ElectricSyncMeta | undefined {\n if (!value || typeof value !== `object`) {\n return undefined\n }\n\n const record = value as Record<string, unknown>\n if (\n record.version !== 1 ||\n !Array.isArray(record.seenTxids) ||\n !record.seenTxids.every(\n (txid) => typeof txid === `number` && Number.isFinite(txid),\n )\n ) {\n return undefined\n }\n\n const resume =\n record.resume === undefined\n ? undefined\n : parseElectricResumeState(record.resume)\n if (record.resume !== undefined && resume === undefined) {\n return undefined\n }\n\n return {\n version: 1,\n ...(resume ? { resume } : {}),\n seenTxids: Array.from(new Set(record.seenTxids)).sort((a, b) => a - b),\n }\n}\n\nfunction mergeElectricSyncMeta(\n current: unknown,\n incoming: unknown,\n): ElectricSyncMeta | unknown {\n const currentMeta = parseElectricSyncMeta(current)\n const incomingMeta = parseElectricSyncMeta(incoming)\n\n if (!incomingMeta) {\n return current\n }\n if (!currentMeta) {\n return incomingMeta\n }\n\n const resume = getNewestElectricResumeState(\n currentMeta.resume,\n incomingMeta.resume,\n )\n\n return {\n version: 1,\n ...(resume ? { resume } : {}),\n seenTxids: Array.from(\n new Set([...currentMeta.seenTxids, ...incomingMeta.seenTxids]),\n ).sort((a, b) => a - b),\n }\n}\n\nfunction getNewestElectricResumeState(\n current: ElectricResumeState | undefined,\n incoming: ElectricResumeState | undefined,\n): ElectricResumeState | undefined {\n if (!current) return incoming\n if (!incoming) return current\n return incoming.updatedAt >= current.updatedAt ? incoming : current\n}\n\n/**\n * Custom match function type - receives stream messages and returns boolean\n * indicating if the mutation has been synchronized\n */\nexport type MatchFunction<T extends Row<unknown>> = (\n message: Message<T>,\n) => boolean\n\n/**\n * Matching strategies for Electric synchronization\n * Handlers can return:\n * - Txid strategy: { txid: number | number[], timeout?: number } (recommended)\n * - Void (no return value) - mutation completes without waiting\n *\n * The optional timeout property specifies how long to wait for the txid(s) in milliseconds.\n * If not specified, defaults to 5000ms.\n */\nexport type MatchingStrategy = {\n txid: Txid | Array<Txid>\n timeout?: number\n} | void\n\n/**\n * Type representing a snapshot end message\n */\ntype SnapshotEndMessage = ControlMessage & {\n headers: { control: `snapshot-end` }\n}\n// The `InferSchemaOutput` and `ResolveType` are copied from the `@tanstack/db` package\n// but we modified `InferSchemaOutput` slightly to restrict the schema output to `Row<unknown>`\n// This is needed in order for `GetExtensions` to be able to infer the parser extensions type from the schema\ntype InferSchemaOutput<T> = T extends StandardSchemaV1\n ? StandardSchemaV1.InferOutput<T> extends Row<unknown>\n ? StandardSchemaV1.InferOutput<T>\n : Record<string, unknown>\n : Record<string, unknown>\n\n/**\n * The mode of sync to use for the collection.\n * @default `eager`\n * @description\n * - `eager`:\n * - syncs all data immediately on preload\n * - collection will be marked as ready once the sync is complete\n * - there is no incremental sync\n * - `on-demand`:\n * - syncs data in incremental snapshots when the collection is queried\n * - collection will be marked as ready immediately after the first snapshot is synced\n * - `progressive`:\n * - syncs all data for the collection in the background\n * - uses incremental snapshots during the initial sync to provide a fast path to the data required for queries\n * - collection will be marked as ready once the full sync is complete\n */\nexport type ElectricSyncMode = SyncMode | `progressive`\n\n/**\n * Configuration interface for Electric collection options\n * @template T - The type of items in the collection\n * @template TSchema - The schema type for validation\n */\nexport interface ElectricCollectionConfig<\n T extends Row<unknown> = Row<unknown>,\n TSchema extends StandardSchemaV1 = never,\n> extends Omit<\n BaseCollectionConfig<\n T,\n string | number,\n TSchema,\n ElectricCollectionUtils<T>,\n any\n >,\n `onInsert` | `onUpdate` | `onDelete` | `syncMode`\n> {\n /**\n * Configuration options for the ElectricSQL ShapeStream\n */\n shapeOptions: ShapeStreamOptions<GetExtensions<T>>\n syncMode?: ElectricSyncMode\n\n /**\n * Internal test hooks (for testing only)\n * Hidden via Symbol to prevent accidental usage in production\n */\n [ELECTRIC_TEST_HOOKS]?: ElectricTestHooks\n\n /**\n * Optional asynchronous handler function called before an insert operation\n * @param params Object containing transaction and collection information\n * @returns Promise resolving to { txid, timeout? } or void\n * @example\n * // Basic Electric insert handler with txid (recommended)\n * onInsert: async ({ transaction }) => {\n * const newItem = transaction.mutations[0].modified\n * const result = await api.todos.create({\n * data: newItem\n * })\n * return { txid: result.txid }\n * }\n *\n * @example\n * // Insert handler with custom timeout\n * onInsert: async ({ transaction }) => {\n * const newItem = transaction.mutations[0].modified\n * const result = await api.todos.create({\n * data: newItem\n * })\n * return { txid: result.txid, timeout: 10000 } // Wait up to 10 seconds\n * }\n *\n * @example\n * // Insert handler with multiple items - return array of txids\n * onInsert: async ({ transaction }) => {\n * const items = transaction.mutations.map(m => m.modified)\n * const results = await Promise.all(\n * items.map(item => api.todos.create({ data: item }))\n * )\n * return { txid: results.map(r => r.txid) }\n * }\n *\n * @example\n * // Use awaitMatch utility for custom matching\n * onInsert: async ({ transaction, collection }) => {\n * const newItem = transaction.mutations[0].modified\n * await api.todos.create({ data: newItem })\n * await collection.utils.awaitMatch(\n * (message) => isChangeMessage(message) &&\n * message.headers.operation === 'insert' &&\n * message.value.name === newItem.name\n * )\n * }\n */\n onInsert?: (\n params: InsertMutationFnParams<\n T,\n string | number,\n ElectricCollectionUtils<T>\n >,\n ) => Promise<MatchingStrategy>\n\n /**\n * Optional asynchronous handler function called before an update operation\n * @param params Object containing transaction and collection information\n * @returns Promise resolving to { txid, timeout? } or void\n * @example\n * // Basic Electric update handler with txid (recommended)\n * onUpdate: async ({ transaction }) => {\n * const { original, changes } = transaction.mutations[0]\n * const result = await api.todos.update({\n * where: { id: original.id },\n * data: changes\n * })\n * return { txid: result.txid }\n * }\n *\n * @example\n * // Use awaitMatch utility for custom matching\n * onUpdate: async ({ transaction, collection }) => {\n * const { original, changes } = transaction.mutations[0]\n * await api.todos.update({ where: { id: original.id }, data: changes })\n * await collection.utils.awaitMatch(\n * (message) => isChangeMessage(message) &&\n * message.headers.operation === 'update' &&\n * message.value.id === original.id\n * )\n * }\n */\n onUpdate?: (\n params: UpdateMutationFnParams<\n T,\n string | number,\n ElectricCollectionUtils<T>\n >,\n ) => Promise<MatchingStrategy>\n\n /**\n * Optional asynchronous handler function called before a delete operation\n * @param params Object containing transaction and collection information\n * @returns Promise resolving to { txid, timeout? } or void\n * @example\n * // Basic Electric delete handler with txid (recommended)\n * onDelete: async ({ transaction }) => {\n * const mutation = transaction.mutations[0]\n * const result = await api.todos.delete({\n * id: mutation.original.id\n * })\n * return { txid: result.txid }\n * }\n *\n * @example\n * // Use awaitMatch utility for custom matching\n * onDelete: async ({ transaction, collection }) => {\n * const mutation = transaction.mutations[0]\n * await api.todos.delete({ id: mutation.original.id })\n * await collection.utils.awaitMatch(\n * (message) => isChangeMessage(message) &&\n * message.headers.operation === 'delete' &&\n * message.value.id === mutation.original.id\n * )\n * }\n */\n onDelete?: (\n params: DeleteMutationFnParams<\n T,\n string | number,\n ElectricCollectionUtils<T>\n >,\n ) => Promise<MatchingStrategy>\n}\n\nfunction isUpToDateMessage<T extends Row<unknown>>(\n message: Message<T>,\n): message is ControlMessage & { up_to_date: true } {\n return isControlMessage(message) && message.headers.control === `up-to-date`\n}\n\nfunction isMustRefetchMessage<T extends Row<unknown>>(\n message: Message<T>,\n): message is ControlMessage & { headers: { control: `must-refetch` } } {\n return isControlMessage(message) && message.headers.control === `must-refetch`\n}\n\nfunction isSnapshotEndMessage<T extends Row<unknown>>(\n message: Message<T>,\n): message is SnapshotEndMessage {\n return isControlMessage(message) && message.headers.control === `snapshot-end`\n}\n\nfunction isSubsetEndMessage<T extends Row<unknown>>(\n message: Message<T>,\n): message is ControlMessage & { headers: { control: `subset-end` } } {\n return (\n isControlMessage(message) &&\n (message.headers.control as string) === `subset-end`\n )\n}\n\nfunction parseSnapshotMessage(message: SnapshotEndMessage): PostgresSnapshot {\n return {\n xmin: message.headers.xmin,\n xmax: message.headers.xmax,\n xip_list: message.headers.xip_list,\n }\n}\n\nfunction toStableSerializable(value: unknown): unknown {\n if (value == null) {\n return value\n }\n\n if (Array.isArray(value)) {\n return value.map((entry) => toStableSerializable(entry))\n }\n\n if (value instanceof Date) {\n return value.toISOString()\n }\n\n if (typeof value === `object`) {\n const record = value as Record<string, unknown>\n const stableRecord: Record<string, unknown> = {}\n const keys = Object.keys(record).sort((left, right) =>\n left < right ? -1 : left > right ? 1 : 0,\n )\n for (const key of keys) {\n stableRecord[key] = toStableSerializable(record[key])\n }\n return stableRecord\n }\n\n return value\n}\n\nfunction getStableShapeIdentity(shapeOptions: {\n url: string\n params?: Record<string, unknown>\n}): string {\n return JSON.stringify(\n toStableSerializable({\n url: shapeOptions.url,\n params: shapeOptions.params ?? null,\n }),\n )\n}\n\n// Check if a message contains txids in its headers\nfunction hasTxids<T extends Row<unknown>>(\n message: Message<T>,\n): message is Message<T> & { headers: { txids?: Array<Txid> } } {\n return `txids` in message.headers && Array.isArray(message.headers.txids)\n}\n\n/**\n * Creates a deduplicated loadSubset handler for progressive/on-demand modes\n * Returns null for eager mode, or a DeduplicatedLoadSubset instance for other modes.\n * Handles fetching snapshots in progressive mode during buffering phase,\n * and requesting snapshots in on-demand mode.\n *\n * When cursor expressions are provided (whereFrom/whereCurrent), makes two\n * requestSnapshot calls:\n * - One for whereFrom (rows > cursor) with limit\n * - One for whereCurrent (rows = cursor, for tie-breaking) without limit\n */\nfunction createLoadSubsetDedupe<T extends Row<unknown>>({\n stream,\n syncMode,\n isBufferingInitialSync,\n begin,\n write,\n commit,\n getCommitCursor,\n waitForCommitsAfter,\n collectionId,\n encodeColumnName,\n signal,\n}: {\n stream: ShapeStream<T>\n syncMode: ElectricSyncMode\n isBufferingInitialSync: () => boolean\n begin: () => void\n write: (mutation: {\n type: `insert` | `update` | `delete`\n value: T\n metadata: Record<string, unknown>\n }) => void\n commit: (signal?: AbortSignal) => SyncAppliedReceipt\n getCommitCursor: () => number\n waitForCommitsAfter: (cursor: number) => Promise<void>\n collectionId?: string\n /**\n * Optional function to encode column names (e.g., camelCase to snake_case).\n * This is typically the `encode` function from shapeOptions.columnMapper.\n */\n encodeColumnName?: ColumnEncoder\n /**\n * Abort signal to check if the stream has been aborted during cleanup.\n * When aborted, errors from requestSnapshot are silently ignored.\n */\n signal: AbortSignal\n}): DeduplicatedLoadSubset | null {\n if (syncMode === `eager`) {\n return null\n }\n\n const compileOptions = encodeColumnName ? { encodeColumnName } : undefined\n const logPrefix = collectionId ? `[${collectionId}] ` : ``\n\n /**\n * Handles errors from snapshot operations. Returns true if the error was\n * handled (signal aborted during cleanup), false if it should be re-thrown.\n */\n function handleSnapshotError(error: unknown, operation: string): boolean {\n if (signal.aborted) {\n debug(`${logPrefix}Ignoring ${operation} error during cleanup: %o`, error)\n return true\n }\n debug(`${logPrefix}Error in ${operation}: %o`, error)\n return false\n }\n\n const loadSubset = async (opts: LoadSubsetOptions) => {\n const commitCursor = getCommitCursor()\n if (opts.signal?.aborted) return\n\n if (isBufferingInitialSync()) {\n const snapshotParams = compileSQL<T>(opts, compileOptions)\n try {\n const { data: rows } = await stream.fetchSnapshot(snapshotParams)\n if (opts.signal?.aborted || !isBufferingInitialSync()) {\n debug(`${logPrefix}Ignoring snapshot - sync completed while fetching`)\n return\n }\n\n if (rows.length > 0) {\n begin()\n for (const row of rows) {\n write({\n type: `insert`,\n value: row.value,\n metadata: { ...row.headers },\n })\n }\n await commit(opts.signal)\n debug(`${logPrefix}Applied snapshot with ${rows.length} rows`)\n }\n } catch (error) {\n if (opts.signal?.aborted) return\n if (handleSnapshotError(error, `fetchSnapshot`)) {\n return\n }\n throw error\n }\n return\n }\n\n if (syncMode === `progressive`) {\n return\n }\n\n const { cursor, where, orderBy, limit } = opts\n\n // When the stream is already up-to-date, it may be in a long-poll wait.\n // Forcing a disconnect-and-refresh ensures requestSnapshot gets a response\n // from a fresh server round-trip rather than waiting for the current poll to end.\n // Some native fetch implementations (notably React Native/Expo) may not abort\n // long-poll requests promptly. Bound the wait so on-demand live queries don't\n // remain loading until the long-poll naturally times out.\n // If the refresh fails or times out, we fall through to requestSnapshot which\n // still works.\n if (stream.isUpToDate) {\n let timeoutId: ReturnType<typeof setTimeout> | undefined\n try {\n await Promise.race([\n stream.forceDisconnectAndRefresh(),\n new Promise<void>((resolve) => {\n timeoutId = setTimeout(\n resolve,\n FORCE_DISCONNECT_AND_REFRESH_TIMEOUT_MS,\n )\n }),\n ])\n } catch (error) {\n if (handleSnapshotError(error, `forceDisconnectAndRefresh`)) {\n return\n }\n debug(\n `${logPrefix}forceDisconnectAndRefresh failed, proceeding to requestSnapshot: %o`,\n error,\n )\n } finally {\n clearTimeout(timeoutId)\n }\n }\n\n if (opts.signal?.aborted) return\n\n // Upstream limitation: ShapeStream.requestSnapshot() publishes its rows\n // through the stream callback before its Promise resolves. It accepts no\n // request signal and exposes no request identity on those messages, so an\n // aborted request can already have installed rows before the check below.\n // Full request-scoped cancellation requires support in the Electric client;\n // matching snapshots by parameters is unsafe for overlapping equal requests.\n try {\n if (cursor) {\n const whereCurrentOpts: LoadSubsetOptions = {\n where: where ? and(where, cursor.whereCurrent) : cursor.whereCurrent,\n orderBy,\n }\n const whereCurrentParams = compileSQL<T>(\n whereCurrentOpts,\n compileOptions,\n )\n\n const whereFromOpts: LoadSubsetOptions = {\n where: where ? and(where, cursor.whereFrom) : cursor.whereFrom,\n orderBy,\n limit,\n }\n const whereFromParams = compileSQL<T>(whereFromOpts, compileOptions)\n\n debug(`${logPrefix}Requesting cursor.whereCurrent snapshot (all ties)`)\n debug(\n `${logPrefix}Requesting cursor.whereFrom snapshot (with limit ${limit})`,\n )\n\n await Promise.all([\n stream.requestSnapshot(whereCurrentParams),\n stream.requestSnapshot(whereFromParams),\n ])\n } else {\n const snapshotParams = compileSQL<T>(opts, compileOptions)\n await stream.requestSnapshot(snapshotParams)\n }\n } catch (error) {\n if (opts.signal?.aborted) return\n if (handleSnapshotError(error, `requestSnapshot`)) {\n return\n }\n throw error\n }\n await waitForCommitsAfter(commitCursor)\n }\n\n return new DeduplicatedLoadSubset({ loadSubset })\n}\n\n/**\n * Type for the awaitTxId utility function\n */\nexport type AwaitTxIdFn = (txId: Txid, timeout?: number) => Promise<boolean>\n\n/**\n * Type for the awaitMatch utility function\n */\nexport type AwaitMatchFn<T extends Row<unknown>> = (\n matchFn: MatchFunction<T>,\n timeout?: number,\n) => Promise<boolean>\n\n/**\n * Electric collection utilities type\n */\nexport interface ElectricCollectionUtils<\n T extends Row<unknown> = Row<unknown>,\n> extends UtilsRecord {\n awaitTxId: AwaitTxIdFn\n awaitMatch: AwaitMatchFn<T>\n}\n\n/**\n * Creates Electric collection options for use with a standard Collection\n *\n * @template T - The explicit type of items in the collection (highest priority)\n * @template TSchema - The schema type for validation and type inference (second priority)\n * @template TFallback - The fallback type if no explicit or schema type is provided\n * @param config - Configuration options for the Electric collection\n * @returns Collection options with utilities\n */\n\n// Overload for when schema is provided\nexport function electricCollectionOptions<T extends StandardSchemaV1>(\n config: ElectricCollectionConfig<InferSchemaOutput<T>, T> & {\n schema: T\n },\n): Omit<\n CollectionConfig<InferSchemaOutput<T>, string | number, T>,\n `utils` | `onInsert` | `onUpdate` | `onDelete`\n> &\n Pick<\n ElectricCollectionConfig<InferSchemaOutput<T>, T>,\n `onInsert` | `onUpdate` | `onDelete`\n > & {\n id?: string\n utils: ElectricCollectionUtils<InferSchemaOutput<T>>\n schema: T\n }\n\n// Overload for when no schema is provided\nexport function electricCollectionOptions<T extends Row<unknown>>(\n config: ElectricCollectionConfig<T> & {\n schema?: never // prohibit schema\n },\n): Omit<\n CollectionConfig<T, string | number>,\n `utils` | `onInsert` | `onUpdate` | `onDelete`\n> &\n Pick<ElectricCollectionConfig<T>, `onInsert` | `onUpdate` | `onDelete`> & {\n id?: string\n utils: ElectricCollectionUtils<T>\n schema?: never // no schema in the result\n }\n\nexport function electricCollectionOptions<T extends Row<unknown>>(\n config: ElectricCollectionConfig<T, any>,\n): Omit<\n CollectionConfig<T, string | number, any, ElectricCollectionUtils<T>>,\n `utils`\n> & {\n id?: string\n utils: ElectricCollectionUtils<T>\n schema?: any\n} {\n const seenTxids = new Store<Set<Txid>>(new Set([]))\n const seenSnapshots = new Store<Array<PostgresSnapshot>>([])\n const hydratedResumeState = new Store<ElectricResumeState | undefined>(\n undefined,\n )\n const internalSyncMode = config.syncMode ?? `eager`\n const finalSyncMode =\n internalSyncMode === `progressive` ? `on-demand` : internalSyncMode\n const pendingMatches = new Store<\n Map<\n string,\n {\n matchFn: (message: Message<any>) => boolean\n resolve: (value: boolean) => void\n reject: (error: Error) => void\n timeoutId: ReturnType<typeof setTimeout>\n matched: boolean\n }\n >\n >(new Map())\n\n // Buffer messages since last up-to-date to handle race conditions\n const currentBatchMessages = new Store<Array<Message<any>>>([])\n\n // Track whether the current batch has been committed (up-to-date received)\n // This allows awaitMatch to resolve immediately for messages from committed batches\n const batchCommitted = new Store<boolean>(false)\n\n /**\n * Helper function to remove multiple matches from the pendingMatches store\n */\n const removePendingMatches = (matchIds: Array<string>) => {\n if (matchIds.length > 0) {\n pendingMatches.setState((current) => {\n const newMatches = new Map(current)\n matchIds.forEach((id) => newMatches.delete(id))\n return newMatches\n })\n }\n }\n\n /**\n * Helper function to resolve and cleanup matched pending matches\n */\n const resolveMatchedPendingMatches = () => {\n const matchesToResolve: Array<string> = []\n pendingMatches.state.forEach((match, matchId) => {\n if (match.matched) {\n clearTimeout(match.timeoutId)\n match.resolve(true)\n matchesToResolve.push(matchId)\n debug(\n `${config.id ? `[${config.id}] ` : ``}awaitMatch resolved on up-to-date for match %s`,\n matchId,\n )\n }\n })\n removePendingMatches(matchesToResolve)\n }\n const sync = createElectricSync<T>(config.shapeOptions, {\n seenTxids,\n seenSnapshots,\n hydratedResumeState,\n syncMode: internalSyncMode,\n pendingMatches,\n currentBatchMessages,\n batchCommitted,\n removePendingMatches,\n resolveMatchedPendingMatches,\n collectionId: config.id,\n testHooks: config[ELECTRIC_TEST_HOOKS],\n })\n\n /**\n * Wait for a specific transaction ID to be synced\n * @param txId The transaction ID to wait for as a number\n * @param timeout Optional timeout in milliseconds (defaults to 5000ms)\n * @returns Promise that resolves when the txId is synced\n */\n const awaitTxId: AwaitTxIdFn = async (\n txId: Txid,\n timeout: number = 5000,\n ): Promise<boolean> => {\n debug(\n `${config.id ? `[${config.id}] ` : ``}awaitTxId called with txid %d`,\n txId,\n )\n if (typeof txId !== `number`) {\n throw new ExpectedNumberInAwaitTxIdError(typeof txId, config.id)\n }\n\n // First check if the txid is in the seenTxids store\n const hasTxid = seenTxids.state.has(txId)\n if (hasTxid) return true\n\n // Then check if the txid is in any of the seen snapshots\n const hasSnapshot = seenSnapshots.state.some((snapshot) =>\n isVisibleInSnapshot(txId, snapshot),\n )\n if (hasSnapshot) return true\n\n return new Promise((resolve, reject) => {\n const cleanup = () => {\n clearTimeout(timeoutId)\n subSeenTxids.unsubscribe()\n subSeenSnapshots.unsubscribe()\n }\n\n const timeoutId = setTimeout(() => {\n cleanup()\n reject(new TimeoutWaitingForTxIdError(txId, config.id))\n }, timeout)\n\n const subSeenTxids = seenTxids.subscribe(() => {\n if (seenTxids.state.has(txId)) {\n debug(\n `${config.id ? `[${config.id}] ` : ``}awaitTxId found match for txid %o`,\n txId,\n )\n cleanup()\n resolve(true)\n }\n })\n\n const subSeenSnapshots = seenSnapshots.subscribe(() => {\n const visibleSnapshot = seenSnapshots.state.find((snapshot) =>\n isVisibleInSnapshot(txId, snapshot),\n )\n if (visibleSnapshot) {\n debug(\n `${config.id ? `[${config.id}] ` : ``}awaitTxId found match for txid %o in snapshot %o`,\n txId,\n visibleSnapshot,\n )\n cleanup()\n resolve(true)\n }\n })\n })\n }\n\n /**\n * Wait for a custom match function to find a matching message\n * @param matchFn Function that returns true when a message matches\n * @param timeout Optional timeout in milliseconds (defaults to 5000ms)\n * @returns Promise that resolves when a matching message is found\n */\n const awaitMatch: AwaitMatchFn<any> = async (\n matchFn: MatchFunction<any>,\n timeout: number = 3000,\n ): Promise<boolean> => {\n debug(\n `${config.id ? `[${config.id}] ` : ``}awaitMatch called with custom function`,\n )\n\n return new Promise((resolve, reject) => {\n const matchId = Math.random().toString(36)\n\n const cleanupMatch = () => {\n pendingMatches.setState((current) => {\n const newMatches = new Map(current)\n newMatches.delete(matchId)\n return newMatches\n })\n }\n\n const onTimeout = () => {\n cleanupMatch()\n reject(new TimeoutWaitingForMatchError(config.id))\n }\n\n const timeoutId = setTimeout(onTimeout, timeout)\n\n // We need access to the stream messages to check against the match function\n // This will be handled by the sync configuration\n const checkMatch = (message: Message<any>) => {\n if (matchFn(message)) {\n debug(\n `${config.id ? `[${config.id}] ` : ``}awaitMatch found matching message, waiting for up-to-date`,\n )\n // Mark as matched but don't resolve yet - wait for up-to-date\n pendingMatches.setState((current) => {\n const newMatches = new Map(current)\n const existing = newMatches.get(matchId)\n if (existing) {\n newMatches.set(matchId, { ...existing, matched: true })\n }\n return newMatches\n })\n return true\n }\n return false\n }\n\n // Check against current batch messages first to handle race conditions\n for (const message of currentBatchMessages.state) {\n if (matchFn(message)) {\n // If batch is committed (up-to-date already received), resolve immediately\n // just like awaitTxId does when it finds a txid in seenTxids\n if (batchCommitted.state) {\n debug(\n `${config.id ? `[${config.id}] ` : ``}awaitMatch found immediate match in committed batch, resolving immediately`,\n )\n clearTimeout(timeoutId)\n resolve(true)\n return\n }\n\n // If batch is not yet committed, register match and wait for up-to-date\n debug(\n `${config.id ? `[${config.id}] ` : ``}awaitMatch found immediate match in current batch, waiting for up-to-date`,\n )\n pendingMatches.setState((current) => {\n const newMatches = new Map(current)\n newMatches.set(matchId, {\n matchFn: checkMatch,\n resolve,\n reject,\n timeoutId,\n matched: true, // Already matched, will resolve on up-to-date\n })\n return newMatches\n })\n return\n }\n }\n\n // Store the match function for the sync process to use\n // We'll add this to a pending matches store\n pendingMatches.setState((current) => {\n const newMatches = new Map(current)\n newMatches.set(matchId, {\n matchFn: checkMatch,\n resolve,\n reject,\n timeoutId,\n matched: false,\n })\n return newMatches\n })\n })\n }\n\n /**\n * Process matching strategy and wait for synchronization\n */\n const processMatchingStrategy = async (\n result: MatchingStrategy,\n ): Promise<void> => {\n // Only wait if result contains txid\n if (result && `txid` in result) {\n const timeout = result.timeout\n // Handle both single txid and array of txids\n if (Array.isArray(result.txid)) {\n await Promise.all(result.txid.map((txid) => awaitTxId(txid, timeout)))\n } else {\n await awaitTxId(result.txid, timeout)\n }\n }\n // If result is void/undefined, don't wait - mutation completes immediately\n }\n\n // Create wrapper handlers for direct persistence operations that handle different matching strategies\n const wrappedOnInsert = config.onInsert\n ? async (\n params: InsertMutationFnParams<\n any,\n string | number,\n ElectricCollectionUtils<T>\n >,\n ) => {\n const handlerResult = await config.onInsert!(params)\n await processMatchingStrategy(handlerResult)\n return handlerResult\n }\n : undefined\n\n const wrappedOnUpdate = config.onUpdate\n ? async (\n params: UpdateMutationFnParams<\n any,\n string | number,\n ElectricCollectionUtils<T>\n >,\n ) => {\n const handlerResult = await config.onUpdate!(params)\n await processMatchingStrategy(handlerResult)\n return handlerResult\n }\n : undefined\n\n const wrappedOnDelete = config.onDelete\n ? async (\n params: DeleteMutationFnParams<\n any,\n string | number,\n ElectricCollectionUtils<T>\n >,\n ) => {\n const handlerResult = await config.onDelete!(params)\n await processMatchingStrategy(handlerResult)\n return handlerResult\n }\n : undefined\n\n // Extract standard Collection config properties\n const {\n shapeOptions: _shapeOptions,\n onInsert: _onInsert,\n onUpdate: _onUpdate,\n onDelete: _onDelete,\n ...restConfig\n } = config\n\n const options = {\n ...restConfig,\n syncMode: finalSyncMode,\n sync: {\n ...sync,\n exportSyncMeta: (): ElectricSyncMeta => ({\n version: 1,\n ...(hydratedResumeState.state\n ? { resume: hydratedResumeState.state }\n : {}),\n seenTxids: Array.from(seenTxids.state).sort((a, b) => a - b),\n }),\n importSyncMeta: (meta: unknown): void => {\n const parsed = parseElectricSyncMeta(meta)\n if (!parsed) {\n return\n }\n\n hydratedResumeState.setState(() => parsed.resume)\n seenTxids.setState(() => new Set(parsed.seenTxids))\n },\n mergeSyncMeta: mergeElectricSyncMeta,\n },\n onInsert: wrappedOnInsert,\n onUpdate: wrappedOnUpdate,\n onDelete: wrappedOnDelete,\n utils: {\n awaitTxId,\n awaitMatch,\n },\n }\n\n return withCollectionConfigFactory(options, () =>\n (\n electricCollectionOptions as (\n nextConfig: ElectricCollectionConfig<T, any>,\n ) => typeof options\n )(config),\n )\n}\n\n/**\n * Internal function to create ElectricSQL sync configuration\n */\nfunction createElectricSync<T extends Row<unknown>>(\n shapeOptions: ShapeStreamOptions<GetExtensions<T>>,\n options: {\n syncMode: ElectricSyncMode\n seenTxids: Store<Set<Txid>>\n seenSnapshots: Store<Array<PostgresSnapshot>>\n hydratedResumeState: Store<ElectricResumeState | undefined>\n pendingMatches: Store<\n Map<\n string,\n {\n matchFn: (message: Message<T>) => boolean\n resolve: (value: boolean) => void\n reject: (error: Error) => void\n timeoutId: ReturnType<typeof setTimeout>\n matched: boolean\n }\n >\n >\n currentBatchMessages: Store<Array<Message<T>>>\n batchCommitted: Store<boolean>\n removePendingMatches: (matchIds: Array<string>) => void\n resolveMatchedPendingMatches: () => void\n collectionId?: string\n testHooks?: ElectricTestHooks\n },\n): SyncConfig<T> {\n const {\n seenTxids,\n seenSnapshots,\n hydratedResumeState,\n syncMode,\n pendingMatches,\n currentBatchMessages,\n batchCommitted,\n removePendingMatches,\n resolveMatchedPendingMatches,\n collectionId,\n testHooks,\n } = options\n const MAX_BATCH_MESSAGES = 1000 // Safety limit for message buffer\n\n // Store for the relation schema information\n const relationSchema = new Store<string | undefined>(undefined)\n\n const tagCache = new Map<MoveTag, ParsedMoveTag>()\n\n // Parses a tag string into a ParsedMoveTag.\n // It memoizes the result parsed tag such that future calls\n // for the same tag string return the same ParsedMoveTag array.\n const parseTag = (tag: MoveTag): ParsedMoveTag => {\n const cachedTag = tagCache.get(tag)\n if (cachedTag) {\n return cachedTag\n }\n\n const parsedTag = parseTagString(tag)\n tagCache.set(tag, parsedTag)\n return parsedTag\n }\n\n // Tag tracking state\n const rowTagSets = new Map<RowId, Set<MoveTag>>()\n const tagIndex: TagIndex = []\n let tagLength: number | undefined = undefined\n\n // DNF state: active_conditions are per-row, disjunct_positions are global\n // (fixed by the shape's WHERE clause, derived once from the first tagged message).\n const rowActiveConditions = new Map<RowId, ActiveConditions>()\n let disjunctPositions: DisjunctPositions | undefined = undefined\n\n /**\n * Initialize the tag index with the correct length\n */\n const initializeTagIndex = (length: number): void => {\n if (tagIndex.length < length) {\n // Extend the index array to the required length\n for (let i = tagIndex.length; i < length; i++) {\n tagIndex[i] = new Map()\n }\n }\n }\n\n /**\n * Add tags to a row and update the tag index\n */\n const addTagsToRow = (\n tags: Array<MoveTag>,\n rowId: RowId,\n rowTagSet: Set<MoveTag>,\n ): void => {\n for (const tag of tags) {\n const parsedTag = parseTag(tag)\n\n // Infer tag length from first tag\n if (tagLength === undefined) {\n tagLength = getTagLength(parsedTag)\n initializeTagIndex(tagLength)\n }\n\n // Validate tag length matches\n const currentTagLength = getTagLength(parsedTag)\n if (currentTagLength !== tagLength) {\n debug(\n `${collectionId ? `[${collectionId}] ` : ``}Tag length mismatch: expected ${tagLength}, got ${currentTagLength}`,\n )\n continue\n }\n\n rowTagSet.add(tag)\n addTagToIndex(parsedTag, rowId, tagIndex, tagLength)\n }\n }\n\n /**\n * Remove tags from a row and update the tag index\n */\n const removeTagsFromRow = (\n removedTags: Array<MoveTag>,\n rowId: RowId,\n rowTagSet: Set<MoveTag>,\n ): void => {\n if (tagLength === undefined) {\n return\n }\n\n for (const tag of removedTags) {\n const parsedTag = parseTag(tag)\n rowTagSet.delete(tag)\n removeTagFromIndex(parsedTag, rowId, tagIndex, tagLength)\n // We aggresively evict the tag from the cache\n // if this tag is shared with another row\n // and is not removed from that other row\n // then next time we encounter the tag it will be parsed again\n tagCache.delete(tag)\n }\n }\n\n /**\n * Process tags for a change message (add and remove tags)\n */\n const processTagsForChangeMessage = (\n tags: Array<MoveTag> | undefined,\n removedTags: Array<MoveTag> | undefined,\n rowId: RowId,\n activeConditions?: ActiveConditions,\n ): Set<MoveTag> => {\n // Initialize tag set for this row if it doesn't exist (needed for checking deletion)\n if (!rowTagSets.has(rowId)) {\n rowTagSets.set(rowId, new Set())\n }\n const rowTagSet = rowTagSets.get(rowId)!\n\n // Add new tags\n if (tags) {\n addTagsToRow(tags, rowId, rowTagSet)\n\n // Derive disjunct positions once — they are fixed by the shape's WHERE clause.\n if (disjunctPositions === undefined) {\n const parsedTags = tags.map(parseTag)\n disjunctPositions = deriveDisjunctPositions(parsedTags)\n }\n }\n\n // Remove tags\n if (removedTags) {\n removeTagsFromRow(removedTags, rowId, rowTagSet)\n }\n\n // Store active conditions if provided (overwrite on re-send)\n if (activeConditions && activeConditions.length > 0) {\n rowActiveConditions.set(rowId, [...activeConditions])\n }\n\n return rowTagSet\n }\n\n /**\n * Clear all tag tracking state (used when truncating)\n */\n const clearTagTrackingState = (): void => {\n rowTagSets.clear()\n tagIndex.length = 0\n tagLength = undefined\n rowActiveConditions.clear()\n disjunctPositions = undefined\n }\n\n /**\n * Remove all tags for a row from both the tag set and the index\n * Used when a row is deleted\n */\n const clearTagsForRow = (rowId: RowId): void => {\n if (tagLength === undefined) {\n return\n }\n\n const rowTagSet = rowTagSets.get(rowId)\n if (!rowTagSet) {\n return\n }\n\n // Remove each tag from the index\n for (const tag of rowTagSet) {\n const parsedTag = parseTag(tag)\n const currentTagLength = getTagLength(parsedTag)\n if (currentTagLength === tagLength) {\n removeTagFromIndex(parsedTag, rowId, tagIndex, tagLength)\n }\n tagCache.delete(tag)\n }\n\n // Remove the row from the tag sets map\n rowTagSets.delete(rowId)\n rowActiveConditions.delete(rowId)\n }\n\n /**\n * Remove matching tags from a row based on a pattern\n * Returns true if the row should be deleted (no longer visible)\n */\n const removeMatchingTagsFromRow = (\n rowId: RowId,\n pattern: MovePattern,\n ): boolean => {\n const rowTagSet = rowTagSets.get(rowId)\n if (!rowTagSet) {\n return false\n }\n\n // DNF mode: check visibility using active conditions.\n // Tag index entries are preserved so that move-in can re-activate positions.\n const activeConditions = rowActiveConditions.get(rowId)\n if (activeConditions && disjunctPositions) {\n // Set the condition at this pattern's position to false\n activeConditions[pattern.pos] = false\n\n if (!rowVisible(activeConditions, disjunctPositions)) {\n // Row is no longer visible — clean up all state including tag index\n for (const tag of rowTagSet) {\n const parsedTag = parseTag(tag)\n removeTagFromIndex(parsedTag, rowId, tagIndex, tagLength!)\n tagCache.delete(tag)\n }\n rowTagSets.delete(rowId)\n rowActiveConditions.delete(rowId)\n return true\n }\n return false\n }\n\n // Simple shape (no subquery dependencies — server sends no active_conditions):\n // Remove matching tags and delete if tag set is empty\n for (const tag of rowTagSet) {\n const parsedTag = parseTag(tag)\n if (tagMatchesPattern(parsedTag, pattern)) {\n rowTagSet.delete(tag)\n removeTagFromIndex(parsedTag, rowId, tagIndex, tagLength!)\n }\n }\n\n if (rowTagSet.size === 0) {\n rowTagSets.delete(rowId)\n return true\n }\n\n return false\n }\n\n /**\n * Process move-out event: remove matching tags from rows and delete rows with empty tag sets\n */\n const processMoveOutEvent = (\n patterns: Array<MovePattern>,\n begin: () => void,\n write: (message: ChangeMessageOrDeleteKeyMessage<T>) => void,\n transactionStarted: boolean,\n ): boolean => {\n if (tagLength === undefined) {\n debug(\n `${collectionId ? `[${collectionId}] ` : ``}Received move-out message but no tag length set yet, ignoring`,\n )\n return transactionStarted\n }\n\n let txStarted = transactionStarted\n\n // Process all patterns and collect rows to delete\n for (const pattern of patterns) {\n // Find all rows that match this pattern\n const affectedRowIds = findRowsMatchingPattern(pattern, tagIndex)\n\n for (const rowId of affectedRowIds) {\n if (removeMatchingTagsFromRow(rowId, pattern)) {\n // Delete rows with empty tag sets\n if (!txStarted) {\n begin()\n txStarted = true\n }\n\n write({\n type: `delete`,\n key: rowId,\n })\n }\n }\n }\n\n return txStarted\n }\n\n /**\n * Process move-in event: re-activate conditions for rows matching the patterns.\n * This is a silent operation — no messages are emitted to the collection.\n */\n const processMoveInEvent = (patterns: Array<MovePattern>): void => {\n if (tagLength === undefined) {\n debug(\n `${collectionId ? `[${collectionId}] ` : ``}Received move-in message but no tag length set yet, ignoring`,\n )\n return\n }\n\n for (const pattern of patterns) {\n const affectedRowIds = findRowsMatchingPattern(pattern, tagIndex)\n\n for (const rowId of affectedRowIds) {\n const activeConditions = rowActiveConditions.get(rowId)\n if (activeConditions) {\n activeConditions[pattern.pos] = true\n }\n }\n }\n }\n\n /**\n * Get the sync metadata for insert operations\n * @returns Record containing relation information\n */\n const getSyncMetadata = (): Record<string, unknown> => {\n // Use the stored schema if available, otherwise default to 'public'\n const schema = relationSchema.state || `public`\n\n return {\n relation: shapeOptions.params?.table\n ? [schema, shapeOptions.params.table]\n : undefined,\n }\n }\n\n let unsubscribeStream: () => void\n\n return {\n sync: (params: Parameters<SyncConfig<T>[`sync`]>[0]) => {\n const {\n begin,\n write,\n commit: commitSyncTransaction,\n markReady,\n markError,\n truncate,\n collection,\n metadata,\n } = params\n let commitSequence = 0\n const pendingAppliedReceipts = new Map<number, Promise<void>>()\n const commit = (signal?: AbortSignal): SyncAppliedReceipt => {\n const sequence = ++commitSequence\n const applied = commitSyncTransaction(signal)\n if (applied === true) {\n return true\n }\n pendingAppliedReceipts.set(sequence, applied)\n const removeReceipt = () => pendingAppliedReceipts.delete(sequence)\n void applied.then(removeReceipt, removeReceipt)\n return applied\n }\n const waitForCommitsAfter = async (cursor: number): Promise<void> => {\n await Promise.all(\n Array.from(pendingAppliedReceipts, ([sequence, applied]) =>\n sequence > cursor ? applied : undefined,\n ),\n )\n }\n const readPersistedResumeState = (): ElectricResumeState | undefined => {\n const persistedResumeState = metadata?.collection.get(`electric:resume`)\n return parseElectricResumeState(persistedResumeState)\n }\n\n const persistedResumeState = getNewestElectricResumeState(\n readPersistedResumeState(),\n hydratedResumeState.state,\n )\n const shapeIdentity = getStableShapeIdentity({\n url: shapeOptions.url,\n params: shapeOptions.params as Record<string, unknown> | undefined,\n })\n const hasIncompatiblePersistedResume =\n persistedResumeState?.kind === `resume` &&\n persistedResumeState.shapeId !== shapeIdentity\n const canUsePersistedResume =\n shapeOptions.offset === undefined &&\n shapeOptions.handle === undefined &&\n persistedResumeState?.kind === `resume` &&\n !hasIncompatiblePersistedResume\n\n // Wrap markReady to wait for test hook in progressive mode\n let progressiveReadyGate: Promise<void> | null = null\n let streamErrorVersion = 0\n const wrappedMarkReady = (\n isBuffering: boolean,\n expectedErrorVersion = streamErrorVersion,\n ) => {\n if (streamErrorVersion !== expectedErrorVersion) return\n\n // Only create gate if we're in buffering phase (first up-to-date)\n if (\n isBuffering &&\n syncMode === `progressive` &&\n testHooks?.beforeMarkingReady\n ) {\n // Create a new gate promise for this sync cycle\n progressiveReadyGate = testHooks.beforeMarkingReady()\n progressiveReadyGate.then(() => {\n if (streamErrorVersion === expectedErrorVersion) {\n markReady()\n }\n })\n } else {\n // No hook, not buffering, or already past first up-to-date\n markReady()\n }\n }\n\n // Abort controller for the stream - wraps the signal if provided\n const abortController = new AbortController()\n\n if (shapeOptions.signal) {\n shapeOptions.signal.addEventListener(\n `abort`,\n () => {\n abortController.abort()\n },\n {\n once: true,\n },\n )\n if (shapeOptions.signal.aborted) {\n abortController.abort()\n }\n }\n\n // Cleanup pending matches on abort\n abortController.signal.addEventListener(`abort`, () => {\n pendingMatches.setState((current) => {\n current.forEach((match) => {\n clearTimeout(match.timeoutId)\n match.reject(new StreamAbortedError())\n })\n return new Map() // Clear all pending matches\n })\n })\n\n const stream = new ShapeStream({\n ...shapeOptions,\n // In on-demand mode, we only want to sync changes, so we set the log to `changes_only`\n log: syncMode === `on-demand` ? `changes_only` : undefined,\n // In on-demand mode, we only need the changes from the point of time the collection was created\n // so we default to `now` when there is no saved offset.\n offset:\n shapeOptions.offset ??\n (canUsePersistedResume\n ? (persistedResumeState.offset as Offset)\n : syncMode === `on-demand`\n ? `now`\n : undefined),\n handle:\n shapeOptions.handle ??\n (canUsePersistedResume ? persistedResumeState.handle : undefined),\n signal: abortController.signal,\n onError: (errorParams) => {\n streamErrorVersion++\n // Note that Electric sends a 409 error on a `must-refetch` message, but the\n // ShapeStream handled this and it will not reach this handler, therefor\n // this handler will not run for a `must-refetch`.\n const initialSyncFailed = collection.status === `loading`\n if (initialSyncFailed) {\n markError(errorParams)\n }\n\n if (shapeOptions.onError) {\n return shapeOptions.onError(errorParams)\n } else {\n console.error(\n `An error occurred while syncing collection: ${collection.id}, \\n` +\n (initialSyncFailed\n ? `the initial sync has been marked as failed. \\n`\n : `the last ready snapshot has been preserved. \\n`) +\n `You can provide an 'onError' handler on the shapeOptions to handle this error, and this message will not be logged.`,\n errorParams,\n )\n }\n\n return\n },\n })\n let transactionStarted = false\n const newTxids = new Set<Txid>()\n const newSnapshots: Array<PostgresSnapshot> = []\n // Track if we've completed initial sync in progressive mode. A persisted\n // resume starts from an already-committed stream offset, so the next\n // up-to-date message must not run the initial atomic swap again.\n let hasReceivedUpToDate =\n syncMode === `progressive` && canUsePersistedResume\n\n // Progressive mode state\n // Helper to determine if we're buffering the initial sync\n const isBufferingInitialSync = () =>\n syncMode === `progressive` && !hasReceivedUpToDate\n const bufferedMessages: Array<Message<T>> = [] // Buffer change messages during initial sync\n\n // Track keys that have been synced to handle overlapping subset queries.\n // When multiple subset queries return the same row, the server sends `insert`\n // for each response. We convert subsequent inserts to updates to avoid\n // duplicate key errors when the row's data has changed between requests.\n const syncedKeys = new Set<string | number>()\n\n const stageResumeMetadata = () => {\n const shapeHandle = stream.shapeHandle\n const lastOffset = stream.lastOffset\n if (!shapeHandle || lastOffset === `-1`) {\n return\n }\n\n const resumeState: ElectricResumeState = {\n kind: `resume`,\n offset: lastOffset,\n handle: shapeHandle,\n shapeId: shapeIdentity,\n updatedAt: Date.now(),\n }\n hydratedResumeState.setState(() => resumeState)\n metadata?.collection.set(`electric:resume`, resumeState)\n }\n\n const commitResetResumeMetadataImmediately = () => {\n const resetState: ElectricResumeState = {\n kind: `reset`,\n updatedAt: Date.now(),\n }\n hydratedResumeState.setState(() => resetState)\n\n if (metadata) {\n begin({ immediate: true })\n metadata.collection.set(`electric:resume`, resetState)\n commit()\n }\n }\n\n if (hasIncompatiblePersistedResume) {\n commitResetResumeMetadataImmediately()\n }\n\n /**\n * Process a change message: handle tags and write the mutation\n */\n const processChangeMessage = (changeMessage: Message<T>) => {\n if (!isChangeMessage(changeMessage)) {\n return\n }\n\n // Process tags if present\n const tags = changeMessage.headers.tags\n const removedTags = changeMessage.headers.removed_tags\n const hasTags = tags || removedTags\n\n // Extract active_conditions from headers (DNF support)\n const activeConditions = changeMessage.headers.active_conditions as\n | ActiveConditions\n | undefined\n\n const rowId = collection.getKeyFromItem(changeMessage.value)\n const operation = changeMessage.headers.operation\n\n // Track synced keys and handle overlapping subset queries.\n // When multiple subset queries return the same row, the server sends\n // `insert` for each response. We convert subsequent inserts to updates\n // to avoid duplicate key errors when the row's data has changed.\n const isDelete = operation === `delete`\n const isDuplicateInsert =\n operation === `insert` && syncedKeys.has(rowId)\n\n if (isDelete) {\n syncedKeys.delete(rowId)\n } else {\n syncedKeys.add(rowId)\n }\n\n if (isDelete) {\n clearTagsForRow(rowId)\n } else if (hasTags) {\n processTagsForChangeMessage(\n tags,\n removedTags,\n rowId,\n activeConditions,\n )\n }\n\n write({\n type: isDuplicateInsert ? `update` : operation,\n value: changeMessage.value,\n // Include the primary key and relation info in the metadata\n metadata: {\n ...changeMessage.headers,\n },\n })\n }\n\n // Create deduplicated loadSubset wrapper for non-eager modes\n // This prevents redundant snapshot requests when multiple concurrent\n // live queries request overlapping or subset predicates\n const loadSubsetDedupe = createLoadSubsetDedupe({\n stream,\n syncMode,\n isBufferingInitialSync,\n begin,\n write,\n commit,\n getCommitCursor: () => commitSequence,\n waitForCommitsAfter,\n collectionId,\n // Pass the columnMapper's encode function to transform column names\n // (e.g., camelCase to snake_case) when compiling SQL for subset queries\n encodeColumnName: shapeOptions.columnMapper?.encode,\n // Pass abort signal so requestSnapshot errors can be ignored during cleanup\n signal: abortController.signal,\n })\n\n unsubscribeStream = stream.subscribe((messages: Array<Message<T>>) => {\n // Track commit point type - up-to-date takes precedence as it also triggers progressive mode atomic swap\n let commitPoint: `up-to-date` | `subset-end` | null = null\n\n // Don't clear the buffer between batches - this preserves messages for awaitMatch\n // to find even if multiple batches arrive before awaitMatch is called.\n // The buffer is naturally limited by MAX_BATCH_MESSAGES (oldest messages are dropped).\n // Reset batchCommitted since we're starting a new batch\n batchCommitted.setState(() => false)\n\n for (const message of messages) {\n // Add message to current batch buffer (for race condition handling)\n if (\n isChangeMessage(message) ||\n isMoveOutMessage(message) ||\n isMoveInMessage(message)\n ) {\n currentBatchMessages.setState((currentBuffer) => {\n const newBuffer = [...currentBuffer, message]\n // Limit buffer size for safety\n if (newBuffer.length > MAX_BATCH_MESSAGES) {\n newBuffer.splice(0, newBuffer.length - MAX_BATCH_MESSAGES)\n }\n return newBuffer\n })\n }\n\n // Check for txids in the message and add them to our store\n // Skip during buffered initial sync in progressive mode (txids will be extracted during atomic swap)\n // EXCEPTION: If a transaction is already started (e.g., from must-refetch), track txids\n // to avoid losing them when messages are written to the existing transaction.\n if (\n hasTxids(message) &&\n (!isBufferingInitialSync() || transactionStarted)\n ) {\n message.headers.txids?.forEach((txid) => newTxids.add(txid))\n }\n\n // Check pending matches against this message\n // Note: matchFn will mark matches internally, we don't resolve here\n const matchesToRemove: Array<string> = []\n pendingMatches.state.forEach((match, matchId) => {\n if (!match.matched) {\n try {\n match.matchFn(message)\n } catch (err) {\n // If matchFn throws, clean up and reject the promise\n clearTimeout(match.timeoutId)\n match.reject(\n err instanceof Error ? err : new Error(String(err)),\n )\n matchesToRemove.push(matchId)\n debug(`matchFn error: %o`, err)\n }\n }\n })\n\n // Remove matches that errored\n removePendingMatches(matchesToRemove)\n\n if (isChangeMessage(message)) {\n // Check if the message contains schema information\n const schema = message.headers.schema\n if (schema && typeof schema === `string`) {\n // Store the schema for future use if it's a valid string\n relationSchema.setState(() => schema)\n }\n\n // In buffered initial sync of progressive mode, buffer messages instead of writing\n // EXCEPTION: If a transaction is already started (e.g., from must-refetch), write\n // directly to it instead of buffering. This prevents orphan transactions.\n if (isBufferingInitialSync() && !transactionStarted) {\n bufferedMessages.push(message)\n } else {\n // Normal processing: write changes immediately\n if (!transactionStarted) {\n begin()\n transactionStarted = true\n }\n\n processChangeMessage(message)\n }\n } else if (isSnapshotEndMessage(message)) {\n // Track postgres snapshot metadata for resolving awaiting mutations\n // Skip during buffered initial sync (will be extracted during atomic swap)\n // EXCEPTION: If a transaction is already started (e.g., from must-refetch), track snapshots\n // to avoid losing them when messages are written to the existing transaction.\n if (!isBufferingInitialSync() || transactionStarted) {\n newSnapshots.push(parseSnapshotMessage(message))\n }\n } else if (isUpToDateMessage(message)) {\n // up-to-date takes precedence - also triggers progressive mode atomic swap\n commitPoint = `up-to-date`\n } else if (isSubsetEndMessage(message)) {\n // subset-end triggers commit but not progressive mode atomic swap\n if (commitPoint !== `up-to-date`) {\n commitPoint = `subset-end`\n }\n } else if (isMoveOutMessage(message)) {\n // Handle move-out event: buffer if buffering, otherwise process immediately\n // EXCEPTION: If a transaction is already started (e.g., from must-refetch), process\n // immediately to avoid orphan transactions.\n if (isBufferingInitialSync() && !transactionStarted) {\n bufferedMessages.push(message)\n } else {\n // Normal processing: process move-out immediately\n transactionStarted = processMoveOutEvent(\n message.headers.patterns,\n begin,\n write,\n transactionStarted,\n )\n }\n } else if (isMoveInMessage(message)) {\n // Handle move-in event: re-activate conditions for matching rows.\n // Buffer if buffering, otherwise process immediately.\n if (isBufferingInitialSync() && !transactionStarted) {\n bufferedMessages.push(message)\n } else {\n processMoveInEvent(message.headers.patterns)\n }\n } else if (isMustRefetchMessage(message)) {\n debug(\n `${collectionId ? `[${collectionId}] ` : ``}Received must-refetch message, starting transaction with truncate`,\n )\n\n commitResetResumeMetadataImmediately()\n\n // Start a transaction and truncate the collection\n if (!transactionStarted) {\n begin()\n transactionStarted = true\n }\n\n truncate()\n\n // Clear tag tracking state\n clearTagTrackingState()\n\n // Clear synced keys tracking since we're starting fresh\n syncedKeys.clear()\n\n // Reset the loadSubset deduplication state since we're starting fresh\n // This ensures that previously loaded predicates don't prevent refetching after truncate\n loadSubsetDedupe?.reset()\n\n // Reset flags so we continue accumulating changes until next up-to-date\n commitPoint = null\n hasReceivedUpToDate = false // Reset for progressive mode (isBufferingInitialSync will reflect this)\n bufferedMessages.length = 0 // Clear buffered messages\n }\n }\n\n if (commitPoint !== null) {\n let applied: SyncAppliedReceipt = true\n const wasBufferingInitialSync = isBufferingInitialSync()\n // PROGRESSIVE MODE: Atomic swap on first up-to-date (not subset-end)\n // EXCEPTION: Skip atomic swap if a transaction is already started (e.g., from must-refetch).\n // In that case, do a normal commit to properly close the existing transaction.\n if (\n isBufferingInitialSync() &&\n commitPoint === `up-to-date` &&\n !transactionStarted\n ) {\n debug(\n `${collectionId ? `[${collectionId}] ` : ``}Progressive mode: Performing atomic swap with ${bufferedMessages.length} buffered messages`,\n )\n\n // Start atomic swap transaction\n begin()\n\n // Truncate to clear all snapshot data\n truncate()\n\n // Clear tag tracking state for atomic swap\n clearTagTrackingState()\n\n // Clear synced keys tracking for atomic swap\n syncedKeys.clear()\n\n // Apply all buffered change messages and extract txids/snapshots\n for (const bufferedMsg of bufferedMessages) {\n if (isChangeMessage(bufferedMsg)) {\n processChangeMessage(bufferedMsg)\n\n // Extract txids from buffered messages (will be committed to store after transaction)\n if (hasTxids(bufferedMsg)) {\n bufferedMsg.headers.txids?.forEach((txid) =>\n newTxids.add(txid),\n )\n }\n } else if (isSnapshotEndMessage(bufferedMsg)) {\n // Extract snapshots from buffered messages (will be committed to store after transaction)\n newSnapshots.push(parseSnapshotMessage(bufferedMsg))\n } else if (isMoveOutMessage(bufferedMsg)) {\n // Process buffered move-out messages during atomic swap\n processMoveOutEvent(\n bufferedMsg.headers.patterns,\n begin,\n write,\n transactionStarted,\n )\n } else if (isMoveInMessage(bufferedMsg)) {\n // Process buffered move-in messages during atomic swap\n processMoveInEvent(bufferedMsg.headers.patterns)\n }\n }\n\n // Commit the atomic swap\n stageResumeMetadata()\n applied = commit()\n\n // Exit buffering phase by marking that we've received up-to-date\n // isBufferingInitialSync() will now return false\n bufferedMessages.length = 0\n\n debug(\n `${collectionId ? `[${collectionId}] ` : ``}Progressive mode: Atomic swap complete, now in normal sync mode`,\n )\n } else {\n // Normal mode or on-demand: commit transaction if one was started\n // Both up-to-date and subset-end trigger a commit\n if (transactionStarted) {\n stageResumeMetadata()\n applied = commit()\n transactionStarted = false\n } else if (commitPoint === `up-to-date` && metadata) {\n begin()\n stageResumeMetadata()\n applied = commit()\n }\n }\n const readyErrorVersion = streamErrorVersion\n if (applied === true) {\n wrappedMarkReady(wasBufferingInitialSync, readyErrorVersion)\n } else {\n void applied.then(\n () =>\n wrappedMarkReady(wasBufferingInitialSync, readyErrorVersion),\n () => undefined,\n )\n }\n\n // Track that we've received the first up-to-date for progressive mode\n if (commitPoint === `up-to-date`) {\n hasReceivedUpToDate = true\n }\n\n // Always commit txids when we receive up-to-date, regardless of transaction state\n seenTxids.setState((currentTxids) => {\n const clonedSeen = new Set<Txid>(currentTxids)\n if (newTxids.size > 0) {\n debug(\n `${collectionId ? `[${collectionId}] ` : ``}new txids synced from pg %O`,\n Array.from(newTxids),\n )\n }\n newTxids.forEach((txid) => clonedSeen.add(txid))\n newTxids.clear()\n return clonedSeen\n })\n\n // Always commit snapshots when we receive up-to-date, regardless of transaction state\n seenSnapshots.setState((currentSnapshots) => {\n const seen = [...currentSnapshots, ...newSnapshots]\n newSnapshots.forEach((snapshot) =>\n debug(\n `${collectionId ? `[${collectionId}] ` : ``}new snapshot synced from pg %o`,\n snapshot,\n ),\n )\n newSnapshots.length = 0\n return seen\n })\n\n // Resolve all matched pending matches on up-to-date or subset-end\n // Set batchCommitted BEFORE resolving to avoid timing window where late awaitMatch\n // calls could register as \"matched\" after resolver pass already ran\n batchCommitted.setState(() => true)\n\n resolveMatchedPendingMatches()\n }\n })\n\n // Return the deduplicated loadSubset if available (on-demand or progressive mode)\n // The loadSubset method is auto-bound, so it can be safely returned directly\n return {\n loadSubset: loadSubsetDedupe?.loadSubset,\n cleanup: () => {\n // Unsubscribe from the stream\n unsubscribeStream()\n // Abort the abort controller to stop the stream\n abortController.abort()\n // Reset deduplication tracking so collection can load fresh data if restarted\n loadSubsetDedupe?.reset()\n hydratedResumeState.setState(() => undefined)\n },\n }\n },\n // Expose the getSyncMetadata function\n getSyncMetadata,\n }\n}\n"],"names":["parseTag","parseTagString","persistedResumeState"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"electric.js","sources":["../../src/electric.ts"],"sourcesContent":["import {\n ShapeStream,\n isChangeMessage,\n isControlMessage,\n isVisibleInSnapshot,\n} from '@electric-sql/client'\nimport { Store } from '@tanstack/store'\nimport DebugModule from 'debug'\nimport {\n DeduplicatedLoadSubset,\n LoadSubsetOperationAbortedError,\n and,\n withCollectionConfigFactory,\n} from '@tanstack/db'\nimport {\n ExpectedNumberInAwaitTxIdError,\n StreamAbortedError,\n TimeoutWaitingForMatchError,\n TimeoutWaitingForTxIdError,\n} from './errors'\nimport { compileSQL } from './sql-compiler'\nimport {\n addTagToIndex,\n deriveDisjunctPositions,\n findRowsMatchingPattern,\n getTagLength,\n isMoveInMessage,\n isMoveOutMessage,\n parseTag as parseTagString,\n removeTagFromIndex,\n rowVisible,\n tagMatchesPattern,\n} from './tag-index'\nimport type { ColumnEncoder } from './sql-compiler'\nimport type {\n ActiveConditions,\n DisjunctPositions,\n MovePattern,\n MoveTag,\n ParsedMoveTag,\n RowId,\n TagIndex,\n} from './tag-index'\nimport type {\n BaseCollectionConfig,\n ChangeMessageOrDeleteKeyMessage,\n CollectionConfig,\n DeleteMutationFnParams,\n InsertMutationFnParams,\n LoadSubsetOptions,\n SyncAppliedReceipt,\n SyncConfig,\n SyncMode,\n UpdateMutationFnParams,\n UtilsRecord,\n} from '@tanstack/db'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type {\n ControlMessage,\n GetExtensions,\n Message,\n Offset,\n PostgresSnapshot,\n Row,\n ShapeStreamOptions,\n} from '@electric-sql/client'\n\n// Re-export for user convenience in custom match functions\nexport { isChangeMessage, isControlMessage } from '@electric-sql/client'\n\nconst debug = DebugModule.debug(`ts/db:electric`)\n\nconst FORCE_DISCONNECT_AND_REFRESH_TIMEOUT_MS = 250\n\n/**\n * Symbol for internal test hooks (hidden from public API)\n */\nexport const ELECTRIC_TEST_HOOKS = Symbol(`electricTestHooks`)\n\n/**\n * Internal test hooks interface (for testing only)\n */\nexport interface ElectricTestHooks {\n /**\n * Called before marking collection ready after first up-to-date in progressive mode\n * Allows tests to pause and validate snapshot phase before atomic swap completes\n */\n beforeMarkingReady?: () => Promise<void>\n}\n\n/**\n * Type representing a transaction ID in ElectricSQL\n */\nexport type Txid = number\n\ntype ElectricResumeState =\n | {\n kind: `resume`\n offset: string\n handle: string\n shapeId: string\n updatedAt: number\n }\n | {\n kind: `reset`\n updatedAt: number\n }\n\ntype ElectricSyncMeta = {\n version: 1\n resume?: ElectricResumeState\n seenTxids: Array<Txid>\n}\n\nfunction parseElectricResumeState(\n value: unknown,\n): ElectricResumeState | undefined {\n if (!value || typeof value !== `object`) {\n return undefined\n }\n\n const record = value as Record<string, unknown>\n if (\n record.kind === `resume` &&\n typeof record.offset === `string` &&\n typeof record.handle === `string` &&\n typeof record.shapeId === `string` &&\n typeof record.updatedAt === `number` &&\n Number.isFinite(record.updatedAt)\n ) {\n return {\n kind: `resume`,\n offset: record.offset,\n handle: record.handle,\n shapeId: record.shapeId,\n updatedAt: record.updatedAt,\n }\n }\n\n if (\n record.kind === `reset` &&\n typeof record.updatedAt === `number` &&\n Number.isFinite(record.updatedAt)\n ) {\n return {\n kind: `reset`,\n updatedAt: record.updatedAt,\n }\n }\n\n return undefined\n}\n\nfunction parseElectricSyncMeta(value: unknown): ElectricSyncMeta | undefined {\n if (!value || typeof value !== `object`) {\n return undefined\n }\n\n const record = value as Record<string, unknown>\n if (\n record.version !== 1 ||\n !Array.isArray(record.seenTxids) ||\n !record.seenTxids.every(\n (txid) => typeof txid === `number` && Number.isFinite(txid),\n )\n ) {\n return undefined\n }\n\n const resume =\n record.resume === undefined\n ? undefined\n : parseElectricResumeState(record.resume)\n if (record.resume !== undefined && resume === undefined) {\n return undefined\n }\n\n return {\n version: 1,\n ...(resume ? { resume } : {}),\n seenTxids: Array.from(new Set(record.seenTxids)).sort((a, b) => a - b),\n }\n}\n\nfunction mergeElectricSyncMeta(\n current: unknown,\n incoming: unknown,\n): ElectricSyncMeta | unknown {\n const currentMeta = parseElectricSyncMeta(current)\n const incomingMeta = parseElectricSyncMeta(incoming)\n\n if (!incomingMeta) {\n return current\n }\n if (!currentMeta) {\n return incomingMeta\n }\n\n const resume = getNewestElectricResumeState(\n currentMeta.resume,\n incomingMeta.resume,\n )\n\n return {\n version: 1,\n ...(resume ? { resume } : {}),\n seenTxids: Array.from(\n new Set([...currentMeta.seenTxids, ...incomingMeta.seenTxids]),\n ).sort((a, b) => a - b),\n }\n}\n\nfunction getNewestElectricResumeState(\n current: ElectricResumeState | undefined,\n incoming: ElectricResumeState | undefined,\n): ElectricResumeState | undefined {\n if (!current) return incoming\n if (!incoming) return current\n return incoming.updatedAt >= current.updatedAt ? incoming : current\n}\n\n/**\n * Custom match function type - receives stream messages and returns boolean\n * indicating if the mutation has been synchronized\n */\nexport type MatchFunction<T extends Row<unknown>> = (\n message: Message<T>,\n) => boolean\n\n/**\n * Matching strategies for Electric synchronization\n * Handlers can return:\n * - Txid strategy: { txid: number | number[], timeout?: number } (recommended)\n * - Void (no return value) - mutation completes without waiting\n *\n * The optional timeout property specifies how long to wait for the txid(s) in milliseconds.\n * If not specified, defaults to 5000ms.\n */\nexport type MatchingStrategy = {\n txid: Txid | Array<Txid>\n timeout?: number\n} | void\n\n/**\n * Type representing a snapshot end message\n */\ntype SnapshotEndMessage = ControlMessage & {\n headers: { control: `snapshot-end` }\n}\n// The `InferSchemaOutput` and `ResolveType` are copied from the `@tanstack/db` package\n// but we modified `InferSchemaOutput` slightly to restrict the schema output to `Row<unknown>`\n// This is needed in order for `GetExtensions` to be able to infer the parser extensions type from the schema\ntype InferSchemaOutput<T> = T extends StandardSchemaV1\n ? StandardSchemaV1.InferOutput<T> extends Row<unknown>\n ? StandardSchemaV1.InferOutput<T>\n : Record<string, unknown>\n : Record<string, unknown>\n\n/**\n * The mode of sync to use for the collection.\n * @default `eager`\n * @description\n * - `eager`:\n * - syncs all data immediately on preload\n * - collection will be marked as ready once the sync is complete\n * - there is no incremental sync\n * - `on-demand`:\n * - syncs data in incremental snapshots when the collection is queried\n * - collection will be marked as ready immediately after the first snapshot is synced\n * - `progressive`:\n * - syncs all data for the collection in the background\n * - uses incremental snapshots during the initial sync to provide a fast path to the data required for queries\n * - collection will be marked as ready once the full sync is complete\n */\nexport type ElectricSyncMode = SyncMode | `progressive`\n\n/**\n * Configuration interface for Electric collection options\n * @template T - The type of items in the collection\n * @template TSchema - The schema type for validation\n */\nexport interface ElectricCollectionConfig<\n T extends Row<unknown> = Row<unknown>,\n TSchema extends StandardSchemaV1 = never,\n> extends Omit<\n BaseCollectionConfig<\n T,\n string | number,\n TSchema,\n ElectricCollectionUtils<T>,\n any\n >,\n `onInsert` | `onUpdate` | `onDelete` | `syncMode`\n> {\n /**\n * Configuration options for the ElectricSQL ShapeStream\n */\n shapeOptions: ShapeStreamOptions<GetExtensions<T>>\n syncMode?: ElectricSyncMode\n\n /**\n * Internal test hooks (for testing only)\n * Hidden via Symbol to prevent accidental usage in production\n */\n [ELECTRIC_TEST_HOOKS]?: ElectricTestHooks\n\n /**\n * Optional asynchronous handler function called before an insert operation\n * @param params Object containing transaction and collection information\n * @returns Promise resolving to { txid, timeout? } or void\n * @example\n * // Basic Electric insert handler with txid (recommended)\n * onInsert: async ({ transaction }) => {\n * const newItem = transaction.mutations[0].modified\n * const result = await api.todos.create({\n * data: newItem\n * })\n * return { txid: result.txid }\n * }\n *\n * @example\n * // Insert handler with custom timeout\n * onInsert: async ({ transaction }) => {\n * const newItem = transaction.mutations[0].modified\n * const result = await api.todos.create({\n * data: newItem\n * })\n * return { txid: result.txid, timeout: 10000 } // Wait up to 10 seconds\n * }\n *\n * @example\n * // Insert handler with multiple items - return array of txids\n * onInsert: async ({ transaction }) => {\n * const items = transaction.mutations.map(m => m.modified)\n * const results = await Promise.all(\n * items.map(item => api.todos.create({ data: item }))\n * )\n * return { txid: results.map(r => r.txid) }\n * }\n *\n * @example\n * // Use awaitMatch utility for custom matching\n * onInsert: async ({ transaction, collection }) => {\n * const newItem = transaction.mutations[0].modified\n * await api.todos.create({ data: newItem })\n * await collection.utils.awaitMatch(\n * (message) => isChangeMessage(message) &&\n * message.headers.operation === 'insert' &&\n * message.value.name === newItem.name\n * )\n * }\n */\n onInsert?: (\n params: InsertMutationFnParams<\n T,\n string | number,\n ElectricCollectionUtils<T>\n >,\n ) => Promise<MatchingStrategy>\n\n /**\n * Optional asynchronous handler function called before an update operation\n * @param params Object containing transaction and collection information\n * @returns Promise resolving to { txid, timeout? } or void\n * @example\n * // Basic Electric update handler with txid (recommended)\n * onUpdate: async ({ transaction }) => {\n * const { original, changes } = transaction.mutations[0]\n * const result = await api.todos.update({\n * where: { id: original.id },\n * data: changes\n * })\n * return { txid: result.txid }\n * }\n *\n * @example\n * // Use awaitMatch utility for custom matching\n * onUpdate: async ({ transaction, collection }) => {\n * const { original, changes } = transaction.mutations[0]\n * await api.todos.update({ where: { id: original.id }, data: changes })\n * await collection.utils.awaitMatch(\n * (message) => isChangeMessage(message) &&\n * message.headers.operation === 'update' &&\n * message.value.id === original.id\n * )\n * }\n */\n onUpdate?: (\n params: UpdateMutationFnParams<\n T,\n string | number,\n ElectricCollectionUtils<T>\n >,\n ) => Promise<MatchingStrategy>\n\n /**\n * Optional asynchronous handler function called before a delete operation\n * @param params Object containing transaction and collection information\n * @returns Promise resolving to { txid, timeout? } or void\n * @example\n * // Basic Electric delete handler with txid (recommended)\n * onDelete: async ({ transaction }) => {\n * const mutation = transaction.mutations[0]\n * const result = await api.todos.delete({\n * id: mutation.original.id\n * })\n * return { txid: result.txid }\n * }\n *\n * @example\n * // Use awaitMatch utility for custom matching\n * onDelete: async ({ transaction, collection }) => {\n * const mutation = transaction.mutations[0]\n * await api.todos.delete({ id: mutation.original.id })\n * await collection.utils.awaitMatch(\n * (message) => isChangeMessage(message) &&\n * message.headers.operation === 'delete' &&\n * message.value.id === mutation.original.id\n * )\n * }\n */\n onDelete?: (\n params: DeleteMutationFnParams<\n T,\n string | number,\n ElectricCollectionUtils<T>\n >,\n ) => Promise<MatchingStrategy>\n}\n\nfunction isUpToDateMessage<T extends Row<unknown>>(\n message: Message<T>,\n): message is ControlMessage & { up_to_date: true } {\n return isControlMessage(message) && message.headers.control === `up-to-date`\n}\n\nfunction isMustRefetchMessage<T extends Row<unknown>>(\n message: Message<T>,\n): message is ControlMessage & { headers: { control: `must-refetch` } } {\n return isControlMessage(message) && message.headers.control === `must-refetch`\n}\n\nfunction isSnapshotEndMessage<T extends Row<unknown>>(\n message: Message<T>,\n): message is SnapshotEndMessage {\n return isControlMessage(message) && message.headers.control === `snapshot-end`\n}\n\nfunction isSubsetEndMessage<T extends Row<unknown>>(\n message: Message<T>,\n): message is ControlMessage & { headers: { control: `subset-end` } } {\n return (\n isControlMessage(message) &&\n (message.headers.control as string) === `subset-end`\n )\n}\n\nfunction parseSnapshotMessage(message: SnapshotEndMessage): PostgresSnapshot {\n return {\n xmin: message.headers.xmin,\n xmax: message.headers.xmax,\n xip_list: message.headers.xip_list,\n }\n}\n\nfunction toStableSerializable(value: unknown): unknown {\n if (value == null) {\n return value\n }\n\n if (Array.isArray(value)) {\n return value.map((entry) => toStableSerializable(entry))\n }\n\n if (value instanceof Date) {\n return value.toISOString()\n }\n\n if (typeof value === `object`) {\n const record = value as Record<string, unknown>\n const stableRecord: Record<string, unknown> = {}\n const keys = Object.keys(record).sort((left, right) =>\n left < right ? -1 : left > right ? 1 : 0,\n )\n for (const key of keys) {\n stableRecord[key] = toStableSerializable(record[key])\n }\n return stableRecord\n }\n\n return value\n}\n\nfunction getStableShapeIdentity(shapeOptions: {\n url: string\n params?: Record<string, unknown>\n}): string {\n return JSON.stringify(\n toStableSerializable({\n url: shapeOptions.url,\n params: shapeOptions.params ?? null,\n }),\n )\n}\n\n// Check if a message contains txids in its headers\nfunction hasTxids<T extends Row<unknown>>(\n message: Message<T>,\n): message is Message<T> & { headers: { txids?: Array<Txid> } } {\n return `txids` in message.headers && Array.isArray(message.headers.txids)\n}\n\n/**\n * Creates a deduplicated loadSubset handler for progressive/on-demand modes\n * Returns null for eager mode, or a DeduplicatedLoadSubset instance for other modes.\n * Handles fetching snapshots in progressive mode during buffering phase,\n * and requesting snapshots in on-demand mode.\n *\n * When cursor expressions are provided (whereFrom/whereCurrent), makes two\n * requestSnapshot calls:\n * - One for whereFrom (rows > cursor) with limit\n * - One for whereCurrent (rows = cursor, for tie-breaking) without limit\n */\nfunction createLoadSubsetDedupe<T extends Row<unknown>>({\n stream,\n syncMode,\n isBufferingInitialSync,\n begin,\n write,\n commit,\n getCommitCursor,\n waitForCommitsAfter,\n collectionId,\n encodeColumnName,\n signal,\n}: {\n stream: ShapeStream<T>\n syncMode: ElectricSyncMode\n isBufferingInitialSync: () => boolean\n begin: () => void\n write: (mutation: {\n type: `insert` | `update` | `delete`\n value: T\n metadata: Record<string, unknown>\n }) => void\n commit: (signal?: AbortSignal) => SyncAppliedReceipt\n getCommitCursor: () => number\n waitForCommitsAfter: (cursor: number) => Promise<void>\n collectionId?: string\n /**\n * Optional function to encode column names (e.g., camelCase to snake_case).\n * This is typically the `encode` function from shapeOptions.columnMapper.\n */\n encodeColumnName?: ColumnEncoder\n /**\n * Abort signal to check if the stream has been aborted during cleanup.\n * When aborted, errors from requestSnapshot are silently ignored.\n */\n signal: AbortSignal\n}): DeduplicatedLoadSubset | null {\n if (syncMode === `eager`) {\n return null\n }\n\n const compileOptions = encodeColumnName ? { encodeColumnName } : undefined\n const logPrefix = collectionId ? `[${collectionId}] ` : ``\n\n const abortReason = (abortedSignal: AbortSignal): unknown =>\n abortedSignal.reason ?? new LoadSubsetOperationAbortedError()\n\n /**\n * Handles errors from snapshot operations. Returns true if the error was\n * handled (signal aborted during cleanup), false if it should be re-thrown.\n */\n function handleSnapshotError(error: unknown, operation: string): boolean {\n if (signal.aborted) {\n debug(`${logPrefix}Ignoring ${operation} error during cleanup: %o`, error)\n return true\n }\n debug(`${logPrefix}Error in ${operation}: %o`, error)\n return false\n }\n\n const loadSubset = async (opts: LoadSubsetOptions) => {\n const commitCursor = getCommitCursor()\n const throwIfAborted = () => {\n if (signal.aborted) throw abortReason(signal)\n if (opts.signal?.aborted) throw abortReason(opts.signal)\n }\n throwIfAborted()\n\n if (isBufferingInitialSync()) {\n const snapshotParams = compileSQL<T>(opts, compileOptions)\n try {\n const { data: rows } = await stream.fetchSnapshot(snapshotParams)\n if (opts.signal?.aborted || !isBufferingInitialSync()) {\n debug(`${logPrefix}Ignoring snapshot - sync completed while fetching`)\n return\n }\n\n if (rows.length > 0) {\n begin()\n for (const row of rows) {\n write({\n type: `insert`,\n value: row.value,\n metadata: { ...row.headers },\n })\n }\n await commit(opts.signal)\n debug(`${logPrefix}Applied snapshot with ${rows.length} rows`)\n }\n } catch (error) {\n if (opts.signal?.aborted) return\n if (handleSnapshotError(error, `fetchSnapshot`)) {\n return\n }\n throw error\n }\n return\n }\n\n if (syncMode === `progressive`) {\n return\n }\n\n const { cursor, where, orderBy, limit } = opts\n\n // When the stream is already up-to-date, it may be in a long-poll wait.\n // Forcing a disconnect-and-refresh ensures requestSnapshot gets a response\n // from a fresh server round-trip rather than waiting for the current poll to end.\n // Some native fetch implementations (notably React Native/Expo) may not abort\n // long-poll requests promptly. Bound the wait so on-demand live queries don't\n // remain loading until the long-poll naturally times out.\n // If the refresh fails or times out, we fall through to requestSnapshot which\n // still works.\n if (stream.isUpToDate) {\n let timeoutId: ReturnType<typeof setTimeout> | undefined\n const abortSignals = [signal, opts.signal].filter(\n (candidate): candidate is AbortSignal => candidate !== undefined,\n )\n let rejectAbort: (reason: unknown) => void = () => {}\n const aborted = new Promise<never>((_resolve, reject) => {\n rejectAbort = reject\n })\n const abort = (event: Event) =>\n rejectAbort(abortReason(event.currentTarget as AbortSignal))\n for (const abortSignal of abortSignals) {\n abortSignal.addEventListener(`abort`, abort, { once: true })\n }\n try {\n await Promise.race([\n stream.forceDisconnectAndRefresh(),\n new Promise<void>((resolve) => {\n timeoutId = setTimeout(\n resolve,\n FORCE_DISCONNECT_AND_REFRESH_TIMEOUT_MS,\n )\n }),\n aborted,\n ])\n } catch (error) {\n if (signal.aborted || opts.signal?.aborted) throw error\n if (handleSnapshotError(error, `forceDisconnectAndRefresh`)) {\n return\n }\n debug(\n `${logPrefix}forceDisconnectAndRefresh failed, proceeding to requestSnapshot: %o`,\n error,\n )\n } finally {\n clearTimeout(timeoutId)\n for (const abortSignal of abortSignals) {\n abortSignal.removeEventListener(`abort`, abort)\n }\n }\n }\n\n throwIfAborted()\n\n // Upstream limitation: ShapeStream.requestSnapshot() publishes its rows\n // through the stream callback before its Promise resolves. It accepts no\n // request signal and exposes no request identity on those messages, so an\n // aborted request can already have installed rows before the check below.\n // Full request-scoped cancellation requires support in the Electric client;\n // matching snapshots by parameters is unsafe for overlapping equal requests.\n try {\n if (cursor) {\n const whereCurrentOpts: LoadSubsetOptions = {\n where: where ? and(where, cursor.whereCurrent) : cursor.whereCurrent,\n orderBy,\n }\n const whereCurrentParams = compileSQL<T>(\n whereCurrentOpts,\n compileOptions,\n )\n\n const whereFromOpts: LoadSubsetOptions = {\n where: where ? and(where, cursor.whereFrom) : cursor.whereFrom,\n orderBy,\n limit,\n }\n const whereFromParams = compileSQL<T>(whereFromOpts, compileOptions)\n\n debug(`${logPrefix}Requesting cursor.whereCurrent snapshot (all ties)`)\n debug(\n `${logPrefix}Requesting cursor.whereFrom snapshot (with limit ${limit})`,\n )\n\n await Promise.all([\n stream.requestSnapshot(whereCurrentParams),\n stream.requestSnapshot(whereFromParams),\n ])\n } else {\n const snapshotParams = compileSQL<T>(opts, compileOptions)\n await stream.requestSnapshot(snapshotParams)\n }\n } catch (error) {\n if (opts.signal?.aborted) return\n if (handleSnapshotError(error, `requestSnapshot`)) {\n return\n }\n throw error\n }\n await waitForCommitsAfter(commitCursor)\n }\n\n return new DeduplicatedLoadSubset({ loadSubset })\n}\n\n/**\n * Type for the awaitTxId utility function\n */\nexport type AwaitTxIdFn = (txId: Txid, timeout?: number) => Promise<boolean>\n\n/**\n * Type for the awaitMatch utility function\n */\nexport type AwaitMatchFn<T extends Row<unknown>> = (\n matchFn: MatchFunction<T>,\n timeout?: number,\n) => Promise<boolean>\n\n/**\n * Electric collection utilities type\n */\nexport interface ElectricCollectionUtils<\n T extends Row<unknown> = Row<unknown>,\n> extends UtilsRecord {\n awaitTxId: AwaitTxIdFn\n awaitMatch: AwaitMatchFn<T>\n}\n\n/**\n * Creates Electric collection options for use with a standard Collection\n *\n * @template T - The explicit type of items in the collection (highest priority)\n * @template TSchema - The schema type for validation and type inference (second priority)\n * @template TFallback - The fallback type if no explicit or schema type is provided\n * @param config - Configuration options for the Electric collection\n * @returns Collection options with utilities\n */\n\n// Overload for when schema is provided\nexport function electricCollectionOptions<T extends StandardSchemaV1>(\n config: ElectricCollectionConfig<InferSchemaOutput<T>, T> & {\n schema: T\n },\n): Omit<\n CollectionConfig<InferSchemaOutput<T>, string | number, T>,\n `utils` | `onInsert` | `onUpdate` | `onDelete`\n> &\n Pick<\n ElectricCollectionConfig<InferSchemaOutput<T>, T>,\n `onInsert` | `onUpdate` | `onDelete`\n > & {\n id?: string\n utils: ElectricCollectionUtils<InferSchemaOutput<T>>\n schema: T\n }\n\n// Overload for when no schema is provided\nexport function electricCollectionOptions<T extends Row<unknown>>(\n config: ElectricCollectionConfig<T> & {\n schema?: never // prohibit schema\n },\n): Omit<\n CollectionConfig<T, string | number>,\n `utils` | `onInsert` | `onUpdate` | `onDelete`\n> &\n Pick<ElectricCollectionConfig<T>, `onInsert` | `onUpdate` | `onDelete`> & {\n id?: string\n utils: ElectricCollectionUtils<T>\n schema?: never // no schema in the result\n }\n\nexport function electricCollectionOptions<T extends Row<unknown>>(\n config: ElectricCollectionConfig<T, any>,\n): Omit<\n CollectionConfig<T, string | number, any, ElectricCollectionUtils<T>>,\n `utils`\n> & {\n id?: string\n utils: ElectricCollectionUtils<T>\n schema?: any\n} {\n const seenTxids = new Store<Set<Txid>>(new Set([]))\n const seenSnapshots = new Store<Array<PostgresSnapshot>>([])\n const hydratedResumeState = new Store<ElectricResumeState | undefined>(\n undefined,\n )\n const internalSyncMode = config.syncMode ?? `eager`\n const finalSyncMode =\n internalSyncMode === `progressive` ? `on-demand` : internalSyncMode\n const pendingMatches = new Store<\n Map<\n string,\n {\n matchFn: (message: Message<any>) => boolean\n resolve: (value: boolean) => void\n reject: (error: Error) => void\n timeoutId: ReturnType<typeof setTimeout>\n matched: boolean\n }\n >\n >(new Map())\n\n // Buffer messages since last up-to-date to handle race conditions\n const currentBatchMessages = new Store<Array<Message<any>>>([])\n\n // Track whether the current batch has been committed (up-to-date received)\n // This allows awaitMatch to resolve immediately for messages from committed batches\n const batchCommitted = new Store<boolean>(false)\n\n /**\n * Helper function to remove multiple matches from the pendingMatches store\n */\n const removePendingMatches = (matchIds: Array<string>) => {\n if (matchIds.length > 0) {\n pendingMatches.setState((current) => {\n const newMatches = new Map(current)\n matchIds.forEach((id) => newMatches.delete(id))\n return newMatches\n })\n }\n }\n\n /**\n * Helper function to resolve and cleanup matched pending matches\n */\n const resolveMatchedPendingMatches = () => {\n const matchesToResolve: Array<string> = []\n pendingMatches.state.forEach((match, matchId) => {\n if (match.matched) {\n clearTimeout(match.timeoutId)\n match.resolve(true)\n matchesToResolve.push(matchId)\n debug(\n `${config.id ? `[${config.id}] ` : ``}awaitMatch resolved on up-to-date for match %s`,\n matchId,\n )\n }\n })\n removePendingMatches(matchesToResolve)\n }\n const sync = createElectricSync<T>(config.shapeOptions, {\n seenTxids,\n seenSnapshots,\n hydratedResumeState,\n syncMode: internalSyncMode,\n pendingMatches,\n currentBatchMessages,\n batchCommitted,\n removePendingMatches,\n resolveMatchedPendingMatches,\n collectionId: config.id,\n testHooks: config[ELECTRIC_TEST_HOOKS],\n })\n\n /**\n * Wait for a specific transaction ID to be synced\n * @param txId The transaction ID to wait for as a number\n * @param timeout Optional timeout in milliseconds (defaults to 5000ms)\n * @returns Promise that resolves when the txId is synced\n */\n const awaitTxId: AwaitTxIdFn = async (\n txId: Txid,\n timeout: number = 5000,\n ): Promise<boolean> => {\n debug(\n `${config.id ? `[${config.id}] ` : ``}awaitTxId called with txid %d`,\n txId,\n )\n if (typeof txId !== `number`) {\n throw new ExpectedNumberInAwaitTxIdError(typeof txId, config.id)\n }\n\n // First check if the txid is in the seenTxids store\n const hasTxid = seenTxids.state.has(txId)\n if (hasTxid) return true\n\n // Then check if the txid is in any of the seen snapshots\n const hasSnapshot = seenSnapshots.state.some((snapshot) =>\n isVisibleInSnapshot(txId, snapshot),\n )\n if (hasSnapshot) return true\n\n return new Promise((resolve, reject) => {\n const cleanup = () => {\n clearTimeout(timeoutId)\n subSeenTxids.unsubscribe()\n subSeenSnapshots.unsubscribe()\n }\n\n const timeoutId = setTimeout(() => {\n cleanup()\n reject(new TimeoutWaitingForTxIdError(txId, config.id))\n }, timeout)\n\n const subSeenTxids = seenTxids.subscribe(() => {\n if (seenTxids.state.has(txId)) {\n debug(\n `${config.id ? `[${config.id}] ` : ``}awaitTxId found match for txid %o`,\n txId,\n )\n cleanup()\n resolve(true)\n }\n })\n\n const subSeenSnapshots = seenSnapshots.subscribe(() => {\n const visibleSnapshot = seenSnapshots.state.find((snapshot) =>\n isVisibleInSnapshot(txId, snapshot),\n )\n if (visibleSnapshot) {\n debug(\n `${config.id ? `[${config.id}] ` : ``}awaitTxId found match for txid %o in snapshot %o`,\n txId,\n visibleSnapshot,\n )\n cleanup()\n resolve(true)\n }\n })\n })\n }\n\n /**\n * Wait for a custom match function to find a matching message\n * @param matchFn Function that returns true when a message matches\n * @param timeout Optional timeout in milliseconds (defaults to 5000ms)\n * @returns Promise that resolves when a matching message is found\n */\n const awaitMatch: AwaitMatchFn<any> = async (\n matchFn: MatchFunction<any>,\n timeout: number = 3000,\n ): Promise<boolean> => {\n debug(\n `${config.id ? `[${config.id}] ` : ``}awaitMatch called with custom function`,\n )\n\n return new Promise((resolve, reject) => {\n const matchId = Math.random().toString(36)\n\n const cleanupMatch = () => {\n pendingMatches.setState((current) => {\n const newMatches = new Map(current)\n newMatches.delete(matchId)\n return newMatches\n })\n }\n\n const onTimeout = () => {\n cleanupMatch()\n reject(new TimeoutWaitingForMatchError(config.id))\n }\n\n const timeoutId = setTimeout(onTimeout, timeout)\n\n // We need access to the stream messages to check against the match function\n // This will be handled by the sync configuration\n const checkMatch = (message: Message<any>) => {\n if (matchFn(message)) {\n debug(\n `${config.id ? `[${config.id}] ` : ``}awaitMatch found matching message, waiting for up-to-date`,\n )\n // Mark as matched but don't resolve yet - wait for up-to-date\n pendingMatches.setState((current) => {\n const newMatches = new Map(current)\n const existing = newMatches.get(matchId)\n if (existing) {\n newMatches.set(matchId, { ...existing, matched: true })\n }\n return newMatches\n })\n return true\n }\n return false\n }\n\n // Check against current batch messages first to handle race conditions\n for (const message of currentBatchMessages.state) {\n if (matchFn(message)) {\n // If batch is committed (up-to-date already received), resolve immediately\n // just like awaitTxId does when it finds a txid in seenTxids\n if (batchCommitted.state) {\n debug(\n `${config.id ? `[${config.id}] ` : ``}awaitMatch found immediate match in committed batch, resolving immediately`,\n )\n clearTimeout(timeoutId)\n resolve(true)\n return\n }\n\n // If batch is not yet committed, register match and wait for up-to-date\n debug(\n `${config.id ? `[${config.id}] ` : ``}awaitMatch found immediate match in current batch, waiting for up-to-date`,\n )\n pendingMatches.setState((current) => {\n const newMatches = new Map(current)\n newMatches.set(matchId, {\n matchFn: checkMatch,\n resolve,\n reject,\n timeoutId,\n matched: true, // Already matched, will resolve on up-to-date\n })\n return newMatches\n })\n return\n }\n }\n\n // Store the match function for the sync process to use\n // We'll add this to a pending matches store\n pendingMatches.setState((current) => {\n const newMatches = new Map(current)\n newMatches.set(matchId, {\n matchFn: checkMatch,\n resolve,\n reject,\n timeoutId,\n matched: false,\n })\n return newMatches\n })\n })\n }\n\n /**\n * Process matching strategy and wait for synchronization\n */\n const processMatchingStrategy = async (\n result: MatchingStrategy,\n ): Promise<void> => {\n // Only wait if result contains txid\n if (result && `txid` in result) {\n const timeout = result.timeout\n // Handle both single txid and array of txids\n if (Array.isArray(result.txid)) {\n await Promise.all(result.txid.map((txid) => awaitTxId(txid, timeout)))\n } else {\n await awaitTxId(result.txid, timeout)\n }\n }\n // If result is void/undefined, don't wait - mutation completes immediately\n }\n\n // Create wrapper handlers for direct persistence operations that handle different matching strategies\n const wrappedOnInsert = config.onInsert\n ? async (\n params: InsertMutationFnParams<\n any,\n string | number,\n ElectricCollectionUtils<T>\n >,\n ) => {\n const handlerResult = await config.onInsert!(params)\n await processMatchingStrategy(handlerResult)\n return handlerResult\n }\n : undefined\n\n const wrappedOnUpdate = config.onUpdate\n ? async (\n params: UpdateMutationFnParams<\n any,\n string | number,\n ElectricCollectionUtils<T>\n >,\n ) => {\n const handlerResult = await config.onUpdate!(params)\n await processMatchingStrategy(handlerResult)\n return handlerResult\n }\n : undefined\n\n const wrappedOnDelete = config.onDelete\n ? async (\n params: DeleteMutationFnParams<\n any,\n string | number,\n ElectricCollectionUtils<T>\n >,\n ) => {\n const handlerResult = await config.onDelete!(params)\n await processMatchingStrategy(handlerResult)\n return handlerResult\n }\n : undefined\n\n // Extract standard Collection config properties\n const {\n shapeOptions: _shapeOptions,\n onInsert: _onInsert,\n onUpdate: _onUpdate,\n onDelete: _onDelete,\n ...restConfig\n } = config\n\n const options = {\n ...restConfig,\n syncMode: finalSyncMode,\n sync: {\n ...sync,\n exportSyncMeta: (): ElectricSyncMeta => ({\n version: 1,\n ...(hydratedResumeState.state\n ? { resume: hydratedResumeState.state }\n : {}),\n seenTxids: Array.from(seenTxids.state).sort((a, b) => a - b),\n }),\n importSyncMeta: (meta: unknown): void => {\n const parsed = parseElectricSyncMeta(meta)\n if (!parsed) {\n return\n }\n\n hydratedResumeState.setState(() => parsed.resume)\n seenTxids.setState(() => new Set(parsed.seenTxids))\n },\n mergeSyncMeta: mergeElectricSyncMeta,\n },\n onInsert: wrappedOnInsert,\n onUpdate: wrappedOnUpdate,\n onDelete: wrappedOnDelete,\n utils: {\n awaitTxId,\n awaitMatch,\n },\n }\n\n return withCollectionConfigFactory(options, () =>\n (\n electricCollectionOptions as (\n nextConfig: ElectricCollectionConfig<T, any>,\n ) => typeof options\n )(config),\n )\n}\n\n/**\n * Internal function to create ElectricSQL sync configuration\n */\nfunction createElectricSync<T extends Row<unknown>>(\n shapeOptions: ShapeStreamOptions<GetExtensions<T>>,\n options: {\n syncMode: ElectricSyncMode\n seenTxids: Store<Set<Txid>>\n seenSnapshots: Store<Array<PostgresSnapshot>>\n hydratedResumeState: Store<ElectricResumeState | undefined>\n pendingMatches: Store<\n Map<\n string,\n {\n matchFn: (message: Message<T>) => boolean\n resolve: (value: boolean) => void\n reject: (error: Error) => void\n timeoutId: ReturnType<typeof setTimeout>\n matched: boolean\n }\n >\n >\n currentBatchMessages: Store<Array<Message<T>>>\n batchCommitted: Store<boolean>\n removePendingMatches: (matchIds: Array<string>) => void\n resolveMatchedPendingMatches: () => void\n collectionId?: string\n testHooks?: ElectricTestHooks\n },\n): SyncConfig<T> {\n const {\n seenTxids,\n seenSnapshots,\n hydratedResumeState,\n syncMode,\n pendingMatches,\n currentBatchMessages,\n batchCommitted,\n removePendingMatches,\n resolveMatchedPendingMatches,\n collectionId,\n testHooks,\n } = options\n const MAX_BATCH_MESSAGES = 1000 // Safety limit for message buffer\n\n // Store for the relation schema information\n const relationSchema = new Store<string | undefined>(undefined)\n\n const tagCache = new Map<MoveTag, ParsedMoveTag>()\n\n // Parses a tag string into a ParsedMoveTag.\n // It memoizes the result parsed tag such that future calls\n // for the same tag string return the same ParsedMoveTag array.\n const parseTag = (tag: MoveTag): ParsedMoveTag => {\n const cachedTag = tagCache.get(tag)\n if (cachedTag) {\n return cachedTag\n }\n\n const parsedTag = parseTagString(tag)\n tagCache.set(tag, parsedTag)\n return parsedTag\n }\n\n // Tag tracking state\n const rowTagSets = new Map<RowId, Set<MoveTag>>()\n const tagIndex: TagIndex = []\n let tagLength: number | undefined = undefined\n\n // DNF state: active_conditions are per-row, disjunct_positions are global\n // (fixed by the shape's WHERE clause, derived once from the first tagged message).\n const rowActiveConditions = new Map<RowId, ActiveConditions>()\n let disjunctPositions: DisjunctPositions | undefined = undefined\n\n /**\n * Initialize the tag index with the correct length\n */\n const initializeTagIndex = (length: number): void => {\n if (tagIndex.length < length) {\n // Extend the index array to the required length\n for (let i = tagIndex.length; i < length; i++) {\n tagIndex[i] = new Map()\n }\n }\n }\n\n /**\n * Add tags to a row and update the tag index\n */\n const addTagsToRow = (\n tags: Array<MoveTag>,\n rowId: RowId,\n rowTagSet: Set<MoveTag>,\n ): void => {\n for (const tag of tags) {\n const parsedTag = parseTag(tag)\n\n // Infer tag length from first tag\n if (tagLength === undefined) {\n tagLength = getTagLength(parsedTag)\n initializeTagIndex(tagLength)\n }\n\n // Validate tag length matches\n const currentTagLength = getTagLength(parsedTag)\n if (currentTagLength !== tagLength) {\n debug(\n `${collectionId ? `[${collectionId}] ` : ``}Tag length mismatch: expected ${tagLength}, got ${currentTagLength}`,\n )\n continue\n }\n\n rowTagSet.add(tag)\n addTagToIndex(parsedTag, rowId, tagIndex, tagLength)\n }\n }\n\n /**\n * Remove tags from a row and update the tag index\n */\n const removeTagsFromRow = (\n removedTags: Array<MoveTag>,\n rowId: RowId,\n rowTagSet: Set<MoveTag>,\n ): void => {\n if (tagLength === undefined) {\n return\n }\n\n for (const tag of removedTags) {\n const parsedTag = parseTag(tag)\n rowTagSet.delete(tag)\n removeTagFromIndex(parsedTag, rowId, tagIndex, tagLength)\n // We aggresively evict the tag from the cache\n // if this tag is shared with another row\n // and is not removed from that other row\n // then next time we encounter the tag it will be parsed again\n tagCache.delete(tag)\n }\n }\n\n /**\n * Process tags for a change message (add and remove tags)\n */\n const processTagsForChangeMessage = (\n tags: Array<MoveTag> | undefined,\n removedTags: Array<MoveTag> | undefined,\n rowId: RowId,\n activeConditions?: ActiveConditions,\n ): Set<MoveTag> => {\n // Initialize tag set for this row if it doesn't exist (needed for checking deletion)\n if (!rowTagSets.has(rowId)) {\n rowTagSets.set(rowId, new Set())\n }\n const rowTagSet = rowTagSets.get(rowId)!\n\n // Add new tags\n if (tags) {\n addTagsToRow(tags, rowId, rowTagSet)\n\n // Derive disjunct positions once — they are fixed by the shape's WHERE clause.\n if (disjunctPositions === undefined) {\n const parsedTags = tags.map(parseTag)\n disjunctPositions = deriveDisjunctPositions(parsedTags)\n }\n }\n\n // Remove tags\n if (removedTags) {\n removeTagsFromRow(removedTags, rowId, rowTagSet)\n }\n\n // Store active conditions if provided (overwrite on re-send)\n if (activeConditions && activeConditions.length > 0) {\n rowActiveConditions.set(rowId, [...activeConditions])\n }\n\n return rowTagSet\n }\n\n /**\n * Clear all tag tracking state (used when truncating)\n */\n const clearTagTrackingState = (): void => {\n rowTagSets.clear()\n tagIndex.length = 0\n tagLength = undefined\n rowActiveConditions.clear()\n disjunctPositions = undefined\n }\n\n /**\n * Remove all tags for a row from both the tag set and the index\n * Used when a row is deleted\n */\n const clearTagsForRow = (rowId: RowId): void => {\n if (tagLength === undefined) {\n return\n }\n\n const rowTagSet = rowTagSets.get(rowId)\n if (!rowTagSet) {\n return\n }\n\n // Remove each tag from the index\n for (const tag of rowTagSet) {\n const parsedTag = parseTag(tag)\n const currentTagLength = getTagLength(parsedTag)\n if (currentTagLength === tagLength) {\n removeTagFromIndex(parsedTag, rowId, tagIndex, tagLength)\n }\n tagCache.delete(tag)\n }\n\n // Remove the row from the tag sets map\n rowTagSets.delete(rowId)\n rowActiveConditions.delete(rowId)\n }\n\n /**\n * Remove matching tags from a row based on a pattern\n * Returns true if the row should be deleted (no longer visible)\n */\n const removeMatchingTagsFromRow = (\n rowId: RowId,\n pattern: MovePattern,\n ): boolean => {\n const rowTagSet = rowTagSets.get(rowId)\n if (!rowTagSet) {\n return false\n }\n\n // DNF mode: check visibility using active conditions.\n // Tag index entries are preserved so that move-in can re-activate positions.\n const activeConditions = rowActiveConditions.get(rowId)\n if (activeConditions && disjunctPositions) {\n // Set the condition at this pattern's position to false\n activeConditions[pattern.pos] = false\n\n if (!rowVisible(activeConditions, disjunctPositions)) {\n // Row is no longer visible — clean up all state including tag index\n for (const tag of rowTagSet) {\n const parsedTag = parseTag(tag)\n removeTagFromIndex(parsedTag, rowId, tagIndex, tagLength!)\n tagCache.delete(tag)\n }\n rowTagSets.delete(rowId)\n rowActiveConditions.delete(rowId)\n return true\n }\n return false\n }\n\n // Simple shape (no subquery dependencies — server sends no active_conditions):\n // Remove matching tags and delete if tag set is empty\n for (const tag of rowTagSet) {\n const parsedTag = parseTag(tag)\n if (tagMatchesPattern(parsedTag, pattern)) {\n rowTagSet.delete(tag)\n removeTagFromIndex(parsedTag, rowId, tagIndex, tagLength!)\n }\n }\n\n if (rowTagSet.size === 0) {\n rowTagSets.delete(rowId)\n return true\n }\n\n return false\n }\n\n /**\n * Process move-out event: remove matching tags from rows and delete rows with empty tag sets\n */\n const processMoveOutEvent = (\n patterns: Array<MovePattern>,\n begin: () => void,\n write: (message: ChangeMessageOrDeleteKeyMessage<T>) => void,\n transactionStarted: boolean,\n ): boolean => {\n if (tagLength === undefined) {\n debug(\n `${collectionId ? `[${collectionId}] ` : ``}Received move-out message but no tag length set yet, ignoring`,\n )\n return transactionStarted\n }\n\n let txStarted = transactionStarted\n\n // Process all patterns and collect rows to delete\n for (const pattern of patterns) {\n // Find all rows that match this pattern\n const affectedRowIds = findRowsMatchingPattern(pattern, tagIndex)\n\n for (const rowId of affectedRowIds) {\n if (removeMatchingTagsFromRow(rowId, pattern)) {\n // Delete rows with empty tag sets\n if (!txStarted) {\n begin()\n txStarted = true\n }\n\n write({\n type: `delete`,\n key: rowId,\n })\n }\n }\n }\n\n return txStarted\n }\n\n /**\n * Process move-in event: re-activate conditions for rows matching the patterns.\n * This is a silent operation — no messages are emitted to the collection.\n */\n const processMoveInEvent = (patterns: Array<MovePattern>): void => {\n if (tagLength === undefined) {\n debug(\n `${collectionId ? `[${collectionId}] ` : ``}Received move-in message but no tag length set yet, ignoring`,\n )\n return\n }\n\n for (const pattern of patterns) {\n const affectedRowIds = findRowsMatchingPattern(pattern, tagIndex)\n\n for (const rowId of affectedRowIds) {\n const activeConditions = rowActiveConditions.get(rowId)\n if (activeConditions) {\n activeConditions[pattern.pos] = true\n }\n }\n }\n }\n\n /**\n * Get the sync metadata for insert operations\n * @returns Record containing relation information\n */\n const getSyncMetadata = (): Record<string, unknown> => {\n // Use the stored schema if available, otherwise default to 'public'\n const schema = relationSchema.state || `public`\n\n return {\n relation: shapeOptions.params?.table\n ? [schema, shapeOptions.params.table]\n : undefined,\n }\n }\n\n let unsubscribeStream: () => void\n\n return {\n sync: (params: Parameters<SyncConfig<T>[`sync`]>[0]) => {\n const {\n begin,\n write,\n commit: commitSyncTransaction,\n markReady,\n markError,\n truncate,\n collection,\n metadata,\n } = params\n let commitSequence = 0\n const pendingAppliedReceipts = new Map<number, Promise<void>>()\n const commit = (signal?: AbortSignal): SyncAppliedReceipt => {\n const sequence = ++commitSequence\n const applied = commitSyncTransaction(signal)\n if (applied === true) {\n return true\n }\n pendingAppliedReceipts.set(sequence, applied)\n const removeReceipt = () => pendingAppliedReceipts.delete(sequence)\n void applied.then(removeReceipt, removeReceipt)\n return applied\n }\n const waitForCommitsAfter = async (cursor: number): Promise<void> => {\n await Promise.all(\n Array.from(pendingAppliedReceipts, ([sequence, applied]) =>\n sequence > cursor ? applied : undefined,\n ),\n )\n }\n const readPersistedResumeState = (): ElectricResumeState | undefined => {\n const persistedResumeState = metadata?.collection.get(`electric:resume`)\n return parseElectricResumeState(persistedResumeState)\n }\n\n const persistedResumeState = getNewestElectricResumeState(\n readPersistedResumeState(),\n hydratedResumeState.state,\n )\n const shapeIdentity = getStableShapeIdentity({\n url: shapeOptions.url,\n params: shapeOptions.params as Record<string, unknown> | undefined,\n })\n const hasIncompatiblePersistedResume =\n persistedResumeState?.kind === `resume` &&\n persistedResumeState.shapeId !== shapeIdentity\n const canUsePersistedResume =\n shapeOptions.offset === undefined &&\n shapeOptions.handle === undefined &&\n persistedResumeState?.kind === `resume` &&\n !hasIncompatiblePersistedResume\n\n // Wrap markReady to wait for test hook in progressive mode\n let progressiveReadyGate: Promise<void> | null = null\n let streamErrorVersion = 0\n const wrappedMarkReady = (\n isBuffering: boolean,\n expectedErrorVersion = streamErrorVersion,\n ) => {\n if (streamErrorVersion !== expectedErrorVersion) return\n\n // Only create gate if we're in buffering phase (first up-to-date)\n if (\n isBuffering &&\n syncMode === `progressive` &&\n testHooks?.beforeMarkingReady\n ) {\n // Create a new gate promise for this sync cycle\n progressiveReadyGate = testHooks.beforeMarkingReady()\n progressiveReadyGate.then(() => {\n if (streamErrorVersion === expectedErrorVersion) {\n markReady()\n }\n })\n } else {\n // No hook, not buffering, or already past first up-to-date\n markReady()\n }\n }\n\n // Abort controller for the stream - wraps the signal if provided\n const abortController = new AbortController()\n const forwardExternalAbort = () => abortController.abort()\n\n if (shapeOptions.signal) {\n shapeOptions.signal.addEventListener(`abort`, forwardExternalAbort, {\n once: true,\n })\n if (shapeOptions.signal.aborted) {\n abortController.abort()\n }\n }\n\n // Cleanup pending matches on abort\n abortController.signal.addEventListener(`abort`, () => {\n pendingMatches.setState((current) => {\n current.forEach((match) => {\n clearTimeout(match.timeoutId)\n match.reject(new StreamAbortedError())\n })\n return new Map() // Clear all pending matches\n })\n })\n\n const stream = new ShapeStream({\n ...shapeOptions,\n // In on-demand mode, we only want to sync changes, so we set the log to `changes_only`\n log: syncMode === `on-demand` ? `changes_only` : undefined,\n // In on-demand mode, we only need the changes from the point of time the collection was created\n // so we default to `now` when there is no saved offset.\n offset:\n shapeOptions.offset ??\n (canUsePersistedResume\n ? (persistedResumeState.offset as Offset)\n : syncMode === `on-demand`\n ? `now`\n : undefined),\n handle:\n shapeOptions.handle ??\n (canUsePersistedResume ? persistedResumeState.handle : undefined),\n signal: abortController.signal,\n onError: (errorParams) => {\n streamErrorVersion++\n // Note that Electric sends a 409 error on a `must-refetch` message, but the\n // ShapeStream handled this and it will not reach this handler, therefor\n // this handler will not run for a `must-refetch`.\n const initialSyncFailed = collection.status === `loading`\n if (initialSyncFailed) {\n markError(errorParams)\n }\n\n if (shapeOptions.onError) {\n return shapeOptions.onError(errorParams)\n } else {\n console.error(\n `An error occurred while syncing collection: ${collection.id}, \\n` +\n (initialSyncFailed\n ? `the initial sync has been marked as failed. \\n`\n : `the last ready snapshot has been preserved. \\n`) +\n `You can provide an 'onError' handler on the shapeOptions to handle this error, and this message will not be logged.`,\n errorParams,\n )\n }\n\n return\n },\n })\n let transactionStarted = false\n const newTxids = new Set<Txid>()\n const newSnapshots: Array<PostgresSnapshot> = []\n // Track if we've completed initial sync in progressive mode. A persisted\n // resume starts from an already-committed stream offset, so the next\n // up-to-date message must not run the initial atomic swap again.\n let hasReceivedUpToDate =\n syncMode === `progressive` && canUsePersistedResume\n\n // Progressive mode state\n // Helper to determine if we're buffering the initial sync\n const isBufferingInitialSync = () =>\n syncMode === `progressive` && !hasReceivedUpToDate\n const bufferedMessages: Array<Message<T>> = [] // Buffer change messages during initial sync\n\n // Track keys that have been synced to handle overlapping subset queries.\n // When multiple subset queries return the same row, the server sends `insert`\n // for each response. We convert subsequent inserts to updates to avoid\n // duplicate key errors when the row's data has changed between requests.\n const syncedKeys = new Set<string | number>()\n\n const stageResumeMetadata = () => {\n const shapeHandle = stream.shapeHandle\n const lastOffset = stream.lastOffset\n if (!shapeHandle || lastOffset === `-1`) {\n return\n }\n\n const resumeState: ElectricResumeState = {\n kind: `resume`,\n offset: lastOffset,\n handle: shapeHandle,\n shapeId: shapeIdentity,\n updatedAt: Date.now(),\n }\n hydratedResumeState.setState(() => resumeState)\n metadata?.collection.set(`electric:resume`, resumeState)\n }\n\n const commitResetResumeMetadataImmediately = () => {\n const resetState: ElectricResumeState = {\n kind: `reset`,\n updatedAt: Date.now(),\n }\n hydratedResumeState.setState(() => resetState)\n\n if (metadata) {\n begin({ immediate: true })\n metadata.collection.set(`electric:resume`, resetState)\n commit()\n }\n }\n\n if (hasIncompatiblePersistedResume) {\n commitResetResumeMetadataImmediately()\n }\n\n /**\n * Process a change message: handle tags and write the mutation\n */\n const processChangeMessage = (changeMessage: Message<T>) => {\n if (!isChangeMessage(changeMessage)) {\n return\n }\n\n // Process tags if present\n const tags = changeMessage.headers.tags\n const removedTags = changeMessage.headers.removed_tags\n const hasTags = tags || removedTags\n\n // Extract active_conditions from headers (DNF support)\n const activeConditions = changeMessage.headers.active_conditions as\n | ActiveConditions\n | undefined\n\n const rowId = collection.getKeyFromItem(changeMessage.value)\n const operation = changeMessage.headers.operation\n\n // Track synced keys and handle overlapping subset queries.\n // When multiple subset queries return the same row, the server sends\n // `insert` for each response. We convert subsequent inserts to updates\n // to avoid duplicate key errors when the row's data has changed.\n const isDelete = operation === `delete`\n const isDuplicateInsert =\n operation === `insert` && syncedKeys.has(rowId)\n\n if (isDelete) {\n syncedKeys.delete(rowId)\n } else {\n syncedKeys.add(rowId)\n }\n\n if (isDelete) {\n clearTagsForRow(rowId)\n } else if (hasTags) {\n processTagsForChangeMessage(\n tags,\n removedTags,\n rowId,\n activeConditions,\n )\n }\n\n write({\n type: isDuplicateInsert ? `update` : operation,\n value: changeMessage.value,\n // Include the primary key and relation info in the metadata\n metadata: {\n ...changeMessage.headers,\n },\n })\n }\n\n // Create deduplicated loadSubset wrapper for non-eager modes\n // This prevents redundant snapshot requests when multiple concurrent\n // live queries request overlapping or subset predicates\n const loadSubsetDedupe = createLoadSubsetDedupe({\n stream,\n syncMode,\n isBufferingInitialSync,\n begin,\n write,\n commit,\n getCommitCursor: () => commitSequence,\n waitForCommitsAfter,\n collectionId,\n // Pass the columnMapper's encode function to transform column names\n // (e.g., camelCase to snake_case) when compiling SQL for subset queries\n encodeColumnName: shapeOptions.columnMapper?.encode,\n // Pass abort signal so requestSnapshot errors can be ignored during cleanup\n signal: abortController.signal,\n })\n\n unsubscribeStream = stream.subscribe((messages: Array<Message<T>>) => {\n // Track commit point type - up-to-date takes precedence as it also triggers progressive mode atomic swap\n let commitPoint: `up-to-date` | `subset-end` | null = null\n\n // Don't clear the buffer between batches - this preserves messages for awaitMatch\n // to find even if multiple batches arrive before awaitMatch is called.\n // The buffer is naturally limited by MAX_BATCH_MESSAGES (oldest messages are dropped).\n // Reset batchCommitted since we're starting a new batch\n batchCommitted.setState(() => false)\n\n for (const message of messages) {\n // Add message to current batch buffer (for race condition handling)\n if (\n isChangeMessage(message) ||\n isMoveOutMessage(message) ||\n isMoveInMessage(message)\n ) {\n currentBatchMessages.setState((currentBuffer) => {\n const newBuffer = [...currentBuffer, message]\n // Limit buffer size for safety\n if (newBuffer.length > MAX_BATCH_MESSAGES) {\n newBuffer.splice(0, newBuffer.length - MAX_BATCH_MESSAGES)\n }\n return newBuffer\n })\n }\n\n // Check for txids in the message and add them to our store\n // Skip during buffered initial sync in progressive mode (txids will be extracted during atomic swap)\n // EXCEPTION: If a transaction is already started (e.g., from must-refetch), track txids\n // to avoid losing them when messages are written to the existing transaction.\n if (\n hasTxids(message) &&\n (!isBufferingInitialSync() || transactionStarted)\n ) {\n message.headers.txids?.forEach((txid) => newTxids.add(txid))\n }\n\n // Check pending matches against this message\n // Note: matchFn will mark matches internally, we don't resolve here\n const matchesToRemove: Array<string> = []\n pendingMatches.state.forEach((match, matchId) => {\n if (!match.matched) {\n try {\n match.matchFn(message)\n } catch (err) {\n // If matchFn throws, clean up and reject the promise\n clearTimeout(match.timeoutId)\n match.reject(\n err instanceof Error ? err : new Error(String(err)),\n )\n matchesToRemove.push(matchId)\n debug(`matchFn error: %o`, err)\n }\n }\n })\n\n // Remove matches that errored\n removePendingMatches(matchesToRemove)\n\n if (isChangeMessage(message)) {\n // Check if the message contains schema information\n const schema = message.headers.schema\n if (schema && typeof schema === `string`) {\n // Store the schema for future use if it's a valid string\n relationSchema.setState(() => schema)\n }\n\n // In buffered initial sync of progressive mode, buffer messages instead of writing\n // EXCEPTION: If a transaction is already started (e.g., from must-refetch), write\n // directly to it instead of buffering. This prevents orphan transactions.\n if (isBufferingInitialSync() && !transactionStarted) {\n bufferedMessages.push(message)\n } else {\n // Normal processing: write changes immediately\n if (!transactionStarted) {\n begin()\n transactionStarted = true\n }\n\n processChangeMessage(message)\n }\n } else if (isSnapshotEndMessage(message)) {\n // Track postgres snapshot metadata for resolving awaiting mutations\n // Skip during buffered initial sync (will be extracted during atomic swap)\n // EXCEPTION: If a transaction is already started (e.g., from must-refetch), track snapshots\n // to avoid losing them when messages are written to the existing transaction.\n if (!isBufferingInitialSync() || transactionStarted) {\n newSnapshots.push(parseSnapshotMessage(message))\n }\n } else if (isUpToDateMessage(message)) {\n // up-to-date takes precedence - also triggers progressive mode atomic swap\n commitPoint = `up-to-date`\n } else if (isSubsetEndMessage(message)) {\n // subset-end triggers commit but not progressive mode atomic swap\n if (commitPoint !== `up-to-date`) {\n commitPoint = `subset-end`\n }\n } else if (isMoveOutMessage(message)) {\n // Handle move-out event: buffer if buffering, otherwise process immediately\n // EXCEPTION: If a transaction is already started (e.g., from must-refetch), process\n // immediately to avoid orphan transactions.\n if (isBufferingInitialSync() && !transactionStarted) {\n bufferedMessages.push(message)\n } else {\n // Normal processing: process move-out immediately\n transactionStarted = processMoveOutEvent(\n message.headers.patterns,\n begin,\n write,\n transactionStarted,\n )\n }\n } else if (isMoveInMessage(message)) {\n // Handle move-in event: re-activate conditions for matching rows.\n // Buffer if buffering, otherwise process immediately.\n if (isBufferingInitialSync() && !transactionStarted) {\n bufferedMessages.push(message)\n } else {\n processMoveInEvent(message.headers.patterns)\n }\n } else if (isMustRefetchMessage(message)) {\n debug(\n `${collectionId ? `[${collectionId}] ` : ``}Received must-refetch message, starting transaction with truncate`,\n )\n\n commitResetResumeMetadataImmediately()\n\n // Start a transaction and truncate the collection\n if (!transactionStarted) {\n begin()\n transactionStarted = true\n }\n\n truncate()\n\n // Clear tag tracking state\n clearTagTrackingState()\n\n // Clear synced keys tracking since we're starting fresh\n syncedKeys.clear()\n\n // Reset the loadSubset deduplication state since we're starting fresh\n // This ensures that previously loaded predicates don't prevent refetching after truncate\n loadSubsetDedupe?.reset()\n\n // Reset flags so we continue accumulating changes until next up-to-date\n commitPoint = null\n hasReceivedUpToDate = false // Reset for progressive mode (isBufferingInitialSync will reflect this)\n bufferedMessages.length = 0 // Clear buffered messages\n }\n }\n\n if (commitPoint !== null) {\n let applied: SyncAppliedReceipt = true\n const wasBufferingInitialSync = isBufferingInitialSync()\n // PROGRESSIVE MODE: Atomic swap on first up-to-date (not subset-end)\n // EXCEPTION: Skip atomic swap if a transaction is already started (e.g., from must-refetch).\n // In that case, do a normal commit to properly close the existing transaction.\n if (\n isBufferingInitialSync() &&\n commitPoint === `up-to-date` &&\n !transactionStarted\n ) {\n debug(\n `${collectionId ? `[${collectionId}] ` : ``}Progressive mode: Performing atomic swap with ${bufferedMessages.length} buffered messages`,\n )\n\n // Start atomic swap transaction\n begin()\n\n // Truncate to clear all snapshot data\n truncate()\n\n // Clear tag tracking state for atomic swap\n clearTagTrackingState()\n\n // Clear synced keys tracking for atomic swap\n syncedKeys.clear()\n\n // Apply all buffered change messages and extract txids/snapshots\n for (const bufferedMsg of bufferedMessages) {\n if (isChangeMessage(bufferedMsg)) {\n processChangeMessage(bufferedMsg)\n\n // Extract txids from buffered messages (will be committed to store after transaction)\n if (hasTxids(bufferedMsg)) {\n bufferedMsg.headers.txids?.forEach((txid) =>\n newTxids.add(txid),\n )\n }\n } else if (isSnapshotEndMessage(bufferedMsg)) {\n // Extract snapshots from buffered messages (will be committed to store after transaction)\n newSnapshots.push(parseSnapshotMessage(bufferedMsg))\n } else if (isMoveOutMessage(bufferedMsg)) {\n // Process buffered move-out messages during atomic swap\n processMoveOutEvent(\n bufferedMsg.headers.patterns,\n begin,\n write,\n transactionStarted,\n )\n } else if (isMoveInMessage(bufferedMsg)) {\n // Process buffered move-in messages during atomic swap\n processMoveInEvent(bufferedMsg.headers.patterns)\n }\n }\n\n // Commit the atomic swap\n stageResumeMetadata()\n applied = commit()\n\n // Exit buffering phase by marking that we've received up-to-date\n // isBufferingInitialSync() will now return false\n bufferedMessages.length = 0\n\n debug(\n `${collectionId ? `[${collectionId}] ` : ``}Progressive mode: Atomic swap complete, now in normal sync mode`,\n )\n } else {\n // Normal mode or on-demand: commit transaction if one was started\n // Both up-to-date and subset-end trigger a commit\n if (transactionStarted) {\n stageResumeMetadata()\n applied = commit()\n transactionStarted = false\n } else if (commitPoint === `up-to-date` && metadata) {\n begin()\n stageResumeMetadata()\n applied = commit()\n }\n }\n const readyErrorVersion = streamErrorVersion\n if (applied === true) {\n wrappedMarkReady(wasBufferingInitialSync, readyErrorVersion)\n } else {\n void applied.then(\n () =>\n wrappedMarkReady(wasBufferingInitialSync, readyErrorVersion),\n () => undefined,\n )\n }\n\n // Track that we've received the first up-to-date for progressive mode\n if (commitPoint === `up-to-date`) {\n hasReceivedUpToDate = true\n }\n\n // Always commit txids when we receive up-to-date, regardless of transaction state\n seenTxids.setState((currentTxids) => {\n const clonedSeen = new Set<Txid>(currentTxids)\n if (newTxids.size > 0) {\n debug(\n `${collectionId ? `[${collectionId}] ` : ``}new txids synced from pg %O`,\n Array.from(newTxids),\n )\n }\n newTxids.forEach((txid) => clonedSeen.add(txid))\n newTxids.clear()\n return clonedSeen\n })\n\n // Always commit snapshots when we receive up-to-date, regardless of transaction state\n seenSnapshots.setState((currentSnapshots) => {\n const seen = [...currentSnapshots, ...newSnapshots]\n newSnapshots.forEach((snapshot) =>\n debug(\n `${collectionId ? `[${collectionId}] ` : ``}new snapshot synced from pg %o`,\n snapshot,\n ),\n )\n newSnapshots.length = 0\n return seen\n })\n\n // Resolve all matched pending matches on up-to-date or subset-end\n // Set batchCommitted BEFORE resolving to avoid timing window where late awaitMatch\n // calls could register as \"matched\" after resolver pass already ran\n batchCommitted.setState(() => true)\n\n resolveMatchedPendingMatches()\n }\n })\n\n // Return the deduplicated loadSubset if available (on-demand or progressive mode)\n // The loadSubset method is auto-bound, so it can be safely returned directly\n return {\n loadSubset: loadSubsetDedupe?.loadSubset,\n cleanup: () => {\n shapeOptions.signal?.removeEventListener(\n `abort`,\n forwardExternalAbort,\n )\n // Unsubscribe from the stream\n unsubscribeStream()\n // Abort the abort controller to stop the stream\n abortController.abort()\n // Reset deduplication tracking so collection can load fresh data if restarted\n loadSubsetDedupe?.reset()\n hydratedResumeState.setState(() => undefined)\n },\n }\n },\n // Expose the getSyncMetadata function\n getSyncMetadata,\n }\n}\n"],"names":["parseTag","parseTagString","persistedResumeState"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/electric-db-collection",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.8",
|
|
4
4
|
"description": "ElectricSQL collection for TanStack DB",
|
|
5
5
|
"author": "Kyle Mathews",
|
|
6
6
|
"license": "MIT",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"@standard-schema/spec": "^1.1.0",
|
|
44
44
|
"@tanstack/store": "^0.9.2",
|
|
45
45
|
"debug": "^4.4.3",
|
|
46
|
-
"@tanstack/db": "0.
|
|
46
|
+
"@tanstack/db": "0.9.0"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@types/debug": "^4.1.12",
|
package/src/electric.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { Store } from '@tanstack/store'
|
|
|
8
8
|
import DebugModule from 'debug'
|
|
9
9
|
import {
|
|
10
10
|
DeduplicatedLoadSubset,
|
|
11
|
+
LoadSubsetOperationAbortedError,
|
|
11
12
|
and,
|
|
12
13
|
withCollectionConfigFactory,
|
|
13
14
|
} from '@tanstack/db'
|
|
@@ -564,6 +565,9 @@ function createLoadSubsetDedupe<T extends Row<unknown>>({
|
|
|
564
565
|
const compileOptions = encodeColumnName ? { encodeColumnName } : undefined
|
|
565
566
|
const logPrefix = collectionId ? `[${collectionId}] ` : ``
|
|
566
567
|
|
|
568
|
+
const abortReason = (abortedSignal: AbortSignal): unknown =>
|
|
569
|
+
abortedSignal.reason ?? new LoadSubsetOperationAbortedError()
|
|
570
|
+
|
|
567
571
|
/**
|
|
568
572
|
* Handles errors from snapshot operations. Returns true if the error was
|
|
569
573
|
* handled (signal aborted during cleanup), false if it should be re-thrown.
|
|
@@ -579,7 +583,11 @@ function createLoadSubsetDedupe<T extends Row<unknown>>({
|
|
|
579
583
|
|
|
580
584
|
const loadSubset = async (opts: LoadSubsetOptions) => {
|
|
581
585
|
const commitCursor = getCommitCursor()
|
|
582
|
-
|
|
586
|
+
const throwIfAborted = () => {
|
|
587
|
+
if (signal.aborted) throw abortReason(signal)
|
|
588
|
+
if (opts.signal?.aborted) throw abortReason(opts.signal)
|
|
589
|
+
}
|
|
590
|
+
throwIfAborted()
|
|
583
591
|
|
|
584
592
|
if (isBufferingInitialSync()) {
|
|
585
593
|
const snapshotParams = compileSQL<T>(opts, compileOptions)
|
|
@@ -628,6 +636,18 @@ function createLoadSubsetDedupe<T extends Row<unknown>>({
|
|
|
628
636
|
// still works.
|
|
629
637
|
if (stream.isUpToDate) {
|
|
630
638
|
let timeoutId: ReturnType<typeof setTimeout> | undefined
|
|
639
|
+
const abortSignals = [signal, opts.signal].filter(
|
|
640
|
+
(candidate): candidate is AbortSignal => candidate !== undefined,
|
|
641
|
+
)
|
|
642
|
+
let rejectAbort: (reason: unknown) => void = () => {}
|
|
643
|
+
const aborted = new Promise<never>((_resolve, reject) => {
|
|
644
|
+
rejectAbort = reject
|
|
645
|
+
})
|
|
646
|
+
const abort = (event: Event) =>
|
|
647
|
+
rejectAbort(abortReason(event.currentTarget as AbortSignal))
|
|
648
|
+
for (const abortSignal of abortSignals) {
|
|
649
|
+
abortSignal.addEventListener(`abort`, abort, { once: true })
|
|
650
|
+
}
|
|
631
651
|
try {
|
|
632
652
|
await Promise.race([
|
|
633
653
|
stream.forceDisconnectAndRefresh(),
|
|
@@ -637,8 +657,10 @@ function createLoadSubsetDedupe<T extends Row<unknown>>({
|
|
|
637
657
|
FORCE_DISCONNECT_AND_REFRESH_TIMEOUT_MS,
|
|
638
658
|
)
|
|
639
659
|
}),
|
|
660
|
+
aborted,
|
|
640
661
|
])
|
|
641
662
|
} catch (error) {
|
|
663
|
+
if (signal.aborted || opts.signal?.aborted) throw error
|
|
642
664
|
if (handleSnapshotError(error, `forceDisconnectAndRefresh`)) {
|
|
643
665
|
return
|
|
644
666
|
}
|
|
@@ -648,10 +670,13 @@ function createLoadSubsetDedupe<T extends Row<unknown>>({
|
|
|
648
670
|
)
|
|
649
671
|
} finally {
|
|
650
672
|
clearTimeout(timeoutId)
|
|
673
|
+
for (const abortSignal of abortSignals) {
|
|
674
|
+
abortSignal.removeEventListener(`abort`, abort)
|
|
675
|
+
}
|
|
651
676
|
}
|
|
652
677
|
}
|
|
653
678
|
|
|
654
|
-
|
|
679
|
+
throwIfAborted()
|
|
655
680
|
|
|
656
681
|
// Upstream limitation: ShapeStream.requestSnapshot() publishes its rows
|
|
657
682
|
// through the stream callback before its Promise resolves. It accepts no
|
|
@@ -1573,17 +1598,12 @@ function createElectricSync<T extends Row<unknown>>(
|
|
|
1573
1598
|
|
|
1574
1599
|
// Abort controller for the stream - wraps the signal if provided
|
|
1575
1600
|
const abortController = new AbortController()
|
|
1601
|
+
const forwardExternalAbort = () => abortController.abort()
|
|
1576
1602
|
|
|
1577
1603
|
if (shapeOptions.signal) {
|
|
1578
|
-
shapeOptions.signal.addEventListener(
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
abortController.abort()
|
|
1582
|
-
},
|
|
1583
|
-
{
|
|
1584
|
-
once: true,
|
|
1585
|
-
},
|
|
1586
|
-
)
|
|
1604
|
+
shapeOptions.signal.addEventListener(`abort`, forwardExternalAbort, {
|
|
1605
|
+
once: true,
|
|
1606
|
+
})
|
|
1587
1607
|
if (shapeOptions.signal.aborted) {
|
|
1588
1608
|
abortController.abort()
|
|
1589
1609
|
}
|
|
@@ -2065,6 +2085,10 @@ function createElectricSync<T extends Row<unknown>>(
|
|
|
2065
2085
|
return {
|
|
2066
2086
|
loadSubset: loadSubsetDedupe?.loadSubset,
|
|
2067
2087
|
cleanup: () => {
|
|
2088
|
+
shapeOptions.signal?.removeEventListener(
|
|
2089
|
+
`abort`,
|
|
2090
|
+
forwardExternalAbort,
|
|
2091
|
+
)
|
|
2068
2092
|
// Unsubscribe from the stream
|
|
2069
2093
|
unsubscribeStream()
|
|
2070
2094
|
// Abort the abort controller to stop the stream
|