@tanstack/electron-db-sqlite-persistence 0.1.2
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 +71 -0
- package/dist/cjs/electron-coordinator.cjs +541 -0
- package/dist/cjs/electron-coordinator.cjs.map +1 -0
- package/dist/cjs/electron-coordinator.d.cts +67 -0
- package/dist/cjs/errors.d.cts +33 -0
- package/dist/cjs/index.cjs +14 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/cjs/index.d.cts +8 -0
- package/dist/cjs/main.cjs +213 -0
- package/dist/cjs/main.cjs.map +1 -0
- package/dist/cjs/main.d.cts +12 -0
- package/dist/cjs/protocol.cjs +7 -0
- package/dist/cjs/protocol.cjs.map +1 -0
- package/dist/cjs/protocol.d.cts +104 -0
- package/dist/cjs/renderer.cjs +221 -0
- package/dist/cjs/renderer.cjs.map +1 -0
- package/dist/cjs/renderer.d.cts +13 -0
- package/dist/esm/electron-coordinator.d.ts +67 -0
- package/dist/esm/electron-coordinator.js +541 -0
- package/dist/esm/electron-coordinator.js.map +1 -0
- package/dist/esm/errors.d.ts +33 -0
- package/dist/esm/index.d.ts +8 -0
- package/dist/esm/index.js +11 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/main.d.ts +12 -0
- package/dist/esm/main.js +213 -0
- package/dist/esm/main.js.map +1 -0
- package/dist/esm/protocol.d.ts +104 -0
- package/dist/esm/protocol.js +7 -0
- package/dist/esm/protocol.js.map +1 -0
- package/dist/esm/renderer.d.ts +13 -0
- package/dist/esm/renderer.js +221 -0
- package/dist/esm/renderer.js.map +1 -0
- package/package.json +80 -0
- package/src/electron-coordinator.ts +842 -0
- package/src/errors.ts +110 -0
- package/src/index.ts +17 -0
- package/src/main.ts +315 -0
- package/src/protocol.ts +137 -0
- package/src/renderer.ts +391 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { PersistedCollectionPersistence } from '@tanstack/db-sqlite-persistence-core';
|
|
2
|
+
import { ElectronPersistedKey, ElectronPersistedRow, ElectronPersistenceRequestEnvelope, ElectronPersistenceResponseEnvelope } from './protocol.js';
|
|
3
|
+
export type ElectronIpcMainLike = {
|
|
4
|
+
handle: (channel: string, listener: (event: unknown, request: ElectronPersistenceRequestEnvelope) => Promise<ElectronPersistenceResponseEnvelope>) => void;
|
|
5
|
+
removeHandler?: (channel: string) => void;
|
|
6
|
+
};
|
|
7
|
+
export type ElectronSQLiteMainProcessOptions = {
|
|
8
|
+
persistence: PersistedCollectionPersistence<ElectronPersistedRow, ElectronPersistedKey>;
|
|
9
|
+
ipcMain: ElectronIpcMainLike;
|
|
10
|
+
channel?: string;
|
|
11
|
+
};
|
|
12
|
+
export declare function exposeElectronSQLitePersistence(options: ElectronSQLiteMainProcessOptions): () => void;
|
package/dist/esm/main.js
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { InvalidPersistedCollectionConfigError } from "@tanstack/db-sqlite-persistence-core";
|
|
2
|
+
import { DEFAULT_ELECTRON_PERSISTENCE_CHANNEL, ELECTRON_PERSISTENCE_PROTOCOL_VERSION } from "./protocol.js";
|
|
3
|
+
function serializeError(error) {
|
|
4
|
+
const fallbackMessage = `Unknown electron persistence error`;
|
|
5
|
+
if (!(error instanceof Error)) {
|
|
6
|
+
return {
|
|
7
|
+
name: `Error`,
|
|
8
|
+
message: fallbackMessage,
|
|
9
|
+
code: void 0
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
const codedError = error;
|
|
13
|
+
return {
|
|
14
|
+
name: error.name || `Error`,
|
|
15
|
+
message: error.message || fallbackMessage,
|
|
16
|
+
stack: error.stack,
|
|
17
|
+
code: typeof codedError.code === `string` ? codedError.code : void 0
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function createErrorResponse(request, error) {
|
|
21
|
+
return {
|
|
22
|
+
v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,
|
|
23
|
+
requestId: request.requestId,
|
|
24
|
+
method: request.method,
|
|
25
|
+
ok: false,
|
|
26
|
+
error: serializeError(error)
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function assertValidRequest(request) {
|
|
30
|
+
if (request.v !== ELECTRON_PERSISTENCE_PROTOCOL_VERSION) {
|
|
31
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
32
|
+
`Unsupported electron persistence protocol version "${request.v}"`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
if (typeof request.requestId !== `string` || request.requestId.trim().length === 0) {
|
|
36
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
37
|
+
`Electron persistence requestId cannot be empty`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
if (typeof request.collectionId !== `string` || request.collectionId.trim().length === 0) {
|
|
41
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
42
|
+
`Electron persistence collectionId cannot be empty`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async function executeRequestAgainstAdapter(request, adapter) {
|
|
47
|
+
switch (request.method) {
|
|
48
|
+
case `loadSubset`: {
|
|
49
|
+
const result = await adapter.loadSubset(
|
|
50
|
+
request.collectionId,
|
|
51
|
+
request.payload.options,
|
|
52
|
+
request.payload.ctx
|
|
53
|
+
);
|
|
54
|
+
return {
|
|
55
|
+
v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,
|
|
56
|
+
requestId: request.requestId,
|
|
57
|
+
method: request.method,
|
|
58
|
+
ok: true,
|
|
59
|
+
result
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
case `loadCollectionMetadata`: {
|
|
63
|
+
if (!adapter.loadCollectionMetadata) {
|
|
64
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
65
|
+
`loadCollectionMetadata is not supported by the configured electron persistence adapter`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
const result = await adapter.loadCollectionMetadata(request.collectionId);
|
|
69
|
+
return {
|
|
70
|
+
v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,
|
|
71
|
+
requestId: request.requestId,
|
|
72
|
+
method: request.method,
|
|
73
|
+
ok: true,
|
|
74
|
+
result
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
case `scanRows`: {
|
|
78
|
+
if (!adapter.scanRows) {
|
|
79
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
80
|
+
`scanRows is not supported by the configured electron persistence adapter`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
const result = await adapter.scanRows(
|
|
84
|
+
request.collectionId,
|
|
85
|
+
request.payload.options
|
|
86
|
+
);
|
|
87
|
+
return {
|
|
88
|
+
v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,
|
|
89
|
+
requestId: request.requestId,
|
|
90
|
+
method: request.method,
|
|
91
|
+
ok: true,
|
|
92
|
+
result
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
case `applyCommittedTx`: {
|
|
96
|
+
await adapter.applyCommittedTx(request.collectionId, request.payload.tx);
|
|
97
|
+
return {
|
|
98
|
+
v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,
|
|
99
|
+
requestId: request.requestId,
|
|
100
|
+
method: request.method,
|
|
101
|
+
ok: true,
|
|
102
|
+
result: null
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
case `ensureIndex`: {
|
|
106
|
+
await adapter.ensureIndex(
|
|
107
|
+
request.collectionId,
|
|
108
|
+
request.payload.signature,
|
|
109
|
+
request.payload.spec
|
|
110
|
+
);
|
|
111
|
+
return {
|
|
112
|
+
v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,
|
|
113
|
+
requestId: request.requestId,
|
|
114
|
+
method: request.method,
|
|
115
|
+
ok: true,
|
|
116
|
+
result: null
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
case `markIndexRemoved`: {
|
|
120
|
+
if (!adapter.markIndexRemoved) {
|
|
121
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
122
|
+
`markIndexRemoved is not supported by the configured electron persistence adapter`
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
await adapter.markIndexRemoved(
|
|
126
|
+
request.collectionId,
|
|
127
|
+
request.payload.signature
|
|
128
|
+
);
|
|
129
|
+
return {
|
|
130
|
+
v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,
|
|
131
|
+
requestId: request.requestId,
|
|
132
|
+
method: request.method,
|
|
133
|
+
ok: true,
|
|
134
|
+
result: null
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
case `pullSince`: {
|
|
138
|
+
if (!adapter.pullSince) {
|
|
139
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
140
|
+
`pullSince is not supported by the configured electron persistence adapter`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
const result = await adapter.pullSince(
|
|
144
|
+
request.collectionId,
|
|
145
|
+
request.payload.fromRowVersion
|
|
146
|
+
);
|
|
147
|
+
return {
|
|
148
|
+
v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,
|
|
149
|
+
requestId: request.requestId,
|
|
150
|
+
method: request.method,
|
|
151
|
+
ok: true,
|
|
152
|
+
result
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
case `getStreamPosition`: {
|
|
156
|
+
if (!adapter.getStreamPosition) {
|
|
157
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
158
|
+
`getStreamPosition is not supported by the configured electron persistence adapter`
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
const position = await adapter.getStreamPosition(request.collectionId);
|
|
162
|
+
return {
|
|
163
|
+
v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,
|
|
164
|
+
requestId: request.requestId,
|
|
165
|
+
method: request.method,
|
|
166
|
+
ok: true,
|
|
167
|
+
result: position
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function resolveModeAwarePersistence(persistence, request) {
|
|
173
|
+
const mode = request.resolution?.mode ?? `sync-absent`;
|
|
174
|
+
const schemaVersion = request.resolution?.schemaVersion;
|
|
175
|
+
const collectionAwarePersistence = persistence.resolvePersistenceForCollection?.({
|
|
176
|
+
collectionId: request.collectionId,
|
|
177
|
+
mode,
|
|
178
|
+
schemaVersion
|
|
179
|
+
});
|
|
180
|
+
if (collectionAwarePersistence) {
|
|
181
|
+
return collectionAwarePersistence;
|
|
182
|
+
}
|
|
183
|
+
const modeAwarePersistence = persistence.resolvePersistenceForMode?.(mode);
|
|
184
|
+
return modeAwarePersistence ?? persistence;
|
|
185
|
+
}
|
|
186
|
+
function exposeElectronSQLitePersistence(options) {
|
|
187
|
+
const channel = options.channel ?? DEFAULT_ELECTRON_PERSISTENCE_CHANNEL;
|
|
188
|
+
options.ipcMain.handle(
|
|
189
|
+
channel,
|
|
190
|
+
async (_event, request) => {
|
|
191
|
+
try {
|
|
192
|
+
assertValidRequest(request);
|
|
193
|
+
const modeAwarePersistence = resolveModeAwarePersistence(
|
|
194
|
+
options.persistence,
|
|
195
|
+
request
|
|
196
|
+
);
|
|
197
|
+
return await executeRequestAgainstAdapter(
|
|
198
|
+
request,
|
|
199
|
+
modeAwarePersistence.adapter
|
|
200
|
+
);
|
|
201
|
+
} catch (error) {
|
|
202
|
+
return createErrorResponse(request, error);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
);
|
|
206
|
+
return () => {
|
|
207
|
+
options.ipcMain.removeHandler?.(channel);
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
export {
|
|
211
|
+
exposeElectronSQLitePersistence
|
|
212
|
+
};
|
|
213
|
+
//# sourceMappingURL=main.js.map
|
|
@@ -0,0 +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 ElectronPersistedRow,\n ElectronPersistedKey\n> & {\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 `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 ElectronPersistedRow,\n ElectronPersistedKey\n >,\n request: ElectronPersistenceRequestEnvelope,\n): PersistedCollectionPersistence<ElectronPersistedRow, ElectronPersistedKey> {\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 ElectronPersistedRow,\n ElectronPersistedKey\n >\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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"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { LoadSubsetOptions } from '@tanstack/db';
|
|
2
|
+
import { PersistedCollectionMode, PersistedIndexSpec, PersistedTx, SQLitePullSinceResult } from '@tanstack/db-sqlite-persistence-core';
|
|
3
|
+
export declare const ELECTRON_PERSISTENCE_PROTOCOL_VERSION: 1;
|
|
4
|
+
export declare const DEFAULT_ELECTRON_PERSISTENCE_CHANNEL = "tanstack-db:sqlite-persistence";
|
|
5
|
+
export type ElectronPersistedRow = Record<string, unknown>;
|
|
6
|
+
export type ElectronPersistedKey = string | number;
|
|
7
|
+
export type ElectronPersistenceResolution = {
|
|
8
|
+
mode: PersistedCollectionMode;
|
|
9
|
+
schemaVersion?: number;
|
|
10
|
+
};
|
|
11
|
+
export type ElectronPersistenceMethod = `loadSubset` | `loadCollectionMetadata` | `scanRows` | `applyCommittedTx` | `ensureIndex` | `markIndexRemoved` | `pullSince` | `getStreamPosition`;
|
|
12
|
+
export type ElectronPersistencePayloadMap = {
|
|
13
|
+
loadSubset: {
|
|
14
|
+
options: LoadSubsetOptions;
|
|
15
|
+
ctx?: {
|
|
16
|
+
requiredIndexSignatures?: ReadonlyArray<string>;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
loadCollectionMetadata: {};
|
|
20
|
+
scanRows: {
|
|
21
|
+
options?: {
|
|
22
|
+
metadataOnly?: boolean;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
applyCommittedTx: {
|
|
26
|
+
tx: PersistedTx<ElectronPersistedRow, ElectronPersistedKey>;
|
|
27
|
+
};
|
|
28
|
+
ensureIndex: {
|
|
29
|
+
signature: string;
|
|
30
|
+
spec: PersistedIndexSpec;
|
|
31
|
+
};
|
|
32
|
+
markIndexRemoved: {
|
|
33
|
+
signature: string;
|
|
34
|
+
};
|
|
35
|
+
pullSince: {
|
|
36
|
+
fromRowVersion: number;
|
|
37
|
+
};
|
|
38
|
+
getStreamPosition: {};
|
|
39
|
+
};
|
|
40
|
+
export type ElectronPersistenceResultMap = {
|
|
41
|
+
loadSubset: Array<{
|
|
42
|
+
key: ElectronPersistedKey;
|
|
43
|
+
value: ElectronPersistedRow;
|
|
44
|
+
}>;
|
|
45
|
+
loadCollectionMetadata: Array<{
|
|
46
|
+
key: string;
|
|
47
|
+
value: unknown;
|
|
48
|
+
}>;
|
|
49
|
+
scanRows: Array<{
|
|
50
|
+
key: ElectronPersistedKey;
|
|
51
|
+
value: ElectronPersistedRow;
|
|
52
|
+
metadata?: unknown;
|
|
53
|
+
}>;
|
|
54
|
+
applyCommittedTx: null;
|
|
55
|
+
ensureIndex: null;
|
|
56
|
+
markIndexRemoved: null;
|
|
57
|
+
pullSince: SQLitePullSinceResult<ElectronPersistedKey>;
|
|
58
|
+
getStreamPosition: {
|
|
59
|
+
latestTerm: number;
|
|
60
|
+
latestSeq: number;
|
|
61
|
+
latestRowVersion: number;
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
export type ElectronSerializedError = {
|
|
65
|
+
name: string;
|
|
66
|
+
message: string;
|
|
67
|
+
stack?: string;
|
|
68
|
+
code?: string;
|
|
69
|
+
};
|
|
70
|
+
export type ElectronPersistenceRequestByMethod = {
|
|
71
|
+
[Method in ElectronPersistenceMethod]: {
|
|
72
|
+
v: number;
|
|
73
|
+
requestId: string;
|
|
74
|
+
collectionId: string;
|
|
75
|
+
resolution?: ElectronPersistenceResolution;
|
|
76
|
+
method: Method;
|
|
77
|
+
payload: ElectronPersistencePayloadMap[Method];
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
export type ElectronPersistenceRequest<TMethod extends ElectronPersistenceMethod = ElectronPersistenceMethod> = ElectronPersistenceRequestByMethod[TMethod];
|
|
81
|
+
export type ElectronPersistenceRequestEnvelope = ElectronPersistenceRequestByMethod[ElectronPersistenceMethod];
|
|
82
|
+
type ElectronPersistenceSuccessResponseByMethod = {
|
|
83
|
+
[Method in ElectronPersistenceMethod]: {
|
|
84
|
+
v: number;
|
|
85
|
+
requestId: string;
|
|
86
|
+
method: Method;
|
|
87
|
+
ok: true;
|
|
88
|
+
result: ElectronPersistenceResultMap[Method];
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
type ElectronPersistenceErrorResponseByMethod = {
|
|
92
|
+
[Method in ElectronPersistenceMethod]: {
|
|
93
|
+
v: number;
|
|
94
|
+
requestId: string;
|
|
95
|
+
method: Method;
|
|
96
|
+
ok: false;
|
|
97
|
+
error: ElectronSerializedError;
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
export type ElectronPersistenceResponse<TMethod extends ElectronPersistenceMethod = ElectronPersistenceMethod> = ElectronPersistenceSuccessResponseByMethod[TMethod] | ElectronPersistenceErrorResponseByMethod[TMethod];
|
|
101
|
+
export type ElectronPersistenceResponseEnvelope = ElectronPersistenceResponse<ElectronPersistenceMethod>;
|
|
102
|
+
export type ElectronPersistenceRequestHandler = (request: ElectronPersistenceRequestEnvelope) => Promise<ElectronPersistenceResponseEnvelope>;
|
|
103
|
+
export type ElectronPersistenceInvoke = (channel: string, request: ElectronPersistenceRequestEnvelope) => Promise<ElectronPersistenceResponseEnvelope>;
|
|
104
|
+
export {};
|
|
@@ -0,0 +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 PersistedTx,\n SQLitePullSinceResult,\n} from '@tanstack/db-sqlite-persistence-core'\n\nexport const ELECTRON_PERSISTENCE_PROTOCOL_VERSION = 1 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 | `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 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 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":"AAQO,MAAM,wCAAwC;AAC9C,MAAM,uCAAuC;"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { PersistedCollectionCoordinator, PersistedCollectionPersistence } from '@tanstack/db-sqlite-persistence-core';
|
|
2
|
+
import { ElectronPersistenceInvoke, ElectronPersistenceRequestEnvelope, ElectronPersistenceResponseEnvelope } from './protocol.js';
|
|
3
|
+
export type ElectronIpcRendererLike = {
|
|
4
|
+
invoke: (channel: string, request: ElectronPersistenceRequestEnvelope) => Promise<ElectronPersistenceResponseEnvelope>;
|
|
5
|
+
};
|
|
6
|
+
export type ElectronSQLitePersistenceOptions = {
|
|
7
|
+
invoke?: ElectronPersistenceInvoke;
|
|
8
|
+
ipcRenderer?: ElectronIpcRendererLike;
|
|
9
|
+
channel?: string;
|
|
10
|
+
timeoutMs?: number;
|
|
11
|
+
coordinator?: PersistedCollectionCoordinator;
|
|
12
|
+
};
|
|
13
|
+
export declare function createElectronSQLitePersistence<T extends object, TKey extends string | number = string | number>(options: ElectronSQLitePersistenceOptions): PersistedCollectionPersistence<T, TKey>;
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { SingleProcessCoordinator, InvalidPersistedCollectionConfigError } from "@tanstack/db-sqlite-persistence-core";
|
|
2
|
+
import { ElectronCollectionCoordinator } from "./electron-coordinator.js";
|
|
3
|
+
import { DEFAULT_ELECTRON_PERSISTENCE_CHANNEL, ELECTRON_PERSISTENCE_PROTOCOL_VERSION } from "./protocol.js";
|
|
4
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 5e3;
|
|
5
|
+
let nextRequestId = 1;
|
|
6
|
+
function createRequestId() {
|
|
7
|
+
const requestId = nextRequestId;
|
|
8
|
+
nextRequestId++;
|
|
9
|
+
return `electron-persistence-${requestId}`;
|
|
10
|
+
}
|
|
11
|
+
function withTimeout(promise, timeoutMs, timeoutMessage) {
|
|
12
|
+
if (timeoutMs <= 0) {
|
|
13
|
+
return promise;
|
|
14
|
+
}
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
const timer = setTimeout(() => {
|
|
17
|
+
reject(new InvalidPersistedCollectionConfigError(timeoutMessage));
|
|
18
|
+
}, timeoutMs);
|
|
19
|
+
promise.then(
|
|
20
|
+
(value) => {
|
|
21
|
+
clearTimeout(timer);
|
|
22
|
+
resolve(value);
|
|
23
|
+
},
|
|
24
|
+
(error) => {
|
|
25
|
+
clearTimeout(timer);
|
|
26
|
+
reject(error);
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function assertValidResponse(response, request) {
|
|
32
|
+
if (response.v !== ELECTRON_PERSISTENCE_PROTOCOL_VERSION) {
|
|
33
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
34
|
+
`Unexpected electron persistence protocol version "${response.v}" in response`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
if (response.requestId !== request.requestId) {
|
|
38
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
39
|
+
`Mismatched electron persistence response request id. Expected "${request.requestId}", received "${response.requestId}"`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
if (response.method !== request.method) {
|
|
43
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
44
|
+
`Mismatched electron persistence response method. Expected "${request.method}", received "${response.method}"`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function createSerializableLoadSubsetOptions(subsetOptions) {
|
|
49
|
+
const { subscription: _subscription, ...serializableOptions } = subsetOptions;
|
|
50
|
+
return serializableOptions;
|
|
51
|
+
}
|
|
52
|
+
function createRendererRequestExecutor(options) {
|
|
53
|
+
const channel = options.channel ?? DEFAULT_ELECTRON_PERSISTENCE_CHANNEL;
|
|
54
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
55
|
+
return async (method, collectionId, payload, resolution) => {
|
|
56
|
+
const request = {
|
|
57
|
+
v: ELECTRON_PERSISTENCE_PROTOCOL_VERSION,
|
|
58
|
+
requestId: createRequestId(),
|
|
59
|
+
collectionId,
|
|
60
|
+
method,
|
|
61
|
+
resolution,
|
|
62
|
+
payload
|
|
63
|
+
};
|
|
64
|
+
const response = await withTimeout(
|
|
65
|
+
options.invoke(channel, request),
|
|
66
|
+
timeoutMs,
|
|
67
|
+
`Electron persistence request timed out (method=${method}, collection=${collectionId}, timeoutMs=${timeoutMs})`
|
|
68
|
+
);
|
|
69
|
+
assertValidResponse(response, request);
|
|
70
|
+
if (!response.ok) {
|
|
71
|
+
const remoteError = new InvalidPersistedCollectionConfigError(
|
|
72
|
+
`${response.error.name}: ${response.error.message}`
|
|
73
|
+
);
|
|
74
|
+
if (typeof response.error.stack === `string`) {
|
|
75
|
+
remoteError.stack = response.error.stack;
|
|
76
|
+
}
|
|
77
|
+
if (typeof response.error.code === `string`) {
|
|
78
|
+
remoteError.code = response.error.code;
|
|
79
|
+
}
|
|
80
|
+
throw remoteError;
|
|
81
|
+
}
|
|
82
|
+
return response.result;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function createResolvedRendererAdapter(executeRequest, resolution) {
|
|
86
|
+
return {
|
|
87
|
+
loadSubset: async (collectionId, subsetOptions, ctx) => {
|
|
88
|
+
const result = await executeRequest(
|
|
89
|
+
`loadSubset`,
|
|
90
|
+
collectionId,
|
|
91
|
+
{
|
|
92
|
+
options: createSerializableLoadSubsetOptions(subsetOptions),
|
|
93
|
+
ctx
|
|
94
|
+
},
|
|
95
|
+
resolution
|
|
96
|
+
);
|
|
97
|
+
return result;
|
|
98
|
+
},
|
|
99
|
+
applyCommittedTx: async (collectionId, tx) => {
|
|
100
|
+
await executeRequest(
|
|
101
|
+
`applyCommittedTx`,
|
|
102
|
+
collectionId,
|
|
103
|
+
{
|
|
104
|
+
tx
|
|
105
|
+
},
|
|
106
|
+
resolution
|
|
107
|
+
);
|
|
108
|
+
},
|
|
109
|
+
loadCollectionMetadata: async (collectionId) => {
|
|
110
|
+
return executeRequest(
|
|
111
|
+
`loadCollectionMetadata`,
|
|
112
|
+
collectionId,
|
|
113
|
+
{},
|
|
114
|
+
resolution
|
|
115
|
+
);
|
|
116
|
+
},
|
|
117
|
+
scanRows: async (collectionId, options) => {
|
|
118
|
+
const result = await executeRequest(
|
|
119
|
+
`scanRows`,
|
|
120
|
+
collectionId,
|
|
121
|
+
{ options },
|
|
122
|
+
resolution
|
|
123
|
+
);
|
|
124
|
+
return result;
|
|
125
|
+
},
|
|
126
|
+
ensureIndex: async (collectionId, signature, spec) => {
|
|
127
|
+
await executeRequest(
|
|
128
|
+
`ensureIndex`,
|
|
129
|
+
collectionId,
|
|
130
|
+
{
|
|
131
|
+
signature,
|
|
132
|
+
spec
|
|
133
|
+
},
|
|
134
|
+
resolution
|
|
135
|
+
);
|
|
136
|
+
},
|
|
137
|
+
markIndexRemoved: async (collectionId, signature) => {
|
|
138
|
+
await executeRequest(
|
|
139
|
+
`markIndexRemoved`,
|
|
140
|
+
collectionId,
|
|
141
|
+
{
|
|
142
|
+
signature
|
|
143
|
+
},
|
|
144
|
+
resolution
|
|
145
|
+
);
|
|
146
|
+
},
|
|
147
|
+
pullSince: async (collectionId, fromRowVersion) => {
|
|
148
|
+
const result = await executeRequest(
|
|
149
|
+
`pullSince`,
|
|
150
|
+
collectionId,
|
|
151
|
+
{
|
|
152
|
+
fromRowVersion
|
|
153
|
+
},
|
|
154
|
+
resolution
|
|
155
|
+
);
|
|
156
|
+
return result;
|
|
157
|
+
},
|
|
158
|
+
getStreamPosition: async (collectionId) => {
|
|
159
|
+
return executeRequest(`getStreamPosition`, collectionId, {}, resolution);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function resolveInvoke(options) {
|
|
164
|
+
if (options.invoke) {
|
|
165
|
+
return options.invoke;
|
|
166
|
+
}
|
|
167
|
+
if (options.ipcRenderer) {
|
|
168
|
+
return (channel, request) => options.ipcRenderer.invoke(channel, request);
|
|
169
|
+
}
|
|
170
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
171
|
+
`Electron renderer persistence requires either invoke or ipcRenderer`
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
function createElectronSQLitePersistence(options) {
|
|
175
|
+
const invoke = resolveInvoke(options);
|
|
176
|
+
const coordinator = options.coordinator ?? new SingleProcessCoordinator();
|
|
177
|
+
const executeRequest = createRendererRequestExecutor({
|
|
178
|
+
invoke,
|
|
179
|
+
channel: options.channel,
|
|
180
|
+
timeoutMs: options.timeoutMs
|
|
181
|
+
});
|
|
182
|
+
const adapterCache = /* @__PURE__ */ new Map();
|
|
183
|
+
const getAdapterForCollection = (mode, schemaVersion) => {
|
|
184
|
+
const schemaVersionKey = schemaVersion === void 0 ? `schema:default` : `schema:${schemaVersion}`;
|
|
185
|
+
const cacheKey = `mode:${mode}|${schemaVersionKey}`;
|
|
186
|
+
const cachedAdapter = adapterCache.get(cacheKey);
|
|
187
|
+
if (cachedAdapter) {
|
|
188
|
+
return cachedAdapter;
|
|
189
|
+
}
|
|
190
|
+
const adapter = createResolvedRendererAdapter(executeRequest, {
|
|
191
|
+
mode,
|
|
192
|
+
schemaVersion
|
|
193
|
+
});
|
|
194
|
+
adapterCache.set(cacheKey, adapter);
|
|
195
|
+
if (coordinator instanceof ElectronCollectionCoordinator) {
|
|
196
|
+
coordinator.setAdapter(adapter);
|
|
197
|
+
}
|
|
198
|
+
return adapter;
|
|
199
|
+
};
|
|
200
|
+
const createCollectionPersistence = (mode, schemaVersion) => ({
|
|
201
|
+
adapter: getAdapterForCollection(
|
|
202
|
+
mode,
|
|
203
|
+
schemaVersion
|
|
204
|
+
),
|
|
205
|
+
coordinator
|
|
206
|
+
});
|
|
207
|
+
const defaultPersistence = createCollectionPersistence(
|
|
208
|
+
`sync-absent`,
|
|
209
|
+
void 0
|
|
210
|
+
);
|
|
211
|
+
return {
|
|
212
|
+
...defaultPersistence,
|
|
213
|
+
resolvePersistenceForCollection: ({ mode, schemaVersion }) => createCollectionPersistence(mode, schemaVersion),
|
|
214
|
+
// Backward compatible fallback for older callers.
|
|
215
|
+
resolvePersistenceForMode: (mode) => createCollectionPersistence(mode, void 0)
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
export {
|
|
219
|
+
createElectronSQLitePersistence
|
|
220
|
+
};
|
|
221
|
+
//# sourceMappingURL=renderer.js.map
|
|
@@ -0,0 +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 T extends object,\n TKey extends string | number = string | number,\n> = PersistedCollectionPersistence<T, TKey>[`adapter`] & {\n loadCollectionMetadata: (\n collectionId: string,\n ) => Promise<Array<{ key: string; value: unknown }>>\n scanRows: (\n collectionId: string,\n options?: { metadataOnly?: boolean },\n ) => Promise<Array<{ key: TKey; value: T; metadata?: unknown }>>\n pullSince: (\n collectionId: string,\n fromRowVersion: number,\n ) => Promise<SQLitePullSinceResult<TKey>>\n getStreamPosition: (collectionId: string) => Promise<{\n latestTerm: number\n latestSeq: number\n latestRowVersion: number\n }>\n}\n\nfunction createResolvedRendererAdapter<\n T extends object,\n TKey extends string | number = string | number,\n>(\n executeRequest: RendererRequestExecutor,\n resolution?: ElectronPersistenceResolution,\n): ElectronRendererResolvedAdapter<T, TKey> {\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<{ key: TKey; value: T }>\n },\n applyCommittedTx: async (\n collectionId: string,\n tx: PersistedTx<T, TKey>,\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<Array<{ key: TKey; value: T; metadata?: unknown }>> => {\n const result = await executeRequest(\n `scanRows`,\n collectionId,\n { options },\n resolution,\n )\n return result as Array<{ key: TKey; value: T; metadata?: unknown }>\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<TKey>> => {\n const result = await executeRequest(\n `pullSince`,\n collectionId,\n {\n fromRowVersion,\n },\n resolution,\n )\n return result as SQLitePullSinceResult<TKey>\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 T extends object,\n TKey extends string | number = string | number,\n>(\n options: ElectronSQLitePersistenceOptions,\n): PersistedCollectionPersistence<T, TKey> {\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<\n string,\n ElectronRendererResolvedAdapter<Record<string, unknown>, string | number>\n >()\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<\n Record<string, unknown>,\n string | number\n >(executeRequest, {\n mode,\n schemaVersion,\n })\n adapterCache.set(cacheKey, adapter)\n\n // Wire the adapter into the coordinator so it can handle\n // leader-side RPCs (applyCommittedTx, pullSince, getStreamPosition, etc.)\n if (coordinator instanceof ElectronCollectionCoordinator) {\n coordinator.setAdapter(adapter)\n }\n\n return adapter\n }\n\n const createCollectionPersistence = (\n mode: PersistedCollectionMode,\n schemaVersion: number | undefined,\n ): PersistedCollectionPersistence<T, TKey> => ({\n adapter: getAdapterForCollection(\n mode,\n schemaVersion,\n ) as unknown as PersistedCollectionPersistence<T, TKey>[`adapter`],\n coordinator,\n })\n\n const defaultPersistence = createCollectionPersistence(\n `sync-absent`,\n undefined,\n )\n\n return {\n ...defaultPersistence,\n resolvePersistenceForCollection: ({ mode, schemaVersion }) =>\n createCollectionPersistence(mode, schemaVersion),\n // Backward compatible fallback for older callers.\n resolvePersistenceForMode: (mode) =>\n createCollectionPersistence(mode, undefined),\n }\n}\n"],"names":[],"mappings":"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"}
|