@tanstack/electron-db-sqlite-persistence 0.2.8 → 0.2.9

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/README.md CHANGED
@@ -108,10 +108,17 @@ publishes nor consumes a coordinator sequence.
108
108
 
109
109
  If a mutating RPC loses its response, Electron coordination replays it only
110
110
  while the requester still knows the same non-null leader id and term. An
111
- unknown initial route or any leader/term change rejects with
112
- `IndeterminateCommitError`; the application must reconcile the outcome. The
113
- coordinator does not retry that mutation against an unknown or replacement
114
- leader.
111
+ unknown initial route or any leader/term change rejects the direct RPC with
112
+ `IndeterminateCommitError`. For a source-backed persisted Collection, the sync
113
+ wrapper can certify the original transaction ID through the replacement leader
114
+ and the main-process SQLite adapter. Application-issued mutations still need
115
+ caller reconciliation. Main and renderer must use matching persistence protocol
116
+ v4 peers for this IPC operation. A main-process adapter used by an
117
+ `ElectronCollectionCoordinator` must implement `reserveLeadershipTerm` so each
118
+ election reserves a durable term before publishing a route. An adapter without
119
+ that capability can still serve single-renderer persistence through
120
+ `SingleProcessCoordinator`; a coordinated election rejects it before route
121
+ publication.
115
122
 
116
123
  Remote-subset requests use the same crash-only boundary. The coordinator
117
124
  projects a live `LoadSubsetOptions` input to the exported
@@ -1,10 +1,10 @@
1
1
  import { ElectronPersistenceMethod, ElectronSerializedError } from './protocol.cjs';
2
2
  type ElectronPersistenceErrorOptions = {
3
- code?: string;
3
+ code?: string | number;
4
4
  cause?: unknown;
5
5
  };
6
6
  export declare class ElectronPersistenceError extends Error {
7
- readonly code: string | undefined;
7
+ readonly code: string | number | undefined;
8
8
  constructor(message: string, options?: ElectronPersistenceErrorOptions);
9
9
  }
10
10
  export declare class UnknownElectronPersistenceCollectionError extends ElectronPersistenceError {
package/dist/cjs/main.cjs CHANGED
@@ -11,12 +11,16 @@ function serializeError(error) {
11
11
  code: void 0
12
12
  };
13
13
  }
14
- const codedError = error;
14
+ const detailedError = error;
15
+ const path = detailedError.path;
15
16
  return {
16
17
  name: error.name || `Error`,
17
18
  message: error.message || fallbackMessage,
18
19
  stack: error.stack,
19
- code: typeof codedError.code === `string` ? codedError.code : void 0
20
+ code: typeof detailedError.code === `string` || typeof detailedError.code === `number` ? detailedError.code : void 0,
21
+ path: typeof path === `string` || Array.isArray(path) && path.every(
22
+ (segment) => typeof segment === `string` || typeof segment === `number`
23
+ ) ? path : void 0
20
24
  };
21
25
  }
22
26
  function createErrorResponse(request, error) {
@@ -117,6 +121,20 @@ async function executeRequestAgainstAdapter(request, adapter) {
117
121
  result: null
118
122
  };
119
123
  }
