cojson-storage-indexeddb 0.3.0-alpha.0

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/.eslintrc.cjs ADDED
@@ -0,0 +1,17 @@
1
+ module.exports = {
2
+ extends: [
3
+ 'eslint:recommended',
4
+ 'plugin:@typescript-eslint/recommended',
5
+ ],
6
+ parser: '@typescript-eslint/parser',
7
+ plugins: ['@typescript-eslint'],
8
+ parserOptions: {
9
+ project: "./tsconfig.json",
10
+ },
11
+ root: true,
12
+ rules: {
13
+ "no-unused-vars": "off",
14
+ "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }],
15
+ // "@typescript-eslint/no-floating-promises": "error",
16
+ },
17
+ };
@@ -0,0 +1,30 @@
1
+ import { SyncMessage, Peer, CojsonInternalTypes } from "cojson";
2
+ import { ReadableStream, WritableStream, ReadableStreamDefaultReader, WritableStreamDefaultWriter } from "isomorphic-streams";
3
+ export declare class IDBStorage {
4
+ db: IDBDatabase;
5
+ fromLocalNode: ReadableStreamDefaultReader<SyncMessage>;
6
+ toLocalNode: WritableStreamDefaultWriter<SyncMessage>;
7
+ constructor(db: IDBDatabase, fromLocalNode: ReadableStream<SyncMessage>, toLocalNode: WritableStream<SyncMessage>);
8
+ static asPeer({ trace, localNodeName, }?: {
9
+ trace?: boolean;
10
+ localNodeName?: string;
11
+ } | undefined): Promise<Peer>;
12
+ static open(fromLocalNode: ReadableStream<SyncMessage>, toLocalNode: WritableStream<SyncMessage>): Promise<IDBStorage>;
13
+ handleSyncMessage(msg: SyncMessage): Promise<void>;
14
+ sendNewContentAfter(theirKnown: CojsonInternalTypes.CoValueKnownState, { coValues, sessions, transactions, signatureAfter, }: {
15
+ coValues: IDBObjectStore;
16
+ sessions: IDBObjectStore;
17
+ transactions: IDBObjectStore;
18
+ signatureAfter: IDBObjectStore;
19
+ }, asDependencyOf?: CojsonInternalTypes.RawCoID): Promise<void>;
20
+ handleLoad(msg: CojsonInternalTypes.LoadMessage): Promise<void>;
21
+ handleContent(msg: CojsonInternalTypes.NewContentMessage): Promise<void>;
22
+ handleKnown(msg: CojsonInternalTypes.KnownStateMessage): Promise<void>;
23
+ handleDone(_msg: CojsonInternalTypes.DoneMessage): void;
24
+ inTransaction(mode: "readwrite" | "readonly"): {
25
+ coValues: IDBObjectStore;
26
+ sessions: IDBObjectStore;
27
+ transactions: IDBObjectStore;
28
+ signatureAfter: IDBObjectStore;
29
+ };
30
+ }
package/dist/index.js ADDED
@@ -0,0 +1,325 @@
1
+ import { cojsonInternals, MAX_RECOMMENDED_TX_SIZE, } from "cojson";
2
+ export class IDBStorage {
3
+ constructor(db, fromLocalNode, toLocalNode) {
4
+ this.db = db;
5
+ this.fromLocalNode = fromLocalNode.getReader();
6
+ this.toLocalNode = toLocalNode.getWriter();
7
+ (async () => {
8
+ let done = false;
9
+ while (!done) {
10
+ const result = await this.fromLocalNode.read();
11
+ done = result.done;
12
+ if (result.value) {
13
+ await this.handleSyncMessage(result.value);
14
+ }
15
+ }
16
+ })();
17
+ }
18
+ static async asPeer({ trace, localNodeName = "local", } = {
19
+ localNodeName: "local",
20
+ }) {
21
+ const [localNodeAsPeer, storageAsPeer] = cojsonInternals.connectedPeers(localNodeName, "storage", { peer1role: "client", peer2role: "server", trace });
22
+ await IDBStorage.open(localNodeAsPeer.incoming, localNodeAsPeer.outgoing);
23
+ return storageAsPeer;
24
+ }
25
+ static async open(fromLocalNode, toLocalNode) {
26
+ const dbPromise = new Promise((resolve, reject) => {
27
+ const request = indexedDB.open("jazz-storage", 4);
28
+ request.onerror = () => {
29
+ reject(request.error);
30
+ };
31
+ request.onsuccess = () => {
32
+ resolve(request.result);
33
+ };
34
+ request.onupgradeneeded = async (ev) => {
35
+ const db = request.result;
36
+ if (ev.oldVersion === 0) {
37
+ const coValues = db.createObjectStore("coValues", {
38
+ autoIncrement: true,
39
+ keyPath: "rowID",
40
+ });
41
+ coValues.createIndex("coValuesById", "id", {
42
+ unique: true,
43
+ });
44
+ const sessions = db.createObjectStore("sessions", {
45
+ autoIncrement: true,
46
+ keyPath: "rowID",
47
+ });
48
+ sessions.createIndex("sessionsByCoValue", "coValue");
49
+ sessions.createIndex("uniqueSessions", ["coValue", "sessionID"], {
50
+ unique: true,
51
+ });
52
+ db.createObjectStore("transactions", {
53
+ keyPath: ["ses", "idx"],
54
+ });
55
+ }
56
+ if (ev.oldVersion <= 1) {
57
+ db.createObjectStore("signatureAfter", {
58
+ keyPath: ["ses", "idx"],
59
+ });
60
+ }
61
+ if (ev.oldVersion !== 0 && ev.oldVersion <= 3) {
62
+ // fix embarrassing off-by-one error for transaction indices
63
+ console.log("Migration: fixing off-by-one error");
64
+ const transaction = ev.target.transaction;
65
+ const txsStore = transaction.objectStore("transactions");
66
+ const txs = await promised(txsStore.getAll());
67
+ for (const tx of txs) {
68
+ await promised(txsStore.delete([tx.ses, tx.idx]));
69
+ tx.idx -= 1;
70
+ await promised(txsStore.add(tx));
71
+ }
72
+ console.log("Migration: fixing off-by-one error - done");
73
+ }
74
+ };
75
+ });
76
+ return new IDBStorage(await dbPromise, fromLocalNode, toLocalNode);
77
+ }
78
+ async handleSyncMessage(msg) {
79
+ switch (msg.action) {
80
+ case "load":
81
+ await this.handleLoad(msg);
82
+ break;
83
+ case "content":
84
+ await this.handleContent(msg);
85
+ break;
86
+ case "known":
87
+ await this.handleKnown(msg);
88
+ break;
89
+ case "done":
90
+ await this.handleDone(msg);
91
+ break;
92
+ }
93
+ }
94
+ async sendNewContentAfter(theirKnown, { coValues, sessions, transactions, signatureAfter, }, asDependencyOf) {
95
+ const coValueRow = await promised(coValues.index("coValuesById").get(theirKnown.id));
96
+ const allOurSessions = coValueRow
97
+ ? await promised(sessions.index("sessionsByCoValue").getAll(coValueRow.rowID))
98
+ : [];
99
+ const ourKnown = {
100
+ id: theirKnown.id,
101
+ header: !!coValueRow,
102
+ sessions: {},
103
+ };
104
+ const newContentPieces = [
105
+ {
106
+ action: "content",
107
+ id: theirKnown.id,
108
+ header: theirKnown.header ? undefined : coValueRow?.header,
109
+ new: {},
110
+ },
111
+ ];
112
+ for (const sessionRow of allOurSessions) {
113
+ ourKnown.sessions[sessionRow.sessionID] = sessionRow.lastIdx;
114
+ if (sessionRow.lastIdx >
115
+ (theirKnown.sessions[sessionRow.sessionID] || 0)) {
116
+ const firstNewTxIdx = theirKnown.sessions[sessionRow.sessionID] || 0;
117
+ const signaturesAndIdxs = await promised(signatureAfter.getAll(IDBKeyRange.bound([sessionRow.rowID, firstNewTxIdx], [sessionRow.rowID, Infinity])));
118
+ // console.log(
119
+ // theirKnown.id,
120
+ // "signaturesAndIdxs",
121
+ // JSON.stringify(signaturesAndIdxs)
122
+ // );
123
+ const newTxInSession = await promised(transactions.getAll(IDBKeyRange.bound([sessionRow.rowID, firstNewTxIdx], [sessionRow.rowID, Infinity])));
124
+ let idx = firstNewTxIdx;
125
+ // console.log(
126
+ // theirKnown.id,
127
+ // "newTxInSession",
128
+ // newTxInSession.length
129
+ // );
130
+ for (const tx of newTxInSession) {
131
+ let sessionEntry = newContentPieces[newContentPieces.length - 1].new[sessionRow.sessionID];
132
+ if (!sessionEntry) {
133
+ sessionEntry = {
134
+ after: idx,
135
+ lastSignature: "WILL_BE_REPLACED",
136
+ newTransactions: [],
137
+ };
138
+ newContentPieces[newContentPieces.length - 1].new[sessionRow.sessionID] = sessionEntry;
139
+ }
140
+ sessionEntry.newTransactions.push(tx.tx);
141
+ if (signaturesAndIdxs[0] &&
142
+ idx === signaturesAndIdxs[0].idx) {
143
+ sessionEntry.lastSignature =
144
+ signaturesAndIdxs[0].signature;
145
+ signaturesAndIdxs.shift();
146
+ newContentPieces.push({
147
+ action: "content",
148
+ id: theirKnown.id,
149
+ new: {},
150
+ });
151
+ }
152
+ else if (idx ===
153
+ firstNewTxIdx + newTxInSession.length - 1) {
154
+ sessionEntry.lastSignature = sessionRow.lastSignature;
155
+ }
156
+ idx += 1;
157
+ }
158
+ }
159
+ }
160
+ const dependedOnCoValues = coValueRow?.header.ruleset.type === "group"
161
+ ? newContentPieces
162
+ .flatMap((piece) => Object.values(piece.new))
163
+ .flatMap((sessionEntry) => sessionEntry.newTransactions.flatMap((tx) => {
164
+ if (tx.privacy !== "trusting")
165
+ return [];
166
+ // TODO: avoid parse here?
167
+ return cojsonInternals
168
+ .parseJSON(tx.changes)
169
+ .map((change) => change &&
170
+ typeof change === "object" &&
171
+ "op" in change &&
172
+ change.op === "set" &&
173
+ "key" in change &&
174
+ change.key)
175
+ .filter((key) => typeof key === "string" &&
176
+ key.startsWith("co_"));
177
+ }))
178
+ : coValueRow?.header.ruleset.type === "ownedByGroup"
179
+ ? [coValueRow?.header.ruleset.group]
180
+ : [];
181
+ for (const dependedOnCoValue of dependedOnCoValues) {
182
+ await this.sendNewContentAfter({ id: dependedOnCoValue, header: false, sessions: {} }, { coValues, sessions, transactions, signatureAfter }, asDependencyOf || theirKnown.id);
183
+ }
184
+ await this.toLocalNode.write({
185
+ action: "known",
186
+ ...ourKnown,
187
+ asDependencyOf,
188
+ });
189
+ const nonEmptyNewContentPieces = newContentPieces.filter((piece) => piece.header || Object.keys(piece.new).length > 0);
190
+ // console.log(theirKnown.id, nonEmptyNewContentPieces);
191
+ for (const piece of nonEmptyNewContentPieces) {
192
+ await this.toLocalNode.write(piece);
193
+ await new Promise((resolve) => setTimeout(resolve, 0));
194
+ }
195
+ }
196
+ handleLoad(msg) {
197
+ return this.sendNewContentAfter(msg, this.inTransaction("readonly"));
198
+ }
199
+ async handleContent(msg) {
200
+ const { coValues, sessions, transactions, signatureAfter } = this.inTransaction("readwrite");
201
+ let storedCoValueRowID = (await promised(coValues.index("coValuesById").get(msg.id)))?.rowID;
202
+ if (storedCoValueRowID === undefined) {
203
+ const header = msg.header;
204
+ if (!header) {
205
+ console.error("Expected to be sent header first");
206
+ await this.toLocalNode.write({
207
+ action: "known",
208
+ id: msg.id,
209
+ header: false,
210
+ sessions: {},
211
+ isCorrection: true,
212
+ });
213
+ return;
214
+ }
215
+ storedCoValueRowID = (await promised(coValues.put({
216
+ id: msg.id,
217
+ header: header,
218
+ })));
219
+ }
220
+ const allOurSessions = await new Promise((resolve) => {
221
+ const allOurSessionsRequest = sessions
222
+ .index("sessionsByCoValue")
223
+ .getAll(storedCoValueRowID);
224
+ allOurSessionsRequest.onsuccess = () => {
225
+ resolve(Object.fromEntries(allOurSessionsRequest.result.map((row) => [row.sessionID, row])));
226
+ };
227
+ });
228
+ const ourKnown = {
229
+ id: msg.id,
230
+ header: true,
231
+ sessions: {},
232
+ };
233
+ let invalidAssumptions = false;
234
+ for (const sessionID of Object.keys(msg.new)) {
235
+ const sessionRow = allOurSessions[sessionID];
236
+ if (sessionRow) {
237
+ ourKnown.sessions[sessionRow.sessionID] = sessionRow.lastIdx;
238
+ }
239
+ if ((sessionRow?.lastIdx || 0) < (msg.new[sessionID]?.after || 0)) {
240
+ invalidAssumptions = true;
241
+ }
242
+ else {
243
+ const newTransactions = msg.new[sessionID]?.newTransactions || [];
244
+ const actuallyNewOffset = (sessionRow?.lastIdx || 0) -
245
+ (msg.new[sessionID]?.after || 0);
246
+ const actuallyNewTransactions = newTransactions.slice(actuallyNewOffset);
247
+ let newBytesSinceLastSignature = (sessionRow?.bytesSinceLastSignature || 0) +
248
+ actuallyNewTransactions.reduce((sum, tx) => sum +
249
+ (tx.privacy === "private"
250
+ ? tx.encryptedChanges.length
251
+ : tx.changes.length), 0);
252
+ const newLastIdx = (sessionRow?.lastIdx || 0) + actuallyNewTransactions.length;
253
+ let shouldWriteSignature = false;
254
+ if (newBytesSinceLastSignature > MAX_RECOMMENDED_TX_SIZE) {
255
+ shouldWriteSignature = true;
256
+ newBytesSinceLastSignature = 0;
257
+ }
258
+ let nextIdx = sessionRow?.lastIdx || 0;
259
+ const sessionUpdate = {
260
+ coValue: storedCoValueRowID,
261
+ sessionID: sessionID,
262
+ lastIdx: newLastIdx,
263
+ lastSignature: msg.new[sessionID].lastSignature,
264
+ bytesSinceLastSignature: newBytesSinceLastSignature,
265
+ };
266
+ const sessionRowID = (await promised(sessions.put(sessionRow?.rowID
267
+ ? {
268
+ rowID: sessionRow.rowID,
269
+ ...sessionUpdate,
270
+ }
271
+ : sessionUpdate)));
272
+ if (shouldWriteSignature) {
273
+ await promised(signatureAfter.put({
274
+ ses: sessionRowID,
275
+ // TODO: newLastIdx is a misnomer, it's actually more like nextIdx or length
276
+ idx: newLastIdx - 1,
277
+ signature: msg.new[sessionID].lastSignature,
278
+ }));
279
+ }
280
+ for (const newTransaction of actuallyNewTransactions) {
281
+ await promised(transactions.add({
282
+ ses: sessionRowID,
283
+ idx: nextIdx,
284
+ tx: newTransaction,
285
+ }));
286
+ nextIdx++;
287
+ }
288
+ }
289
+ }
290
+ if (invalidAssumptions) {
291
+ await this.toLocalNode.write({
292
+ action: "known",
293
+ ...ourKnown,
294
+ isCorrection: invalidAssumptions,
295
+ });
296
+ }
297
+ }
298
+ handleKnown(msg) {
299
+ return this.sendNewContentAfter(msg, this.inTransaction("readonly"));
300
+ }
301
+ handleDone(_msg) { }
302
+ inTransaction(mode) {
303
+ const tx = this.db.transaction(["coValues", "sessions", "transactions", "signatureAfter"], mode);
304
+ tx.onerror = (event) => {
305
+ const target = event.target;
306
+ throw new Error(`Error in transaction (${target?.source?.name}): ${target?.error}`, { cause: target?.error });
307
+ };
308
+ const coValues = tx.objectStore("coValues");
309
+ const sessions = tx.objectStore("sessions");
310
+ const transactions = tx.objectStore("transactions");
311
+ const signatureAfter = tx.objectStore("signatureAfter");
312
+ return { coValues, sessions, transactions, signatureAfter };
313
+ }
314
+ }
315
+ function promised(request) {
316
+ return new Promise((resolve, reject) => {
317
+ request.onsuccess = () => {
318
+ resolve(request.result);
319
+ };
320
+ request.onerror = () => {
321
+ reject(request.error);
322
+ };
323
+ });
324
+ }
325
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,29 @@
1
+ import { expect, test } from "vitest";
2
+ import { AnonymousControlledAccount, LocalNode, cojsonInternals } from "cojson";
3
+ import { IDBStorage } from ".";
4
+ test.skip("Should be able to initialize and load from empty DB", async () => {
5
+ const agentSecret = cojsonInternals.newRandomAgentSecret();
6
+ const node = new LocalNode(new AnonymousControlledAccount(agentSecret), cojsonInternals.newRandomSessionID(cojsonInternals.getAgentID(agentSecret)));
7
+ node.syncManager.addPeer(await IDBStorage.asPeer({ trace: true }));
8
+ console.log("yay!");
9
+ const _group = node.createGroup();
10
+ await new Promise((resolve) => setTimeout(resolve, 200));
11
+ expect(node.syncManager.peers["storage"]).toBeDefined();
12
+ });
13
+ test("Should be able to sync data to database and then load that from a new node", async () => {
14
+ const agentSecret = cojsonInternals.newRandomAgentSecret();
15
+ const node1 = new LocalNode(new AnonymousControlledAccount(agentSecret), cojsonInternals.newRandomSessionID(cojsonInternals.getAgentID(agentSecret)));
16
+ node1.syncManager.addPeer(await IDBStorage.asPeer({ trace: true, localNodeName: "node1" }));
17
+ console.log("yay!");
18
+ const group = node1.createGroup();
19
+ const map = group.createMap();
20
+ map.edit((m) => {
21
+ m.set("hello", "world");
22
+ });
23
+ await new Promise((resolve) => setTimeout(resolve, 200));
24
+ const node2 = new LocalNode(new AnonymousControlledAccount(agentSecret), cojsonInternals.newRandomSessionID(cojsonInternals.getAgentID(agentSecret)));
25
+ node2.syncManager.addPeer(await IDBStorage.asPeer({ trace: true, localNodeName: "node2" }));
26
+ const map2 = await node2.load(map.id);
27
+ expect(map2.get("hello")).toBe("world");
28
+ });
29
+ //# sourceMappingURL=index.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.js","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,0BAA0B,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AAChF,OAAO,EAAE,UAAU,EAAE,MAAM,GAAG,CAAC;AAE/B,IAAI,CAAC,IAAI,CAAC,qDAAqD,EAAE,KAAK,IAAI,EAAE;IACxE,MAAM,WAAW,GAAG,eAAe,CAAC,oBAAoB,EAAE,CAAC;IAE3D,MAAM,IAAI,GAAG,IAAI,SAAS,CACtB,IAAI,0BAA0B,CAAC,WAAW,CAAC,EAC3C,eAAe,CAAC,kBAAkB,CAC9B,eAAe,CAAC,UAAU,CAAC,WAAW,CAAC,CAC1C,CACJ,CAAC;IAEF,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,MAAM,UAAU,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAEnE,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAEpB,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IAElC,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;IAEzD,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;AAC5D,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,4EAA4E,EAAE,KAAK,IAAI,EAAE;IAC1F,MAAM,WAAW,GAAG,eAAe,CAAC,oBAAoB,EAAE,CAAC;IAE3D,MAAM,KAAK,GAAG,IAAI,SAAS,CACvB,IAAI,0BAA0B,CAAC,WAAW,CAAC,EAC3C,eAAe,CAAC,kBAAkB,CAC9B,eAAe,CAAC,UAAU,CAAC,WAAW,CAAC,CAC1C,CACJ,CAAC;IAEF,KAAK,CAAC,WAAW,CAAC,OAAO,CACrB,MAAM,UAAU,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,CAAC,CACnE,CAAC;IAEF,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAEpB,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;IAElC,MAAM,GAAG,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;IAE9B,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE;QACX,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;IAEzD,MAAM,KAAK,GAAG,IAAI,SAAS,CACvB,IAAI,0BAA0B,CAAC,WAAW,CAAC,EAC3C,eAAe,CAAC,kBAAkB,CAC9B,eAAe,CAAC,UAAU,CAAC,WAAW,CAAC,CAC1C,CACJ,CAAC;IAEF,KAAK,CAAC,WAAW,CAAC,OAAO,CACrB,MAAM,UAAU,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,CAAC,CACnE,CAAC;IAEF,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEtC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AAC5C,CAAC,CAAC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "cojson-storage-indexeddb",
3
+ "version": "0.3.0-alpha.0",
4
+ "main": "dist/index.js",
5
+ "types": "dist/index.d.ts",
6
+ "license": "MIT",
7
+ "dependencies": {
8
+ "cojson": "^0.3.0-alpha.0",
9
+ "typescript": "^5.1.6"
10
+ },
11
+ "devDependencies": {
12
+ "@vitest/browser": "^0.34.1",
13
+ "vitest": "^0.34.1",
14
+ "webdriverio": "^8.15.0"
15
+ },
16
+ "scripts": {
17
+ "test": "vitest --browser chrome",
18
+ "lint": "eslint src/**/*.ts",
19
+ "build": "npm run lint && rm -rf ./dist && tsc --declaration --sourceMap --outDir dist",
20
+ "prepublishOnly": "npm run build"
21
+ },
22
+ "gitHead": "ed0428bf97684660b6079ab766488adaad563851"
23
+ }
@@ -0,0 +1,66 @@
1
+ import { expect, test } from "vitest";
2
+ import { AnonymousControlledAccount, LocalNode, cojsonInternals } from "cojson";
3
+ import { IDBStorage } from ".";
4
+
5
+ test.skip("Should be able to initialize and load from empty DB", async () => {
6
+ const agentSecret = cojsonInternals.newRandomAgentSecret();
7
+
8
+ const node = new LocalNode(
9
+ new AnonymousControlledAccount(agentSecret),
10
+ cojsonInternals.newRandomSessionID(
11
+ cojsonInternals.getAgentID(agentSecret)
12
+ )
13
+ );
14
+
15
+ node.syncManager.addPeer(await IDBStorage.asPeer({ trace: true }));
16
+
17
+ console.log("yay!");
18
+
19
+ const _group = node.createGroup();
20
+
21
+ await new Promise((resolve) => setTimeout(resolve, 200));
22
+
23
+ expect(node.syncManager.peers["storage"]).toBeDefined();
24
+ });
25
+
26
+ test("Should be able to sync data to database and then load that from a new node", async () => {
27
+ const agentSecret = cojsonInternals.newRandomAgentSecret();
28
+
29
+ const node1 = new LocalNode(
30
+ new AnonymousControlledAccount(agentSecret),
31
+ cojsonInternals.newRandomSessionID(
32
+ cojsonInternals.getAgentID(agentSecret)
33
+ )
34
+ );
35
+
36
+ node1.syncManager.addPeer(
37
+ await IDBStorage.asPeer({ trace: true, localNodeName: "node1" })
38
+ );
39
+
40
+ console.log("yay!");
41
+
42
+ const group = node1.createGroup();
43
+
44
+ const map = group.createMap();
45
+
46
+ map.edit((m) => {
47
+ m.set("hello", "world");
48
+ });
49
+
50
+ await new Promise((resolve) => setTimeout(resolve, 200));
51
+
52
+ const node2 = new LocalNode(
53
+ new AnonymousControlledAccount(agentSecret),
54
+ cojsonInternals.newRandomSessionID(
55
+ cojsonInternals.getAgentID(agentSecret)
56
+ )
57
+ );
58
+
59
+ node2.syncManager.addPeer(
60
+ await IDBStorage.asPeer({ trace: true, localNodeName: "node2" })
61
+ );
62
+
63
+ const map2 = await node2.load(map.id);
64
+
65
+ expect(map2.get("hello")).toBe("world");
66
+ });
package/src/index.ts ADDED
@@ -0,0 +1,560 @@
1
+ import {
2
+ cojsonInternals,
3
+ SessionID,
4
+ SyncMessage,
5
+ Peer,
6
+ CojsonInternalTypes,
7
+ MAX_RECOMMENDED_TX_SIZE,
8
+ } from "cojson";
9
+ import {
10
+ ReadableStream,
11
+ WritableStream,
12
+ ReadableStreamDefaultReader,
13
+ WritableStreamDefaultWriter,
14
+ } from "isomorphic-streams";
15
+
16
+ type CoValueRow = {
17
+ id: CojsonInternalTypes.RawCoID;
18
+ header: CojsonInternalTypes.CoValueHeader;
19
+ };
20
+
21
+ type StoredCoValueRow = CoValueRow & { rowID: number };
22
+
23
+ type SessionRow = {
24
+ coValue: number;
25
+ sessionID: SessionID;
26
+ lastIdx: number;
27
+ lastSignature: CojsonInternalTypes.Signature;
28
+ bytesSinceLastSignature?: number;
29
+ };
30
+
31
+ type StoredSessionRow = SessionRow & { rowID: number };
32
+
33
+ type TransactionRow = {
34
+ ses: number;
35
+ idx: number;
36
+ tx: CojsonInternalTypes.Transaction;
37
+ };
38
+
39
+ type SignatureAfterRow = {
40
+ ses: number;
41
+ idx: number;
42
+ signature: CojsonInternalTypes.Signature;
43
+ };
44
+
45
+ export class IDBStorage {
46
+ db: IDBDatabase;
47
+ fromLocalNode!: ReadableStreamDefaultReader<SyncMessage>;
48
+ toLocalNode: WritableStreamDefaultWriter<SyncMessage>;
49
+
50
+ constructor(
51
+ db: IDBDatabase,
52
+ fromLocalNode: ReadableStream<SyncMessage>,
53
+ toLocalNode: WritableStream<SyncMessage>
54
+ ) {
55
+ this.db = db;
56
+ this.fromLocalNode = fromLocalNode.getReader();
57
+ this.toLocalNode = toLocalNode.getWriter();
58
+
59
+ (async () => {
60
+ let done = false;
61
+ while (!done) {
62
+ const result = await this.fromLocalNode.read();
63
+ done = result.done;
64
+
65
+ if (result.value) {
66
+ await this.handleSyncMessage(result.value);
67
+ }
68
+ }
69
+ })();
70
+ }
71
+
72
+ static async asPeer(
73
+ {
74
+ trace,
75
+ localNodeName = "local",
76
+ }: { trace?: boolean; localNodeName?: string } | undefined = {
77
+ localNodeName: "local",
78
+ }
79
+ ): Promise<Peer> {
80
+ const [localNodeAsPeer, storageAsPeer] = cojsonInternals.connectedPeers(
81
+ localNodeName,
82
+ "storage",
83
+ { peer1role: "client", peer2role: "server", trace }
84
+ );
85
+
86
+ await IDBStorage.open(
87
+ localNodeAsPeer.incoming,
88
+ localNodeAsPeer.outgoing
89
+ );
90
+
91
+ return storageAsPeer;
92
+ }
93
+
94
+ static async open(
95
+ fromLocalNode: ReadableStream<SyncMessage>,
96
+ toLocalNode: WritableStream<SyncMessage>
97
+ ) {
98
+ const dbPromise = new Promise<IDBDatabase>((resolve, reject) => {
99
+ const request = indexedDB.open("jazz-storage", 4);
100
+ request.onerror = () => {
101
+ reject(request.error);
102
+ };
103
+ request.onsuccess = () => {
104
+ resolve(request.result);
105
+ };
106
+ request.onupgradeneeded = async (ev) => {
107
+ const db = request.result;
108
+ if (ev.oldVersion === 0) {
109
+ const coValues = db.createObjectStore("coValues", {
110
+ autoIncrement: true,
111
+ keyPath: "rowID",
112
+ });
113
+
114
+ coValues.createIndex("coValuesById", "id", {
115
+ unique: true,
116
+ });
117
+
118
+ const sessions = db.createObjectStore("sessions", {
119
+ autoIncrement: true,
120
+ keyPath: "rowID",
121
+ });
122
+
123
+ sessions.createIndex("sessionsByCoValue", "coValue");
124
+ sessions.createIndex(
125
+ "uniqueSessions",
126
+ ["coValue", "sessionID"],
127
+ {
128
+ unique: true,
129
+ }
130
+ );
131
+
132
+ db.createObjectStore("transactions", {
133
+ keyPath: ["ses", "idx"],
134
+ });
135
+ }
136
+ if (ev.oldVersion <= 1) {
137
+ db.createObjectStore("signatureAfter", {
138
+ keyPath: ["ses", "idx"],
139
+ });
140
+ }
141
+ if (ev.oldVersion !== 0 && ev.oldVersion <= 3) {
142
+ // fix embarrassing off-by-one error for transaction indices
143
+ console.log("Migration: fixing off-by-one error");
144
+ const transaction = (ev.target as unknown as {transaction: IDBTransaction}).transaction;
145
+
146
+ const txsStore = transaction.objectStore("transactions");
147
+ const txs = await promised(txsStore.getAll());
148
+
149
+ for (const tx of txs) {
150
+ await promised(txsStore.delete([tx.ses, tx.idx]));
151
+ tx.idx -= 1;
152
+ await promised(txsStore.add(tx));
153
+ }
154
+ console.log("Migration: fixing off-by-one error - done");
155
+ }
156
+ };
157
+ });
158
+
159
+ return new IDBStorage(await dbPromise, fromLocalNode, toLocalNode);
160
+ }
161
+
162
+ async handleSyncMessage(msg: SyncMessage) {
163
+ switch (msg.action) {
164
+ case "load":
165
+ await this.handleLoad(msg);
166
+ break;
167
+ case "content":
168
+ await this.handleContent(msg);
169
+ break;
170
+ case "known":
171
+ await this.handleKnown(msg);
172
+ break;
173
+ case "done":
174
+ await this.handleDone(msg);
175
+ break;
176
+ }
177
+ }
178
+
179
+ async sendNewContentAfter(
180
+ theirKnown: CojsonInternalTypes.CoValueKnownState,
181
+ {
182
+ coValues,
183
+ sessions,
184
+ transactions,
185
+ signatureAfter,
186
+ }: {
187
+ coValues: IDBObjectStore;
188
+ sessions: IDBObjectStore;
189
+ transactions: IDBObjectStore;
190
+ signatureAfter: IDBObjectStore;
191
+ },
192
+ asDependencyOf?: CojsonInternalTypes.RawCoID
193
+ ) {
194
+ const coValueRow = await promised<StoredCoValueRow | undefined>(
195
+ coValues.index("coValuesById").get(theirKnown.id)
196
+ );
197
+
198
+ const allOurSessions = coValueRow
199
+ ? await promised<StoredSessionRow[]>(
200
+ sessions.index("sessionsByCoValue").getAll(coValueRow.rowID)
201
+ )
202
+ : [];
203
+
204
+ const ourKnown: CojsonInternalTypes.CoValueKnownState = {
205
+ id: theirKnown.id,
206
+ header: !!coValueRow,
207
+ sessions: {},
208
+ };
209
+
210
+ const newContentPieces: CojsonInternalTypes.NewContentMessage[] = [
211
+ {
212
+ action: "content",
213
+ id: theirKnown.id,
214
+ header: theirKnown.header ? undefined : coValueRow?.header,
215
+ new: {},
216
+ },
217
+ ];
218
+
219
+ for (const sessionRow of allOurSessions) {
220
+ ourKnown.sessions[sessionRow.sessionID] = sessionRow.lastIdx;
221
+
222
+ if (
223
+ sessionRow.lastIdx >
224
+ (theirKnown.sessions[sessionRow.sessionID] || 0)
225
+ ) {
226
+ const firstNewTxIdx =
227
+ theirKnown.sessions[sessionRow.sessionID] || 0;
228
+
229
+ const signaturesAndIdxs = await promised<SignatureAfterRow[]>(
230
+ signatureAfter.getAll(
231
+ IDBKeyRange.bound(
232
+ [sessionRow.rowID, firstNewTxIdx],
233
+ [sessionRow.rowID, Infinity]
234
+ )
235
+ )
236
+ );
237
+
238
+ // console.log(
239
+ // theirKnown.id,
240
+ // "signaturesAndIdxs",
241
+ // JSON.stringify(signaturesAndIdxs)
242
+ // );
243
+
244
+ const newTxInSession = await promised<TransactionRow[]>(
245
+ transactions.getAll(
246
+ IDBKeyRange.bound(
247
+ [sessionRow.rowID, firstNewTxIdx],
248
+ [sessionRow.rowID, Infinity]
249
+ )
250
+ )
251
+ );
252
+
253
+ let idx = firstNewTxIdx;
254
+
255
+ // console.log(
256
+ // theirKnown.id,
257
+ // "newTxInSession",
258
+ // newTxInSession.length
259
+ // );
260
+
261
+ for (const tx of newTxInSession) {
262
+ let sessionEntry =
263
+ newContentPieces[newContentPieces.length - 1]!.new[
264
+ sessionRow.sessionID
265
+ ];
266
+ if (!sessionEntry) {
267
+ sessionEntry = {
268
+ after: idx,
269
+ lastSignature: "WILL_BE_REPLACED" as CojsonInternalTypes.Signature,
270
+ newTransactions: [],
271
+ };
272
+ newContentPieces[newContentPieces.length - 1]!.new[
273
+ sessionRow.sessionID
274
+ ] = sessionEntry;
275
+ }
276
+
277
+ sessionEntry.newTransactions.push(tx.tx);
278
+
279
+ if (
280
+ signaturesAndIdxs[0] &&
281
+ idx === signaturesAndIdxs[0].idx
282
+ ) {
283
+ sessionEntry.lastSignature =
284
+ signaturesAndIdxs[0].signature;
285
+ signaturesAndIdxs.shift();
286
+ newContentPieces.push({
287
+ action: "content",
288
+ id: theirKnown.id,
289
+ new: {},
290
+ });
291
+ } else if (
292
+ idx ===
293
+ firstNewTxIdx + newTxInSession.length - 1
294
+ ) {
295
+ sessionEntry.lastSignature = sessionRow.lastSignature;
296
+ }
297
+ idx += 1;
298
+ }
299
+ }
300
+ }
301
+
302
+ const dependedOnCoValues =
303
+ coValueRow?.header.ruleset.type === "group"
304
+ ? newContentPieces
305
+ .flatMap((piece) => Object.values(piece.new))
306
+ .flatMap((sessionEntry) =>
307
+ sessionEntry.newTransactions.flatMap((tx) => {
308
+ if (tx.privacy !== "trusting") return [];
309
+ // TODO: avoid parse here?
310
+ return cojsonInternals
311
+ .parseJSON(tx.changes)
312
+ .map(
313
+ (change) =>
314
+ change &&
315
+ typeof change === "object" &&
316
+ "op" in change &&
317
+ change.op === "set" &&
318
+ "key" in change &&
319
+ change.key
320
+ )
321
+ .filter(
322
+ (
323
+ key
324
+ ): key is CojsonInternalTypes.RawCoID =>
325
+ typeof key === "string" &&
326
+ key.startsWith("co_")
327
+ );
328
+ })
329
+ )
330
+ : coValueRow?.header.ruleset.type === "ownedByGroup"
331
+ ? [coValueRow?.header.ruleset.group]
332
+ : [];
333
+
334
+ for (const dependedOnCoValue of dependedOnCoValues) {
335
+ await this.sendNewContentAfter(
336
+ { id: dependedOnCoValue, header: false, sessions: {} },
337
+ { coValues, sessions, transactions, signatureAfter },
338
+ asDependencyOf || theirKnown.id
339
+ );
340
+ }
341
+
342
+ await this.toLocalNode.write({
343
+ action: "known",
344
+ ...ourKnown,
345
+ asDependencyOf,
346
+ });
347
+
348
+ const nonEmptyNewContentPieces = newContentPieces.filter(
349
+ (piece) => piece.header || Object.keys(piece.new).length > 0
350
+ );
351
+
352
+ // console.log(theirKnown.id, nonEmptyNewContentPieces);
353
+
354
+ for (const piece of nonEmptyNewContentPieces) {
355
+ await this.toLocalNode.write(piece);
356
+ await new Promise((resolve) => setTimeout(resolve, 0));
357
+ }
358
+ }
359
+
360
+ handleLoad(msg: CojsonInternalTypes.LoadMessage) {
361
+ return this.sendNewContentAfter(msg, this.inTransaction("readonly"));
362
+ }
363
+
364
+ async handleContent(msg: CojsonInternalTypes.NewContentMessage) {
365
+ const { coValues, sessions, transactions, signatureAfter } =
366
+ this.inTransaction("readwrite");
367
+
368
+ let storedCoValueRowID = (
369
+ await promised<StoredCoValueRow | undefined>(
370
+ coValues.index("coValuesById").get(msg.id)
371
+ )
372
+ )?.rowID;
373
+
374
+ if (storedCoValueRowID === undefined) {
375
+ const header = msg.header;
376
+ if (!header) {
377
+ console.error("Expected to be sent header first");
378
+ await this.toLocalNode.write({
379
+ action: "known",
380
+ id: msg.id,
381
+ header: false,
382
+ sessions: {},
383
+ isCorrection: true,
384
+ });
385
+ return;
386
+ }
387
+
388
+ storedCoValueRowID = (await promised<IDBValidKey>(
389
+ coValues.put({
390
+ id: msg.id,
391
+ header: header,
392
+ } satisfies CoValueRow)
393
+ )) as number;
394
+ }
395
+
396
+ const allOurSessions = await new Promise<{
397
+ [sessionID: SessionID]: StoredSessionRow;
398
+ }>((resolve) => {
399
+ const allOurSessionsRequest = sessions
400
+ .index("sessionsByCoValue")
401
+ .getAll(storedCoValueRowID);
402
+
403
+ allOurSessionsRequest.onsuccess = () => {
404
+ resolve(
405
+ Object.fromEntries(
406
+ (
407
+ allOurSessionsRequest.result as StoredSessionRow[]
408
+ ).map((row) => [row.sessionID, row])
409
+ )
410
+ );
411
+ };
412
+ });
413
+
414
+ const ourKnown: CojsonInternalTypes.CoValueKnownState = {
415
+ id: msg.id,
416
+ header: true,
417
+ sessions: {},
418
+ };
419
+ let invalidAssumptions = false;
420
+
421
+ for (const sessionID of Object.keys(msg.new) as SessionID[]) {
422
+ const sessionRow = allOurSessions[sessionID];
423
+ if (sessionRow) {
424
+ ourKnown.sessions[sessionRow.sessionID] = sessionRow.lastIdx;
425
+ }
426
+
427
+ if ((sessionRow?.lastIdx || 0) < (msg.new[sessionID]?.after || 0)) {
428
+ invalidAssumptions = true;
429
+ } else {
430
+ const newTransactions =
431
+ msg.new[sessionID]?.newTransactions || [];
432
+
433
+ const actuallyNewOffset =
434
+ (sessionRow?.lastIdx || 0) -
435
+ (msg.new[sessionID]?.after || 0);
436
+
437
+ const actuallyNewTransactions =
438
+ newTransactions.slice(actuallyNewOffset);
439
+
440
+ let newBytesSinceLastSignature =
441
+ (sessionRow?.bytesSinceLastSignature || 0) +
442
+ actuallyNewTransactions.reduce(
443
+ (sum, tx) =>
444
+ sum +
445
+ (tx.privacy === "private"
446
+ ? tx.encryptedChanges.length
447
+ : tx.changes.length),
448
+ 0
449
+ );
450
+
451
+ const newLastIdx =
452
+ (sessionRow?.lastIdx || 0) + actuallyNewTransactions.length;
453
+
454
+ let shouldWriteSignature = false;
455
+
456
+ if (newBytesSinceLastSignature > MAX_RECOMMENDED_TX_SIZE) {
457
+ shouldWriteSignature = true;
458
+ newBytesSinceLastSignature = 0;
459
+ }
460
+
461
+ let nextIdx = sessionRow?.lastIdx || 0;
462
+
463
+ const sessionUpdate = {
464
+ coValue: storedCoValueRowID,
465
+ sessionID: sessionID,
466
+ lastIdx: newLastIdx,
467
+ lastSignature: msg.new[sessionID]!.lastSignature,
468
+ bytesSinceLastSignature: newBytesSinceLastSignature,
469
+ };
470
+
471
+ const sessionRowID = (await promised(
472
+ sessions.put(
473
+ sessionRow?.rowID
474
+ ? {
475
+ rowID: sessionRow.rowID,
476
+ ...sessionUpdate,
477
+ }
478
+ : sessionUpdate
479
+ )
480
+ )) as number;
481
+
482
+ if (shouldWriteSignature) {
483
+ await promised(
484
+ signatureAfter.put({
485
+ ses: sessionRowID,
486
+ // TODO: newLastIdx is a misnomer, it's actually more like nextIdx or length
487
+ idx: newLastIdx - 1,
488
+ signature: msg.new[sessionID]!.lastSignature,
489
+ } satisfies SignatureAfterRow)
490
+ );
491
+ }
492
+
493
+ for (const newTransaction of actuallyNewTransactions) {
494
+ await promised(
495
+ transactions.add({
496
+ ses: sessionRowID,
497
+ idx: nextIdx,
498
+ tx: newTransaction,
499
+ } satisfies TransactionRow)
500
+ );
501
+ nextIdx++;
502
+ }
503
+ }
504
+ }
505
+
506
+ if (invalidAssumptions) {
507
+ await this.toLocalNode.write({
508
+ action: "known",
509
+ ...ourKnown,
510
+ isCorrection: invalidAssumptions,
511
+ });
512
+ }
513
+ }
514
+
515
+ handleKnown(msg: CojsonInternalTypes.KnownStateMessage) {
516
+ return this.sendNewContentAfter(msg, this.inTransaction("readonly"));
517
+ }
518
+
519
+ handleDone(_msg: CojsonInternalTypes.DoneMessage) {}
520
+
521
+ inTransaction(mode: "readwrite" | "readonly"): {
522
+ coValues: IDBObjectStore;
523
+ sessions: IDBObjectStore;
524
+ transactions: IDBObjectStore;
525
+ signatureAfter: IDBObjectStore;
526
+ } {
527
+ const tx = this.db.transaction(
528
+ ["coValues", "sessions", "transactions", "signatureAfter"],
529
+ mode
530
+ );
531
+
532
+ tx.onerror = (event) => {
533
+ const target = event.target as unknown as {
534
+ error: DOMException;
535
+ source?: { name: string };
536
+ } | null;
537
+ throw new Error(
538
+ `Error in transaction (${target?.source?.name}): ${target?.error}`,
539
+ { cause: target?.error }
540
+ );
541
+ };
542
+ const coValues = tx.objectStore("coValues");
543
+ const sessions = tx.objectStore("sessions");
544
+ const transactions = tx.objectStore("transactions");
545
+ const signatureAfter = tx.objectStore("signatureAfter");
546
+
547
+ return { coValues, sessions, transactions, signatureAfter };
548
+ }
549
+ }
550
+
551
+ function promised<T>(request: IDBRequest<T>): Promise<T> {
552
+ return new Promise<T>((resolve, reject) => {
553
+ request.onsuccess = () => {
554
+ resolve(request.result);
555
+ };
556
+ request.onerror = () => {
557
+ reject(request.error);
558
+ };
559
+ });
560
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "lib": ["ESNext", "DOM"],
4
+ "module": "esnext",
5
+ "target": "ES2020",
6
+ "moduleResolution": "bundler",
7
+ "moduleDetection": "force",
8
+ "strict": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "noUncheckedIndexedAccess": true,
12
+ "esModuleInterop": true,
13
+ },
14
+ "include": ["./src/**/*"],
15
+ }