cojson-storage-indexeddb 0.8.32 → 0.8.35
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/CHANGELOG.md +19 -0
- package/dist/idbClient.js +98 -0
- package/dist/idbClient.js.map +1 -0
- package/dist/idbNode.js +78 -0
- package/dist/idbNode.js.map +1 -0
- package/dist/index.js +1 -410
- package/dist/index.js.map +1 -1
- package/dist/syncManager.js +198 -0
- package/dist/syncManager.js.map +1 -0
- package/dist/syncUtils.js +95 -0
- package/dist/syncUtils.js.map +1 -0
- package/dist/tests/fixtureMessages.js +83 -0
- package/dist/tests/fixtureMessages.js.map +1 -0
- package/dist/tests/getDependedOnCoValues.test.js +149 -0
- package/dist/tests/getDependedOnCoValues.test.js.map +1 -0
- package/dist/tests/{index.test.js → idbNode.test.js} +3 -3
- package/dist/tests/idbNode.test.js.map +1 -0
- package/dist/tests/syncManager.test.js +251 -0
- package/dist/tests/syncManager.test.js.map +1 -0
- package/package.json +2 -2
- package/src/idbClient.ts +237 -0
- package/src/idbNode.ts +123 -0
- package/src/index.ts +1 -733
- package/src/syncManager.ts +336 -0
- package/src/syncUtils.ts +146 -0
- package/src/tests/fixtureMessages.ts +95 -0
- package/src/tests/getDependedOnCoValues.test.ts +179 -0
- package/src/tests/{index.test.ts → idbNode.test.ts} +2 -2
- package/src/tests/syncManager.test.ts +337 -0
- package/dist/tests/index.test.js.map +0 -1
package/src/index.ts
CHANGED
|
@@ -1,733 +1 @@
|
|
|
1
|
-
|
|
2
|
-
CojsonInternalTypes,
|
|
3
|
-
IncomingSyncStream,
|
|
4
|
-
MAX_RECOMMENDED_TX_SIZE,
|
|
5
|
-
OutgoingSyncQueue,
|
|
6
|
-
Peer,
|
|
7
|
-
RawAccountID,
|
|
8
|
-
SessionID,
|
|
9
|
-
SyncMessage,
|
|
10
|
-
cojsonInternals,
|
|
11
|
-
} from "cojson";
|
|
12
|
-
import { SyncPromise } from "./syncPromises.js";
|
|
13
|
-
|
|
14
|
-
type CoValueRow = {
|
|
15
|
-
id: CojsonInternalTypes.RawCoID;
|
|
16
|
-
header: CojsonInternalTypes.CoValueHeader;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
type StoredCoValueRow = CoValueRow & { rowID: number };
|
|
20
|
-
|
|
21
|
-
type SessionRow = {
|
|
22
|
-
coValue: number;
|
|
23
|
-
sessionID: SessionID;
|
|
24
|
-
lastIdx: number;
|
|
25
|
-
lastSignature: CojsonInternalTypes.Signature;
|
|
26
|
-
bytesSinceLastSignature?: number;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
type StoredSessionRow = SessionRow & { rowID: number };
|
|
30
|
-
|
|
31
|
-
type TransactionRow = {
|
|
32
|
-
ses: number;
|
|
33
|
-
idx: number;
|
|
34
|
-
tx: CojsonInternalTypes.Transaction;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
type SignatureAfterRow = {
|
|
38
|
-
ses: number;
|
|
39
|
-
idx: number;
|
|
40
|
-
signature: CojsonInternalTypes.Signature;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
export class IDBStorage {
|
|
44
|
-
db: IDBDatabase;
|
|
45
|
-
toLocalNode: OutgoingSyncQueue;
|
|
46
|
-
|
|
47
|
-
constructor(
|
|
48
|
-
db: IDBDatabase,
|
|
49
|
-
fromLocalNode: IncomingSyncStream,
|
|
50
|
-
toLocalNode: OutgoingSyncQueue,
|
|
51
|
-
) {
|
|
52
|
-
this.db = db;
|
|
53
|
-
this.toLocalNode = toLocalNode;
|
|
54
|
-
|
|
55
|
-
const processMessages = async () => {
|
|
56
|
-
for await (const msg of fromLocalNode) {
|
|
57
|
-
try {
|
|
58
|
-
if (msg === "Disconnected" || msg === "PingTimeout") {
|
|
59
|
-
throw new Error("Unexpected Disconnected message");
|
|
60
|
-
}
|
|
61
|
-
await this.handleSyncMessage(msg);
|
|
62
|
-
} catch (e) {
|
|
63
|
-
console.error(
|
|
64
|
-
new Error(
|
|
65
|
-
`Error reading from localNode, handling msg\n\n${JSON.stringify(
|
|
66
|
-
msg,
|
|
67
|
-
(k, v) =>
|
|
68
|
-
k === "changes" || k === "encryptedChanges"
|
|
69
|
-
? v.slice(0, 20) + "..."
|
|
70
|
-
: v,
|
|
71
|
-
)}`,
|
|
72
|
-
{ cause: e },
|
|
73
|
-
),
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
processMessages().catch((e) =>
|
|
80
|
-
console.error("Error in processMessages in IndexedDB", e),
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
static async asPeer(
|
|
85
|
-
{
|
|
86
|
-
trace,
|
|
87
|
-
localNodeName = "local",
|
|
88
|
-
}: { trace?: boolean; localNodeName?: string } | undefined = {
|
|
89
|
-
localNodeName: "local",
|
|
90
|
-
},
|
|
91
|
-
): Promise<Peer> {
|
|
92
|
-
const [localNodeAsPeer, storageAsPeer] = cojsonInternals.connectedPeers(
|
|
93
|
-
localNodeName,
|
|
94
|
-
"indexedDB",
|
|
95
|
-
{
|
|
96
|
-
peer1role: "client",
|
|
97
|
-
peer2role: "storage",
|
|
98
|
-
trace,
|
|
99
|
-
crashOnClose: true,
|
|
100
|
-
},
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
await IDBStorage.open(localNodeAsPeer.incoming, localNodeAsPeer.outgoing);
|
|
104
|
-
|
|
105
|
-
return { ...storageAsPeer, priority: 100 };
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
static async open(
|
|
109
|
-
fromLocalNode: IncomingSyncStream,
|
|
110
|
-
toLocalNode: OutgoingSyncQueue,
|
|
111
|
-
) {
|
|
112
|
-
const dbPromise = new Promise<IDBDatabase>((resolve, reject) => {
|
|
113
|
-
const request = indexedDB.open("jazz-storage", 4);
|
|
114
|
-
request.onerror = () => {
|
|
115
|
-
reject(request.error);
|
|
116
|
-
};
|
|
117
|
-
request.onsuccess = () => {
|
|
118
|
-
resolve(request.result);
|
|
119
|
-
};
|
|
120
|
-
request.onupgradeneeded = async (ev) => {
|
|
121
|
-
const db = request.result;
|
|
122
|
-
if (ev.oldVersion === 0) {
|
|
123
|
-
const coValues = db.createObjectStore("coValues", {
|
|
124
|
-
autoIncrement: true,
|
|
125
|
-
keyPath: "rowID",
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
coValues.createIndex("coValuesById", "id", {
|
|
129
|
-
unique: true,
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
const sessions = db.createObjectStore("sessions", {
|
|
133
|
-
autoIncrement: true,
|
|
134
|
-
keyPath: "rowID",
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
sessions.createIndex("sessionsByCoValue", "coValue");
|
|
138
|
-
sessions.createIndex("uniqueSessions", ["coValue", "sessionID"], {
|
|
139
|
-
unique: true,
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
db.createObjectStore("transactions", {
|
|
143
|
-
keyPath: ["ses", "idx"],
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
if (ev.oldVersion <= 1) {
|
|
147
|
-
db.createObjectStore("signatureAfter", {
|
|
148
|
-
keyPath: ["ses", "idx"],
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
};
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
return new IDBStorage(await dbPromise, fromLocalNode, toLocalNode);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
async handleSyncMessage(msg: SyncMessage) {
|
|
158
|
-
switch (msg.action) {
|
|
159
|
-
case "load":
|
|
160
|
-
await this.handleLoad(msg);
|
|
161
|
-
break;
|
|
162
|
-
case "content":
|
|
163
|
-
await this.handleContent(msg);
|
|
164
|
-
break;
|
|
165
|
-
case "known":
|
|
166
|
-
await this.handleKnown(msg);
|
|
167
|
-
break;
|
|
168
|
-
case "done":
|
|
169
|
-
await this.handleDone(msg);
|
|
170
|
-
break;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
currentTx:
|
|
175
|
-
| {
|
|
176
|
-
id: number;
|
|
177
|
-
tx: IDBTransaction;
|
|
178
|
-
stores: {
|
|
179
|
-
coValues: IDBObjectStore;
|
|
180
|
-
sessions: IDBObjectStore;
|
|
181
|
-
transactions: IDBObjectStore;
|
|
182
|
-
signatureAfter: IDBObjectStore;
|
|
183
|
-
};
|
|
184
|
-
startedAt: number;
|
|
185
|
-
pendingRequests: ((txEntry: {
|
|
186
|
-
stores: {
|
|
187
|
-
coValues: IDBObjectStore;
|
|
188
|
-
sessions: IDBObjectStore;
|
|
189
|
-
transactions: IDBObjectStore;
|
|
190
|
-
signatureAfter: IDBObjectStore;
|
|
191
|
-
};
|
|
192
|
-
}) => void)[];
|
|
193
|
-
}
|
|
194
|
-
| undefined;
|
|
195
|
-
currentTxID = 0;
|
|
196
|
-
|
|
197
|
-
makeRequest<T>(
|
|
198
|
-
handler: (stores: {
|
|
199
|
-
coValues: IDBObjectStore;
|
|
200
|
-
sessions: IDBObjectStore;
|
|
201
|
-
transactions: IDBObjectStore;
|
|
202
|
-
signatureAfter: IDBObjectStore;
|
|
203
|
-
}) => IDBRequest,
|
|
204
|
-
): SyncPromise<T> {
|
|
205
|
-
return new SyncPromise((resolve, reject) => {
|
|
206
|
-
let txEntry = this.currentTx;
|
|
207
|
-
|
|
208
|
-
const requestEntry = ({
|
|
209
|
-
stores,
|
|
210
|
-
}: {
|
|
211
|
-
stores: {
|
|
212
|
-
coValues: IDBObjectStore;
|
|
213
|
-
sessions: IDBObjectStore;
|
|
214
|
-
transactions: IDBObjectStore;
|
|
215
|
-
signatureAfter: IDBObjectStore;
|
|
216
|
-
};
|
|
217
|
-
}) => {
|
|
218
|
-
const request = handler(stores);
|
|
219
|
-
request.onerror = () => {
|
|
220
|
-
console.error("Error in request", request.error);
|
|
221
|
-
this.currentTx = undefined;
|
|
222
|
-
reject(request.error);
|
|
223
|
-
// TODO: recover pending requests in new tx
|
|
224
|
-
};
|
|
225
|
-
request.onsuccess = () => {
|
|
226
|
-
const value = request.result as T;
|
|
227
|
-
resolve(value);
|
|
228
|
-
|
|
229
|
-
const next = txEntry!.pendingRequests.shift();
|
|
230
|
-
|
|
231
|
-
if (next) {
|
|
232
|
-
next({ stores });
|
|
233
|
-
} else {
|
|
234
|
-
if (this.currentTx === txEntry) {
|
|
235
|
-
this.currentTx = undefined;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
};
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
if (!txEntry || performance.now() - txEntry.startedAt > 20) {
|
|
242
|
-
const tx = this.db.transaction(
|
|
243
|
-
["coValues", "sessions", "transactions", "signatureAfter"],
|
|
244
|
-
"readwrite",
|
|
245
|
-
);
|
|
246
|
-
txEntry = {
|
|
247
|
-
id: this.currentTxID++,
|
|
248
|
-
tx,
|
|
249
|
-
stores: {
|
|
250
|
-
coValues: tx.objectStore("coValues"),
|
|
251
|
-
sessions: tx.objectStore("sessions"),
|
|
252
|
-
transactions: tx.objectStore("transactions"),
|
|
253
|
-
signatureAfter: tx.objectStore("signatureAfter"),
|
|
254
|
-
},
|
|
255
|
-
startedAt: performance.now(),
|
|
256
|
-
pendingRequests: [],
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
// console.time("IndexedDB TX" + txEntry.id);
|
|
260
|
-
|
|
261
|
-
// txEntry.tx.oncomplete = () => {
|
|
262
|
-
// console.timeEnd("IndexedDB TX" + txEntry!.id);
|
|
263
|
-
// };
|
|
264
|
-
|
|
265
|
-
this.currentTx = txEntry;
|
|
266
|
-
|
|
267
|
-
requestEntry(txEntry);
|
|
268
|
-
} else {
|
|
269
|
-
txEntry.pendingRequests.push(requestEntry);
|
|
270
|
-
// console.log(
|
|
271
|
-
// "Queued request in TX " + txEntry.id,
|
|
272
|
-
// txEntry.pendingRequests.length
|
|
273
|
-
// );
|
|
274
|
-
}
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
sendNewContentAfter(
|
|
279
|
-
theirKnown: CojsonInternalTypes.CoValueKnownState,
|
|
280
|
-
asDependencyOf?: CojsonInternalTypes.RawCoID,
|
|
281
|
-
): SyncPromise<void> {
|
|
282
|
-
return this.makeRequest<StoredCoValueRow | undefined>(({ coValues }) =>
|
|
283
|
-
coValues.index("coValuesById").get(theirKnown.id),
|
|
284
|
-
)
|
|
285
|
-
.then((coValueRow) => {
|
|
286
|
-
return (
|
|
287
|
-
coValueRow
|
|
288
|
-
? this.makeRequest<StoredSessionRow[]>(({ sessions }) =>
|
|
289
|
-
sessions.index("sessionsByCoValue").getAll(coValueRow.rowID),
|
|
290
|
-
)
|
|
291
|
-
: SyncPromise.resolve([])
|
|
292
|
-
).then((allOurSessions) => {
|
|
293
|
-
const ourKnown: CojsonInternalTypes.CoValueKnownState = {
|
|
294
|
-
id: theirKnown.id,
|
|
295
|
-
header: !!coValueRow,
|
|
296
|
-
sessions: {},
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
const newContentPieces: CojsonInternalTypes.NewContentMessage[] = [
|
|
300
|
-
{
|
|
301
|
-
action: "content",
|
|
302
|
-
id: theirKnown.id,
|
|
303
|
-
header: theirKnown.header ? undefined : coValueRow?.header,
|
|
304
|
-
new: {},
|
|
305
|
-
priority: cojsonInternals.getPriorityFromHeader(
|
|
306
|
-
coValueRow?.header,
|
|
307
|
-
),
|
|
308
|
-
},
|
|
309
|
-
];
|
|
310
|
-
|
|
311
|
-
return SyncPromise.all(
|
|
312
|
-
allOurSessions.map((sessionRow) => {
|
|
313
|
-
ourKnown.sessions[sessionRow.sessionID] = sessionRow.lastIdx;
|
|
314
|
-
|
|
315
|
-
if (
|
|
316
|
-
sessionRow.lastIdx >
|
|
317
|
-
(theirKnown.sessions[sessionRow.sessionID] || 0)
|
|
318
|
-
) {
|
|
319
|
-
const firstNewTxIdx =
|
|
320
|
-
theirKnown.sessions[sessionRow.sessionID] || 0;
|
|
321
|
-
|
|
322
|
-
return this.makeRequest<SignatureAfterRow[]>(
|
|
323
|
-
({ signatureAfter }) =>
|
|
324
|
-
signatureAfter.getAll(
|
|
325
|
-
IDBKeyRange.bound(
|
|
326
|
-
[sessionRow.rowID, firstNewTxIdx],
|
|
327
|
-
[sessionRow.rowID, Infinity],
|
|
328
|
-
),
|
|
329
|
-
),
|
|
330
|
-
).then((signaturesAndIdxs) => {
|
|
331
|
-
// console.log(
|
|
332
|
-
// theirKnown.id,
|
|
333
|
-
// "signaturesAndIdxs",
|
|
334
|
-
// JSON.stringify(signaturesAndIdxs)
|
|
335
|
-
// );
|
|
336
|
-
|
|
337
|
-
return this.makeRequest<TransactionRow[]>(
|
|
338
|
-
({ transactions }) =>
|
|
339
|
-
transactions.getAll(
|
|
340
|
-
IDBKeyRange.bound(
|
|
341
|
-
[sessionRow.rowID, firstNewTxIdx],
|
|
342
|
-
[sessionRow.rowID, Infinity],
|
|
343
|
-
),
|
|
344
|
-
),
|
|
345
|
-
).then((newTxsInSession) => {
|
|
346
|
-
collectNewTxs(
|
|
347
|
-
newTxsInSession,
|
|
348
|
-
newContentPieces,
|
|
349
|
-
sessionRow,
|
|
350
|
-
signaturesAndIdxs,
|
|
351
|
-
theirKnown,
|
|
352
|
-
firstNewTxIdx,
|
|
353
|
-
);
|
|
354
|
-
});
|
|
355
|
-
});
|
|
356
|
-
} else {
|
|
357
|
-
return SyncPromise.resolve();
|
|
358
|
-
}
|
|
359
|
-
}),
|
|
360
|
-
).then(() => {
|
|
361
|
-
const dependedOnCoValues = getDependedOnCoValues(
|
|
362
|
-
coValueRow,
|
|
363
|
-
newContentPieces,
|
|
364
|
-
theirKnown,
|
|
365
|
-
);
|
|
366
|
-
|
|
367
|
-
return SyncPromise.all(
|
|
368
|
-
dependedOnCoValues.map((dependedOnCoValue) =>
|
|
369
|
-
this.sendNewContentAfter(
|
|
370
|
-
{
|
|
371
|
-
id: dependedOnCoValue,
|
|
372
|
-
header: false,
|
|
373
|
-
sessions: {},
|
|
374
|
-
},
|
|
375
|
-
asDependencyOf || theirKnown.id,
|
|
376
|
-
),
|
|
377
|
-
),
|
|
378
|
-
).then(() => {
|
|
379
|
-
// we're done with IndexedDB stuff here so can use native Promises again
|
|
380
|
-
setTimeout(() => {
|
|
381
|
-
this.toLocalNode
|
|
382
|
-
.push({
|
|
383
|
-
action: "known",
|
|
384
|
-
...ourKnown,
|
|
385
|
-
asDependencyOf,
|
|
386
|
-
})
|
|
387
|
-
.catch((e) => console.error("Error sending known state", e));
|
|
388
|
-
|
|
389
|
-
const nonEmptyNewContentPieces = newContentPieces.filter(
|
|
390
|
-
(piece) => piece.header || Object.keys(piece.new).length > 0,
|
|
391
|
-
);
|
|
392
|
-
|
|
393
|
-
// console.log(theirKnown.id, nonEmptyNewContentPieces);
|
|
394
|
-
|
|
395
|
-
for (const piece of nonEmptyNewContentPieces) {
|
|
396
|
-
this.toLocalNode
|
|
397
|
-
.push(piece)
|
|
398
|
-
.catch((e) =>
|
|
399
|
-
console.error("Error sending new content piece", e),
|
|
400
|
-
);
|
|
401
|
-
}
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
return Promise.resolve();
|
|
405
|
-
});
|
|
406
|
-
});
|
|
407
|
-
});
|
|
408
|
-
})
|
|
409
|
-
.then(() => {});
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
handleLoad(msg: CojsonInternalTypes.LoadMessage) {
|
|
413
|
-
return this.sendNewContentAfter(msg);
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
handleContent(msg: CojsonInternalTypes.NewContentMessage): SyncPromise<void> {
|
|
417
|
-
return this.makeRequest<StoredCoValueRow | undefined>(({ coValues }) =>
|
|
418
|
-
coValues.index("coValuesById").get(msg.id),
|
|
419
|
-
)
|
|
420
|
-
.then((coValueRow) => {
|
|
421
|
-
if (coValueRow?.rowID === undefined) {
|
|
422
|
-
const header = msg.header;
|
|
423
|
-
if (!header) {
|
|
424
|
-
console.error("Expected to be sent header first");
|
|
425
|
-
this.toLocalNode
|
|
426
|
-
.push({
|
|
427
|
-
action: "known",
|
|
428
|
-
id: msg.id,
|
|
429
|
-
header: false,
|
|
430
|
-
sessions: {},
|
|
431
|
-
isCorrection: true,
|
|
432
|
-
})
|
|
433
|
-
.catch((e) => console.error("Error sending known state", e));
|
|
434
|
-
return SyncPromise.resolve();
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
return this.makeRequest<IDBValidKey>(({ coValues }) =>
|
|
438
|
-
coValues.put({
|
|
439
|
-
id: msg.id,
|
|
440
|
-
header: header,
|
|
441
|
-
} satisfies CoValueRow),
|
|
442
|
-
) as SyncPromise<number>;
|
|
443
|
-
} else {
|
|
444
|
-
return SyncPromise.resolve(coValueRow.rowID);
|
|
445
|
-
}
|
|
446
|
-
})
|
|
447
|
-
.then((storedCoValueRowID: number) => {
|
|
448
|
-
void this.makeRequest<StoredSessionRow[]>(({ sessions }) =>
|
|
449
|
-
sessions.index("sessionsByCoValue").getAll(storedCoValueRowID),
|
|
450
|
-
).then((allOurSessionsEntries) => {
|
|
451
|
-
const allOurSessions: {
|
|
452
|
-
[sessionID: SessionID]: StoredSessionRow;
|
|
453
|
-
} = Object.fromEntries(
|
|
454
|
-
allOurSessionsEntries.map((row) => [row.sessionID, row]),
|
|
455
|
-
);
|
|
456
|
-
|
|
457
|
-
const ourKnown: CojsonInternalTypes.CoValueKnownState = {
|
|
458
|
-
id: msg.id,
|
|
459
|
-
header: true,
|
|
460
|
-
sessions: {},
|
|
461
|
-
};
|
|
462
|
-
let invalidAssumptions = false;
|
|
463
|
-
|
|
464
|
-
return Promise.all(
|
|
465
|
-
(Object.keys(msg.new) as SessionID[]).map((sessionID) => {
|
|
466
|
-
const sessionRow = allOurSessions[sessionID];
|
|
467
|
-
if (sessionRow) {
|
|
468
|
-
ourKnown.sessions[sessionRow.sessionID] = sessionRow.lastIdx;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
if (
|
|
472
|
-
(sessionRow?.lastIdx || 0) < (msg.new[sessionID]?.after || 0)
|
|
473
|
-
) {
|
|
474
|
-
invalidAssumptions = true;
|
|
475
|
-
} else {
|
|
476
|
-
return this.putNewTxs(
|
|
477
|
-
msg,
|
|
478
|
-
sessionID,
|
|
479
|
-
sessionRow,
|
|
480
|
-
storedCoValueRowID,
|
|
481
|
-
);
|
|
482
|
-
}
|
|
483
|
-
}),
|
|
484
|
-
).then(() => {
|
|
485
|
-
if (invalidAssumptions) {
|
|
486
|
-
this.toLocalNode
|
|
487
|
-
.push({
|
|
488
|
-
action: "known",
|
|
489
|
-
...ourKnown,
|
|
490
|
-
isCorrection: invalidAssumptions,
|
|
491
|
-
})
|
|
492
|
-
.catch((e) => console.error("Error sending known state", e));
|
|
493
|
-
}
|
|
494
|
-
});
|
|
495
|
-
});
|
|
496
|
-
});
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
private putNewTxs(
|
|
500
|
-
msg: CojsonInternalTypes.NewContentMessage,
|
|
501
|
-
sessionID: SessionID,
|
|
502
|
-
sessionRow: StoredSessionRow | undefined,
|
|
503
|
-
storedCoValueRowID: number,
|
|
504
|
-
) {
|
|
505
|
-
const newTransactions = msg.new[sessionID]?.newTransactions || [];
|
|
506
|
-
|
|
507
|
-
const actuallyNewOffset =
|
|
508
|
-
(sessionRow?.lastIdx || 0) - (msg.new[sessionID]?.after || 0);
|
|
509
|
-
|
|
510
|
-
const actuallyNewTransactions = newTransactions.slice(actuallyNewOffset);
|
|
511
|
-
|
|
512
|
-
let newBytesSinceLastSignature =
|
|
513
|
-
(sessionRow?.bytesSinceLastSignature || 0) +
|
|
514
|
-
actuallyNewTransactions.reduce(
|
|
515
|
-
(sum, tx) =>
|
|
516
|
-
sum +
|
|
517
|
-
(tx.privacy === "private"
|
|
518
|
-
? tx.encryptedChanges.length
|
|
519
|
-
: tx.changes.length),
|
|
520
|
-
0,
|
|
521
|
-
);
|
|
522
|
-
|
|
523
|
-
const newLastIdx =
|
|
524
|
-
(sessionRow?.lastIdx || 0) + actuallyNewTransactions.length;
|
|
525
|
-
|
|
526
|
-
let shouldWriteSignature = false;
|
|
527
|
-
|
|
528
|
-
if (newBytesSinceLastSignature > MAX_RECOMMENDED_TX_SIZE) {
|
|
529
|
-
shouldWriteSignature = true;
|
|
530
|
-
newBytesSinceLastSignature = 0;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
const nextIdx = sessionRow?.lastIdx || 0;
|
|
534
|
-
|
|
535
|
-
const sessionUpdate = {
|
|
536
|
-
coValue: storedCoValueRowID,
|
|
537
|
-
sessionID: sessionID,
|
|
538
|
-
lastIdx: newLastIdx,
|
|
539
|
-
lastSignature: msg.new[sessionID]!.lastSignature,
|
|
540
|
-
bytesSinceLastSignature: newBytesSinceLastSignature,
|
|
541
|
-
};
|
|
542
|
-
|
|
543
|
-
return this.makeRequest<number>(({ sessions }) =>
|
|
544
|
-
sessions.put(
|
|
545
|
-
sessionRow?.rowID
|
|
546
|
-
? {
|
|
547
|
-
rowID: sessionRow.rowID,
|
|
548
|
-
...sessionUpdate,
|
|
549
|
-
}
|
|
550
|
-
: sessionUpdate,
|
|
551
|
-
),
|
|
552
|
-
).then((sessionRowID) => {
|
|
553
|
-
let maybePutRequest;
|
|
554
|
-
if (shouldWriteSignature) {
|
|
555
|
-
maybePutRequest = this.makeRequest(({ signatureAfter }) =>
|
|
556
|
-
signatureAfter.put({
|
|
557
|
-
ses: sessionRowID,
|
|
558
|
-
// TODO: newLastIdx is a misnomer, it's actually more like nextIdx or length
|
|
559
|
-
idx: newLastIdx - 1,
|
|
560
|
-
signature: msg.new[sessionID]!.lastSignature,
|
|
561
|
-
} satisfies SignatureAfterRow),
|
|
562
|
-
);
|
|
563
|
-
} else {
|
|
564
|
-
maybePutRequest = SyncPromise.resolve();
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
return maybePutRequest.then(() =>
|
|
568
|
-
Promise.all(
|
|
569
|
-
actuallyNewTransactions.map((newTransaction, i) => {
|
|
570
|
-
return this.makeRequest(({ transactions }) =>
|
|
571
|
-
transactions.add({
|
|
572
|
-
ses: sessionRowID,
|
|
573
|
-
idx: nextIdx + i,
|
|
574
|
-
tx: newTransaction,
|
|
575
|
-
} satisfies TransactionRow),
|
|
576
|
-
);
|
|
577
|
-
}),
|
|
578
|
-
),
|
|
579
|
-
);
|
|
580
|
-
});
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
handleKnown(msg: CojsonInternalTypes.KnownStateMessage) {
|
|
584
|
-
return this.sendNewContentAfter(msg);
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
handleDone(_msg: CojsonInternalTypes.DoneMessage) {}
|
|
588
|
-
|
|
589
|
-
// inTransaction(mode: "readwrite" | "readonly"): {
|
|
590
|
-
// coValues: IDBObjectStore;
|
|
591
|
-
// sessions: IDBObjectStore;
|
|
592
|
-
// transactions: IDBObjectStore;
|
|
593
|
-
// signatureAfter: IDBObjectStore;
|
|
594
|
-
// } {
|
|
595
|
-
// const tx = this.db.transaction(
|
|
596
|
-
// ["coValues", "sessions", "transactions", "signatureAfter"],
|
|
597
|
-
// mode
|
|
598
|
-
// );
|
|
599
|
-
|
|
600
|
-
// const txID = lastTx;
|
|
601
|
-
// lastTx++;
|
|
602
|
-
// console.time("IndexedDB TX" + txID);
|
|
603
|
-
|
|
604
|
-
// tx.onerror = (event) => {
|
|
605
|
-
// const target = event.target as unknown as {
|
|
606
|
-
// error: DOMException;
|
|
607
|
-
// source?: { name: string };
|
|
608
|
-
// } | null;
|
|
609
|
-
// throw new Error(
|
|
610
|
-
// `Error in transaction (${target?.source?.name}): ${target?.error}`,
|
|
611
|
-
// { cause: target?.error }
|
|
612
|
-
// );
|
|
613
|
-
// };
|
|
614
|
-
// tx.oncomplete = () => {
|
|
615
|
-
// console.timeEnd("IndexedDB TX" + txID);
|
|
616
|
-
// }
|
|
617
|
-
// const coValues = tx.objectStore("coValues");
|
|
618
|
-
// const sessions = tx.objectStore("sessions");
|
|
619
|
-
// const transactions = tx.objectStore("transactions");
|
|
620
|
-
// const signatureAfter = tx.objectStore("signatureAfter");
|
|
621
|
-
|
|
622
|
-
// return { coValues, sessions, transactions, signatureAfter };
|
|
623
|
-
// }
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
function collectNewTxs(
|
|
627
|
-
newTxsInSession: TransactionRow[],
|
|
628
|
-
newContentPieces: CojsonInternalTypes.NewContentMessage[],
|
|
629
|
-
sessionRow: StoredSessionRow,
|
|
630
|
-
signaturesAndIdxs: SignatureAfterRow[],
|
|
631
|
-
theirKnown: CojsonInternalTypes.CoValueKnownState,
|
|
632
|
-
firstNewTxIdx: number,
|
|
633
|
-
) {
|
|
634
|
-
let idx = firstNewTxIdx;
|
|
635
|
-
|
|
636
|
-
// console.log(
|
|
637
|
-
// theirKnown.id,
|
|
638
|
-
// "newTxInSession",
|
|
639
|
-
// newTxInSession.length
|
|
640
|
-
// );
|
|
641
|
-
for (const tx of newTxsInSession) {
|
|
642
|
-
let sessionEntry =
|
|
643
|
-
newContentPieces[newContentPieces.length - 1]!.new[sessionRow.sessionID];
|
|
644
|
-
if (!sessionEntry) {
|
|
645
|
-
sessionEntry = {
|
|
646
|
-
after: idx,
|
|
647
|
-
lastSignature: "WILL_BE_REPLACED" as CojsonInternalTypes.Signature,
|
|
648
|
-
newTransactions: [],
|
|
649
|
-
};
|
|
650
|
-
newContentPieces[newContentPieces.length - 1]!.new[sessionRow.sessionID] =
|
|
651
|
-
sessionEntry;
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
sessionEntry.newTransactions.push(tx.tx);
|
|
655
|
-
|
|
656
|
-
if (signaturesAndIdxs[0] && idx === signaturesAndIdxs[0].idx) {
|
|
657
|
-
sessionEntry.lastSignature = signaturesAndIdxs[0].signature;
|
|
658
|
-
signaturesAndIdxs.shift();
|
|
659
|
-
newContentPieces.push({
|
|
660
|
-
action: "content",
|
|
661
|
-
id: theirKnown.id,
|
|
662
|
-
new: {},
|
|
663
|
-
priority: cojsonInternals.getPriorityFromHeader(undefined),
|
|
664
|
-
});
|
|
665
|
-
} else if (idx === firstNewTxIdx + newTxsInSession.length - 1) {
|
|
666
|
-
sessionEntry.lastSignature = sessionRow.lastSignature;
|
|
667
|
-
}
|
|
668
|
-
idx += 1;
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
function getDependedOnCoValues(
|
|
673
|
-
coValueRow: StoredCoValueRow | undefined,
|
|
674
|
-
newContentPieces: CojsonInternalTypes.NewContentMessage[],
|
|
675
|
-
theirKnown: CojsonInternalTypes.CoValueKnownState,
|
|
676
|
-
) {
|
|
677
|
-
return coValueRow?.header.ruleset.type === "group"
|
|
678
|
-
? newContentPieces
|
|
679
|
-
.flatMap((piece) => Object.values(piece.new))
|
|
680
|
-
.flatMap((sessionEntry) =>
|
|
681
|
-
sessionEntry.newTransactions.flatMap((tx) => {
|
|
682
|
-
if (tx.privacy !== "trusting") return [];
|
|
683
|
-
// TODO: avoid parse here?
|
|
684
|
-
return cojsonInternals
|
|
685
|
-
.parseJSON(tx.changes)
|
|
686
|
-
.map(
|
|
687
|
-
(change) =>
|
|
688
|
-
change &&
|
|
689
|
-
typeof change === "object" &&
|
|
690
|
-
"op" in change &&
|
|
691
|
-
change.op === "set" &&
|
|
692
|
-
"key" in change &&
|
|
693
|
-
change.key,
|
|
694
|
-
)
|
|
695
|
-
.filter(
|
|
696
|
-
(key): key is CojsonInternalTypes.RawCoID =>
|
|
697
|
-
typeof key === "string" && key.startsWith("co_"),
|
|
698
|
-
);
|
|
699
|
-
}),
|
|
700
|
-
)
|
|
701
|
-
: coValueRow?.header.ruleset.type === "ownedByGroup"
|
|
702
|
-
? [
|
|
703
|
-
coValueRow?.header.ruleset.group,
|
|
704
|
-
...new Set(
|
|
705
|
-
newContentPieces.flatMap((piece) =>
|
|
706
|
-
Object.keys(piece.new)
|
|
707
|
-
.map((sessionID) =>
|
|
708
|
-
cojsonInternals.accountOrAgentIDfromSessionID(
|
|
709
|
-
sessionID as SessionID,
|
|
710
|
-
),
|
|
711
|
-
)
|
|
712
|
-
.filter(
|
|
713
|
-
(accountID): accountID is RawAccountID =>
|
|
714
|
-
cojsonInternals.isAccountID(accountID) &&
|
|
715
|
-
accountID !== theirKnown.id,
|
|
716
|
-
),
|
|
717
|
-
),
|
|
718
|
-
),
|
|
719
|
-
]
|
|
720
|
-
: [];
|
|
721
|
-
}
|
|
722
|
-
// let lastTx = 0;
|
|
723
|
-
|
|
724
|
-
// function promised<T>(request: IDBRequest<T>): Promise<T> {
|
|
725
|
-
// return new Promise<T>((resolve, reject) => {
|
|
726
|
-
// request.onsuccess = () => {
|
|
727
|
-
// resolve(request.result);
|
|
728
|
-
// };
|
|
729
|
-
// request.onerror = () => {
|
|
730
|
-
// reject(request.error);
|
|
731
|
-
// };
|
|
732
|
-
// });
|
|
733
|
-
// }
|
|
1
|
+
export { IDBNode, IDBNode as IDBStorage } from "./idbNode";
|