124
+ case `reconcileCommittedTx`: {
125
+ const result = adapter.reconcileCommittedTx ? await adapter.reconcileCommittedTx(
126
+ request.collectionId,
127
+ request.payload.tx,
128
+ request.payload.anchor
129
+ ) : { kind: `unknown` };
130
+ return {
131
+ v: protocol.ELECTRON_PERSISTENCE_PROTOCOL_VERSION,
132
+ requestId: request.requestId,
133
+ method: request.method,
134
+ ok: true,
135
+ result
136
+ };
137
+ }
120
138
  case `ensureIndex`: {
121
139
  await adapter.ensureIndex(
122
140
  request.collectionId,
@@ -167,6 +185,24 @@ async function executeRequestAgainstAdapter(request, adapter) {
167
185
  result
168
186
  };
169
187
  }
188
+ case `reserveLeadershipTerm`: {
189
+ if (!adapter.reserveLeadershipTerm) {
190
+ throw new dbSqlitePersistenceCore.InvalidPersistedCollectionConfigError(
191
+ `reserveLeadershipTerm is not supported by the configured electron persistence adapter`
192
+ );
193
+ }
194
+ const position = await adapter.reserveLeadershipTerm(
195
+ request.collectionId,
196
+ request.payload.observedTerm
197
+ );
198
+ return {
199
+ v: protocol.ELECTRON_PERSISTENCE_PROTOCOL_VERSION,
200
+ requestId: request.requestId,
201
+ method: request.method,
202
+ ok: true,
203
+ result: position
204
+ };
205
+ }
170
206
  case `getStreamPosition`: {
171
207
  if (!adapter.getStreamPosition) {
172
208
  throw new dbSqlitePersistenceCore.InvalidPersistedCollectionConfigError(
@@ -1 +1 @@
1
- {"version":3,"file":"main.cjs","sources":["../../src/main.ts"],"sourcesContent":["import { InvalidPersistedCollectionConfigError } from '@tanstack/db-sqlite-persistence-core'\nimport {\n DEFAULT_ELECTRON_PERSISTENCE_CHANNEL,\n ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n} from './protocol'\nimport type {\n PersistedCollectionPersistence,\n PersistenceAdapter,\n SQLitePullSinceResult,\n} from '@tanstack/db-sqlite-persistence-core'\nimport type {\n ElectronPersistedKey,\n ElectronPersistedRow,\n ElectronPersistenceRequestEnvelope,\n ElectronPersistenceResponseEnvelope,\n ElectronSerializedError,\n} from './protocol'\n\ntype ElectronMainPersistenceAdapter = PersistenceAdapter & {\n loadCollectionMetadata?: (\n collectionId: string,\n ) => Promise<Array<{ key: string; value: unknown }>>\n scanRows?: (\n collectionId: string,\n options?: { metadataOnly?: boolean },\n ) => Promise<\n Array<{\n key: ElectronPersistedKey\n value: ElectronPersistedRow\n metadata?: unknown\n }>\n >\n pullSince?: (\n collectionId: string,\n fromRowVersion: number,\n ) => Promise<SQLitePullSinceResult<ElectronPersistedKey>>\n getStreamPosition?: (collectionId: string) => Promise<{\n latestTerm: number\n latestSeq: number\n latestRowVersion: number\n }>\n}\n\nfunction serializeError(error: unknown): ElectronSerializedError {\n const fallbackMessage = `Unknown electron persistence error`\n\n if (!(error instanceof Error)) {\n return {\n name: `Error`,\n message: fallbackMessage,\n code: undefined,\n }\n }\n\n const codedError = error as Error & { code?: unknown }\n return {\n name: error.name || `Error`,\n message: error.message || fallbackMessage,\n stack: error.stack,\n code: typeof codedError.code === `string` ? codedError.code : undefined,\n }\n}\n\nfunction createErrorResponse(\n request: ElectronPersistenceRequestEnvelope,\n error: unknown,\n): ElectronPersistenceResponseEnvelope {\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: false,\n error: serializeError(error),\n }\n}\n\nfunction assertValidRequest(request: ElectronPersistenceRequestEnvelope): void {\n if (request.v !== ELECTRON_PERSISTENCE_PROTOCOL_VERSION) {\n throw new InvalidPersistedCollectionConfigError(\n `Unsupported electron persistence protocol version \"${request.v}\"`,\n )\n }\n\n if (\n typeof request.requestId !== `string` ||\n request.requestId.trim().length === 0\n ) {\n throw new InvalidPersistedCollectionConfigError(\n `Electron persistence requestId cannot be empty`,\n )\n }\n\n if (\n typeof request.collectionId !== `string` ||\n request.collectionId.trim().length === 0\n ) {\n throw new InvalidPersistedCollectionConfigError(\n `Electron persistence collectionId cannot be empty`,\n )\n }\n}\n\nasync function executeRequestAgainstAdapter(\n request: ElectronPersistenceRequestEnvelope,\n adapter: ElectronMainPersistenceAdapter,\n): Promise<ElectronPersistenceResponseEnvelope> {\n switch (request.method) {\n case `loadSubset`: {\n const result = await adapter.loadSubset(\n request.collectionId,\n request.payload.options,\n request.payload.ctx,\n )\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result,\n }\n }\n\n case `loadResumeSnapshot`: {\n const result = await adapter.loadResumeSnapshot(\n request.collectionId,\n request.payload.ctx,\n )\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result,\n }\n }\n\n case `loadCollectionMetadata`: {\n if (!adapter.loadCollectionMetadata) {\n throw new InvalidPersistedCollectionConfigError(\n `loadCollectionMetadata is not supported by the configured electron persistence adapter`,\n )\n }\n const result = await adapter.loadCollectionMetadata(request.collectionId)\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result,\n }\n }\n\n case `scanRows`: {\n if (!adapter.scanRows) {\n throw new InvalidPersistedCollectionConfigError(\n `scanRows is not supported by the configured electron persistence adapter`,\n )\n }\n const result = await adapter.scanRows(\n request.collectionId,\n request.payload.options,\n )\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result,\n }\n }\n\n case `applyCommittedTx`: {\n await adapter.applyCommittedTx(request.collectionId, request.payload.tx)\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result: null,\n }\n }\n\n case `ensureIndex`: {\n await adapter.ensureIndex(\n request.collectionId,\n request.payload.signature,\n request.payload.spec,\n )\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result: null,\n }\n }\n\n case `markIndexRemoved`: {\n if (!adapter.markIndexRemoved) {\n throw new InvalidPersistedCollectionConfigError(\n `markIndexRemoved is not supported by the configured electron persistence adapter`,\n )\n }\n await adapter.markIndexRemoved(\n request.collectionId,\n request.payload.signature,\n )\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result: null,\n }\n }\n\n case `pullSince`: {\n if (!adapter.pullSince) {\n throw new InvalidPersistedCollectionConfigError(\n `pullSince is not supported by the configured electron persistence adapter`,\n )\n }\n const result = await adapter.pullSince(\n request.collectionId,\n request.payload.fromRowVersion,\n )\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result,\n }\n }\n\n case `getStreamPosition`: {\n if (!adapter.getStreamPosition) {\n throw new InvalidPersistedCollectionConfigError(\n `getStreamPosition is not supported by the configured electron persistence adapter`,\n )\n }\n const position = await adapter.getStreamPosition(request.collectionId)\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result: position,\n }\n }\n }\n}\n\nfunction resolveModeAwarePersistence(\n persistence: PersistedCollectionPersistence,\n request: ElectronPersistenceRequestEnvelope,\n): PersistedCollectionPersistence {\n const mode = request.resolution?.mode ?? `sync-absent`\n const schemaVersion = request.resolution?.schemaVersion\n const collectionAwarePersistence =\n persistence.resolvePersistenceForCollection?.({\n collectionId: request.collectionId,\n mode,\n schemaVersion,\n })\n if (collectionAwarePersistence) {\n return collectionAwarePersistence\n }\n\n const modeAwarePersistence = persistence.resolvePersistenceForMode?.(mode)\n return modeAwarePersistence ?? persistence\n}\n\nexport type ElectronIpcMainLike = {\n handle: (\n channel: string,\n listener: (\n event: unknown,\n request: ElectronPersistenceRequestEnvelope,\n ) => Promise<ElectronPersistenceResponseEnvelope>,\n ) => void\n removeHandler?: (channel: string) => void\n}\n\nexport type ElectronSQLiteMainProcessOptions = {\n persistence: PersistedCollectionPersistence\n ipcMain: ElectronIpcMainLike\n channel?: string\n}\n\nexport function exposeElectronSQLitePersistence(\n options: ElectronSQLiteMainProcessOptions,\n): () => void {\n const channel = options.channel ?? DEFAULT_ELECTRON_PERSISTENCE_CHANNEL\n options.ipcMain.handle(\n channel,\n async (\n _event,\n request: ElectronPersistenceRequestEnvelope,\n ): Promise<ElectronPersistenceResponseEnvelope> => {\n try {\n assertValidRequest(request)\n const modeAwarePersistence = resolveModeAwarePersistence(\n options.persistence,\n request,\n )\n return await executeRequestAgainstAdapter(\n request,\n modeAwarePersistence.adapter,\n )\n } catch (error) {\n return createErrorResponse(request, error)\n }\n },\n )\n\n return () => {\n options.ipcMain.removeHandler?.(channel)\n }\n}\n"],"names":["ELECTRON_PERSISTENCE_PROTOCOL_VERSION","InvalidPersistedCollectionConfigError","DEFAULT_ELECTRON_PERSISTENCE_CHANNEL"],"mappings":"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"}
1
+ {"version":3,"file":"main.cjs","sources":["../../src/main.ts"],"sourcesContent":["import { InvalidPersistedCollectionConfigError } from '@tanstack/db-sqlite-persistence-core'\nimport {\n DEFAULT_ELECTRON_PERSISTENCE_CHANNEL,\n ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n} from './protocol'\nimport type {\n PersistedCollectionPersistence,\n PersistenceAdapter,\n SQLitePullSinceResult,\n} from '@tanstack/db-sqlite-persistence-core'\nimport type {\n ElectronPersistedKey,\n ElectronPersistedRow,\n ElectronPersistenceRequestEnvelope,\n ElectronPersistenceResponseEnvelope,\n ElectronSerializedError,\n} from './protocol'\n\ntype ElectronMainPersistenceAdapter = PersistenceAdapter & {\n loadCollectionMetadata?: (\n collectionId: string,\n ) => Promise<Array<{ key: string; value: unknown }>>\n scanRows?: (\n collectionId: string,\n options?: { metadataOnly?: boolean },\n ) => Promise<\n Array<{\n key: ElectronPersistedKey\n value: ElectronPersistedRow\n metadata?: unknown\n }>\n >\n pullSince?: (\n collectionId: string,\n fromRowVersion: number,\n ) => Promise<SQLitePullSinceResult<ElectronPersistedKey>>\n getStreamPosition?: (collectionId: string) => Promise<{\n latestTerm: number\n latestSeq: number\n latestRowVersion: number\n }>\n}\n\nfunction serializeError(error: unknown): ElectronSerializedError {\n const fallbackMessage = `Unknown electron persistence error`\n\n if (!(error instanceof Error)) {\n return {\n name: `Error`,\n message: fallbackMessage,\n code: undefined,\n }\n }\n\n const detailedError = error as Error & { code?: unknown; path?: unknown }\n const path = detailedError.path\n return {\n name: error.name || `Error`,\n message: error.message || fallbackMessage,\n stack: error.stack,\n code:\n typeof detailedError.code === `string` ||\n typeof detailedError.code === `number`\n ? detailedError.code\n : undefined,\n path:\n typeof path === `string` ||\n (Array.isArray(path) &&\n path.every(\n (segment: unknown) =>\n typeof segment === `string` || typeof segment === `number`,\n ))\n ? (path as string | ReadonlyArray<string | number>)\n : undefined,\n }\n}\n\nfunction createErrorResponse(\n request: ElectronPersistenceRequestEnvelope,\n error: unknown,\n): ElectronPersistenceResponseEnvelope {\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: false,\n error: serializeError(error),\n }\n}\n\nfunction assertValidRequest(request: ElectronPersistenceRequestEnvelope): void {\n if (request.v !== ELECTRON_PERSISTENCE_PROTOCOL_VERSION) {\n throw new InvalidPersistedCollectionConfigError(\n `Unsupported electron persistence protocol version \"${request.v}\"`,\n )\n }\n\n if (\n typeof request.requestId !== `string` ||\n request.requestId.trim().length === 0\n ) {\n throw new InvalidPersistedCollectionConfigError(\n `Electron persistence requestId cannot be empty`,\n )\n }\n\n if (\n typeof request.collectionId !== `string` ||\n request.collectionId.trim().length === 0\n ) {\n throw new InvalidPersistedCollectionConfigError(\n `Electron persistence collectionId cannot be empty`,\n )\n }\n}\n\nasync function executeRequestAgainstAdapter(\n request: ElectronPersistenceRequestEnvelope,\n adapter: ElectronMainPersistenceAdapter,\n): Promise<ElectronPersistenceResponseEnvelope> {\n switch (request.method) {\n case `loadSubset`: {\n const result = await adapter.loadSubset(\n request.collectionId,\n request.payload.options,\n request.payload.ctx,\n )\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result,\n }\n }\n\n case `loadResumeSnapshot`: {\n const result = await adapter.loadResumeSnapshot(\n request.collectionId,\n request.payload.ctx,\n )\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result,\n }\n }\n\n case `loadCollectionMetadata`: {\n if (!adapter.loadCollectionMetadata) {\n throw new InvalidPersistedCollectionConfigError(\n `loadCollectionMetadata is not supported by the configured electron persistence adapter`,\n )\n }\n const result = await adapter.loadCollectionMetadata(request.collectionId)\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result,\n }\n }\n\n case `scanRows`: {\n if (!adapter.scanRows) {\n throw new InvalidPersistedCollectionConfigError(\n `scanRows is not supported by the configured electron persistence adapter`,\n )\n }\n const result = await adapter.scanRows(\n request.collectionId,\n request.payload.options,\n )\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result,\n }\n }\n\n case `applyCommittedTx`: {\n await adapter.applyCommittedTx(request.collectionId, request.payload.tx)\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result: null,\n }\n }\n\n case `reconcileCommittedTx`: {\n const result = adapter.reconcileCommittedTx\n ? await adapter.reconcileCommittedTx(\n request.collectionId,\n request.payload.tx,\n request.payload.anchor,\n )\n : { kind: `unknown` as const }\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result,\n }\n }\n\n case `ensureIndex`: {\n await adapter.ensureIndex(\n request.collectionId,\n request.payload.signature,\n request.payload.spec,\n )\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result: null,\n }\n }\n\n case `markIndexRemoved`: {\n if (!adapter.markIndexRemoved) {\n throw new InvalidPersistedCollectionConfigError(\n `markIndexRemoved is not supported by the configured electron persistence adapter`,\n )\n }\n await adapter.markIndexRemoved(\n request.collectionId,\n request.payload.signature,\n )\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result: null,\n }\n }\n\n case `pullSince`: {\n if (!adapter.pullSince) {\n throw new InvalidPersistedCollectionConfigError(\n `pullSince is not supported by the configured electron persistence adapter`,\n )\n }\n const result = await adapter.pullSince(\n request.collectionId,\n request.payload.fromRowVersion,\n )\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result,\n }\n }\n\n case `reserveLeadershipTerm`: {\n if (!adapter.reserveLeadershipTerm) {\n throw new InvalidPersistedCollectionConfigError(\n `reserveLeadershipTerm is not supported by the configured electron persistence adapter`,\n )\n }\n const position = await adapter.reserveLeadershipTerm(\n request.collectionId,\n request.payload.observedTerm,\n )\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result: position,\n }\n }\n\n case `getStreamPosition`: {\n if (!adapter.getStreamPosition) {\n throw new InvalidPersistedCollectionConfigError(\n `getStreamPosition is not supported by the configured electron persistence adapter`,\n )\n }\n const position = await adapter.getStreamPosition(request.collectionId)\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result: position,\n }\n }\n }\n}\n\nfunction resolveModeAwarePersistence(\n persistence: PersistedCollectionPersistence,\n request: ElectronPersistenceRequestEnvelope,\n): PersistedCollectionPersistence {\n const mode = request.resolution?.mode ?? `sync-absent`\n const schemaVersion = request.resolution?.schemaVersion\n const collectionAwarePersistence =\n persistence.resolvePersistenceForCollection?.({\n collectionId: request.collectionId,\n mode,\n schemaVersion,\n })\n if (collectionAwarePersistence) {\n return collectionAwarePersistence\n }\n\n const modeAwarePersistence = persistence.resolvePersistenceForMode?.(mode)\n return modeAwarePersistence ?? persistence\n}\n\nexport type ElectronIpcMainLike = {\n handle: (\n channel: string,\n listener: (\n event: unknown,\n request: ElectronPersistenceRequestEnvelope,\n ) => Promise<ElectronPersistenceResponseEnvelope>,\n ) => void\n removeHandler?: (channel: string) => void\n}\n\nexport type ElectronSQLiteMainProcessOptions = {\n persistence: PersistedCollectionPersistence\n ipcMain: ElectronIpcMainLike\n channel?: string\n}\n\nexport function exposeElectronSQLitePersistence(\n options: ElectronSQLiteMainProcessOptions,\n): () => void {\n const channel = options.channel ?? DEFAULT_ELECTRON_PERSISTENCE_CHANNEL\n options.ipcMain.handle(\n channel,\n async (\n _event,\n request: ElectronPersistenceRequestEnvelope,\n ): Promise<ElectronPersistenceResponseEnvelope> => {\n try {\n assertValidRequest(request)\n const modeAwarePersistence = resolveModeAwarePersistence(\n options.persistence,\n request,\n )\n return await executeRequestAgainstAdapter(\n request,\n modeAwarePersistence.adapter,\n )\n } catch (error) {\n return createErrorResponse(request, error)\n }\n },\n )\n\n return () => {\n options.ipcMain.removeHandler?.(channel)\n }\n}\n"],"names":["ELECTRON_PERSISTENCE_PROTOCOL_VERSION","InvalidPersistedCollectionConfigError","DEFAULT_ELECTRON_PERSISTENCE_CHANNEL"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const ELECTRON_PERSISTENCE_PROTOCOL_VERSION = 2;
3
+ const ELECTRON_PERSISTENCE_PROTOCOL_VERSION = 4;
4
4
  const DEFAULT_ELECTRON_PERSISTENCE_CHANNEL = `tanstack-db:sqlite-persistence`;
5
5
  exports.DEFAULT_ELECTRON_PERSISTENCE_CHANNEL = DEFAULT_ELECTRON_PERSISTENCE_CHANNEL;
6
6
  exports.ELECTRON_PERSISTENCE_PROTOCOL_VERSION = ELECTRON_PERSISTENCE_PROTOCOL_VERSION;
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.cjs","sources":["../../src/protocol.ts"],"sourcesContent":["import type { LoadSubsetOptions } from '@tanstack/db'\nimport type {\n PersistedCollectionMode,\n PersistedIndexSpec,\n PersistedKeySetEvidence,\n PersistedTx,\n SQLitePullSinceResult,\n} from '@tanstack/db-sqlite-persistence-core'\n\nexport const ELECTRON_PERSISTENCE_PROTOCOL_VERSION = 2 as const\nexport const DEFAULT_ELECTRON_PERSISTENCE_CHANNEL = `tanstack-db:sqlite-persistence`\n\nexport type ElectronPersistedRow = Record<string, unknown>\nexport type ElectronPersistedKey = string | number\n\nexport type ElectronPersistenceResolution = {\n mode: PersistedCollectionMode\n schemaVersion?: number\n}\n\nexport type ElectronPersistenceMethod =\n | `loadSubset`\n | `loadResumeSnapshot`\n | `loadCollectionMetadata`\n | `scanRows`\n | `applyCommittedTx`\n | `ensureIndex`\n | `markIndexRemoved`\n | `pullSince`\n | `getStreamPosition`\n\nexport type ElectronPersistencePayloadMap = {\n loadSubset: {\n options: LoadSubsetOptions\n ctx?: { requiredIndexSignatures?: ReadonlyArray<string> }\n }\n loadResumeSnapshot: {\n ctx?: {\n requiredIndexSignatures?: ReadonlyArray<string>\n includeRows?: boolean\n }\n }\n loadCollectionMetadata: {}\n scanRows: {\n options?: {\n metadataOnly?: boolean\n }\n }\n applyCommittedTx: {\n tx: PersistedTx<ElectronPersistedRow, ElectronPersistedKey>\n }\n ensureIndex: {\n signature: string\n spec: PersistedIndexSpec\n }\n markIndexRemoved: {\n signature: string\n }\n pullSince: {\n fromRowVersion: number\n }\n getStreamPosition: {}\n}\n\nexport type ElectronPersistenceResultMap = {\n loadSubset: Array<{ key: ElectronPersistedKey; value: ElectronPersistedRow }>\n loadResumeSnapshot: {\n rows: Array<{\n key: ElectronPersistedKey\n value: ElectronPersistedRow\n metadata?: unknown\n }>\n keySet?: PersistedKeySetEvidence\n collectionMetadata: Array<{ key: string; value: unknown }>\n latestTerm: number\n latestSeq: number\n latestRowVersion: number\n resetEpoch: number\n }\n loadCollectionMetadata: Array<{ key: string; value: unknown }>\n scanRows: Array<{\n key: ElectronPersistedKey\n value: ElectronPersistedRow\n metadata?: unknown\n }>\n applyCommittedTx: null\n ensureIndex: null\n markIndexRemoved: null\n pullSince: SQLitePullSinceResult<ElectronPersistedKey>\n getStreamPosition: {\n latestTerm: number\n latestSeq: number\n latestRowVersion: number\n }\n}\n\nexport type ElectronSerializedError = {\n name: string\n message: string\n stack?: string\n code?: string\n}\n\nexport type ElectronPersistenceRequestByMethod = {\n [Method in ElectronPersistenceMethod]: {\n v: number\n requestId: string\n collectionId: string\n resolution?: ElectronPersistenceResolution\n method: Method\n payload: ElectronPersistencePayloadMap[Method]\n }\n}\n\nexport type ElectronPersistenceRequest<\n TMethod extends ElectronPersistenceMethod = ElectronPersistenceMethod,\n> = ElectronPersistenceRequestByMethod[TMethod]\n\nexport type ElectronPersistenceRequestEnvelope =\n ElectronPersistenceRequestByMethod[ElectronPersistenceMethod]\n\ntype ElectronPersistenceSuccessResponseByMethod = {\n [Method in ElectronPersistenceMethod]: {\n v: number\n requestId: string\n method: Method\n ok: true\n result: ElectronPersistenceResultMap[Method]\n }\n}\n\ntype ElectronPersistenceErrorResponseByMethod = {\n [Method in ElectronPersistenceMethod]: {\n v: number\n requestId: string\n method: Method\n ok: false\n error: ElectronSerializedError\n }\n}\n\nexport type ElectronPersistenceResponse<\n TMethod extends ElectronPersistenceMethod = ElectronPersistenceMethod,\n> =\n | ElectronPersistenceSuccessResponseByMethod[TMethod]\n | ElectronPersistenceErrorResponseByMethod[TMethod]\n\nexport type ElectronPersistenceResponseEnvelope =\n ElectronPersistenceResponse<ElectronPersistenceMethod>\n\nexport type ElectronPersistenceRequestHandler = (\n request: ElectronPersistenceRequestEnvelope,\n) => Promise<ElectronPersistenceResponseEnvelope>\n\nexport type ElectronPersistenceInvoke = (\n channel: string,\n request: ElectronPersistenceRequestEnvelope,\n) => Promise<ElectronPersistenceResponseEnvelope>\n"],"names":[],"mappings":";;AASO,MAAM,wCAAwC;AAC9C,MAAM,uCAAuC;;;"}
1
+ {"version":3,"file":"protocol.cjs","sources":["../../src/protocol.ts"],"sourcesContent":["import type { LoadSubsetOptions } from '@tanstack/db'\nimport type {\n CommittedTxAnchor,\n PersistedCollectionMode,\n PersistedIndexSpec,\n PersistedKeySetEvidence,\n PersistedTx,\n ReconciledCommittedTx,\n SQLitePullSinceResult,\n} from '@tanstack/db-sqlite-persistence-core'\n\nexport const ELECTRON_PERSISTENCE_PROTOCOL_VERSION = 4 as const\nexport const DEFAULT_ELECTRON_PERSISTENCE_CHANNEL = `tanstack-db:sqlite-persistence`\n\nexport type ElectronPersistedRow = Record<string, unknown>\nexport type ElectronPersistedKey = string | number\n\nexport type ElectronPersistenceResolution = {\n mode: PersistedCollectionMode\n schemaVersion?: number\n}\n\nexport type ElectronPersistenceMethod =\n | `loadSubset`\n | `loadResumeSnapshot`\n | `loadCollectionMetadata`\n | `scanRows`\n | `applyCommittedTx`\n | `reconcileCommittedTx`\n | `ensureIndex`\n | `markIndexRemoved`\n | `pullSince`\n | `getStreamPosition`\n | `reserveLeadershipTerm`\n\nexport type ElectronPersistencePayloadMap = {\n loadSubset: {\n options: LoadSubsetOptions\n ctx?: { requiredIndexSignatures?: ReadonlyArray<string> }\n }\n loadResumeSnapshot: {\n ctx?: {\n requiredIndexSignatures?: ReadonlyArray<string>\n includeRows?: boolean\n }\n }\n loadCollectionMetadata: {}\n scanRows: {\n options?: {\n metadataOnly?: boolean\n }\n }\n applyCommittedTx: {\n tx: PersistedTx<ElectronPersistedRow, ElectronPersistedKey>\n }\n reconcileCommittedTx: {\n tx: PersistedTx<ElectronPersistedRow, ElectronPersistedKey>\n anchor: CommittedTxAnchor\n }\n ensureIndex: {\n signature: string\n spec: PersistedIndexSpec\n }\n markIndexRemoved: {\n signature: string\n }\n pullSince: {\n fromRowVersion: number\n }\n getStreamPosition: {}\n reserveLeadershipTerm: { observedTerm: number }\n}\n\nexport type ElectronPersistenceResultMap = {\n loadSubset: Array<{ key: ElectronPersistedKey; value: ElectronPersistedRow }>\n loadResumeSnapshot: {\n rows: Array<{\n key: ElectronPersistedKey\n value: ElectronPersistedRow\n metadata?: unknown\n }>\n keySet?: PersistedKeySetEvidence\n collectionMetadata: Array<{ key: string; value: unknown }>\n latestTerm: number\n latestSeq: number\n latestRowVersion: number\n resetEpoch: number\n }\n loadCollectionMetadata: Array<{ key: string; value: unknown }>\n scanRows: Array<{\n key: ElectronPersistedKey\n value: ElectronPersistedRow\n metadata?: unknown\n }>\n applyCommittedTx: null\n reconcileCommittedTx: ReconciledCommittedTx\n ensureIndex: null\n markIndexRemoved: null\n pullSince: SQLitePullSinceResult<ElectronPersistedKey>\n getStreamPosition: {\n latestTerm: number\n latestSeq: number\n latestRowVersion: number\n }\n reserveLeadershipTerm: {\n latestTerm: number\n latestSeq: number\n latestRowVersion: number\n }\n}\n\nexport type ElectronSerializedError = {\n name: string\n message: string\n stack?: string\n code?: string | number\n path?: string | ReadonlyArray<string | number>\n}\n\nexport type ElectronPersistenceRequestByMethod = {\n [Method in ElectronPersistenceMethod]: {\n v: number\n requestId: string\n collectionId: string\n resolution?: ElectronPersistenceResolution\n method: Method\n payload: ElectronPersistencePayloadMap[Method]\n }\n}\n\nexport type ElectronPersistenceRequest<\n TMethod extends ElectronPersistenceMethod = ElectronPersistenceMethod,\n> = ElectronPersistenceRequestByMethod[TMethod]\n\nexport type ElectronPersistenceRequestEnvelope =\n ElectronPersistenceRequestByMethod[ElectronPersistenceMethod]\n\ntype ElectronPersistenceSuccessResponseByMethod = {\n [Method in ElectronPersistenceMethod]: {\n v: number\n requestId: string\n method: Method\n ok: true\n result: ElectronPersistenceResultMap[Method]\n }\n}\n\ntype ElectronPersistenceErrorResponseByMethod = {\n [Method in ElectronPersistenceMethod]: {\n v: number\n requestId: string\n method: Method\n ok: false\n error: ElectronSerializedError\n }\n}\n\nexport type ElectronPersistenceResponse<\n TMethod extends ElectronPersistenceMethod = ElectronPersistenceMethod,\n> =\n | ElectronPersistenceSuccessResponseByMethod[TMethod]\n | ElectronPersistenceErrorResponseByMethod[TMethod]\n\nexport type ElectronPersistenceResponseEnvelope =\n ElectronPersistenceResponse<ElectronPersistenceMethod>\n\nexport type ElectronPersistenceRequestHandler = (\n request: ElectronPersistenceRequestEnvelope,\n) => Promise<ElectronPersistenceResponseEnvelope>\n\nexport type ElectronPersistenceInvoke = (\n channel: string,\n request: ElectronPersistenceRequestEnvelope,\n) => Promise<ElectronPersistenceResponseEnvelope>\n"],"names":[],"mappings":";;AAWO,MAAM,wCAAwC;AAC9C,MAAM,uCAAuC;;;"}
@@ -1,6 +1,6 @@
1
1
  import { LoadSubsetOptions } from '@tanstack/db';
2
- import { PersistedCollectionMode, PersistedIndexSpec, PersistedKeySetEvidence, PersistedTx, SQLitePullSinceResult } from '@tanstack/db-sqlite-persistence-core';
3
- export declare const ELECTRON_PERSISTENCE_PROTOCOL_VERSION: 2;
2
+ import { CommittedTxAnchor, PersistedCollectionMode, PersistedIndexSpec, PersistedKeySetEvidence, PersistedTx, ReconciledCommittedTx, SQLitePullSinceResult } from '@tanstack/db-sqlite-persistence-core';
3
+ export declare const ELECTRON_PERSISTENCE_PROTOCOL_VERSION: 4;
4
4
  export declare const DEFAULT_ELECTRON_PERSISTENCE_CHANNEL = "tanstack-db:sqlite-persistence";
5
5
  export type ElectronPersistedRow = Record<string, unknown>;
6
6
  export type ElectronPersistedKey = string | number;
@@ -8,7 +8,7 @@ export type ElectronPersistenceResolution = {
8
8
  mode: PersistedCollectionMode;
9
9
  schemaVersion?: number;
10
10
  };
11
- export type ElectronPersistenceMethod = `loadSubset` | `loadResumeSnapshot` | `loadCollectionMetadata` | `scanRows` | `applyCommittedTx` | `ensureIndex` | `markIndexRemoved` | `pullSince` | `getStreamPosition`;
11
+ export type ElectronPersistenceMethod = `loadSubset` | `loadResumeSnapshot` | `loadCollectionMetadata` | `scanRows` | `applyCommittedTx` | `reconcileCommittedTx` | `ensureIndex` | `markIndexRemoved` | `pullSince` | `getStreamPosition` | `reserveLeadershipTerm`;
12
12
  export type ElectronPersistencePayloadMap = {
13
13
  loadSubset: {
14
14
  options: LoadSubsetOptions;
@@ -31,6 +31,10 @@ export type ElectronPersistencePayloadMap = {
31
31
  applyCommittedTx: {
32
32
  tx: PersistedTx<ElectronPersistedRow, ElectronPersistedKey>;
33
33
  };
34
+ reconcileCommittedTx: {
35
+ tx: PersistedTx<ElectronPersistedRow, ElectronPersistedKey>;
36
+ anchor: CommittedTxAnchor;
37
+ };
34
38
  ensureIndex: {
35
39
  signature: string;
36
40
  spec: PersistedIndexSpec;
@@ -42,6 +46,9 @@ export type ElectronPersistencePayloadMap = {
42
46
  fromRowVersion: number;
43
47
  };
44
48
  getStreamPosition: {};
49
+ reserveLeadershipTerm: {
50
+ observedTerm: number;
51
+ };
45
52
  };
46
53
  export type ElectronPersistenceResultMap = {
47
54
  loadSubset: Array<{
@@ -74,6 +81,7 @@ export type ElectronPersistenceResultMap = {
74
81
  metadata?: unknown;
75
82
  }>;
76
83
  applyCommittedTx: null;
84
+ reconcileCommittedTx: ReconciledCommittedTx;
77
85
  ensureIndex: null;
78
86
  markIndexRemoved: null;
79
87
  pullSince: SQLitePullSinceResult<ElectronPersistedKey>;
@@ -82,12 +90,18 @@ export type ElectronPersistenceResultMap = {
82
90
  latestSeq: number;
83
91
  latestRowVersion: number;
84
92
  };
93
+ reserveLeadershipTerm: {
94
+ latestTerm: number;
95
+ latestSeq: number;
96
+ latestRowVersion: number;
97
+ };
85
98
  };
86
99
  export type ElectronSerializedError = {
87
100
  name: string;
88
101
  message: string;
89
102
  stack?: string;
90
- code?: string;
103
+ code?: string | number;
104
+ path?: string | ReadonlyArray<string | number>;
91
105
  };
92
106
  export type ElectronPersistenceRequestByMethod = {
93
107
  [Method in ElectronPersistenceMethod]: {
@@ -76,9 +76,12 @@ function createRendererRequestExecutor(options) {
76
76
  if (typeof response.error.stack === `string`) {
77
77
  remoteError.stack = response.error.stack;
78
78
  }
79
- if (typeof response.error.code === `string`) {
79
+ if (typeof response.error.code === `string` || typeof response.error.code === `number`) {
80
80
  remoteError.code = response.error.code;
81
81
  }
82
+ if (response.error.path !== void 0) {
83
+ remoteError.path = response.error.path;
84
+ }
82
85
  throw remoteError;
83
86
  }
84
87
  return response.result;
@@ -116,6 +119,14 @@ function createResolvedRendererAdapter(executeRequest, resolution) {
116
119
  resolution
117
120
  );
118
121
  },
122
+ reconcileCommittedTx: async (collectionId, tx, anchor) => {
123
+ return executeRequest(
124
+ `reconcileCommittedTx`,
125
+ collectionId,
126
+ { tx, anchor },
127
+ resolution
128
+ );
129
+ },
119
130
  loadCollectionMetadata: async (collectionId) => {
120
131
  return executeRequest(
121
132
  `loadCollectionMetadata`,
@@ -167,7 +178,13 @@ function createResolvedRendererAdapter(executeRequest, resolution) {
167
178
  },
168
179
  getStreamPosition: async (collectionId) => {
169
180
  return executeRequest(`getStreamPosition`, collectionId, {}, resolution);
170
- }
181
+ },
182
+ reserveLeadershipTerm: async (collectionId, observedTerm) => executeRequest(
183
+ `reserveLeadershipTerm`,
184
+ collectionId,
185
+ { observedTerm },
186
+ resolution
187
+ )
171
188
  };
172
189
  }
173
190
  function resolveInvoke(options) {
@@ -1 +1 @@
1
- {"version":3,"file":"renderer.cjs","sources":["../../src/renderer.ts"],"sourcesContent":["import {\n InvalidPersistedCollectionConfigError,\n SingleProcessCoordinator,\n} from '@tanstack/db-sqlite-persistence-core'\nimport { ElectronCollectionCoordinator } from './electron-coordinator'\nimport {\n DEFAULT_ELECTRON_PERSISTENCE_CHANNEL,\n ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n} from './protocol'\nimport type {\n PersistedCollectionCoordinator,\n PersistedCollectionMode,\n PersistedCollectionPersistence,\n PersistedIndexSpec,\n PersistedTx,\n SQLitePullSinceResult,\n} from '@tanstack/db-sqlite-persistence-core'\nimport type {\n ElectronPersistedKey,\n ElectronPersistedRow,\n ElectronPersistenceInvoke,\n ElectronPersistenceMethod,\n ElectronPersistencePayloadMap,\n ElectronPersistenceRequest,\n ElectronPersistenceRequestEnvelope,\n ElectronPersistenceResolution,\n ElectronPersistenceResponseEnvelope,\n ElectronPersistenceResultMap,\n} from './protocol'\nimport type { LoadSubsetOptions } from '@tanstack/db'\n\nconst DEFAULT_REQUEST_TIMEOUT_MS = 5_000\nlet nextRequestId = 1\n\nfunction createRequestId(): string {\n const requestId = nextRequestId\n nextRequestId++\n return `electron-persistence-${requestId}`\n}\n\nfunction withTimeout<T>(\n promise: Promise<T>,\n timeoutMs: number,\n timeoutMessage: string,\n): Promise<T> {\n if (timeoutMs <= 0) {\n return promise\n }\n\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => {\n reject(new InvalidPersistedCollectionConfigError(timeoutMessage))\n }, timeoutMs)\n\n promise.then(\n (value) => {\n clearTimeout(timer)\n resolve(value)\n },\n (error: unknown) => {\n clearTimeout(timer)\n reject(error)\n },\n )\n })\n}\n\nfunction assertValidResponse(\n response: ElectronPersistenceResponseEnvelope,\n request: ElectronPersistenceRequestEnvelope,\n): void {\n if (response.v !== ELECTRON_PERSISTENCE_PROTOCOL_VERSION) {\n throw new InvalidPersistedCollectionConfigError(\n `Unexpected electron persistence protocol version \"${response.v}\" in response`,\n )\n }\n\n if (response.requestId !== request.requestId) {\n throw new InvalidPersistedCollectionConfigError(\n `Mismatched electron persistence response request id. Expected \"${request.requestId}\", received \"${response.requestId}\"`,\n )\n }\n\n if (response.method !== request.method) {\n throw new InvalidPersistedCollectionConfigError(\n `Mismatched electron persistence response method. Expected \"${request.method}\", received \"${response.method}\"`,\n )\n }\n}\n\nfunction createSerializableLoadSubsetOptions(\n subsetOptions: LoadSubsetOptions,\n): LoadSubsetOptions {\n const { subscription: _subscription, ...serializableOptions } = subsetOptions\n return serializableOptions\n}\n\ntype RendererRequestExecutor = <TMethod extends ElectronPersistenceMethod>(\n method: TMethod,\n collectionId: string,\n payload: ElectronPersistencePayloadMap[TMethod],\n resolution?: ElectronPersistenceResolution,\n) => Promise<ElectronPersistenceResultMap[TMethod]>\n\nfunction createRendererRequestExecutor(options: {\n invoke: ElectronPersistenceInvoke\n channel?: string\n timeoutMs?: number\n}): RendererRequestExecutor {\n const channel = options.channel ?? DEFAULT_ELECTRON_PERSISTENCE_CHANNEL\n const timeoutMs = options.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS\n\n return async <TMethod extends ElectronPersistenceMethod>(\n method: TMethod,\n collectionId: string,\n payload: ElectronPersistencePayloadMap[TMethod],\n resolution?: ElectronPersistenceResolution,\n ) => {\n const request = {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: createRequestId(),\n collectionId,\n method,\n resolution,\n payload,\n } as ElectronPersistenceRequest\n\n const response = await withTimeout(\n options.invoke(channel, request),\n timeoutMs,\n `Electron persistence request timed out (method=${method}, collection=${collectionId}, timeoutMs=${timeoutMs})`,\n )\n assertValidResponse(response, request)\n\n if (!response.ok) {\n const remoteError = new InvalidPersistedCollectionConfigError(\n `${response.error.name}: ${response.error.message}`,\n )\n if (typeof response.error.stack === `string`) {\n remoteError.stack = response.error.stack\n }\n if (typeof response.error.code === `string`) {\n ;(remoteError as Error & { code?: string }).code = response.error.code\n }\n throw remoteError\n }\n\n return response.result as ElectronPersistenceResultMap[TMethod]\n }\n}\n\ntype ElectronRendererResolvedAdapter =\n PersistedCollectionPersistence[`adapter`] & {\n loadCollectionMetadata: (\n collectionId: string,\n ) => Promise<Array<{ key: string; value: unknown }>>\n scanRows: (\n collectionId: string,\n options?: { metadataOnly?: boolean },\n ) => Promise<\n Array<{\n key: string | number\n value: Record<string, unknown>\n metadata?: unknown\n }>\n >\n pullSince: (\n collectionId: string,\n fromRowVersion: number,\n ) => Promise<SQLitePullSinceResult<string | number>>\n getStreamPosition: (collectionId: string) => Promise<{\n latestTerm: number\n latestSeq: number\n latestRowVersion: number\n }>\n }\n\nfunction createResolvedRendererAdapter(\n executeRequest: RendererRequestExecutor,\n resolution?: ElectronPersistenceResolution,\n): ElectronRendererResolvedAdapter {\n return {\n loadSubset: async (\n collectionId: string,\n subsetOptions: LoadSubsetOptions,\n ctx?: { requiredIndexSignatures?: ReadonlyArray<string> },\n ) => {\n const result = await executeRequest(\n `loadSubset`,\n collectionId,\n {\n options: createSerializableLoadSubsetOptions(subsetOptions),\n ctx,\n },\n resolution,\n )\n\n return result as Array<{\n key: string | number\n value: Record<string, unknown>\n }>\n },\n loadResumeSnapshot: async (\n collectionId: string,\n ctx?: {\n requiredIndexSignatures?: ReadonlyArray<string>\n includeRows?: boolean\n },\n ) => {\n return executeRequest(\n `loadResumeSnapshot`,\n collectionId,\n { ctx },\n resolution,\n )\n },\n applyCommittedTx: async (\n collectionId: string,\n tx: PersistedTx<Record<string, unknown>, string | number>,\n ): Promise<void> => {\n await executeRequest(\n `applyCommittedTx`,\n collectionId,\n {\n tx: tx as PersistedTx<ElectronPersistedRow, ElectronPersistedKey>,\n },\n resolution,\n )\n },\n loadCollectionMetadata: async (\n collectionId: string,\n ): Promise<Array<{ key: string; value: unknown }>> => {\n return executeRequest(\n `loadCollectionMetadata`,\n collectionId,\n {},\n resolution,\n )\n },\n scanRows: async (\n collectionId: string,\n options?: { metadataOnly?: boolean },\n ): Promise<\n Array<{\n key: string | number\n value: Record<string, unknown>\n metadata?: unknown\n }>\n > => {\n const result = await executeRequest(\n `scanRows`,\n collectionId,\n { options },\n resolution,\n )\n return result as Array<{\n key: string | number\n value: Record<string, unknown>\n metadata?: unknown\n }>\n },\n ensureIndex: async (\n collectionId: string,\n signature: string,\n spec: PersistedIndexSpec,\n ): Promise<void> => {\n await executeRequest(\n `ensureIndex`,\n collectionId,\n {\n signature,\n spec,\n },\n resolution,\n )\n },\n markIndexRemoved: async (\n collectionId: string,\n signature: string,\n ): Promise<void> => {\n await executeRequest(\n `markIndexRemoved`,\n collectionId,\n {\n signature,\n },\n resolution,\n )\n },\n pullSince: async (\n collectionId: string,\n fromRowVersion: number,\n ): Promise<SQLitePullSinceResult<string | number>> => {\n const result = await executeRequest(\n `pullSince`,\n collectionId,\n {\n fromRowVersion,\n },\n resolution,\n )\n return result as SQLitePullSinceResult<string | number>\n },\n getStreamPosition: async (\n collectionId: string,\n ): Promise<{\n latestTerm: number\n latestSeq: number\n latestRowVersion: number\n }> => {\n return executeRequest(`getStreamPosition`, collectionId, {}, resolution)\n },\n }\n}\n\nexport type ElectronIpcRendererLike = {\n invoke: (\n channel: string,\n request: ElectronPersistenceRequestEnvelope,\n ) => Promise<ElectronPersistenceResponseEnvelope>\n}\n\nexport type ElectronSQLitePersistenceOptions = {\n invoke?: ElectronPersistenceInvoke\n ipcRenderer?: ElectronIpcRendererLike\n channel?: string\n timeoutMs?: number\n coordinator?: PersistedCollectionCoordinator\n}\n\nfunction resolveInvoke(\n options: ElectronSQLitePersistenceOptions,\n): ElectronPersistenceInvoke {\n if (options.invoke) {\n return options.invoke\n }\n\n if (options.ipcRenderer) {\n return (channel, request) => options.ipcRenderer!.invoke(channel, request)\n }\n\n throw new InvalidPersistedCollectionConfigError(\n `Electron renderer persistence requires either invoke or ipcRenderer`,\n )\n}\n\nexport function createElectronSQLitePersistence(\n options: ElectronSQLitePersistenceOptions,\n): PersistedCollectionPersistence {\n const invoke = resolveInvoke(options)\n const coordinator = options.coordinator ?? new SingleProcessCoordinator()\n const executeRequest = createRendererRequestExecutor({\n invoke,\n channel: options.channel,\n timeoutMs: options.timeoutMs,\n })\n const adapterCache = new Map<string, ElectronRendererResolvedAdapter>()\n\n const getAdapterForCollection = (\n mode: PersistedCollectionMode,\n schemaVersion: number | undefined,\n ) => {\n const schemaVersionKey =\n schemaVersion === undefined ? `schema:default` : `schema:${schemaVersion}`\n const cacheKey = `mode:${mode}|${schemaVersionKey}`\n const cachedAdapter = adapterCache.get(cacheKey)\n if (cachedAdapter) {\n return cachedAdapter\n }\n\n const adapter = createResolvedRendererAdapter(executeRequest, {\n mode,\n schemaVersion,\n })\n adapterCache.set(cacheKey, adapter)\n return adapter\n }\n\n const createCollectionPersistence = (\n collectionId: string | undefined,\n mode: PersistedCollectionMode,\n schemaVersion: number | undefined,\n ): PersistedCollectionPersistence => {\n const adapter = getAdapterForCollection(mode, schemaVersion)\n if (coordinator instanceof ElectronCollectionCoordinator) {\n if (collectionId === undefined) {\n coordinator.setAdapter(adapter)\n } else {\n coordinator.setAdapterForCollection(collectionId, adapter)\n }\n }\n return { adapter, coordinator }\n }\n\n const defaultPersistence = createCollectionPersistence(\n undefined,\n `sync-absent`,\n undefined,\n )\n if (coordinator instanceof ElectronCollectionCoordinator) {\n coordinator.setAdapter(defaultPersistence.adapter)\n }\n\n return {\n ...defaultPersistence,\n resolvePersistenceForCollection: ({ collectionId, mode, schemaVersion }) =>\n createCollectionPersistence(collectionId, mode, schemaVersion),\n // Backward compatible fallback for older callers.\n resolvePersistenceForMode: (mode) =>\n createCollectionPersistence(undefined, mode, undefined),\n }\n}\n"],"names":["InvalidPersistedCollectionConfigError","ELECTRON_PERSISTENCE_PROTOCOL_VERSION","DEFAULT_ELECTRON_PERSISTENCE_CHANNEL","SingleProcessCoordinator","ElectronCollectionCoordinator"],"mappings":"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"}
1
+ {"version":3,"file":"renderer.cjs","sources":["../../src/renderer.ts"],"sourcesContent":["import {\n InvalidPersistedCollectionConfigError,\n SingleProcessCoordinator,\n} from '@tanstack/db-sqlite-persistence-core'\nimport { ElectronCollectionCoordinator } from './electron-coordinator'\nimport {\n DEFAULT_ELECTRON_PERSISTENCE_CHANNEL,\n ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n} from './protocol'\nimport type {\n CommittedTxAnchor,\n PersistedCollectionCoordinator,\n PersistedCollectionMode,\n PersistedCollectionPersistence,\n PersistedIndexSpec,\n PersistedTx,\n ReconciledCommittedTx,\n SQLitePullSinceResult,\n} from '@tanstack/db-sqlite-persistence-core'\nimport type {\n ElectronPersistedKey,\n ElectronPersistedRow,\n ElectronPersistenceInvoke,\n ElectronPersistenceMethod,\n ElectronPersistencePayloadMap,\n ElectronPersistenceRequest,\n ElectronPersistenceRequestEnvelope,\n ElectronPersistenceResolution,\n ElectronPersistenceResponseEnvelope,\n ElectronPersistenceResultMap,\n} from './protocol'\nimport type { LoadSubsetOptions } from '@tanstack/db'\n\nconst DEFAULT_REQUEST_TIMEOUT_MS = 5_000\nlet nextRequestId = 1\n\nfunction createRequestId(): string {\n const requestId = nextRequestId\n nextRequestId++\n return `electron-persistence-${requestId}`\n}\n\nfunction withTimeout<T>(\n promise: Promise<T>,\n timeoutMs: number,\n timeoutMessage: string,\n): Promise<T> {\n if (timeoutMs <= 0) {\n return promise\n }\n\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => {\n reject(new InvalidPersistedCollectionConfigError(timeoutMessage))\n }, timeoutMs)\n\n promise.then(\n (value) => {\n clearTimeout(timer)\n resolve(value)\n },\n (error: unknown) => {\n clearTimeout(timer)\n reject(error)\n },\n )\n })\n}\n\nfunction assertValidResponse(\n response: ElectronPersistenceResponseEnvelope,\n request: ElectronPersistenceRequestEnvelope,\n): void {\n if (response.v !== ELECTRON_PERSISTENCE_PROTOCOL_VERSION) {\n throw new InvalidPersistedCollectionConfigError(\n `Unexpected electron persistence protocol version \"${response.v}\" in response`,\n )\n }\n\n if (response.requestId !== request.requestId) {\n throw new InvalidPersistedCollectionConfigError(\n `Mismatched electron persistence response request id. Expected \"${request.requestId}\", received \"${response.requestId}\"`,\n )\n }\n\n if (response.method !== request.method) {\n throw new InvalidPersistedCollectionConfigError(\n `Mismatched electron persistence response method. Expected \"${request.method}\", received \"${response.method}\"`,\n )\n }\n}\n\nfunction createSerializableLoadSubsetOptions(\n subsetOptions: LoadSubsetOptions,\n): LoadSubsetOptions {\n const { subscription: _subscription, ...serializableOptions } = subsetOptions\n return serializableOptions\n}\n\ntype RendererRequestExecutor = <TMethod extends ElectronPersistenceMethod>(\n method: TMethod,\n collectionId: string,\n payload: ElectronPersistencePayloadMap[TMethod],\n resolution?: ElectronPersistenceResolution,\n) => Promise<ElectronPersistenceResultMap[TMethod]>\n\nfunction createRendererRequestExecutor(options: {\n invoke: ElectronPersistenceInvoke\n channel?: string\n timeoutMs?: number\n}): RendererRequestExecutor {\n const channel = options.channel ?? DEFAULT_ELECTRON_PERSISTENCE_CHANNEL\n const timeoutMs = options.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS\n\n return async <TMethod extends ElectronPersistenceMethod>(\n method: TMethod,\n collectionId: string,\n payload: ElectronPersistencePayloadMap[TMethod],\n resolution?: ElectronPersistenceResolution,\n ) => {\n const request = {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: createRequestId(),\n collectionId,\n method,\n resolution,\n payload,\n } as ElectronPersistenceRequest\n\n const response = await withTimeout(\n options.invoke(channel, request),\n timeoutMs,\n `Electron persistence request timed out (method=${method}, collection=${collectionId}, timeoutMs=${timeoutMs})`,\n )\n assertValidResponse(response, request)\n\n if (!response.ok) {\n const remoteError = new InvalidPersistedCollectionConfigError(\n `${response.error.name}: ${response.error.message}`,\n )\n if (typeof response.error.stack === `string`) {\n remoteError.stack = response.error.stack\n }\n if (\n typeof response.error.code === `string` ||\n typeof response.error.code === `number`\n ) {\n ;(remoteError as Error & { code?: string | number }).code =\n response.error.code\n }\n if (response.error.path !== undefined) {\n ;(\n remoteError as Error & {\n path?: string | ReadonlyArray<string | number>\n }\n ).path = response.error.path\n }\n throw remoteError\n }\n\n return response.result as ElectronPersistenceResultMap[TMethod]\n }\n}\n\ntype ElectronRendererResolvedAdapter =\n PersistedCollectionPersistence[`adapter`] & {\n loadCollectionMetadata: (\n collectionId: string,\n ) => Promise<Array<{ key: string; value: unknown }>>\n scanRows: (\n collectionId: string,\n options?: { metadataOnly?: boolean },\n ) => Promise<\n Array<{\n key: string | number\n value: Record<string, unknown>\n metadata?: unknown\n }>\n >\n pullSince: (\n collectionId: string,\n fromRowVersion: number,\n ) => Promise<SQLitePullSinceResult<string | number>>\n getStreamPosition: (collectionId: string) => Promise<{\n latestTerm: number\n latestSeq: number\n latestRowVersion: number\n }>\n reserveLeadershipTerm: (\n collectionId: string,\n observedTerm: number,\n ) => Promise<{\n latestTerm: number\n latestSeq: number\n latestRowVersion: number\n }>\n }\n\nfunction createResolvedRendererAdapter(\n executeRequest: RendererRequestExecutor,\n resolution?: ElectronPersistenceResolution,\n): ElectronRendererResolvedAdapter {\n return {\n loadSubset: async (\n collectionId: string,\n subsetOptions: LoadSubsetOptions,\n ctx?: { requiredIndexSignatures?: ReadonlyArray<string> },\n ) => {\n const result = await executeRequest(\n `loadSubset`,\n collectionId,\n {\n options: createSerializableLoadSubsetOptions(subsetOptions),\n ctx,\n },\n resolution,\n )\n\n return result as Array<{\n key: string | number\n value: Record<string, unknown>\n }>\n },\n loadResumeSnapshot: async (\n collectionId: string,\n ctx?: {\n requiredIndexSignatures?: ReadonlyArray<string>\n includeRows?: boolean\n },\n ) => {\n return executeRequest(\n `loadResumeSnapshot`,\n collectionId,\n { ctx },\n resolution,\n )\n },\n applyCommittedTx: async (\n collectionId: string,\n tx: PersistedTx<Record<string, unknown>, string | number>,\n ): Promise<void> => {\n await executeRequest(\n `applyCommittedTx`,\n collectionId,\n {\n tx: tx as PersistedTx<ElectronPersistedRow, ElectronPersistedKey>,\n },\n resolution,\n )\n },\n reconcileCommittedTx: async (\n collectionId: string,\n tx: PersistedTx<Record<string, unknown>, string | number>,\n anchor: CommittedTxAnchor,\n ): Promise<ReconciledCommittedTx> => {\n return executeRequest(\n `reconcileCommittedTx`,\n collectionId,\n { tx, anchor },\n resolution,\n )\n },\n loadCollectionMetadata: async (\n collectionId: string,\n ): Promise<Array<{ key: string; value: unknown }>> => {\n return executeRequest(\n `loadCollectionMetadata`,\n collectionId,\n {},\n resolution,\n )\n },\n scanRows: async (\n collectionId: string,\n options?: { metadataOnly?: boolean },\n ): Promise<\n Array<{\n key: string | number\n value: Record<string, unknown>\n metadata?: unknown\n }>\n > => {\n const result = await executeRequest(\n `scanRows`,\n collectionId,\n { options },\n resolution,\n )\n return result as Array<{\n key: string | number\n value: Record<string, unknown>\n metadata?: unknown\n }>\n },\n ensureIndex: async (\n collectionId: string,\n signature: string,\n spec: PersistedIndexSpec,\n ): Promise<void> => {\n await executeRequest(\n `ensureIndex`,\n collectionId,\n {\n signature,\n spec,\n },\n resolution,\n )\n },\n markIndexRemoved: async (\n collectionId: string,\n signature: string,\n ): Promise<void> => {\n await executeRequest(\n `markIndexRemoved`,\n collectionId,\n {\n signature,\n },\n resolution,\n )\n },\n pullSince: async (\n collectionId: string,\n fromRowVersion: number,\n ): Promise<SQLitePullSinceResult<string | number>> => {\n const result = await executeRequest(\n `pullSince`,\n collectionId,\n {\n fromRowVersion,\n },\n resolution,\n )\n return result as SQLitePullSinceResult<string | number>\n },\n getStreamPosition: async (\n collectionId: string,\n ): Promise<{\n latestTerm: number\n latestSeq: number\n latestRowVersion: number\n }> => {\n return executeRequest(`getStreamPosition`, collectionId, {}, resolution)\n },\n reserveLeadershipTerm: async (collectionId, observedTerm) =>\n executeRequest(\n `reserveLeadershipTerm`,\n collectionId,\n { observedTerm },\n resolution,\n ),\n }\n}\n\nexport type ElectronIpcRendererLike = {\n invoke: (\n channel: string,\n request: ElectronPersistenceRequestEnvelope,\n ) => Promise<ElectronPersistenceResponseEnvelope>\n}\n\nexport type ElectronSQLitePersistenceOptions = {\n invoke?: ElectronPersistenceInvoke\n ipcRenderer?: ElectronIpcRendererLike\n channel?: string\n timeoutMs?: number\n coordinator?: PersistedCollectionCoordinator\n}\n\nfunction resolveInvoke(\n options: ElectronSQLitePersistenceOptions,\n): ElectronPersistenceInvoke {\n if (options.invoke) {\n return options.invoke\n }\n\n if (options.ipcRenderer) {\n return (channel, request) => options.ipcRenderer!.invoke(channel, request)\n }\n\n throw new InvalidPersistedCollectionConfigError(\n `Electron renderer persistence requires either invoke or ipcRenderer`,\n )\n}\n\nexport function createElectronSQLitePersistence(\n options: ElectronSQLitePersistenceOptions,\n): PersistedCollectionPersistence {\n const invoke = resolveInvoke(options)\n const coordinator = options.coordinator ?? new SingleProcessCoordinator()\n const executeRequest = createRendererRequestExecutor({\n invoke,\n channel: options.channel,\n timeoutMs: options.timeoutMs,\n })\n const adapterCache = new Map<string, ElectronRendererResolvedAdapter>()\n\n const getAdapterForCollection = (\n mode: PersistedCollectionMode,\n schemaVersion: number | undefined,\n ) => {\n const schemaVersionKey =\n schemaVersion === undefined ? `schema:default` : `schema:${schemaVersion}`\n const cacheKey = `mode:${mode}|${schemaVersionKey}`\n const cachedAdapter = adapterCache.get(cacheKey)\n if (cachedAdapter) {\n return cachedAdapter\n }\n\n const adapter = createResolvedRendererAdapter(executeRequest, {\n mode,\n schemaVersion,\n })\n adapterCache.set(cacheKey, adapter)\n return adapter\n }\n\n const createCollectionPersistence = (\n collectionId: string | undefined,\n mode: PersistedCollectionMode,\n schemaVersion: number | undefined,\n ): PersistedCollectionPersistence => {\n const adapter = getAdapterForCollection(mode, schemaVersion)\n if (coordinator instanceof ElectronCollectionCoordinator) {\n if (collectionId === undefined) {\n coordinator.setAdapter(adapter)\n } else {\n coordinator.setAdapterForCollection(collectionId, adapter)\n }\n }\n return { adapter, coordinator }\n }\n\n const defaultPersistence = createCollectionPersistence(\n undefined,\n `sync-absent`,\n undefined,\n )\n if (coordinator instanceof ElectronCollectionCoordinator) {\n coordinator.setAdapter(defaultPersistence.adapter)\n }\n\n return {\n ...defaultPersistence,\n resolvePersistenceForCollection: ({ collectionId, mode, schemaVersion }) =>\n createCollectionPersistence(collectionId, mode, schemaVersion),\n // Backward compatible fallback for older callers.\n resolvePersistenceForMode: (mode) =>\n createCollectionPersistence(undefined, mode, undefined),\n }\n}\n"],"names":["InvalidPersistedCollectionConfigError","ELECTRON_PERSISTENCE_PROTOCOL_VERSION","DEFAULT_ELECTRON_PERSISTENCE_CHANNEL","SingleProcessCoordinator","ElectronCollectionCoordinator"],"mappings":"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"}
@@ -1,10 +1,10 @@
1
1
  import { ElectronPersistenceMethod, ElectronSerializedError } from './protocol.js';
2
2
  type ElectronPersistenceErrorOptions = {
3
- code?: string;
3
+ code?: string | number;
4
4
  cause?: unknown;
5
5
  };
6
6
  export declare class ElectronPersistenceError extends Error {
7
- readonly code: string | undefined;
7
+ readonly code: string | number | undefined;
8
8
  constructor(message: string, options?: ElectronPersistenceErrorOptions);
9
9
  }
10
10
  export declare class UnknownElectronPersistenceCollectionError extends ElectronPersistenceError {
package/dist/esm/main.js CHANGED
@@ -9,12 +9,16 @@ function serializeError(error) {
9
9
  code: void 0
10
10
  };
11
11
  }
12
- const codedError = error;
12
+ const detailedError = error;
13
+ const path = detailedError.path;
13
14
  return {
14
15
  name: error.name || `Error`,
15
16
  message: error.message || fallbackMessage,
16
17
  stack: error.stack,
17
- code: typeof codedError.code === `string` ? codedError.code : void 0
18
+ code: typeof detailedError.code === `string` || typeof detailedError.code === `number` ? detailedError.code : void 0,
19
+ path: typeof path === `string` || Array.isArray(path) && path.every(
20
+ (segment) => typeof segment === `string` || typeof segment === `number`
21
+ ) ? path : void 0
18
22
  };
19
23
  }
20
24
  function createErrorResponse(request, error) {
@@ -115,6 +119,20 @@ async function executeRequestAgainstAdapter(request, adapter) {
115
119
  result: null
116
120
  };
117
121
  }
122
+ case `reconcileCommittedTx`: {
123
+ const result = adapter.reconcileCommittedTx ? await adapter.reconcileCommittedTx(
124
+ request.collectionId,
125
+ request.payload.tx,
126
+ request.payload.anchor
127
+ ) : { kind: `unknown` };
128
+ return {
129
+ v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,
130
+ requestId: request.requestId,
131
+ method: request.method,
132
+ ok: true,
133
+ result
134
+ };
135
+ }
118
136
  case `ensureIndex`: {
119
137
  await adapter.ensureIndex(
120
138
  request.collectionId,
@@ -165,6 +183,24 @@ async function executeRequestAgainstAdapter(request, adapter) {
165
183
  result
166
184
  };
167
185
  }
186
+ case `reserveLeadershipTerm`: {
187
+ if (!adapter.reserveLeadershipTerm) {
188
+ throw new InvalidPersistedCollectionConfigError(
189
+ `reserveLeadershipTerm is not supported by the configured electron persistence adapter`
190
+ );
191
+ }
192
+ const position = await adapter.reserveLeadershipTerm(
193
+ request.collectionId,
194
+ request.payload.observedTerm
195
+ );
196
+ return {
197
+ v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,
198
+ requestId: request.requestId,
199
+ method: request.method,
200
+ ok: true,
201
+ result: position
202
+ };
203
+ }
168
204
  case `getStreamPosition`: {
169
205
  if (!adapter.getStreamPosition) {
170
206
  throw new InvalidPersistedCollectionConfigError(
@@ -1 +1 @@
1
- {"version":3,"file":"main.js","sources":["../../src/main.ts"],"sourcesContent":["import { InvalidPersistedCollectionConfigError } from '@tanstack/db-sqlite-persistence-core'\nimport {\n DEFAULT_ELECTRON_PERSISTENCE_CHANNEL,\n ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n} from './protocol'\nimport type {\n PersistedCollectionPersistence,\n PersistenceAdapter,\n SQLitePullSinceResult,\n} from '@tanstack/db-sqlite-persistence-core'\nimport type {\n ElectronPersistedKey,\n ElectronPersistedRow,\n ElectronPersistenceRequestEnvelope,\n ElectronPersistenceResponseEnvelope,\n ElectronSerializedError,\n} from './protocol'\n\ntype ElectronMainPersistenceAdapter = PersistenceAdapter & {\n loadCollectionMetadata?: (\n collectionId: string,\n ) => Promise<Array<{ key: string; value: unknown }>>\n scanRows?: (\n collectionId: string,\n options?: { metadataOnly?: boolean },\n ) => Promise<\n Array<{\n key: ElectronPersistedKey\n value: ElectronPersistedRow\n metadata?: unknown\n }>\n >\n pullSince?: (\n collectionId: string,\n fromRowVersion: number,\n ) => Promise<SQLitePullSinceResult<ElectronPersistedKey>>\n getStreamPosition?: (collectionId: string) => Promise<{\n latestTerm: number\n latestSeq: number\n latestRowVersion: number\n }>\n}\n\nfunction serializeError(error: unknown): ElectronSerializedError {\n const fallbackMessage = `Unknown electron persistence error`\n\n if (!(error instanceof Error)) {\n return {\n name: `Error`,\n message: fallbackMessage,\n code: undefined,\n }\n }\n\n const codedError = error as Error & { code?: unknown }\n return {\n name: error.name || `Error`,\n message: error.message || fallbackMessage,\n stack: error.stack,\n code: typeof codedError.code === `string` ? codedError.code : undefined,\n }\n}\n\nfunction createErrorResponse(\n request: ElectronPersistenceRequestEnvelope,\n error: unknown,\n): ElectronPersistenceResponseEnvelope {\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: false,\n error: serializeError(error),\n }\n}\n\nfunction assertValidRequest(request: ElectronPersistenceRequestEnvelope): void {\n if (request.v !== ELECTRON_PERSISTENCE_PROTOCOL_VERSION) {\n throw new InvalidPersistedCollectionConfigError(\n `Unsupported electron persistence protocol version \"${request.v}\"`,\n )\n }\n\n if (\n typeof request.requestId !== `string` ||\n request.requestId.trim().length === 0\n ) {\n throw new InvalidPersistedCollectionConfigError(\n `Electron persistence requestId cannot be empty`,\n )\n }\n\n if (\n typeof request.collectionId !== `string` ||\n request.collectionId.trim().length === 0\n ) {\n throw new InvalidPersistedCollectionConfigError(\n `Electron persistence collectionId cannot be empty`,\n )\n }\n}\n\nasync function executeRequestAgainstAdapter(\n request: ElectronPersistenceRequestEnvelope,\n adapter: ElectronMainPersistenceAdapter,\n): Promise<ElectronPersistenceResponseEnvelope> {\n switch (request.method) {\n case `loadSubset`: {\n const result = await adapter.loadSubset(\n request.collectionId,\n request.payload.options,\n request.payload.ctx,\n )\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result,\n }\n }\n\n case `loadResumeSnapshot`: {\n const result = await adapter.loadResumeSnapshot(\n request.collectionId,\n request.payload.ctx,\n )\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result,\n }\n }\n\n case `loadCollectionMetadata`: {\n if (!adapter.loadCollectionMetadata) {\n throw new InvalidPersistedCollectionConfigError(\n `loadCollectionMetadata is not supported by the configured electron persistence adapter`,\n )\n }\n const result = await adapter.loadCollectionMetadata(request.collectionId)\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result,\n }\n }\n\n case `scanRows`: {\n if (!adapter.scanRows) {\n throw new InvalidPersistedCollectionConfigError(\n `scanRows is not supported by the configured electron persistence adapter`,\n )\n }\n const result = await adapter.scanRows(\n request.collectionId,\n request.payload.options,\n )\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result,\n }\n }\n\n case `applyCommittedTx`: {\n await adapter.applyCommittedTx(request.collectionId, request.payload.tx)\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result: null,\n }\n }\n\n case `ensureIndex`: {\n await adapter.ensureIndex(\n request.collectionId,\n request.payload.signature,\n request.payload.spec,\n )\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result: null,\n }\n }\n\n case `markIndexRemoved`: {\n if (!adapter.markIndexRemoved) {\n throw new InvalidPersistedCollectionConfigError(\n `markIndexRemoved is not supported by the configured electron persistence adapter`,\n )\n }\n await adapter.markIndexRemoved(\n request.collectionId,\n request.payload.signature,\n )\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result: null,\n }\n }\n\n case `pullSince`: {\n if (!adapter.pullSince) {\n throw new InvalidPersistedCollectionConfigError(\n `pullSince is not supported by the configured electron persistence adapter`,\n )\n }\n const result = await adapter.pullSince(\n request.collectionId,\n request.payload.fromRowVersion,\n )\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result,\n }\n }\n\n case `getStreamPosition`: {\n if (!adapter.getStreamPosition) {\n throw new InvalidPersistedCollectionConfigError(\n `getStreamPosition is not supported by the configured electron persistence adapter`,\n )\n }\n const position = await adapter.getStreamPosition(request.collectionId)\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result: position,\n }\n }\n }\n}\n\nfunction resolveModeAwarePersistence(\n persistence: PersistedCollectionPersistence,\n request: ElectronPersistenceRequestEnvelope,\n): PersistedCollectionPersistence {\n const mode = request.resolution?.mode ?? `sync-absent`\n const schemaVersion = request.resolution?.schemaVersion\n const collectionAwarePersistence =\n persistence.resolvePersistenceForCollection?.({\n collectionId: request.collectionId,\n mode,\n schemaVersion,\n })\n if (collectionAwarePersistence) {\n return collectionAwarePersistence\n }\n\n const modeAwarePersistence = persistence.resolvePersistenceForMode?.(mode)\n return modeAwarePersistence ?? persistence\n}\n\nexport type ElectronIpcMainLike = {\n handle: (\n channel: string,\n listener: (\n event: unknown,\n request: ElectronPersistenceRequestEnvelope,\n ) => Promise<ElectronPersistenceResponseEnvelope>,\n ) => void\n removeHandler?: (channel: string) => void\n}\n\nexport type ElectronSQLiteMainProcessOptions = {\n persistence: PersistedCollectionPersistence\n ipcMain: ElectronIpcMainLike\n channel?: string\n}\n\nexport function exposeElectronSQLitePersistence(\n options: ElectronSQLiteMainProcessOptions,\n): () => void {\n const channel = options.channel ?? DEFAULT_ELECTRON_PERSISTENCE_CHANNEL\n options.ipcMain.handle(\n channel,\n async (\n _event,\n request: ElectronPersistenceRequestEnvelope,\n ): Promise<ElectronPersistenceResponseEnvelope> => {\n try {\n assertValidRequest(request)\n const modeAwarePersistence = resolveModeAwarePersistence(\n options.persistence,\n request,\n )\n return await executeRequestAgainstAdapter(\n request,\n modeAwarePersistence.adapter,\n )\n } catch (error) {\n return createErrorResponse(request, error)\n }\n },\n )\n\n return () => {\n options.ipcMain.removeHandler?.(channel)\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"main.js","sources":["../../src/main.ts"],"sourcesContent":["import { InvalidPersistedCollectionConfigError } from '@tanstack/db-sqlite-persistence-core'\nimport {\n DEFAULT_ELECTRON_PERSISTENCE_CHANNEL,\n ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n} from './protocol'\nimport type {\n PersistedCollectionPersistence,\n PersistenceAdapter,\n SQLitePullSinceResult,\n} from '@tanstack/db-sqlite-persistence-core'\nimport type {\n ElectronPersistedKey,\n ElectronPersistedRow,\n ElectronPersistenceRequestEnvelope,\n ElectronPersistenceResponseEnvelope,\n ElectronSerializedError,\n} from './protocol'\n\ntype ElectronMainPersistenceAdapter = PersistenceAdapter & {\n loadCollectionMetadata?: (\n collectionId: string,\n ) => Promise<Array<{ key: string; value: unknown }>>\n scanRows?: (\n collectionId: string,\n options?: { metadataOnly?: boolean },\n ) => Promise<\n Array<{\n key: ElectronPersistedKey\n value: ElectronPersistedRow\n metadata?: unknown\n }>\n >\n pullSince?: (\n collectionId: string,\n fromRowVersion: number,\n ) => Promise<SQLitePullSinceResult<ElectronPersistedKey>>\n getStreamPosition?: (collectionId: string) => Promise<{\n latestTerm: number\n latestSeq: number\n latestRowVersion: number\n }>\n}\n\nfunction serializeError(error: unknown): ElectronSerializedError {\n const fallbackMessage = `Unknown electron persistence error`\n\n if (!(error instanceof Error)) {\n return {\n name: `Error`,\n message: fallbackMessage,\n code: undefined,\n }\n }\n\n const detailedError = error as Error & { code?: unknown; path?: unknown }\n const path = detailedError.path\n return {\n name: error.name || `Error`,\n message: error.message || fallbackMessage,\n stack: error.stack,\n code:\n typeof detailedError.code === `string` ||\n typeof detailedError.code === `number`\n ? detailedError.code\n : undefined,\n path:\n typeof path === `string` ||\n (Array.isArray(path) &&\n path.every(\n (segment: unknown) =>\n typeof segment === `string` || typeof segment === `number`,\n ))\n ? (path as string | ReadonlyArray<string | number>)\n : undefined,\n }\n}\n\nfunction createErrorResponse(\n request: ElectronPersistenceRequestEnvelope,\n error: unknown,\n): ElectronPersistenceResponseEnvelope {\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: false,\n error: serializeError(error),\n }\n}\n\nfunction assertValidRequest(request: ElectronPersistenceRequestEnvelope): void {\n if (request.v !== ELECTRON_PERSISTENCE_PROTOCOL_VERSION) {\n throw new InvalidPersistedCollectionConfigError(\n `Unsupported electron persistence protocol version \"${request.v}\"`,\n )\n }\n\n if (\n typeof request.requestId !== `string` ||\n request.requestId.trim().length === 0\n ) {\n throw new InvalidPersistedCollectionConfigError(\n `Electron persistence requestId cannot be empty`,\n )\n }\n\n if (\n typeof request.collectionId !== `string` ||\n request.collectionId.trim().length === 0\n ) {\n throw new InvalidPersistedCollectionConfigError(\n `Electron persistence collectionId cannot be empty`,\n )\n }\n}\n\nasync function executeRequestAgainstAdapter(\n request: ElectronPersistenceRequestEnvelope,\n adapter: ElectronMainPersistenceAdapter,\n): Promise<ElectronPersistenceResponseEnvelope> {\n switch (request.method) {\n case `loadSubset`: {\n const result = await adapter.loadSubset(\n request.collectionId,\n request.payload.options,\n request.payload.ctx,\n )\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result,\n }\n }\n\n case `loadResumeSnapshot`: {\n const result = await adapter.loadResumeSnapshot(\n request.collectionId,\n request.payload.ctx,\n )\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result,\n }\n }\n\n case `loadCollectionMetadata`: {\n if (!adapter.loadCollectionMetadata) {\n throw new InvalidPersistedCollectionConfigError(\n `loadCollectionMetadata is not supported by the configured electron persistence adapter`,\n )\n }\n const result = await adapter.loadCollectionMetadata(request.collectionId)\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result,\n }\n }\n\n case `scanRows`: {\n if (!adapter.scanRows) {\n throw new InvalidPersistedCollectionConfigError(\n `scanRows is not supported by the configured electron persistence adapter`,\n )\n }\n const result = await adapter.scanRows(\n request.collectionId,\n request.payload.options,\n )\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result,\n }\n }\n\n case `applyCommittedTx`: {\n await adapter.applyCommittedTx(request.collectionId, request.payload.tx)\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result: null,\n }\n }\n\n case `reconcileCommittedTx`: {\n const result = adapter.reconcileCommittedTx\n ? await adapter.reconcileCommittedTx(\n request.collectionId,\n request.payload.tx,\n request.payload.anchor,\n )\n : { kind: `unknown` as const }\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result,\n }\n }\n\n case `ensureIndex`: {\n await adapter.ensureIndex(\n request.collectionId,\n request.payload.signature,\n request.payload.spec,\n )\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result: null,\n }\n }\n\n case `markIndexRemoved`: {\n if (!adapter.markIndexRemoved) {\n throw new InvalidPersistedCollectionConfigError(\n `markIndexRemoved is not supported by the configured electron persistence adapter`,\n )\n }\n await adapter.markIndexRemoved(\n request.collectionId,\n request.payload.signature,\n )\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result: null,\n }\n }\n\n case `pullSince`: {\n if (!adapter.pullSince) {\n throw new InvalidPersistedCollectionConfigError(\n `pullSince is not supported by the configured electron persistence adapter`,\n )\n }\n const result = await adapter.pullSince(\n request.collectionId,\n request.payload.fromRowVersion,\n )\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result,\n }\n }\n\n case `reserveLeadershipTerm`: {\n if (!adapter.reserveLeadershipTerm) {\n throw new InvalidPersistedCollectionConfigError(\n `reserveLeadershipTerm is not supported by the configured electron persistence adapter`,\n )\n }\n const position = await adapter.reserveLeadershipTerm(\n request.collectionId,\n request.payload.observedTerm,\n )\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result: position,\n }\n }\n\n case `getStreamPosition`: {\n if (!adapter.getStreamPosition) {\n throw new InvalidPersistedCollectionConfigError(\n `getStreamPosition is not supported by the configured electron persistence adapter`,\n )\n }\n const position = await adapter.getStreamPosition(request.collectionId)\n return {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: request.requestId,\n method: request.method,\n ok: true,\n result: position,\n }\n }\n }\n}\n\nfunction resolveModeAwarePersistence(\n persistence: PersistedCollectionPersistence,\n request: ElectronPersistenceRequestEnvelope,\n): PersistedCollectionPersistence {\n const mode = request.resolution?.mode ?? `sync-absent`\n const schemaVersion = request.resolution?.schemaVersion\n const collectionAwarePersistence =\n persistence.resolvePersistenceForCollection?.({\n collectionId: request.collectionId,\n mode,\n schemaVersion,\n })\n if (collectionAwarePersistence) {\n return collectionAwarePersistence\n }\n\n const modeAwarePersistence = persistence.resolvePersistenceForMode?.(mode)\n return modeAwarePersistence ?? persistence\n}\n\nexport type ElectronIpcMainLike = {\n handle: (\n channel: string,\n listener: (\n event: unknown,\n request: ElectronPersistenceRequestEnvelope,\n ) => Promise<ElectronPersistenceResponseEnvelope>,\n ) => void\n removeHandler?: (channel: string) => void\n}\n\nexport type ElectronSQLiteMainProcessOptions = {\n persistence: PersistedCollectionPersistence\n ipcMain: ElectronIpcMainLike\n channel?: string\n}\n\nexport function exposeElectronSQLitePersistence(\n options: ElectronSQLiteMainProcessOptions,\n): () => void {\n const channel = options.channel ?? DEFAULT_ELECTRON_PERSISTENCE_CHANNEL\n options.ipcMain.handle(\n channel,\n async (\n _event,\n request: ElectronPersistenceRequestEnvelope,\n ): Promise<ElectronPersistenceResponseEnvelope> => {\n try {\n assertValidRequest(request)\n const modeAwarePersistence = resolveModeAwarePersistence(\n options.persistence,\n request,\n )\n return await executeRequestAgainstAdapter(\n request,\n modeAwarePersistence.adapter,\n )\n } catch (error) {\n return createErrorResponse(request, error)\n }\n },\n )\n\n return () => {\n options.ipcMain.removeHandler?.(channel)\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { LoadSubsetOptions } from '@tanstack/db';
2
- import { PersistedCollectionMode, PersistedIndexSpec, PersistedKeySetEvidence, PersistedTx, SQLitePullSinceResult } from '@tanstack/db-sqlite-persistence-core';
3
- export declare const ELECTRON_PERSISTENCE_PROTOCOL_VERSION: 2;
2
+ import { CommittedTxAnchor, PersistedCollectionMode, PersistedIndexSpec, PersistedKeySetEvidence, PersistedTx, ReconciledCommittedTx, SQLitePullSinceResult } from '@tanstack/db-sqlite-persistence-core';
3
+ export declare const ELECTRON_PERSISTENCE_PROTOCOL_VERSION: 4;
4
4
  export declare const DEFAULT_ELECTRON_PERSISTENCE_CHANNEL = "tanstack-db:sqlite-persistence";
5
5
  export type ElectronPersistedRow = Record<string, unknown>;
6
6
  export type ElectronPersistedKey = string | number;
@@ -8,7 +8,7 @@ export type ElectronPersistenceResolution = {
8
8
  mode: PersistedCollectionMode;
9
9
  schemaVersion?: number;
10
10
  };
11
- export type ElectronPersistenceMethod = `loadSubset` | `loadResumeSnapshot` | `loadCollectionMetadata` | `scanRows` | `applyCommittedTx` | `ensureIndex` | `markIndexRemoved` | `pullSince` | `getStreamPosition`;
11
+ export type ElectronPersistenceMethod = `loadSubset` | `loadResumeSnapshot` | `loadCollectionMetadata` | `scanRows` | `applyCommittedTx` | `reconcileCommittedTx` | `ensureIndex` | `markIndexRemoved` | `pullSince` | `getStreamPosition` | `reserveLeadershipTerm`;
12
12
  export type ElectronPersistencePayloadMap = {
13
13
  loadSubset: {
14
14
  options: LoadSubsetOptions;
@@ -31,6 +31,10 @@ export type ElectronPersistencePayloadMap = {
31
31
  applyCommittedTx: {
32
32
  tx: PersistedTx<ElectronPersistedRow, ElectronPersistedKey>;
33
33
  };
34
+ reconcileCommittedTx: {
35
+ tx: PersistedTx<ElectronPersistedRow, ElectronPersistedKey>;
36
+ anchor: CommittedTxAnchor;
37
+ };
34
38
  ensureIndex: {
35
39
  signature: string;
36
40
  spec: PersistedIndexSpec;
@@ -42,6 +46,9 @@ export type ElectronPersistencePayloadMap = {
42
46
  fromRowVersion: number;
43
47
  };
44
48
  getStreamPosition: {};
49
+ reserveLeadershipTerm: {
50
+ observedTerm: number;
51
+ };
45
52
  };
46
53
  export type ElectronPersistenceResultMap = {
47
54
  loadSubset: Array<{
@@ -74,6 +81,7 @@ export type ElectronPersistenceResultMap = {
74
81
  metadata?: unknown;
75
82
  }>;
76
83
  applyCommittedTx: null;
84
+ reconcileCommittedTx: ReconciledCommittedTx;
77
85
  ensureIndex: null;
78
86
  markIndexRemoved: null;
79
87
  pullSince: SQLitePullSinceResult<ElectronPersistedKey>;
@@ -82,12 +90,18 @@ export type ElectronPersistenceResultMap = {
82
90
  latestSeq: number;
83
91
  latestRowVersion: number;
84
92
  };
93
+ reserveLeadershipTerm: {
94
+ latestTerm: number;
95
+ latestSeq: number;
96
+ latestRowVersion: number;
97
+ };
85
98
  };
86
99
  export type ElectronSerializedError = {
87
100
  name: string;
88
101
  message: string;
89
102
  stack?: string;
90
- code?: string;
103
+ code?: string | number;
104
+ path?: string | ReadonlyArray<string | number>;
91
105
  };
92
106
  export type ElectronPersistenceRequestByMethod = {
93
107
  [Method in ElectronPersistenceMethod]: {
@@ -1,4 +1,4 @@
1
- const ELECTRON_PERSISTENCE_PROTOCOL_VERSION = 2;
1
+ const ELECTRON_PERSISTENCE_PROTOCOL_VERSION = 4;
2
2
  const DEFAULT_ELECTRON_PERSISTENCE_CHANNEL = `tanstack-db:sqlite-persistence`;
3
3
  export {
4
4
  DEFAULT_ELECTRON_PERSISTENCE_CHANNEL,
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.js","sources":["../../src/protocol.ts"],"sourcesContent":["import type { LoadSubsetOptions } from '@tanstack/db'\nimport type {\n PersistedCollectionMode,\n PersistedIndexSpec,\n PersistedKeySetEvidence,\n PersistedTx,\n SQLitePullSinceResult,\n} from '@tanstack/db-sqlite-persistence-core'\n\nexport const ELECTRON_PERSISTENCE_PROTOCOL_VERSION = 2 as const\nexport const DEFAULT_ELECTRON_PERSISTENCE_CHANNEL = `tanstack-db:sqlite-persistence`\n\nexport type ElectronPersistedRow = Record<string, unknown>\nexport type ElectronPersistedKey = string | number\n\nexport type ElectronPersistenceResolution = {\n mode: PersistedCollectionMode\n schemaVersion?: number\n}\n\nexport type ElectronPersistenceMethod =\n | `loadSubset`\n | `loadResumeSnapshot`\n | `loadCollectionMetadata`\n | `scanRows`\n | `applyCommittedTx`\n | `ensureIndex`\n | `markIndexRemoved`\n | `pullSince`\n | `getStreamPosition`\n\nexport type ElectronPersistencePayloadMap = {\n loadSubset: {\n options: LoadSubsetOptions\n ctx?: { requiredIndexSignatures?: ReadonlyArray<string> }\n }\n loadResumeSnapshot: {\n ctx?: {\n requiredIndexSignatures?: ReadonlyArray<string>\n includeRows?: boolean\n }\n }\n loadCollectionMetadata: {}\n scanRows: {\n options?: {\n metadataOnly?: boolean\n }\n }\n applyCommittedTx: {\n tx: PersistedTx<ElectronPersistedRow, ElectronPersistedKey>\n }\n ensureIndex: {\n signature: string\n spec: PersistedIndexSpec\n }\n markIndexRemoved: {\n signature: string\n }\n pullSince: {\n fromRowVersion: number\n }\n getStreamPosition: {}\n}\n\nexport type ElectronPersistenceResultMap = {\n loadSubset: Array<{ key: ElectronPersistedKey; value: ElectronPersistedRow }>\n loadResumeSnapshot: {\n rows: Array<{\n key: ElectronPersistedKey\n value: ElectronPersistedRow\n metadata?: unknown\n }>\n keySet?: PersistedKeySetEvidence\n collectionMetadata: Array<{ key: string; value: unknown }>\n latestTerm: number\n latestSeq: number\n latestRowVersion: number\n resetEpoch: number\n }\n loadCollectionMetadata: Array<{ key: string; value: unknown }>\n scanRows: Array<{\n key: ElectronPersistedKey\n value: ElectronPersistedRow\n metadata?: unknown\n }>\n applyCommittedTx: null\n ensureIndex: null\n markIndexRemoved: null\n pullSince: SQLitePullSinceResult<ElectronPersistedKey>\n getStreamPosition: {\n latestTerm: number\n latestSeq: number\n latestRowVersion: number\n }\n}\n\nexport type ElectronSerializedError = {\n name: string\n message: string\n stack?: string\n code?: string\n}\n\nexport type ElectronPersistenceRequestByMethod = {\n [Method in ElectronPersistenceMethod]: {\n v: number\n requestId: string\n collectionId: string\n resolution?: ElectronPersistenceResolution\n method: Method\n payload: ElectronPersistencePayloadMap[Method]\n }\n}\n\nexport type ElectronPersistenceRequest<\n TMethod extends ElectronPersistenceMethod = ElectronPersistenceMethod,\n> = ElectronPersistenceRequestByMethod[TMethod]\n\nexport type ElectronPersistenceRequestEnvelope =\n ElectronPersistenceRequestByMethod[ElectronPersistenceMethod]\n\ntype ElectronPersistenceSuccessResponseByMethod = {\n [Method in ElectronPersistenceMethod]: {\n v: number\n requestId: string\n method: Method\n ok: true\n result: ElectronPersistenceResultMap[Method]\n }\n}\n\ntype ElectronPersistenceErrorResponseByMethod = {\n [Method in ElectronPersistenceMethod]: {\n v: number\n requestId: string\n method: Method\n ok: false\n error: ElectronSerializedError\n }\n}\n\nexport type ElectronPersistenceResponse<\n TMethod extends ElectronPersistenceMethod = ElectronPersistenceMethod,\n> =\n | ElectronPersistenceSuccessResponseByMethod[TMethod]\n | ElectronPersistenceErrorResponseByMethod[TMethod]\n\nexport type ElectronPersistenceResponseEnvelope =\n ElectronPersistenceResponse<ElectronPersistenceMethod>\n\nexport type ElectronPersistenceRequestHandler = (\n request: ElectronPersistenceRequestEnvelope,\n) => Promise<ElectronPersistenceResponseEnvelope>\n\nexport type ElectronPersistenceInvoke = (\n channel: string,\n request: ElectronPersistenceRequestEnvelope,\n) => Promise<ElectronPersistenceResponseEnvelope>\n"],"names":[],"mappings":"AASO,MAAM,wCAAwC;AAC9C,MAAM,uCAAuC;"}
1
+ {"version":3,"file":"protocol.js","sources":["../../src/protocol.ts"],"sourcesContent":["import type { LoadSubsetOptions } from '@tanstack/db'\nimport type {\n CommittedTxAnchor,\n PersistedCollectionMode,\n PersistedIndexSpec,\n PersistedKeySetEvidence,\n PersistedTx,\n ReconciledCommittedTx,\n SQLitePullSinceResult,\n} from '@tanstack/db-sqlite-persistence-core'\n\nexport const ELECTRON_PERSISTENCE_PROTOCOL_VERSION = 4 as const\nexport const DEFAULT_ELECTRON_PERSISTENCE_CHANNEL = `tanstack-db:sqlite-persistence`\n\nexport type ElectronPersistedRow = Record<string, unknown>\nexport type ElectronPersistedKey = string | number\n\nexport type ElectronPersistenceResolution = {\n mode: PersistedCollectionMode\n schemaVersion?: number\n}\n\nexport type ElectronPersistenceMethod =\n | `loadSubset`\n | `loadResumeSnapshot`\n | `loadCollectionMetadata`\n | `scanRows`\n | `applyCommittedTx`\n | `reconcileCommittedTx`\n | `ensureIndex`\n | `markIndexRemoved`\n | `pullSince`\n | `getStreamPosition`\n | `reserveLeadershipTerm`\n\nexport type ElectronPersistencePayloadMap = {\n loadSubset: {\n options: LoadSubsetOptions\n ctx?: { requiredIndexSignatures?: ReadonlyArray<string> }\n }\n loadResumeSnapshot: {\n ctx?: {\n requiredIndexSignatures?: ReadonlyArray<string>\n includeRows?: boolean\n }\n }\n loadCollectionMetadata: {}\n scanRows: {\n options?: {\n metadataOnly?: boolean\n }\n }\n applyCommittedTx: {\n tx: PersistedTx<ElectronPersistedRow, ElectronPersistedKey>\n }\n reconcileCommittedTx: {\n tx: PersistedTx<ElectronPersistedRow, ElectronPersistedKey>\n anchor: CommittedTxAnchor\n }\n ensureIndex: {\n signature: string\n spec: PersistedIndexSpec\n }\n markIndexRemoved: {\n signature: string\n }\n pullSince: {\n fromRowVersion: number\n }\n getStreamPosition: {}\n reserveLeadershipTerm: { observedTerm: number }\n}\n\nexport type ElectronPersistenceResultMap = {\n loadSubset: Array<{ key: ElectronPersistedKey; value: ElectronPersistedRow }>\n loadResumeSnapshot: {\n rows: Array<{\n key: ElectronPersistedKey\n value: ElectronPersistedRow\n metadata?: unknown\n }>\n keySet?: PersistedKeySetEvidence\n collectionMetadata: Array<{ key: string; value: unknown }>\n latestTerm: number\n latestSeq: number\n latestRowVersion: number\n resetEpoch: number\n }\n loadCollectionMetadata: Array<{ key: string; value: unknown }>\n scanRows: Array<{\n key: ElectronPersistedKey\n value: ElectronPersistedRow\n metadata?: unknown\n }>\n applyCommittedTx: null\n reconcileCommittedTx: ReconciledCommittedTx\n ensureIndex: null\n markIndexRemoved: null\n pullSince: SQLitePullSinceResult<ElectronPersistedKey>\n getStreamPosition: {\n latestTerm: number\n latestSeq: number\n latestRowVersion: number\n }\n reserveLeadershipTerm: {\n latestTerm: number\n latestSeq: number\n latestRowVersion: number\n }\n}\n\nexport type ElectronSerializedError = {\n name: string\n message: string\n stack?: string\n code?: string | number\n path?: string | ReadonlyArray<string | number>\n}\n\nexport type ElectronPersistenceRequestByMethod = {\n [Method in ElectronPersistenceMethod]: {\n v: number\n requestId: string\n collectionId: string\n resolution?: ElectronPersistenceResolution\n method: Method\n payload: ElectronPersistencePayloadMap[Method]\n }\n}\n\nexport type ElectronPersistenceRequest<\n TMethod extends ElectronPersistenceMethod = ElectronPersistenceMethod,\n> = ElectronPersistenceRequestByMethod[TMethod]\n\nexport type ElectronPersistenceRequestEnvelope =\n ElectronPersistenceRequestByMethod[ElectronPersistenceMethod]\n\ntype ElectronPersistenceSuccessResponseByMethod = {\n [Method in ElectronPersistenceMethod]: {\n v: number\n requestId: string\n method: Method\n ok: true\n result: ElectronPersistenceResultMap[Method]\n }\n}\n\ntype ElectronPersistenceErrorResponseByMethod = {\n [Method in ElectronPersistenceMethod]: {\n v: number\n requestId: string\n method: Method\n ok: false\n error: ElectronSerializedError\n }\n}\n\nexport type ElectronPersistenceResponse<\n TMethod extends ElectronPersistenceMethod = ElectronPersistenceMethod,\n> =\n | ElectronPersistenceSuccessResponseByMethod[TMethod]\n | ElectronPersistenceErrorResponseByMethod[TMethod]\n\nexport type ElectronPersistenceResponseEnvelope =\n ElectronPersistenceResponse<ElectronPersistenceMethod>\n\nexport type ElectronPersistenceRequestHandler = (\n request: ElectronPersistenceRequestEnvelope,\n) => Promise<ElectronPersistenceResponseEnvelope>\n\nexport type ElectronPersistenceInvoke = (\n channel: string,\n request: ElectronPersistenceRequestEnvelope,\n) => Promise<ElectronPersistenceResponseEnvelope>\n"],"names":[],"mappings":"AAWO,MAAM,wCAAwC;AAC9C,MAAM,uCAAuC;"}
@@ -74,9 +74,12 @@ function createRendererRequestExecutor(options) {
74
74
  if (typeof response.error.stack === `string`) {
75
75
  remoteError.stack = response.error.stack;
76
76
  }
77
- if (typeof response.error.code === `string`) {
77
+ if (typeof response.error.code === `string` || typeof response.error.code === `number`) {
78
78
  remoteError.code = response.error.code;
79
79
  }
80
+ if (response.error.path !== void 0) {
81
+ remoteError.path = response.error.path;
82
+ }
80
83
  throw remoteError;
81
84
  }
82
85
  return response.result;
@@ -114,6 +117,14 @@ function createResolvedRendererAdapter(executeRequest, resolution) {
114
117
  resolution
115
118
  );
116
119
  },
120
+ reconcileCommittedTx: async (collectionId, tx, anchor) => {
121
+ return executeRequest(
122
+ `reconcileCommittedTx`,
123
+ collectionId,
124
+ { tx, anchor },
125
+ resolution
126
+ );
127
+ },
117
128
  loadCollectionMetadata: async (collectionId) => {
118
129
  return executeRequest(
119
130
  `loadCollectionMetadata`,
@@ -165,7 +176,13 @@ function createResolvedRendererAdapter(executeRequest, resolution) {
165
176
  },
166
177
  getStreamPosition: async (collectionId) => {
167
178
  return executeRequest(`getStreamPosition`, collectionId, {}, resolution);
168
- }
179
+ },
180
+ reserveLeadershipTerm: async (collectionId, observedTerm) => executeRequest(
181
+ `reserveLeadershipTerm`,
182
+ collectionId,
183
+ { observedTerm },
184
+ resolution
185
+ )
169
186
  };
170
187
  }
171
188
  function resolveInvoke(options) {
@@ -1 +1 @@
1
- {"version":3,"file":"renderer.js","sources":["../../src/renderer.ts"],"sourcesContent":["import {\n InvalidPersistedCollectionConfigError,\n SingleProcessCoordinator,\n} from '@tanstack/db-sqlite-persistence-core'\nimport { ElectronCollectionCoordinator } from './electron-coordinator'\nimport {\n DEFAULT_ELECTRON_PERSISTENCE_CHANNEL,\n ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n} from './protocol'\nimport type {\n PersistedCollectionCoordinator,\n PersistedCollectionMode,\n PersistedCollectionPersistence,\n PersistedIndexSpec,\n PersistedTx,\n SQLitePullSinceResult,\n} from '@tanstack/db-sqlite-persistence-core'\nimport type {\n ElectronPersistedKey,\n ElectronPersistedRow,\n ElectronPersistenceInvoke,\n ElectronPersistenceMethod,\n ElectronPersistencePayloadMap,\n ElectronPersistenceRequest,\n ElectronPersistenceRequestEnvelope,\n ElectronPersistenceResolution,\n ElectronPersistenceResponseEnvelope,\n ElectronPersistenceResultMap,\n} from './protocol'\nimport type { LoadSubsetOptions } from '@tanstack/db'\n\nconst DEFAULT_REQUEST_TIMEOUT_MS = 5_000\nlet nextRequestId = 1\n\nfunction createRequestId(): string {\n const requestId = nextRequestId\n nextRequestId++\n return `electron-persistence-${requestId}`\n}\n\nfunction withTimeout<T>(\n promise: Promise<T>,\n timeoutMs: number,\n timeoutMessage: string,\n): Promise<T> {\n if (timeoutMs <= 0) {\n return promise\n }\n\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => {\n reject(new InvalidPersistedCollectionConfigError(timeoutMessage))\n }, timeoutMs)\n\n promise.then(\n (value) => {\n clearTimeout(timer)\n resolve(value)\n },\n (error: unknown) => {\n clearTimeout(timer)\n reject(error)\n },\n )\n })\n}\n\nfunction assertValidResponse(\n response: ElectronPersistenceResponseEnvelope,\n request: ElectronPersistenceRequestEnvelope,\n): void {\n if (response.v !== ELECTRON_PERSISTENCE_PROTOCOL_VERSION) {\n throw new InvalidPersistedCollectionConfigError(\n `Unexpected electron persistence protocol version \"${response.v}\" in response`,\n )\n }\n\n if (response.requestId !== request.requestId) {\n throw new InvalidPersistedCollectionConfigError(\n `Mismatched electron persistence response request id. Expected \"${request.requestId}\", received \"${response.requestId}\"`,\n )\n }\n\n if (response.method !== request.method) {\n throw new InvalidPersistedCollectionConfigError(\n `Mismatched electron persistence response method. Expected \"${request.method}\", received \"${response.method}\"`,\n )\n }\n}\n\nfunction createSerializableLoadSubsetOptions(\n subsetOptions: LoadSubsetOptions,\n): LoadSubsetOptions {\n const { subscription: _subscription, ...serializableOptions } = subsetOptions\n return serializableOptions\n}\n\ntype RendererRequestExecutor = <TMethod extends ElectronPersistenceMethod>(\n method: TMethod,\n collectionId: string,\n payload: ElectronPersistencePayloadMap[TMethod],\n resolution?: ElectronPersistenceResolution,\n) => Promise<ElectronPersistenceResultMap[TMethod]>\n\nfunction createRendererRequestExecutor(options: {\n invoke: ElectronPersistenceInvoke\n channel?: string\n timeoutMs?: number\n}): RendererRequestExecutor {\n const channel = options.channel ?? DEFAULT_ELECTRON_PERSISTENCE_CHANNEL\n const timeoutMs = options.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS\n\n return async <TMethod extends ElectronPersistenceMethod>(\n method: TMethod,\n collectionId: string,\n payload: ElectronPersistencePayloadMap[TMethod],\n resolution?: ElectronPersistenceResolution,\n ) => {\n const request = {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: createRequestId(),\n collectionId,\n method,\n resolution,\n payload,\n } as ElectronPersistenceRequest\n\n const response = await withTimeout(\n options.invoke(channel, request),\n timeoutMs,\n `Electron persistence request timed out (method=${method}, collection=${collectionId}, timeoutMs=${timeoutMs})`,\n )\n assertValidResponse(response, request)\n\n if (!response.ok) {\n const remoteError = new InvalidPersistedCollectionConfigError(\n `${response.error.name}: ${response.error.message}`,\n )\n if (typeof response.error.stack === `string`) {\n remoteError.stack = response.error.stack\n }\n if (typeof response.error.code === `string`) {\n ;(remoteError as Error & { code?: string }).code = response.error.code\n }\n throw remoteError\n }\n\n return response.result as ElectronPersistenceResultMap[TMethod]\n }\n}\n\ntype ElectronRendererResolvedAdapter =\n PersistedCollectionPersistence[`adapter`] & {\n loadCollectionMetadata: (\n collectionId: string,\n ) => Promise<Array<{ key: string; value: unknown }>>\n scanRows: (\n collectionId: string,\n options?: { metadataOnly?: boolean },\n ) => Promise<\n Array<{\n key: string | number\n value: Record<string, unknown>\n metadata?: unknown\n }>\n >\n pullSince: (\n collectionId: string,\n fromRowVersion: number,\n ) => Promise<SQLitePullSinceResult<string | number>>\n getStreamPosition: (collectionId: string) => Promise<{\n latestTerm: number\n latestSeq: number\n latestRowVersion: number\n }>\n }\n\nfunction createResolvedRendererAdapter(\n executeRequest: RendererRequestExecutor,\n resolution?: ElectronPersistenceResolution,\n): ElectronRendererResolvedAdapter {\n return {\n loadSubset: async (\n collectionId: string,\n subsetOptions: LoadSubsetOptions,\n ctx?: { requiredIndexSignatures?: ReadonlyArray<string> },\n ) => {\n const result = await executeRequest(\n `loadSubset`,\n collectionId,\n {\n options: createSerializableLoadSubsetOptions(subsetOptions),\n ctx,\n },\n resolution,\n )\n\n return result as Array<{\n key: string | number\n value: Record<string, unknown>\n }>\n },\n loadResumeSnapshot: async (\n collectionId: string,\n ctx?: {\n requiredIndexSignatures?: ReadonlyArray<string>\n includeRows?: boolean\n },\n ) => {\n return executeRequest(\n `loadResumeSnapshot`,\n collectionId,\n { ctx },\n resolution,\n )\n },\n applyCommittedTx: async (\n collectionId: string,\n tx: PersistedTx<Record<string, unknown>, string | number>,\n ): Promise<void> => {\n await executeRequest(\n `applyCommittedTx`,\n collectionId,\n {\n tx: tx as PersistedTx<ElectronPersistedRow, ElectronPersistedKey>,\n },\n resolution,\n )\n },\n loadCollectionMetadata: async (\n collectionId: string,\n ): Promise<Array<{ key: string; value: unknown }>> => {\n return executeRequest(\n `loadCollectionMetadata`,\n collectionId,\n {},\n resolution,\n )\n },\n scanRows: async (\n collectionId: string,\n options?: { metadataOnly?: boolean },\n ): Promise<\n Array<{\n key: string | number\n value: Record<string, unknown>\n metadata?: unknown\n }>\n > => {\n const result = await executeRequest(\n `scanRows`,\n collectionId,\n { options },\n resolution,\n )\n return result as Array<{\n key: string | number\n value: Record<string, unknown>\n metadata?: unknown\n }>\n },\n ensureIndex: async (\n collectionId: string,\n signature: string,\n spec: PersistedIndexSpec,\n ): Promise<void> => {\n await executeRequest(\n `ensureIndex`,\n collectionId,\n {\n signature,\n spec,\n },\n resolution,\n )\n },\n markIndexRemoved: async (\n collectionId: string,\n signature: string,\n ): Promise<void> => {\n await executeRequest(\n `markIndexRemoved`,\n collectionId,\n {\n signature,\n },\n resolution,\n )\n },\n pullSince: async (\n collectionId: string,\n fromRowVersion: number,\n ): Promise<SQLitePullSinceResult<string | number>> => {\n const result = await executeRequest(\n `pullSince`,\n collectionId,\n {\n fromRowVersion,\n },\n resolution,\n )\n return result as SQLitePullSinceResult<string | number>\n },\n getStreamPosition: async (\n collectionId: string,\n ): Promise<{\n latestTerm: number\n latestSeq: number\n latestRowVersion: number\n }> => {\n return executeRequest(`getStreamPosition`, collectionId, {}, resolution)\n },\n }\n}\n\nexport type ElectronIpcRendererLike = {\n invoke: (\n channel: string,\n request: ElectronPersistenceRequestEnvelope,\n ) => Promise<ElectronPersistenceResponseEnvelope>\n}\n\nexport type ElectronSQLitePersistenceOptions = {\n invoke?: ElectronPersistenceInvoke\n ipcRenderer?: ElectronIpcRendererLike\n channel?: string\n timeoutMs?: number\n coordinator?: PersistedCollectionCoordinator\n}\n\nfunction resolveInvoke(\n options: ElectronSQLitePersistenceOptions,\n): ElectronPersistenceInvoke {\n if (options.invoke) {\n return options.invoke\n }\n\n if (options.ipcRenderer) {\n return (channel, request) => options.ipcRenderer!.invoke(channel, request)\n }\n\n throw new InvalidPersistedCollectionConfigError(\n `Electron renderer persistence requires either invoke or ipcRenderer`,\n )\n}\n\nexport function createElectronSQLitePersistence(\n options: ElectronSQLitePersistenceOptions,\n): PersistedCollectionPersistence {\n const invoke = resolveInvoke(options)\n const coordinator = options.coordinator ?? new SingleProcessCoordinator()\n const executeRequest = createRendererRequestExecutor({\n invoke,\n channel: options.channel,\n timeoutMs: options.timeoutMs,\n })\n const adapterCache = new Map<string, ElectronRendererResolvedAdapter>()\n\n const getAdapterForCollection = (\n mode: PersistedCollectionMode,\n schemaVersion: number | undefined,\n ) => {\n const schemaVersionKey =\n schemaVersion === undefined ? `schema:default` : `schema:${schemaVersion}`\n const cacheKey = `mode:${mode}|${schemaVersionKey}`\n const cachedAdapter = adapterCache.get(cacheKey)\n if (cachedAdapter) {\n return cachedAdapter\n }\n\n const adapter = createResolvedRendererAdapter(executeRequest, {\n mode,\n schemaVersion,\n })\n adapterCache.set(cacheKey, adapter)\n return adapter\n }\n\n const createCollectionPersistence = (\n collectionId: string | undefined,\n mode: PersistedCollectionMode,\n schemaVersion: number | undefined,\n ): PersistedCollectionPersistence => {\n const adapter = getAdapterForCollection(mode, schemaVersion)\n if (coordinator instanceof ElectronCollectionCoordinator) {\n if (collectionId === undefined) {\n coordinator.setAdapter(adapter)\n } else {\n coordinator.setAdapterForCollection(collectionId, adapter)\n }\n }\n return { adapter, coordinator }\n }\n\n const defaultPersistence = createCollectionPersistence(\n undefined,\n `sync-absent`,\n undefined,\n )\n if (coordinator instanceof ElectronCollectionCoordinator) {\n coordinator.setAdapter(defaultPersistence.adapter)\n }\n\n return {\n ...defaultPersistence,\n resolvePersistenceForCollection: ({ collectionId, mode, schemaVersion }) =>\n createCollectionPersistence(collectionId, mode, schemaVersion),\n // Backward compatible fallback for older callers.\n resolvePersistenceForMode: (mode) =>\n createCollectionPersistence(undefined, mode, undefined),\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"renderer.js","sources":["../../src/renderer.ts"],"sourcesContent":["import {\n InvalidPersistedCollectionConfigError,\n SingleProcessCoordinator,\n} from '@tanstack/db-sqlite-persistence-core'\nimport { ElectronCollectionCoordinator } from './electron-coordinator'\nimport {\n DEFAULT_ELECTRON_PERSISTENCE_CHANNEL,\n ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n} from './protocol'\nimport type {\n CommittedTxAnchor,\n PersistedCollectionCoordinator,\n PersistedCollectionMode,\n PersistedCollectionPersistence,\n PersistedIndexSpec,\n PersistedTx,\n ReconciledCommittedTx,\n SQLitePullSinceResult,\n} from '@tanstack/db-sqlite-persistence-core'\nimport type {\n ElectronPersistedKey,\n ElectronPersistedRow,\n ElectronPersistenceInvoke,\n ElectronPersistenceMethod,\n ElectronPersistencePayloadMap,\n ElectronPersistenceRequest,\n ElectronPersistenceRequestEnvelope,\n ElectronPersistenceResolution,\n ElectronPersistenceResponseEnvelope,\n ElectronPersistenceResultMap,\n} from './protocol'\nimport type { LoadSubsetOptions } from '@tanstack/db'\n\nconst DEFAULT_REQUEST_TIMEOUT_MS = 5_000\nlet nextRequestId = 1\n\nfunction createRequestId(): string {\n const requestId = nextRequestId\n nextRequestId++\n return `electron-persistence-${requestId}`\n}\n\nfunction withTimeout<T>(\n promise: Promise<T>,\n timeoutMs: number,\n timeoutMessage: string,\n): Promise<T> {\n if (timeoutMs <= 0) {\n return promise\n }\n\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => {\n reject(new InvalidPersistedCollectionConfigError(timeoutMessage))\n }, timeoutMs)\n\n promise.then(\n (value) => {\n clearTimeout(timer)\n resolve(value)\n },\n (error: unknown) => {\n clearTimeout(timer)\n reject(error)\n },\n )\n })\n}\n\nfunction assertValidResponse(\n response: ElectronPersistenceResponseEnvelope,\n request: ElectronPersistenceRequestEnvelope,\n): void {\n if (response.v !== ELECTRON_PERSISTENCE_PROTOCOL_VERSION) {\n throw new InvalidPersistedCollectionConfigError(\n `Unexpected electron persistence protocol version \"${response.v}\" in response`,\n )\n }\n\n if (response.requestId !== request.requestId) {\n throw new InvalidPersistedCollectionConfigError(\n `Mismatched electron persistence response request id. Expected \"${request.requestId}\", received \"${response.requestId}\"`,\n )\n }\n\n if (response.method !== request.method) {\n throw new InvalidPersistedCollectionConfigError(\n `Mismatched electron persistence response method. Expected \"${request.method}\", received \"${response.method}\"`,\n )\n }\n}\n\nfunction createSerializableLoadSubsetOptions(\n subsetOptions: LoadSubsetOptions,\n): LoadSubsetOptions {\n const { subscription: _subscription, ...serializableOptions } = subsetOptions\n return serializableOptions\n}\n\ntype RendererRequestExecutor = <TMethod extends ElectronPersistenceMethod>(\n method: TMethod,\n collectionId: string,\n payload: ElectronPersistencePayloadMap[TMethod],\n resolution?: ElectronPersistenceResolution,\n) => Promise<ElectronPersistenceResultMap[TMethod]>\n\nfunction createRendererRequestExecutor(options: {\n invoke: ElectronPersistenceInvoke\n channel?: string\n timeoutMs?: number\n}): RendererRequestExecutor {\n const channel = options.channel ?? DEFAULT_ELECTRON_PERSISTENCE_CHANNEL\n const timeoutMs = options.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS\n\n return async <TMethod extends ElectronPersistenceMethod>(\n method: TMethod,\n collectionId: string,\n payload: ElectronPersistencePayloadMap[TMethod],\n resolution?: ElectronPersistenceResolution,\n ) => {\n const request = {\n v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,\n requestId: createRequestId(),\n collectionId,\n method,\n resolution,\n payload,\n } as ElectronPersistenceRequest\n\n const response = await withTimeout(\n options.invoke(channel, request),\n timeoutMs,\n `Electron persistence request timed out (method=${method}, collection=${collectionId}, timeoutMs=${timeoutMs})`,\n )\n assertValidResponse(response, request)\n\n if (!response.ok) {\n const remoteError = new InvalidPersistedCollectionConfigError(\n `${response.error.name}: ${response.error.message}`,\n )\n if (typeof response.error.stack === `string`) {\n remoteError.stack = response.error.stack\n }\n if (\n typeof response.error.code === `string` ||\n typeof response.error.code === `number`\n ) {\n ;(remoteError as Error & { code?: string | number }).code =\n response.error.code\n }\n if (response.error.path !== undefined) {\n ;(\n remoteError as Error & {\n path?: string | ReadonlyArray<string | number>\n }\n ).path = response.error.path\n }\n throw remoteError\n }\n\n return response.result as ElectronPersistenceResultMap[TMethod]\n }\n}\n\ntype ElectronRendererResolvedAdapter =\n PersistedCollectionPersistence[`adapter`] & {\n loadCollectionMetadata: (\n collectionId: string,\n ) => Promise<Array<{ key: string; value: unknown }>>\n scanRows: (\n collectionId: string,\n options?: { metadataOnly?: boolean },\n ) => Promise<\n Array<{\n key: string | number\n value: Record<string, unknown>\n metadata?: unknown\n }>\n >\n pullSince: (\n collectionId: string,\n fromRowVersion: number,\n ) => Promise<SQLitePullSinceResult<string | number>>\n getStreamPosition: (collectionId: string) => Promise<{\n latestTerm: number\n latestSeq: number\n latestRowVersion: number\n }>\n reserveLeadershipTerm: (\n collectionId: string,\n observedTerm: number,\n ) => Promise<{\n latestTerm: number\n latestSeq: number\n latestRowVersion: number\n }>\n }\n\nfunction createResolvedRendererAdapter(\n executeRequest: RendererRequestExecutor,\n resolution?: ElectronPersistenceResolution,\n): ElectronRendererResolvedAdapter {\n return {\n loadSubset: async (\n collectionId: string,\n subsetOptions: LoadSubsetOptions,\n ctx?: { requiredIndexSignatures?: ReadonlyArray<string> },\n ) => {\n const result = await executeRequest(\n `loadSubset`,\n collectionId,\n {\n options: createSerializableLoadSubsetOptions(subsetOptions),\n ctx,\n },\n resolution,\n )\n\n return result as Array<{\n key: string | number\n value: Record<string, unknown>\n }>\n },\n loadResumeSnapshot: async (\n collectionId: string,\n ctx?: {\n requiredIndexSignatures?: ReadonlyArray<string>\n includeRows?: boolean\n },\n ) => {\n return executeRequest(\n `loadResumeSnapshot`,\n collectionId,\n { ctx },\n resolution,\n )\n },\n applyCommittedTx: async (\n collectionId: string,\n tx: PersistedTx<Record<string, unknown>, string | number>,\n ): Promise<void> => {\n await executeRequest(\n `applyCommittedTx`,\n collectionId,\n {\n tx: tx as PersistedTx<ElectronPersistedRow, ElectronPersistedKey>,\n },\n resolution,\n )\n },\n reconcileCommittedTx: async (\n collectionId: string,\n tx: PersistedTx<Record<string, unknown>, string | number>,\n anchor: CommittedTxAnchor,\n ): Promise<ReconciledCommittedTx> => {\n return executeRequest(\n `reconcileCommittedTx`,\n collectionId,\n { tx, anchor },\n resolution,\n )\n },\n loadCollectionMetadata: async (\n collectionId: string,\n ): Promise<Array<{ key: string; value: unknown }>> => {\n return executeRequest(\n `loadCollectionMetadata`,\n collectionId,\n {},\n resolution,\n )\n },\n scanRows: async (\n collectionId: string,\n options?: { metadataOnly?: boolean },\n ): Promise<\n Array<{\n key: string | number\n value: Record<string, unknown>\n metadata?: unknown\n }>\n > => {\n const result = await executeRequest(\n `scanRows`,\n collectionId,\n { options },\n resolution,\n )\n return result as Array<{\n key: string | number\n value: Record<string, unknown>\n metadata?: unknown\n }>\n },\n ensureIndex: async (\n collectionId: string,\n signature: string,\n spec: PersistedIndexSpec,\n ): Promise<void> => {\n await executeRequest(\n `ensureIndex`,\n collectionId,\n {\n signature,\n spec,\n },\n resolution,\n )\n },\n markIndexRemoved: async (\n collectionId: string,\n signature: string,\n ): Promise<void> => {\n await executeRequest(\n `markIndexRemoved`,\n collectionId,\n {\n signature,\n },\n resolution,\n )\n },\n pullSince: async (\n collectionId: string,\n fromRowVersion: number,\n ): Promise<SQLitePullSinceResult<string | number>> => {\n const result = await executeRequest(\n `pullSince`,\n collectionId,\n {\n fromRowVersion,\n },\n resolution,\n )\n return result as SQLitePullSinceResult<string | number>\n },\n getStreamPosition: async (\n collectionId: string,\n ): Promise<{\n latestTerm: number\n latestSeq: number\n latestRowVersion: number\n }> => {\n return executeRequest(`getStreamPosition`, collectionId, {}, resolution)\n },\n reserveLeadershipTerm: async (collectionId, observedTerm) =>\n executeRequest(\n `reserveLeadershipTerm`,\n collectionId,\n { observedTerm },\n resolution,\n ),\n }\n}\n\nexport type ElectronIpcRendererLike = {\n invoke: (\n channel: string,\n request: ElectronPersistenceRequestEnvelope,\n ) => Promise<ElectronPersistenceResponseEnvelope>\n}\n\nexport type ElectronSQLitePersistenceOptions = {\n invoke?: ElectronPersistenceInvoke\n ipcRenderer?: ElectronIpcRendererLike\n channel?: string\n timeoutMs?: number\n coordinator?: PersistedCollectionCoordinator\n}\n\nfunction resolveInvoke(\n options: ElectronSQLitePersistenceOptions,\n): ElectronPersistenceInvoke {\n if (options.invoke) {\n return options.invoke\n }\n\n if (options.ipcRenderer) {\n return (channel, request) => options.ipcRenderer!.invoke(channel, request)\n }\n\n throw new InvalidPersistedCollectionConfigError(\n `Electron renderer persistence requires either invoke or ipcRenderer`,\n )\n}\n\nexport function createElectronSQLitePersistence(\n options: ElectronSQLitePersistenceOptions,\n): PersistedCollectionPersistence {\n const invoke = resolveInvoke(options)\n const coordinator = options.coordinator ?? new SingleProcessCoordinator()\n const executeRequest = createRendererRequestExecutor({\n invoke,\n channel: options.channel,\n timeoutMs: options.timeoutMs,\n })\n const adapterCache = new Map<string, ElectronRendererResolvedAdapter>()\n\n const getAdapterForCollection = (\n mode: PersistedCollectionMode,\n schemaVersion: number | undefined,\n ) => {\n const schemaVersionKey =\n schemaVersion === undefined ? `schema:default` : `schema:${schemaVersion}`\n const cacheKey = `mode:${mode}|${schemaVersionKey}`\n const cachedAdapter = adapterCache.get(cacheKey)\n if (cachedAdapter) {\n return cachedAdapter\n }\n\n const adapter = createResolvedRendererAdapter(executeRequest, {\n mode,\n schemaVersion,\n })\n adapterCache.set(cacheKey, adapter)\n return adapter\n }\n\n const createCollectionPersistence = (\n collectionId: string | undefined,\n mode: PersistedCollectionMode,\n schemaVersion: number | undefined,\n ): PersistedCollectionPersistence => {\n const adapter = getAdapterForCollection(mode, schemaVersion)\n if (coordinator instanceof ElectronCollectionCoordinator) {\n if (collectionId === undefined) {\n coordinator.setAdapter(adapter)\n } else {\n coordinator.setAdapterForCollection(collectionId, adapter)\n }\n }\n return { adapter, coordinator }\n }\n\n const defaultPersistence = createCollectionPersistence(\n undefined,\n `sync-absent`,\n undefined,\n )\n if (coordinator instanceof ElectronCollectionCoordinator) {\n coordinator.setAdapter(defaultPersistence.adapter)\n }\n\n return {\n ...defaultPersistence,\n resolvePersistenceForCollection: ({ collectionId, mode, schemaVersion }) =>\n createCollectionPersistence(collectionId, mode, schemaVersion),\n // Backward compatible fallback for older callers.\n resolvePersistenceForMode: (mode) =>\n createCollectionPersistence(undefined, mode, undefined),\n }\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/electron-db-sqlite-persistence",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
4
4
  "description": "Electron SQLite persisted collection bridge for TanStack DB",
5
5
  "author": "TanStack Team",
6
6
  "license": "MIT",
@@ -60,7 +60,7 @@
60
60
  "src"
61
61
  ],
62
62
  "dependencies": {
63
- "@tanstack/db-sqlite-persistence-core": "0.4.7"
63
+ "@tanstack/db-sqlite-persistence-core": "0.4.8"
64
64
  },
65
65
  "peerDependencies": {
66
66
  "typescript": ">=4.7"
package/src/errors.ts CHANGED
@@ -4,12 +4,12 @@ import type {
4
4
  } from './protocol'
5
5
 
6
6
  type ElectronPersistenceErrorOptions = {
7
- code?: string
7
+ code?: string | number
8
8
  cause?: unknown
9
9
  }
10
10
 
11
11
  export class ElectronPersistenceError extends Error {
12
- readonly code: string | undefined
12
+ readonly code: string | number | undefined
13
13
 
14
14
  constructor(message: string, options?: ElectronPersistenceErrorOptions) {
15
15
  super(message, { cause: options?.cause })
package/src/main.ts CHANGED
@@ -52,12 +52,26 @@ function serializeError(error: unknown): ElectronSerializedError {
52
52
  }
53
53
  }
54
54
 
55
- const codedError = error as Error & { code?: unknown }
55
+ const detailedError = error as Error & { code?: unknown; path?: unknown }
56
+ const path = detailedError.path
56
57
  return {
57
58
  name: error.name || `Error`,
58
59
  message: error.message || fallbackMessage,
59
60
  stack: error.stack,
60
- code: typeof codedError.code === `string` ? codedError.code : undefined,
61
+ code:
62
+ typeof detailedError.code === `string` ||
63
+ typeof detailedError.code === `number`
64
+ ? detailedError.code
65
+ : undefined,
66
+ path:
67
+ typeof path === `string` ||
68
+ (Array.isArray(path) &&
69
+ path.every(
70
+ (segment: unknown) =>
71
+ typeof segment === `string` || typeof segment === `number`,
72
+ ))
73
+ ? (path as string | ReadonlyArray<string | number>)
74
+ : undefined,
61
75
  }
62
76
  }
63
77
 
@@ -180,6 +194,23 @@ async function executeRequestAgainstAdapter(
180
194
  }
181
195
  }
182
196
 
197
+ case `reconcileCommittedTx`: {
198
+ const result = adapter.reconcileCommittedTx
199
+ ? await adapter.reconcileCommittedTx(
200
+ request.collectionId,
201
+ request.payload.tx,
202
+ request.payload.anchor,
203
+ )
204
+ : { kind: `unknown` as const }
205
+ return {
206
+ v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,
207
+ requestId: request.requestId,
208
+ method: request.method,
209
+ ok: true,
210
+ result,
211
+ }
212
+ }
213
+
183
214
  case `ensureIndex`: {
184
215
  await adapter.ensureIndex(
185
216
  request.collectionId,
@@ -233,6 +264,25 @@ async function executeRequestAgainstAdapter(
233
264
  }
234
265
  }
235
266
 
267
+ case `reserveLeadershipTerm`: {
268
+ if (!adapter.reserveLeadershipTerm) {
269
+ throw new InvalidPersistedCollectionConfigError(
270
+ `reserveLeadershipTerm is not supported by the configured electron persistence adapter`,
271
+ )
272
+ }
273
+ const position = await adapter.reserveLeadershipTerm(
274
+ request.collectionId,
275
+ request.payload.observedTerm,
276
+ )
277
+ return {
278
+ v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,
279
+ requestId: request.requestId,
280
+ method: request.method,
281
+ ok: true,
282
+ result: position,
283
+ }
284
+ }
285
+
236
286
  case `getStreamPosition`: {
237
287
  if (!adapter.getStreamPosition) {
238
288
  throw new InvalidPersistedCollectionConfigError(
package/src/protocol.ts CHANGED
@@ -1,13 +1,15 @@
1
1
  import type { LoadSubsetOptions } from '@tanstack/db'
2
2
  import type {
3
+ CommittedTxAnchor,
3
4
  PersistedCollectionMode,
4
5
  PersistedIndexSpec,
5
6
  PersistedKeySetEvidence,
6
7
  PersistedTx,
8
+ ReconciledCommittedTx,
7
9
  SQLitePullSinceResult,
8
10
  } from '@tanstack/db-sqlite-persistence-core'
9
11
 
10
- export const ELECTRON_PERSISTENCE_PROTOCOL_VERSION = 2 as const
12
+ export const ELECTRON_PERSISTENCE_PROTOCOL_VERSION = 4 as const
11
13
  export const DEFAULT_ELECTRON_PERSISTENCE_CHANNEL = `tanstack-db:sqlite-persistence`
12
14
 
13
15
  export type ElectronPersistedRow = Record<string, unknown>
@@ -24,10 +26,12 @@ export type ElectronPersistenceMethod =
24
26
  | `loadCollectionMetadata`
25
27
  | `scanRows`
26
28
  | `applyCommittedTx`
29
+ | `reconcileCommittedTx`
27
30
  | `ensureIndex`
28
31
  | `markIndexRemoved`
29
32
  | `pullSince`
30
33
  | `getStreamPosition`
34
+ | `reserveLeadershipTerm`
31
35
 
32
36
  export type ElectronPersistencePayloadMap = {
33
37
  loadSubset: {
@@ -49,6 +53,10 @@ export type ElectronPersistencePayloadMap = {
49
53
  applyCommittedTx: {
50
54
  tx: PersistedTx<ElectronPersistedRow, ElectronPersistedKey>
51
55
  }
56
+ reconcileCommittedTx: {
57
+ tx: PersistedTx<ElectronPersistedRow, ElectronPersistedKey>
58
+ anchor: CommittedTxAnchor
59
+ }
52
60
  ensureIndex: {
53
61
  signature: string
54
62
  spec: PersistedIndexSpec
@@ -60,6 +68,7 @@ export type ElectronPersistencePayloadMap = {
60
68
  fromRowVersion: number
61
69
  }
62
70
  getStreamPosition: {}
71
+ reserveLeadershipTerm: { observedTerm: number }
63
72
  }
64
73
 
65
74
  export type ElectronPersistenceResultMap = {
@@ -84,6 +93,7 @@ export type ElectronPersistenceResultMap = {
84
93
  metadata?: unknown
85
94
  }>
86
95
  applyCommittedTx: null
96
+ reconcileCommittedTx: ReconciledCommittedTx
87
97
  ensureIndex: null
88
98
  markIndexRemoved: null
89
99
  pullSince: SQLitePullSinceResult<ElectronPersistedKey>
@@ -92,13 +102,19 @@ export type ElectronPersistenceResultMap = {
92
102
  latestSeq: number
93
103
  latestRowVersion: number
94
104
  }
105
+ reserveLeadershipTerm: {
106
+ latestTerm: number
107
+ latestSeq: number
108
+ latestRowVersion: number
109
+ }
95
110
  }
96
111
 
97
112
  export type ElectronSerializedError = {
98
113
  name: string
99
114
  message: string
100
115
  stack?: string
101
- code?: string
116
+ code?: string | number
117
+ path?: string | ReadonlyArray<string | number>
102
118
  }
103
119
 
104
120
  export type ElectronPersistenceRequestByMethod = {
package/src/renderer.ts CHANGED
@@ -8,11 +8,13 @@ import {
8
8
  ELECTRON_PERSISTENCE_PROTOCOL_VERSION,
9
9
  } from './protocol'
10
10
  import type {
11
+ CommittedTxAnchor,
11
12
  PersistedCollectionCoordinator,
12
13
  PersistedCollectionMode,
13
14
  PersistedCollectionPersistence,
14
15
  PersistedIndexSpec,
15
16
  PersistedTx,
17
+ ReconciledCommittedTx,
16
18
  SQLitePullSinceResult,
17
19
  } from '@tanstack/db-sqlite-persistence-core'
18
20
  import type {
@@ -139,8 +141,19 @@ function createRendererRequestExecutor(options: {
139
141
  if (typeof response.error.stack === `string`) {
140
142
  remoteError.stack = response.error.stack
141
143
  }
142
- if (typeof response.error.code === `string`) {
143
- ;(remoteError as Error & { code?: string }).code = response.error.code
144
+ if (
145
+ typeof response.error.code === `string` ||
146
+ typeof response.error.code === `number`
147
+ ) {
148
+ ;(remoteError as Error & { code?: string | number }).code =
149
+ response.error.code
150
+ }
151
+ if (response.error.path !== undefined) {
152
+ ;(
153
+ remoteError as Error & {
154
+ path?: string | ReadonlyArray<string | number>
155
+ }
156
+ ).path = response.error.path
144
157
  }
145
158
  throw remoteError
146
159
  }
@@ -173,6 +186,14 @@ type ElectronRendererResolvedAdapter =
173
186
  latestSeq: number
174
187
  latestRowVersion: number
175
188
  }>
189
+ reserveLeadershipTerm: (
190
+ collectionId: string,
191
+ observedTerm: number,
192
+ ) => Promise<{
193
+ latestTerm: number
194
+ latestSeq: number
195
+ latestRowVersion: number
196
+ }>
176
197
  }
177
198
 
178
199
  function createResolvedRendererAdapter(
@@ -227,6 +248,18 @@ function createResolvedRendererAdapter(
227
248
  resolution,
228
249
  )
229
250
  },
251
+ reconcileCommittedTx: async (
252
+ collectionId: string,
253
+ tx: PersistedTx<Record<string, unknown>, string | number>,
254
+ anchor: CommittedTxAnchor,
255
+ ): Promise<ReconciledCommittedTx> => {
256
+ return executeRequest(
257
+ `reconcileCommittedTx`,
258
+ collectionId,
259
+ { tx, anchor },
260
+ resolution,
261
+ )
262
+ },
230
263
  loadCollectionMetadata: async (
231
264
  collectionId: string,
232
265
  ): Promise<Array<{ key: string; value: unknown }>> => {
@@ -310,6 +343,13 @@ function createResolvedRendererAdapter(
310
343
  }> => {
311
344
  return executeRequest(`getStreamPosition`, collectionId, {}, resolution)
312
345
  },
346
+ reserveLeadershipTerm: async (collectionId, observedTerm) =>
347
+ executeRequest(
348
+ `reserveLeadershipTerm`,
349
+ collectionId,
350
+ { observedTerm },
351
+ resolution,
352
+ ),
313
353
  }
314
354
  }
315
355