cojson 0.19.3 → 0.19.5
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +15 -0
- package/dist/coValues/coList.d.ts +10 -4
- package/dist/coValues/coList.d.ts.map +1 -1
- package/dist/coValues/coList.js +30 -4
- package/dist/coValues/coList.js.map +1 -1
- package/dist/coValues/coMap.d.ts +3 -2
- package/dist/coValues/coMap.d.ts.map +1 -1
- package/dist/coValues/coMap.js +8 -10
- package/dist/coValues/coMap.js.map +1 -1
- package/dist/coValues/coPlainText.d.ts +2 -1
- package/dist/coValues/coPlainText.d.ts.map +1 -1
- package/dist/coValues/coPlainText.js +5 -2
- package/dist/coValues/coPlainText.js.map +1 -1
- package/dist/coValues/group.d.ts +7 -3
- package/dist/coValues/group.d.ts.map +1 -1
- package/dist/coValues/group.js +70 -24
- package/dist/coValues/group.js.map +1 -1
- package/dist/storage/sqlite/client.d.ts +3 -3
- package/dist/storage/sqlite/client.d.ts.map +1 -1
- package/dist/storage/sqlite/client.js +1 -1
- package/dist/storage/sqlite/client.js.map +1 -1
- package/dist/storage/sqliteAsync/client.d.ts +3 -3
- package/dist/storage/sqliteAsync/client.d.ts.map +1 -1
- package/dist/storage/sqliteAsync/client.js +1 -1
- package/dist/storage/sqliteAsync/client.js.map +1 -1
- package/dist/storage/storageAsync.d.ts.map +1 -1
- package/dist/storage/storageAsync.js +7 -7
- package/dist/storage/storageAsync.js.map +1 -1
- package/dist/storage/storageSync.d.ts.map +1 -1
- package/dist/storage/storageSync.js +7 -7
- package/dist/storage/storageSync.js.map +1 -1
- package/dist/storage/types.d.ts +18 -14
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/tests/coList.test.js +28 -1
- package/dist/tests/coList.test.js.map +1 -1
- package/dist/tests/group.parentGroupCache.test.d.ts +2 -0
- package/dist/tests/group.parentGroupCache.test.d.ts.map +1 -0
- package/dist/tests/group.parentGroupCache.test.js +216 -0
- package/dist/tests/group.parentGroupCache.test.js.map +1 -0
- package/package.json +3 -3
- package/src/coValues/coList.ts +41 -8
- package/src/coValues/coMap.ts +15 -11
- package/src/coValues/coPlainText.ts +6 -2
- package/src/coValues/group.ts +99 -24
- package/src/storage/sqlite/client.ts +6 -3
- package/src/storage/sqliteAsync/client.ts +8 -3
- package/src/storage/storageAsync.ts +8 -5
- package/src/storage/storageSync.ts +8 -5
- package/src/storage/types.ts +43 -37
- package/src/tests/coList.test.ts +36 -3
- package/src/tests/group.parentGroupCache.test.ts +293 -0
package/src/coValues/group.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { CoID } from "../coValue.js";
|
|
|
3
3
|
import type {
|
|
4
4
|
AvailableCoValueCore,
|
|
5
5
|
CoValueCore,
|
|
6
|
+
DecryptedTransaction,
|
|
6
7
|
} from "../coValueCore/coValueCore.js";
|
|
7
8
|
import type { CoValueUniqueness } from "../coValueCore/verifiedState.js";
|
|
8
9
|
import type {
|
|
@@ -161,6 +162,40 @@ function rotateReadKeyIfNeeded(group: RawGroup) {
|
|
|
161
162
|
}
|
|
162
163
|
}
|
|
163
164
|
|
|
165
|
+
class TimeBasedEntry<T> {
|
|
166
|
+
changes: { madeAt: number; value: T }[] = [];
|
|
167
|
+
|
|
168
|
+
addChange(madeAt: number, value: T) {
|
|
169
|
+
const changes = this.changes;
|
|
170
|
+
const newChange = { madeAt, value };
|
|
171
|
+
// Insert the change in chronological order
|
|
172
|
+
// Find the correct position by searching backwards from the end
|
|
173
|
+
let insertIndex = changes.length;
|
|
174
|
+
while (insertIndex > 0 && changes[insertIndex - 1]!.madeAt > madeAt) {
|
|
175
|
+
insertIndex--;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Insert at the correct position to maintain chronological order
|
|
179
|
+
if (insertIndex === changes.length) {
|
|
180
|
+
changes.push(newChange);
|
|
181
|
+
} else {
|
|
182
|
+
changes.splice(insertIndex, 0, newChange);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
getLatest() {
|
|
187
|
+
return this.changes[this.changes.length - 1]?.value;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
getAtTime(atTime?: number) {
|
|
191
|
+
if (atTime === undefined) {
|
|
192
|
+
return this.getLatest();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return this.changes.findLast((change) => change.madeAt <= atTime)?.value;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
164
199
|
/** A `Group` is a scope for permissions of its members (`"reader" | "writer" | "admin"`), applying to objects owned by that group.
|
|
165
200
|
*
|
|
166
201
|
* A `Group` object exposes methods for permission management and allows you to create new CoValues owned by that group.
|
|
@@ -189,6 +224,19 @@ export class RawGroup<
|
|
|
189
224
|
|
|
190
225
|
_lastReadableKeyId?: KeyID;
|
|
191
226
|
|
|
227
|
+
// Not using class field initializers because they run after that the CoMap constructor
|
|
228
|
+
// calls processNewTransactions, which would reset the parentGroupsChanges map
|
|
229
|
+
private declare parentGroupsChanges: Map<
|
|
230
|
+
CoID<RawGroup>,
|
|
231
|
+
TimeBasedEntry<ParentGroupReferenceRole>
|
|
232
|
+
>;
|
|
233
|
+
|
|
234
|
+
protected resetInternalState() {
|
|
235
|
+
super.resetInternalState();
|
|
236
|
+
this.parentGroupsChanges = new Map();
|
|
237
|
+
this._lastReadableKeyId = undefined;
|
|
238
|
+
}
|
|
239
|
+
|
|
192
240
|
constructor(
|
|
193
241
|
core: AvailableCoValueCore,
|
|
194
242
|
options?: {
|
|
@@ -200,6 +248,45 @@ export class RawGroup<
|
|
|
200
248
|
this.migrate();
|
|
201
249
|
}
|
|
202
250
|
|
|
251
|
+
// We override the handleNewTransaction hook from CoMap to build the parent group cache
|
|
252
|
+
override handleNewTransaction(transaction: DecryptedTransaction): void {
|
|
253
|
+
if (!this.parentGroupsChanges) {
|
|
254
|
+
this.parentGroupsChanges = new Map();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Build parent group cache incrementally
|
|
258
|
+
for (const changeValue of transaction.changes) {
|
|
259
|
+
const change = changeValue as {
|
|
260
|
+
op: "set" | "del";
|
|
261
|
+
key: string;
|
|
262
|
+
value?: any;
|
|
263
|
+
};
|
|
264
|
+
if (change.op === "set" && isParentGroupReference(change.key)) {
|
|
265
|
+
this.updateParentGroupCache(
|
|
266
|
+
change.key,
|
|
267
|
+
change.value as ParentGroupReferenceRole,
|
|
268
|
+
transaction.madeAt,
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
private updateParentGroupCache(
|
|
275
|
+
key: string,
|
|
276
|
+
value: any,
|
|
277
|
+
timestamp: number,
|
|
278
|
+
): void {
|
|
279
|
+
const parentGroupId = key.substring(7) as CoID<RawGroup>; // Remove 'parent_' prefix
|
|
280
|
+
|
|
281
|
+
let entry = this.parentGroupsChanges.get(parentGroupId);
|
|
282
|
+
if (!entry) {
|
|
283
|
+
entry = new TimeBasedEntry<ParentGroupReferenceRole>();
|
|
284
|
+
this.parentGroupsChanges.set(parentGroupId, entry);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
entry.addChange(timestamp, value as ParentGroupReferenceRole);
|
|
288
|
+
}
|
|
289
|
+
|
|
203
290
|
migrate() {
|
|
204
291
|
if (!this.core.isGroup()) {
|
|
205
292
|
return;
|
|
@@ -247,15 +334,13 @@ export class RawGroup<
|
|
|
247
334
|
|
|
248
335
|
let roleInfo: Role | undefined = roleHere;
|
|
249
336
|
|
|
250
|
-
for (const
|
|
251
|
-
|
|
337
|
+
for (const [parentGroupId, entry] of this.parentGroupsChanges.entries()) {
|
|
338
|
+
const role = entry.getAtTime(this.atTimeFilter);
|
|
252
339
|
|
|
253
|
-
|
|
340
|
+
if (!role || role === "revoked") continue;
|
|
254
341
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
const role = this.get(key) ?? "extend";
|
|
258
|
-
const parentRole = group.roleOfInternal(accountID);
|
|
342
|
+
const parentGroup = this.getParentGroup(parentGroupId, this.atTimeFilter);
|
|
343
|
+
const parentRole = parentGroup.roleOfInternal(accountID);
|
|
259
344
|
|
|
260
345
|
if (!isInheritableRole(parentRole)) {
|
|
261
346
|
continue;
|
|
@@ -277,13 +362,9 @@ export class RawGroup<
|
|
|
277
362
|
return roleInfo;
|
|
278
363
|
}
|
|
279
364
|
|
|
280
|
-
|
|
281
|
-
if (this.get(key) === "revoked") {
|
|
282
|
-
return null;
|
|
283
|
-
}
|
|
284
|
-
|
|
365
|
+
getParentGroup(id: CoID<RawGroup>, atTime?: number) {
|
|
285
366
|
const parent = this.core.node.expectCoValueLoaded(
|
|
286
|
-
|
|
367
|
+
id,
|
|
287
368
|
"Expected parent group to be loaded",
|
|
288
369
|
);
|
|
289
370
|
|
|
@@ -296,21 +377,15 @@ export class RawGroup<
|
|
|
296
377
|
}
|
|
297
378
|
}
|
|
298
379
|
|
|
299
|
-
getParentGroups(
|
|
380
|
+
getParentGroups() {
|
|
300
381
|
const groups: RawGroup[] = [];
|
|
301
382
|
|
|
302
|
-
for (const
|
|
303
|
-
|
|
383
|
+
for (const [parentGroupId, entry] of this.parentGroupsChanges.entries()) {
|
|
384
|
+
const role = entry.getAtTime(this.atTimeFilter);
|
|
304
385
|
|
|
305
|
-
|
|
386
|
+
if (!role || role === "revoked") continue;
|
|
306
387
|
|
|
307
|
-
|
|
308
|
-
if (atTime) {
|
|
309
|
-
groups.push(group.atTime(atTime));
|
|
310
|
-
} else {
|
|
311
|
-
groups.push(group);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
388
|
+
groups.push(this.getParentGroup(parentGroupId, this.atTimeFilter));
|
|
314
389
|
}
|
|
315
390
|
|
|
316
391
|
return groups;
|
|
@@ -8,6 +8,7 @@ import { logger } from "../../logger.js";
|
|
|
8
8
|
import type { NewContentMessage } from "../../sync.js";
|
|
9
9
|
import type {
|
|
10
10
|
DBClientInterfaceSync,
|
|
11
|
+
DBTransactionInterfaceSync,
|
|
11
12
|
SessionRow,
|
|
12
13
|
SignatureAfterRow,
|
|
13
14
|
StoredCoValueRow,
|
|
@@ -31,7 +32,9 @@ export function getErrorMessage(error: unknown) {
|
|
|
31
32
|
return error instanceof Error ? error.message : "Unknown error";
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
export class SQLiteClient
|
|
35
|
+
export class SQLiteClient
|
|
36
|
+
implements DBClientInterfaceSync, DBTransactionInterfaceSync
|
|
37
|
+
{
|
|
35
38
|
private readonly db: SQLiteDatabaseDriver;
|
|
36
39
|
|
|
37
40
|
constructor(db: SQLiteDatabaseDriver) {
|
|
@@ -187,8 +190,8 @@ export class SQLiteClient implements DBClientInterfaceSync {
|
|
|
187
190
|
);
|
|
188
191
|
}
|
|
189
192
|
|
|
190
|
-
transaction(operationsCallback: () => unknown) {
|
|
191
|
-
this.db.transaction(operationsCallback);
|
|
193
|
+
transaction(operationsCallback: (tx: DBTransactionInterfaceSync) => unknown) {
|
|
194
|
+
this.db.transaction(() => operationsCallback(this));
|
|
192
195
|
return undefined;
|
|
193
196
|
}
|
|
194
197
|
}
|
|
@@ -8,6 +8,7 @@ import { logger } from "../../logger.js";
|
|
|
8
8
|
import type { NewContentMessage } from "../../sync.js";
|
|
9
9
|
import type {
|
|
10
10
|
DBClientInterfaceAsync,
|
|
11
|
+
DBTransactionInterfaceAsync,
|
|
11
12
|
SessionRow,
|
|
12
13
|
SignatureAfterRow,
|
|
13
14
|
StoredCoValueRow,
|
|
@@ -31,7 +32,9 @@ export function getErrorMessage(error: unknown) {
|
|
|
31
32
|
return error instanceof Error ? error.message : "Unknown error";
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
export class SQLiteClientAsync
|
|
35
|
+
export class SQLiteClientAsync
|
|
36
|
+
implements DBClientInterfaceAsync, DBTransactionInterfaceAsync
|
|
37
|
+
{
|
|
35
38
|
private readonly db: SQLiteDatabaseDriverAsync;
|
|
36
39
|
|
|
37
40
|
constructor(db: SQLiteDatabaseDriverAsync) {
|
|
@@ -194,7 +197,9 @@ export class SQLiteClientAsync implements DBClientInterfaceAsync {
|
|
|
194
197
|
);
|
|
195
198
|
}
|
|
196
199
|
|
|
197
|
-
async transaction(
|
|
198
|
-
|
|
200
|
+
async transaction(
|
|
201
|
+
operationsCallback: (tx: DBTransactionInterfaceAsync) => Promise<unknown>,
|
|
202
|
+
) {
|
|
203
|
+
return this.db.transaction(() => operationsCallback(this));
|
|
199
204
|
}
|
|
200
205
|
}
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
import type {
|
|
26
26
|
CorrectionCallback,
|
|
27
27
|
DBClientInterfaceAsync,
|
|
28
|
+
DBTransactionInterfaceAsync,
|
|
28
29
|
SignatureAfterRow,
|
|
29
30
|
StoredCoValueRow,
|
|
30
31
|
StoredSessionRow,
|
|
@@ -280,8 +281,8 @@ export class StorageApiAsync implements StorageAPI {
|
|
|
280
281
|
let invalidAssumptions = false;
|
|
281
282
|
|
|
282
283
|
for (const sessionID of Object.keys(msg.new) as SessionID[]) {
|
|
283
|
-
await this.dbClient.transaction(async () => {
|
|
284
|
-
const sessionRow = await
|
|
284
|
+
await this.dbClient.transaction(async (tx) => {
|
|
285
|
+
const sessionRow = await tx.getSingleCoValueSession(
|
|
285
286
|
storedCoValueRowID,
|
|
286
287
|
sessionID,
|
|
287
288
|
);
|
|
@@ -301,6 +302,7 @@ export class StorageApiAsync implements StorageAPI {
|
|
|
301
302
|
invalidAssumptions = true;
|
|
302
303
|
} else {
|
|
303
304
|
const newLastIdx = await this.putNewTxs(
|
|
305
|
+
tx,
|
|
304
306
|
msg,
|
|
305
307
|
sessionID,
|
|
306
308
|
sessionRow,
|
|
@@ -321,6 +323,7 @@ export class StorageApiAsync implements StorageAPI {
|
|
|
321
323
|
}
|
|
322
324
|
|
|
323
325
|
private async putNewTxs(
|
|
326
|
+
tx: DBTransactionInterfaceAsync,
|
|
324
327
|
msg: NewContentMessage,
|
|
325
328
|
sessionID: SessionID,
|
|
326
329
|
sessionRow: StoredSessionRow | undefined,
|
|
@@ -363,13 +366,13 @@ export class StorageApiAsync implements StorageAPI {
|
|
|
363
366
|
bytesSinceLastSignature,
|
|
364
367
|
};
|
|
365
368
|
|
|
366
|
-
const sessionRowID: number = await
|
|
369
|
+
const sessionRowID: number = await tx.addSessionUpdate({
|
|
367
370
|
sessionUpdate,
|
|
368
371
|
sessionRow,
|
|
369
372
|
});
|
|
370
373
|
|
|
371
374
|
if (shouldWriteSignature) {
|
|
372
|
-
await
|
|
375
|
+
await tx.addSignatureAfter({
|
|
373
376
|
sessionRowID,
|
|
374
377
|
idx: newLastIdx - 1,
|
|
375
378
|
signature: msg.new[sessionID].lastSignature,
|
|
@@ -378,7 +381,7 @@ export class StorageApiAsync implements StorageAPI {
|
|
|
378
381
|
|
|
379
382
|
await Promise.all(
|
|
380
383
|
actuallyNewTransactions.map((newTransaction, i) =>
|
|
381
|
-
|
|
384
|
+
tx.addTransaction(sessionRowID, nextIdx + i, newTransaction),
|
|
382
385
|
),
|
|
383
386
|
);
|
|
384
387
|
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
import type {
|
|
26
26
|
CorrectionCallback,
|
|
27
27
|
DBClientInterfaceSync,
|
|
28
|
+
DBTransactionInterfaceSync,
|
|
28
29
|
SignatureAfterRow,
|
|
29
30
|
StoredCoValueRow,
|
|
30
31
|
StoredSessionRow,
|
|
@@ -257,8 +258,8 @@ export class StorageApiSync implements StorageAPI {
|
|
|
257
258
|
let invalidAssumptions = false;
|
|
258
259
|
|
|
259
260
|
for (const sessionID of Object.keys(msg.new) as SessionID[]) {
|
|
260
|
-
this.dbClient.transaction(() => {
|
|
261
|
-
const sessionRow =
|
|
261
|
+
this.dbClient.transaction((tx) => {
|
|
262
|
+
const sessionRow = tx.getSingleCoValueSession(
|
|
262
263
|
storedCoValueRowID,
|
|
263
264
|
sessionID,
|
|
264
265
|
);
|
|
@@ -275,6 +276,7 @@ export class StorageApiSync implements StorageAPI {
|
|
|
275
276
|
invalidAssumptions = true;
|
|
276
277
|
} else {
|
|
277
278
|
const newLastIdx = this.putNewTxs(
|
|
279
|
+
tx,
|
|
278
280
|
msg,
|
|
279
281
|
sessionID,
|
|
280
282
|
sessionRow,
|
|
@@ -295,6 +297,7 @@ export class StorageApiSync implements StorageAPI {
|
|
|
295
297
|
}
|
|
296
298
|
|
|
297
299
|
private putNewTxs(
|
|
300
|
+
tx: DBTransactionInterfaceSync,
|
|
298
301
|
msg: NewContentMessage,
|
|
299
302
|
sessionID: SessionID,
|
|
300
303
|
sessionRow: StoredSessionRow | undefined,
|
|
@@ -338,13 +341,13 @@ export class StorageApiSync implements StorageAPI {
|
|
|
338
341
|
bytesSinceLastSignature,
|
|
339
342
|
};
|
|
340
343
|
|
|
341
|
-
const sessionRowID: number =
|
|
344
|
+
const sessionRowID: number = tx.addSessionUpdate({
|
|
342
345
|
sessionUpdate,
|
|
343
346
|
sessionRow,
|
|
344
347
|
});
|
|
345
348
|
|
|
346
349
|
if (shouldWriteSignature) {
|
|
347
|
-
|
|
350
|
+
tx.addSignatureAfter({
|
|
348
351
|
sessionRowID,
|
|
349
352
|
idx: newLastIdx - 1,
|
|
350
353
|
signature: msg.new[sessionID].lastSignature,
|
|
@@ -352,7 +355,7 @@ export class StorageApiSync implements StorageAPI {
|
|
|
352
355
|
}
|
|
353
356
|
|
|
354
357
|
actuallyNewTransactions.map((newTransaction, i) =>
|
|
355
|
-
|
|
358
|
+
tx.addTransaction(sessionRowID, nextIdx + i, newTransaction),
|
|
356
359
|
);
|
|
357
360
|
|
|
358
361
|
return newLastIdx;
|
package/src/storage/types.ts
CHANGED
|
@@ -61,34 +61,12 @@ export type SignatureAfterRow = {
|
|
|
61
61
|
signature: Signature;
|
|
62
62
|
};
|
|
63
63
|
|
|
64
|
-
export interface
|
|
65
|
-
getCoValue(
|
|
66
|
-
coValueId: string,
|
|
67
|
-
): Promise<StoredCoValueRow | undefined> | undefined;
|
|
68
|
-
|
|
69
|
-
upsertCoValue(
|
|
70
|
-
id: string,
|
|
71
|
-
header?: CoValueHeader,
|
|
72
|
-
): Promise<number | undefined>;
|
|
73
|
-
|
|
74
|
-
getCoValueSessions(coValueRowId: number): Promise<StoredSessionRow[]>;
|
|
75
|
-
|
|
64
|
+
export interface DBTransactionInterfaceAsync {
|
|
76
65
|
getSingleCoValueSession(
|
|
77
66
|
coValueRowId: number,
|
|
78
67
|
sessionID: SessionID,
|
|
79
68
|
): Promise<StoredSessionRow | undefined>;
|
|
80
69
|
|
|
81
|
-
getNewTransactionInSession(
|
|
82
|
-
sessionRowId: number,
|
|
83
|
-
fromIdx: number,
|
|
84
|
-
toIdx: number,
|
|
85
|
-
): Promise<TransactionRow[]>;
|
|
86
|
-
|
|
87
|
-
getSignatures(
|
|
88
|
-
sessionRowId: number,
|
|
89
|
-
firstNewTxIdx: number,
|
|
90
|
-
): Promise<SignatureAfterRow[]>;
|
|
91
|
-
|
|
92
70
|
addSessionUpdate({
|
|
93
71
|
sessionUpdate,
|
|
94
72
|
sessionRow,
|
|
@@ -112,32 +90,41 @@ export interface DBClientInterfaceAsync {
|
|
|
112
90
|
idx: number;
|
|
113
91
|
signature: Signature;
|
|
114
92
|
}): Promise<unknown>;
|
|
115
|
-
|
|
116
|
-
transaction(callback: () => unknown): Promise<unknown>;
|
|
117
93
|
}
|
|
118
94
|
|
|
119
|
-
export interface
|
|
120
|
-
getCoValue(
|
|
121
|
-
|
|
122
|
-
|
|
95
|
+
export interface DBClientInterfaceAsync {
|
|
96
|
+
getCoValue(
|
|
97
|
+
coValueId: string,
|
|
98
|
+
): Promise<StoredCoValueRow | undefined> | undefined;
|
|
123
99
|
|
|
124
|
-
|
|
100
|
+
upsertCoValue(
|
|
101
|
+
id: string,
|
|
102
|
+
header?: CoValueHeader,
|
|
103
|
+
): Promise<number | undefined>;
|
|
125
104
|
|
|
126
|
-
|
|
127
|
-
coValueRowId: number,
|
|
128
|
-
sessionID: SessionID,
|
|
129
|
-
): StoredSessionRow | undefined;
|
|
105
|
+
getCoValueSessions(coValueRowId: number): Promise<StoredSessionRow[]>;
|
|
130
106
|
|
|
131
107
|
getNewTransactionInSession(
|
|
132
108
|
sessionRowId: number,
|
|
133
109
|
fromIdx: number,
|
|
134
110
|
toIdx: number,
|
|
135
|
-
): TransactionRow[]
|
|
111
|
+
): Promise<TransactionRow[]>;
|
|
136
112
|
|
|
137
113
|
getSignatures(
|
|
138
114
|
sessionRowId: number,
|
|
139
115
|
firstNewTxIdx: number,
|
|
140
|
-
):
|
|
116
|
+
): Promise<SignatureAfterRow[]>;
|
|
117
|
+
|
|
118
|
+
transaction(
|
|
119
|
+
callback: (tx: DBTransactionInterfaceAsync) => Promise<unknown>,
|
|
120
|
+
): Promise<unknown>;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface DBTransactionInterfaceSync {
|
|
124
|
+
getSingleCoValueSession(
|
|
125
|
+
coValueRowId: number,
|
|
126
|
+
sessionID: SessionID,
|
|
127
|
+
): StoredSessionRow | undefined;
|
|
141
128
|
|
|
142
129
|
addSessionUpdate({
|
|
143
130
|
sessionUpdate,
|
|
@@ -162,6 +149,25 @@ export interface DBClientInterfaceSync {
|
|
|
162
149
|
idx: number;
|
|
163
150
|
signature: Signature;
|
|
164
151
|
}): number | undefined | unknown;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface DBClientInterfaceSync {
|
|
155
|
+
getCoValue(coValueId: string): StoredCoValueRow | undefined;
|
|
156
|
+
|
|
157
|
+
upsertCoValue(id: string, header?: CoValueHeader): number | undefined;
|
|
158
|
+
|
|
159
|
+
getCoValueSessions(coValueRowId: number): StoredSessionRow[];
|
|
160
|
+
|
|
161
|
+
getNewTransactionInSession(
|
|
162
|
+
sessionRowId: number,
|
|
163
|
+
fromIdx: number,
|
|
164
|
+
toIdx: number,
|
|
165
|
+
): TransactionRow[];
|
|
166
|
+
|
|
167
|
+
getSignatures(
|
|
168
|
+
sessionRowId: number,
|
|
169
|
+
firstNewTxIdx: number,
|
|
170
|
+
): Pick<SignatureAfterRow, "idx" | "signature">[];
|
|
165
171
|
|
|
166
|
-
transaction(callback: () => unknown): unknown;
|
|
172
|
+
transaction(callback: (tx: DBTransactionInterfaceSync) => unknown): unknown;
|
|
167
173
|
}
|
package/src/tests/coList.test.ts
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, test } from "vitest";
|
|
2
2
|
import { expectList } from "../coValue.js";
|
|
3
3
|
import { WasmCrypto } from "../crypto/WasmCrypto.js";
|
|
4
|
-
import { LocalNode } from "../localNode.js";
|
|
5
|
-
import { expectGroup } from "../typeUtils/expectGroup.js";
|
|
6
4
|
import {
|
|
5
|
+
hotSleep,
|
|
7
6
|
loadCoValueOrFail,
|
|
8
7
|
nodeWithRandomAgentAndSessionID,
|
|
9
|
-
randomAgentAndSessionID,
|
|
10
8
|
setupTestNode,
|
|
11
9
|
waitFor,
|
|
12
10
|
} from "./testUtils.js";
|
|
@@ -429,6 +427,41 @@ test("Should ignore unknown meta transactions", () => {
|
|
|
429
427
|
expect(content.toJSON()).toEqual(["first"]);
|
|
430
428
|
});
|
|
431
429
|
|
|
430
|
+
describe("CoList Time Travel", () => {
|
|
431
|
+
test("atTime should return a time travel entity", () => {
|
|
432
|
+
const node = nodeWithRandomAgentAndSessionID();
|
|
433
|
+
const coValue = node.createCoValue({
|
|
434
|
+
type: "colist",
|
|
435
|
+
ruleset: { type: "unsafeAllowAll" },
|
|
436
|
+
meta: null,
|
|
437
|
+
...Crypto.createdNowUnique(),
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
const content = expectList(coValue.getCurrentContent());
|
|
441
|
+
|
|
442
|
+
const beforeA = hotSleep(10);
|
|
443
|
+
content.append("first", 0, "trusting");
|
|
444
|
+
|
|
445
|
+
const beforeB = hotSleep(10);
|
|
446
|
+
content.append("second", 0, "trusting");
|
|
447
|
+
|
|
448
|
+
const beforeC = hotSleep(10);
|
|
449
|
+
content.delete(0, "trusting");
|
|
450
|
+
|
|
451
|
+
const beforeD = hotSleep(10);
|
|
452
|
+
content.prepend("third", 0, "trusting");
|
|
453
|
+
|
|
454
|
+
expect(content.toJSON()).toEqual(["third", "second"]);
|
|
455
|
+
|
|
456
|
+
expect(content.atTime(0).toJSON()).toEqual([]);
|
|
457
|
+
expect(content.atTime(beforeA).toJSON()).toEqual([]);
|
|
458
|
+
expect(content.atTime(beforeB).toJSON()).toEqual(["first"]);
|
|
459
|
+
expect(content.atTime(beforeC).toJSON()).toEqual(["first", "second"]);
|
|
460
|
+
expect(content.atTime(beforeD).toJSON()).toEqual(["second"]);
|
|
461
|
+
expect(content.atTime(Date.now()).toJSON()).toEqual(["third", "second"]);
|
|
462
|
+
});
|
|
463
|
+
});
|
|
464
|
+
|
|
432
465
|
describe("CoList Branching", () => {
|
|
433
466
|
test("should handle concurrent appends from multiple branches", async () => {
|
|
434
467
|
const client1 = setupTestNode({
|