@procedures/collections 0.56.0 → 0.58.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/CHANGELOG.md +27 -0
- package/README.md +1 -1
- package/dist/testing/index.d.ts +0 -8
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +0 -8
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/storage-contract.d.ts +3 -2
- package/dist/testing/storage-contract.d.ts.map +1 -1
- package/dist/testing/storage-contract.js +162 -155
- package/dist/testing/storage-contract.js.map +1 -1
- package/package.json +11 -2
- package/src/testing/index.ts +0 -9
- package/src/testing/storage-contract.ts +163 -155
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
* `@procedures/sqlite`. Before, each package copied the cases of `InMemoryStorage`.
|
|
7
7
|
*
|
|
8
8
|
* The suite is in the test-only entry `@procedures/collections/testing`. It imports no test
|
|
9
|
-
* framework: the caller gives `describe` and `it`, and the checks throw `
|
|
10
|
-
* storage package imports the suite without a cycle, and a runtime entry
|
|
9
|
+
* framework: the caller gives `describe` and `it`, and the checks throw the `AssertionError` of
|
|
10
|
+
* `node:assert`. So a storage package imports the suite without a cycle, and a runtime entry does
|
|
11
|
+
* not load vitest.
|
|
11
12
|
*
|
|
12
13
|
* ```ts
|
|
13
14
|
* import { describe, it } from "vitest";
|
|
@@ -42,7 +43,14 @@ import {
|
|
|
42
43
|
type StorageSelector,
|
|
43
44
|
type VersionConflict,
|
|
44
45
|
} from "@procedures/client/query";
|
|
45
|
-
import
|
|
46
|
+
import assert from "node:assert/strict";
|
|
47
|
+
import { inspect } from "node:util";
|
|
48
|
+
import { rejects } from "@mark1russell7/rigor-testkit";
|
|
49
|
+
|
|
50
|
+
/** This function gives the text of a value for the message of a failed check, on one line. */
|
|
51
|
+
function show(value: unknown): string {
|
|
52
|
+
return inspect(value, { breakLength: Infinity, depth: null });
|
|
53
|
+
}
|
|
46
54
|
|
|
47
55
|
/** The item of the suite. Its `id` is its key, so a `HybridStorage` can sync it. */
|
|
48
56
|
export interface ContractItem {
|
|
@@ -324,7 +332,7 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
324
332
|
["size", storage.size()],
|
|
325
333
|
["getBatch", storage.getBatch(["a"])],
|
|
326
334
|
];
|
|
327
|
-
for (const [name, result] of results)
|
|
335
|
+
for (const [name, result] of results) assert.ok(isPromise(result), `${name}() did not give a promise`);
|
|
328
336
|
await Promise.all(results.map(([, result]) => result));
|
|
329
337
|
for (const [name, start] of [
|
|
330
338
|
["set", () => storage.set("a", item("a", 1))],
|
|
@@ -334,7 +342,7 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
334
342
|
["clear", () => storage.clear()],
|
|
335
343
|
] as Array<[string, () => Promise<unknown>]>) {
|
|
336
344
|
const result = start();
|
|
337
|
-
|
|
345
|
+
assert.ok(isPromise(result), `${name}() did not give a promise`);
|
|
338
346
|
await result;
|
|
339
347
|
}
|
|
340
348
|
}),
|
|
@@ -342,16 +350,16 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
342
350
|
|
|
343
351
|
add("an empty storage has no item", () =>
|
|
344
352
|
withStorage(create, async (storage) => {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
if (storage.getEntries)
|
|
354
|
-
if (storage.getIds)
|
|
353
|
+
assert.deepStrictEqual(await storage.size(), 0, "size() of an empty storage");
|
|
354
|
+
assert.deepStrictEqual(await storage.getAll(), [], "getAll() of an empty storage");
|
|
355
|
+
assert.deepStrictEqual(await storage.get("a"), undefined, "get() of a missing id");
|
|
356
|
+
assert.deepStrictEqual(await storage.has("a"), false, "has() of a missing id");
|
|
357
|
+
assert.deepStrictEqual(await storage.getBatch(["a", "b"]), new Map(), "getBatch() of missing ids");
|
|
358
|
+
assert.deepStrictEqual(await storage.find(() => true), [], "find() in an empty storage");
|
|
359
|
+
assert.deepStrictEqual(await storage.delete("a"), false, "delete() of a missing id");
|
|
360
|
+
assert.deepStrictEqual(await storage.deleteBatch(["a", "b"]), 0, "deleteBatch() of missing ids");
|
|
361
|
+
if (storage.getEntries) assert.deepStrictEqual(await storage.getEntries(), [], "getEntries() of an empty storage");
|
|
362
|
+
if (storage.getIds) assert.deepStrictEqual(await storage.getIds(), [], "getIds() of an empty storage");
|
|
355
363
|
}),
|
|
356
364
|
);
|
|
357
365
|
|
|
@@ -359,41 +367,41 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
359
367
|
withStorage(create, async (storage) => {
|
|
360
368
|
await storage.set("a", item("a", 1, ["x"]));
|
|
361
369
|
await storage.set("b", item("b", 2));
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
370
|
+
assert.deepStrictEqual(await storage.get("a"), item("a", 1, ["x"]), "get() after set()");
|
|
371
|
+
assert.deepStrictEqual(await storage.has("a"), true, "has() after set()");
|
|
372
|
+
assert.deepStrictEqual(await storage.size(), 2, "size() after two set() calls");
|
|
365
373
|
await storage.set("a", item("a", 10));
|
|
366
|
-
|
|
367
|
-
|
|
374
|
+
assert.deepStrictEqual(await storage.get("a"), item("a", 10), "get() after a second set() of the id");
|
|
375
|
+
assert.deepStrictEqual(await storage.size(), 2, "size() after a second set() of an id");
|
|
368
376
|
}),
|
|
369
377
|
);
|
|
370
378
|
|
|
371
379
|
add("delete() gives true for an item that existed, and deleteBatch() counts the items that existed", () =>
|
|
372
380
|
withStorage(create, async (storage) => {
|
|
373
381
|
await storage.setBatch([["a", item("a", 1)], ["b", item("b", 2)], ["c", item("c", 3)]]);
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
382
|
+
assert.deepStrictEqual(await storage.delete("a"), true, "delete() of an item");
|
|
383
|
+
assert.deepStrictEqual(await storage.delete("a"), false, "a second delete() of the id");
|
|
384
|
+
assert.deepStrictEqual(await storage.has("a"), false, "has() after delete()");
|
|
385
|
+
assert.deepStrictEqual(await storage.get("a"), undefined, "get() after delete()");
|
|
386
|
+
assert.deepStrictEqual(await storage.deleteBatch(["b", "missing", "b"]), 1, "deleteBatch() with a missing id and a repeated id");
|
|
387
|
+
assert.deepStrictEqual(ids(await storage.getAll()), ["c"], "getAll() after the deletes");
|
|
380
388
|
}),
|
|
381
389
|
);
|
|
382
390
|
|
|
383
391
|
add("setBatch() and getBatch(): the last value of an id wins, a missing id is not in the map, and empty batches change nothing", () =>
|
|
384
392
|
withStorage(create, async (storage) => {
|
|
385
393
|
await storage.setBatch([["a", item("a", 1)], ["b", item("b", 2)], ["a", item("a", 3)]]);
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
394
|
+
assert.deepStrictEqual(await storage.get("a"), item("a", 3), "get() of an id that setBatch() set two times");
|
|
395
|
+
assert.deepStrictEqual(await storage.size(), 2, "size() after setBatch() with a repeated id");
|
|
396
|
+
assert.deepStrictEqual(
|
|
389
397
|
await storage.getBatch(["b", "missing", "a"]),
|
|
390
398
|
new Map([["b", item("b", 2)], ["a", item("a", 3)]]),
|
|
391
399
|
"getBatch() with a missing id",
|
|
392
400
|
);
|
|
393
401
|
await storage.setBatch([]);
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
402
|
+
assert.deepStrictEqual(await storage.deleteBatch([]), 0, "deleteBatch() of no id");
|
|
403
|
+
assert.deepStrictEqual(await storage.getBatch([]), new Map(), "getBatch() of no id");
|
|
404
|
+
assert.deepStrictEqual(await storage.size(), 2, "size() after the empty batches");
|
|
397
405
|
}),
|
|
398
406
|
);
|
|
399
407
|
|
|
@@ -401,15 +409,15 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
401
409
|
withStorage(create, async (storage) => {
|
|
402
410
|
await storage.setBatch([["c", item("c", 3)], ["a", item("a", 1)], ["b", item("b", 2)]]);
|
|
403
411
|
await storage.set("a", item("a", 10));
|
|
404
|
-
|
|
412
|
+
assert.deepStrictEqual(ids(await storage.getAll()), ["c", "a", "b"], "getAll() after a second set() of an id: the id keeps its place");
|
|
405
413
|
// A delete, then a set: the id goes to the end
|
|
406
414
|
await storage.delete("c");
|
|
407
415
|
await storage.set("c", item("c", 30));
|
|
408
416
|
const expected: Array<[string, ContractItem]> = [["a", item("a", 10)], ["b", item("b", 2)], ["c", item("c", 30)]];
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
if (storage.getEntries)
|
|
412
|
-
if (storage.getIds)
|
|
417
|
+
assert.deepStrictEqual(await storage.getAll(), expected.map(([, value]) => value), "getAll() after a delete and a new set() of an id");
|
|
418
|
+
assert.deepStrictEqual(ids(await storage.find(() => true)), ["a", "b", "c"], "find() keeps the order of the entries");
|
|
419
|
+
if (storage.getEntries) assert.deepStrictEqual(await storage.getEntries(), expected, "getEntries() keeps the order of the entries");
|
|
420
|
+
if (storage.getIds) assert.deepStrictEqual(await storage.getIds(), ["a", "b", "c"], "getIds() keeps the order of the entries");
|
|
413
421
|
}),
|
|
414
422
|
);
|
|
415
423
|
|
|
@@ -420,20 +428,20 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
420
428
|
await storage.delete("a");
|
|
421
429
|
await storage.set("a", item("a", 10));
|
|
422
430
|
const first = await storage.getIds!();
|
|
423
|
-
|
|
431
|
+
assert.deepStrictEqual(first, ["c", "b", "a"], "getIds() after a delete and a new set() of an id");
|
|
424
432
|
first.splice(0);
|
|
425
|
-
|
|
426
|
-
if (storage.getEntries)
|
|
433
|
+
assert.deepStrictEqual(await storage.getIds!(), ["c", "b", "a"], "getIds() after a change of an earlier result");
|
|
434
|
+
if (storage.getEntries) assert.deepStrictEqual(await storage.getIds!(), (await storage.getEntries()).map(([id]) => id), "getIds() and the ids of getEntries()");
|
|
427
435
|
await storage.clear();
|
|
428
|
-
|
|
436
|
+
assert.deepStrictEqual(await storage.getIds!(), [], "getIds() after clear()");
|
|
429
437
|
}),
|
|
430
438
|
);
|
|
431
439
|
|
|
432
440
|
add("find() gives the matching items in order, and an error of the predicate rejects the promise", () =>
|
|
433
441
|
withStorage(create, async (storage) => {
|
|
434
442
|
await storage.setBatch([["a", item("a", 1)], ["b", item("b", 2)], ["c", item("c", 3)], ["d", item("d", 4)]]);
|
|
435
|
-
|
|
436
|
-
|
|
443
|
+
assert.deepStrictEqual(ids(await storage.find((value) => value.n % 2 === 0)), ["b", "d"], "find() of the even items");
|
|
444
|
+
assert.deepStrictEqual(await storage.find(() => false), [], "find() without a match");
|
|
437
445
|
const error = await rejects(
|
|
438
446
|
() =>
|
|
439
447
|
storage.find(() => {
|
|
@@ -441,7 +449,7 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
441
449
|
}),
|
|
442
450
|
"find() with a predicate that throws",
|
|
443
451
|
);
|
|
444
|
-
|
|
452
|
+
assert.ok(error instanceof Error && error.message.includes("the predicate failed"), `find() must reject with the error of the predicate, not ${show(error instanceof Error ? error.message : error)}`);
|
|
445
453
|
}),
|
|
446
454
|
);
|
|
447
455
|
|
|
@@ -449,12 +457,12 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
449
457
|
withStorage(create, async (storage) => {
|
|
450
458
|
await storage.setBatch([["a", item("a", 1)], ["b", item("b", 2)], ["c", item("c", 3)]]);
|
|
451
459
|
await storage.clear();
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
460
|
+
assert.deepStrictEqual(await storage.size(), 0, "size() after clear()");
|
|
461
|
+
assert.deepStrictEqual(await storage.getAll(), [], "getAll() after clear()");
|
|
462
|
+
assert.deepStrictEqual(await storage.has("a"), false, "has() after clear()");
|
|
455
463
|
await storage.clear();
|
|
456
464
|
await storage.set("d", item("d", 4));
|
|
457
|
-
|
|
465
|
+
assert.deepStrictEqual(await storage.getAll(), [item("d", 4)], "getAll() after clear() and set()");
|
|
458
466
|
}),
|
|
459
467
|
);
|
|
460
468
|
|
|
@@ -470,9 +478,9 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
470
478
|
batch.delete("a");
|
|
471
479
|
batch.set("z", item("z", 0));
|
|
472
480
|
if (storage.getEntries) (await storage.getEntries()).splice(0);
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
481
|
+
assert.deepStrictEqual(ids(await storage.getAll()), ["a", "b"], "getAll() after changes of the results");
|
|
482
|
+
assert.deepStrictEqual(await storage.has("z"), false, "has() of an id that only a result got");
|
|
483
|
+
assert.deepStrictEqual(await storage.getBatch(["a"]), new Map([["a", item("a", 1)]]), "getBatch() after a change of a result");
|
|
476
484
|
}),
|
|
477
485
|
);
|
|
478
486
|
|
|
@@ -486,8 +494,8 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
486
494
|
await storage.setBatch([["b", batch]]);
|
|
487
495
|
batch.tags!.push("changed after setBatch");
|
|
488
496
|
const read = (await storage.get("a"))!;
|
|
489
|
-
|
|
490
|
-
|
|
497
|
+
assert.deepStrictEqual(read, item("a", 1, ["x"]), "get() after a change of the value that set() took");
|
|
498
|
+
assert.deepStrictEqual(await storage.get("b"), item("b", 2, ["y"]), "get() after a change of the value that setBatch() took");
|
|
491
499
|
read.n = 98;
|
|
492
500
|
read.tags!.push("changed after get");
|
|
493
501
|
(await storage.getAll())[0]!.n = 97;
|
|
@@ -512,10 +520,10 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
512
520
|
});
|
|
513
521
|
await storage.set("c", item("c", 3, ["z"]));
|
|
514
522
|
stop();
|
|
515
|
-
|
|
523
|
+
assert.deepStrictEqual(await storage.get("c"), item("c", 3, ["z"]), "get() after an observer changed the value of a change");
|
|
516
524
|
}
|
|
517
|
-
|
|
518
|
-
|
|
525
|
+
assert.deepStrictEqual(await storage.get("a"), item("a", 1, ["x"]), "get() after changes of the read values");
|
|
526
|
+
assert.deepStrictEqual(await storage.get("b"), item("b", 2, ["y"]), "get() of the other item after changes of the read values");
|
|
519
527
|
}),
|
|
520
528
|
);
|
|
521
529
|
|
|
@@ -533,14 +541,14 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
533
541
|
["null", null],
|
|
534
542
|
];
|
|
535
543
|
await storage.setBatch(values);
|
|
536
|
-
for (const [id, value] of values)
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
544
|
+
for (const [id, value] of values) assert.deepStrictEqual(await storage.get(id), value, `get("${id}")`);
|
|
545
|
+
assert.deepStrictEqual(await storage.getAll(), values.map(([, value]) => value), "getAll() of the JSON values");
|
|
546
|
+
assert.deepStrictEqual(await storage.getBatch(values.map(([id]) => id)), new Map(values), "getBatch() of the JSON values");
|
|
547
|
+
assert.deepStrictEqual(await storage.size(), values.length, "size() with the JSON values");
|
|
548
|
+
assert.deepStrictEqual(await storage.has("null"), true, "has() of an id whose value is null");
|
|
541
549
|
await storage.set("single", null);
|
|
542
|
-
|
|
543
|
-
|
|
550
|
+
assert.deepStrictEqual(await storage.get("single"), null, "get() of an id that set() gave the value null");
|
|
551
|
+
assert.deepStrictEqual(await storage.get("missing"), undefined, "get() of a missing id, next to a stored null");
|
|
544
552
|
}),
|
|
545
553
|
);
|
|
546
554
|
|
|
@@ -548,8 +556,8 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
548
556
|
withStorage(create, async (storage) => {
|
|
549
557
|
const count = 20;
|
|
550
558
|
await Promise.all(Array.from({ length: count }, (_, n) => storage.set(`k${n}`, item(`k${n}`, n))));
|
|
551
|
-
|
|
552
|
-
|
|
559
|
+
assert.deepStrictEqual(await storage.size(), count, `size() after ${count} writes at the same time`);
|
|
560
|
+
assert.deepStrictEqual(ids(await storage.getAll()).sort(), Array.from({ length: count }, (_, n) => `k${n}`).sort(), "the ids after the writes at the same time");
|
|
553
561
|
}),
|
|
554
562
|
);
|
|
555
563
|
|
|
@@ -561,10 +569,10 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
561
569
|
await storage.setBatch(entries);
|
|
562
570
|
await storage.set("c", item("c", 30));
|
|
563
571
|
const first = await changes();
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
572
|
+
assert.ok(typeof first.version === "string" && first.version !== "", `the version must be a text that is not empty, not ${show(first.version)}`);
|
|
573
|
+
assert.deepStrictEqual(first.full, true, "full of the first call");
|
|
574
|
+
assert.deepStrictEqual(first.changed, [["c", item("c", 30)], ["a", item("a", 1)], ["b", item("b", 2)]], "changed of the first call: every item, in the order of the entries");
|
|
575
|
+
assert.deepStrictEqual(first.deleted, [], "deleted of a full result");
|
|
568
576
|
}),
|
|
569
577
|
);
|
|
570
578
|
|
|
@@ -579,10 +587,10 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
579
587
|
await storage.delete("missing");
|
|
580
588
|
await storage.set("d", item("d", 4));
|
|
581
589
|
const delta = await changes(start.version);
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
590
|
+
assert.deepStrictEqual(delta.full, false, "full of a delta");
|
|
591
|
+
assert.deepStrictEqual(delta.changed, [["a", item("a", 10)], ["d", item("d", 4)]], "changed of a delta");
|
|
592
|
+
assert.deepStrictEqual(delta.deleted, ["b"], "deleted of a delta");
|
|
593
|
+
assert.ok(delta.version !== start.version, "the version must change after a write");
|
|
586
594
|
}),
|
|
587
595
|
);
|
|
588
596
|
|
|
@@ -599,7 +607,7 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
599
607
|
await storage.delete("c");
|
|
600
608
|
await storage.setBatch([["e", item("e", 5)], ["b", item("b", 21)]]);
|
|
601
609
|
const delta = await changes(start.version);
|
|
602
|
-
|
|
610
|
+
assert.deepStrictEqual(
|
|
603
611
|
{ changed: delta.changed, deleted: delta.deleted },
|
|
604
612
|
{ changed: [["a", item("a", 10)], ["e", item("e", 5)], ["b", item("b", 21)]], deleted: ["d", "c"] },
|
|
605
613
|
"the order of a delta: an id written again after another id comes after it",
|
|
@@ -609,7 +617,7 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
609
617
|
await storage.set("c", item("c", 30));
|
|
610
618
|
await storage.deleteBatch(["e", "a"]);
|
|
611
619
|
const next = await changes(delta.version);
|
|
612
|
-
|
|
620
|
+
assert.deepStrictEqual({ changed: next.changed, deleted: next.deleted }, { changed: [["c", item("c", 30)]], deleted: ["e", "a"] }, "the order of the next delta");
|
|
613
621
|
}),
|
|
614
622
|
);
|
|
615
623
|
|
|
@@ -622,14 +630,14 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
622
630
|
await storage.delete("b");
|
|
623
631
|
await storage.set("b", item("b", 20));
|
|
624
632
|
const back = await changes(start.version);
|
|
625
|
-
|
|
633
|
+
assert.deepStrictEqual({ full: back.full, changed: back.changed, deleted: back.deleted }, { full: false, changed: [["b", item("b", 20)]], deleted: [] }, "a delete, then a set of the id");
|
|
626
634
|
|
|
627
635
|
// The entries are a, c, b now: b went to the end at its new set
|
|
628
636
|
await storage.clear();
|
|
629
637
|
const cleared = await changes(back.version);
|
|
630
|
-
|
|
638
|
+
assert.deepStrictEqual({ full: cleared.full, changed: cleared.changed, deleted: cleared.deleted }, { full: false, changed: [], deleted: ["a", "c", "b"] }, "the changes of clear()");
|
|
631
639
|
const quiet = await changes(cleared.version);
|
|
632
|
-
|
|
640
|
+
assert.deepStrictEqual({ full: quiet.full, changed: quiet.changed, deleted: quiet.deleted }, { full: false, changed: [], deleted: [] }, "the changes without a write");
|
|
633
641
|
}),
|
|
634
642
|
);
|
|
635
643
|
|
|
@@ -641,12 +649,12 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
641
649
|
await changes();
|
|
642
650
|
for (const unknown of ["not a version", ""]) {
|
|
643
651
|
const result = await changes(unknown);
|
|
644
|
-
|
|
652
|
+
assert.deepStrictEqual({ full: result.full, changed: result.changed, deleted: result.deleted }, { full: true, changed: [["a", item("a", 1)], ["b", item("b", 2)]], deleted: [] }, `the changes since ${show(unknown)}`);
|
|
645
653
|
}
|
|
646
654
|
await withStorage(create, async (other) => {
|
|
647
655
|
await other.set("x", item("x", 9));
|
|
648
656
|
const foreign = (await changesOf(other)()).version;
|
|
649
|
-
|
|
657
|
+
assert.deepStrictEqual((await changes(foreign)).full, true, "full for a version of another storage");
|
|
650
658
|
});
|
|
651
659
|
}),
|
|
652
660
|
);
|
|
@@ -662,12 +670,12 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
662
670
|
// Two deletes fit in the log
|
|
663
671
|
await storage.deleteBatch(["a", "b"]);
|
|
664
672
|
const two = await changes(start.version);
|
|
665
|
-
|
|
673
|
+
assert.deepStrictEqual({ full: two.full, changed: two.changed, deleted: two.deleted }, { full: false, changed: [], deleted: ["a", "b"] }, "two deletes in a log of two");
|
|
666
674
|
// The third delete drops the oldest one: the start version gives all items now
|
|
667
675
|
await storage.delete("c");
|
|
668
676
|
const after = await changes(start.version);
|
|
669
|
-
|
|
670
|
-
|
|
677
|
+
assert.deepStrictEqual({ full: after.full, changed: after.changed, deleted: after.deleted }, { full: true, changed: [["d", item("d", 4)]], deleted: [] }, "a version older than a dropped delete");
|
|
678
|
+
assert.deepStrictEqual((await changes(after.version)).full, false, "a version after the dropped deletes still gives a delta");
|
|
671
679
|
}),
|
|
672
680
|
);
|
|
673
681
|
}
|
|
@@ -702,7 +710,7 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
702
710
|
label = `delete(${id})`;
|
|
703
711
|
const existed = model.delete(id);
|
|
704
712
|
if (existed) note(id, true);
|
|
705
|
-
|
|
713
|
+
assert.deepStrictEqual(await storage.delete(id), existed, `the result of step ${step}: ${label}`);
|
|
706
714
|
} else if (kind < 15) {
|
|
707
715
|
const batch: Array<[string, ContractItem]> = Array.from({ length: 1 + random(3) }, (_, index) => {
|
|
708
716
|
const id = pick();
|
|
@@ -722,7 +730,7 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
722
730
|
if (existed) note(id, true);
|
|
723
731
|
return existed;
|
|
724
732
|
}).length;
|
|
725
|
-
|
|
733
|
+
assert.deepStrictEqual(await storage.deleteBatch(batch), expected, `the result of step ${step}: ${label}`);
|
|
726
734
|
} else if (kind < 19) {
|
|
727
735
|
label = "clear()";
|
|
728
736
|
await storage.clear();
|
|
@@ -732,17 +740,17 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
732
740
|
label = "a saved version";
|
|
733
741
|
if (hasChanges) versions.push({ version: (await storage.getChangesSince!()).version, entries: new Map(model), at: clock });
|
|
734
742
|
}
|
|
735
|
-
|
|
743
|
+
assert.deepStrictEqual(await storage.getAll(), [...model.values()], `getAll() after step ${step}: ${label}`);
|
|
736
744
|
}
|
|
737
|
-
|
|
738
|
-
if (storage.getEntries)
|
|
739
|
-
if (storage.getIds)
|
|
745
|
+
assert.deepStrictEqual(await storage.size(), model.size, "size() after the sequence");
|
|
746
|
+
if (storage.getEntries) assert.deepStrictEqual(await storage.getEntries(), [...model.entries()], "getEntries() after the sequence");
|
|
747
|
+
if (storage.getIds) assert.deepStrictEqual(await storage.getIds(), [...model.keys()], "getIds() after the sequence");
|
|
740
748
|
if (!hasChanges) return;
|
|
741
|
-
|
|
749
|
+
assert.ok(versions.length > 0, "the sequence saved no version: change the seed");
|
|
742
750
|
for (const [index, saved] of versions.entries()) {
|
|
743
751
|
const result = await storage.getChangesSince!(saved.version);
|
|
744
752
|
if (result.full) {
|
|
745
|
-
|
|
753
|
+
assert.deepStrictEqual(result.changed, [...model.entries()], `the full changes since version ${index}: every item, in the order of the entries`);
|
|
746
754
|
continue;
|
|
747
755
|
}
|
|
748
756
|
// A delta: the changes after the version, in the order of the last change of each id
|
|
@@ -751,11 +759,11 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
751
759
|
changed: after.filter(([, change]) => !change.deleted).map(([id]): [string, ContractItem] => [id, model.get(id)!]),
|
|
752
760
|
deleted: after.filter(([, change]) => change.deleted).map(([id]) => id),
|
|
753
761
|
};
|
|
754
|
-
|
|
762
|
+
assert.deepStrictEqual({ changed: result.changed, deleted: result.deleted }, expected, `the changes since version ${index}`);
|
|
755
763
|
const rebuilt = new Map(saved.entries);
|
|
756
764
|
for (const id of result.deleted) rebuilt.delete(id);
|
|
757
765
|
for (const [id, value] of result.changed) rebuilt.set(id, value);
|
|
758
|
-
|
|
766
|
+
assert.deepStrictEqual(rebuilt, new Map(model), `the items of version ${index} with its changes`);
|
|
759
767
|
}
|
|
760
768
|
}),
|
|
761
769
|
);
|
|
@@ -771,8 +779,8 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
771
779
|
await storage.setBatch([...DOCUMENTS]);
|
|
772
780
|
for (const [selector, expected] of SELECTOR_CASES) {
|
|
773
781
|
const result = await storage.query!(selector);
|
|
774
|
-
|
|
775
|
-
|
|
782
|
+
assert.deepStrictEqual(entryIds(result), expected, `the ids of query(${show(selector)})`);
|
|
783
|
+
assert.deepStrictEqual(result, queryEntries(DOCUMENTS, selector), `the entries of query(${show(selector)})`);
|
|
776
784
|
}
|
|
777
785
|
}),
|
|
778
786
|
);
|
|
@@ -783,9 +791,9 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
783
791
|
const storage = typed as unknown as JsonStorage;
|
|
784
792
|
await storage.setBatch([...DOCUMENTS]);
|
|
785
793
|
for (const [options, expected] of OPTION_CASES) {
|
|
786
|
-
|
|
794
|
+
assert.deepStrictEqual(entryIds(await storage.query!({}, options)), expected, `the ids of query({}, ${show(options)})`);
|
|
787
795
|
}
|
|
788
|
-
|
|
796
|
+
assert.deepStrictEqual(entryIds(await storage.query!({ n: { $exists: true } }, { sort: { n: -1 }, limit: 2 })), ["e", "d"], "a selector with a sort and a limit");
|
|
789
797
|
}),
|
|
790
798
|
);
|
|
791
799
|
|
|
@@ -833,7 +841,7 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
833
841
|
expected = undefined;
|
|
834
842
|
}
|
|
835
843
|
if (expected === undefined) await rejects(() => storage.query!(selector, options), label);
|
|
836
|
-
else
|
|
844
|
+
else assert.deepStrictEqual(await storage.query!(selector, options), expected, label);
|
|
837
845
|
}
|
|
838
846
|
}),
|
|
839
847
|
);
|
|
@@ -856,8 +864,8 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
856
864
|
await storage.setBatch([...DOCUMENTS]);
|
|
857
865
|
for (const [selector, expected] of REGEX_CASES) {
|
|
858
866
|
const result = await storage.query!(selector);
|
|
859
|
-
|
|
860
|
-
|
|
867
|
+
assert.deepStrictEqual(entryIds(result), expected, `the ids of query(${show(selector)})`);
|
|
868
|
+
assert.deepStrictEqual(result, queryEntries(DOCUMENTS, selector, {}, REGEX_POLICY), `the entries of query(${show(selector)})`);
|
|
861
869
|
}
|
|
862
870
|
await rejects(() => storage.query!(REDOS), `query(${show(REDOS)}) under a policy with $regex`);
|
|
863
871
|
}),
|
|
@@ -903,7 +911,7 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
903
911
|
[{}, { skip: 5 }, ["f", "g"]],
|
|
904
912
|
];
|
|
905
913
|
for (const [selector, query, expected] of permitted) {
|
|
906
|
-
|
|
914
|
+
assert.deepStrictEqual(entryIds(await storage.query!(selector, query)), expected, `the ids of query(${show(selector)}, ${show(query)})`);
|
|
907
915
|
}
|
|
908
916
|
},
|
|
909
917
|
),
|
|
@@ -919,19 +927,19 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
919
927
|
if (lacks(typed, "patch", context)) return;
|
|
920
928
|
const storage = typed as unknown as JsonStorage;
|
|
921
929
|
await storage.set("a", { id: "a", n: 1, tags: ["x"], meta: { kind: "k1", score: 10 } });
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
930
|
+
assert.deepStrictEqual(await storage.patch!("a", { n: 2 }), { id: "a", n: 2, tags: ["x"], meta: { kind: "k1", score: 10 } }, "patch() of one field");
|
|
931
|
+
assert.deepStrictEqual(await storage.patch!("a", { meta: { score: 11, extra: true }, tags: null }), { id: "a", n: 2, meta: { kind: "k1", score: 11, extra: true } }, "patch() of an object field and a null field");
|
|
932
|
+
assert.deepStrictEqual(await storage.patch!("a", { tags: ["y", "z"], meta: { extra: null } }), { id: "a", n: 2, meta: { kind: "k1", score: 11 }, tags: ["y", "z"] }, "patch() of an array field and a nested null");
|
|
933
|
+
assert.deepStrictEqual(await storage.get("a"), { id: "a", n: 2, meta: { kind: "k1", score: 11 }, tags: ["y", "z"] }, "get() after the patches");
|
|
934
|
+
assert.deepStrictEqual(await storage.patch!("missing", { n: 1 }), undefined, "patch() of a missing id");
|
|
935
|
+
assert.deepStrictEqual(await storage.has("missing"), false, "has() of a missing id after patch()");
|
|
936
|
+
assert.deepStrictEqual(await storage.size(), 1, "size() after patch() of a missing id");
|
|
929
937
|
await storage.set("plain", 5);
|
|
930
|
-
|
|
938
|
+
assert.deepStrictEqual(await storage.patch!("plain", { n: 1 }), { n: 1 }, "patch() of an item that is not an object");
|
|
931
939
|
for (const fields of [null, [1], "x", 3]) {
|
|
932
940
|
await rejects(() => storage.patch!("a", fields as never), `patch() with the fields ${show(fields)}`);
|
|
933
941
|
}
|
|
934
|
-
|
|
942
|
+
assert.deepStrictEqual(await storage.get("a"), { id: "a", n: 2, meta: { kind: "k1", score: 11 }, tags: ["y", "z"] }, "get() after the patches that were not correct");
|
|
935
943
|
}),
|
|
936
944
|
);
|
|
937
945
|
|
|
@@ -940,12 +948,12 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
940
948
|
if (lacks(typed, "patch", context)) return;
|
|
941
949
|
const storage = typed as unknown as JsonStorage;
|
|
942
950
|
await storage.set("a", { id: "a", n: 1, tags: ["x", "y"], meta: { kind: "k1", score: 10 } });
|
|
943
|
-
|
|
951
|
+
assert.deepStrictEqual(
|
|
944
952
|
await storage.patch!("a", { $set: { "meta.kind": "k2", note: null, "tags.1": "z", "deep.new.path": 1 }, $unset: ["meta.score"] }),
|
|
945
953
|
{ id: "a", n: 1, tags: ["x", "z"], meta: { kind: "k2" }, note: null, deep: { new: { path: 1 } } },
|
|
946
954
|
"patch() with $set and $unset",
|
|
947
955
|
);
|
|
948
|
-
|
|
956
|
+
assert.deepStrictEqual(
|
|
949
957
|
await storage.patch!("a", { $unset: ["tags.0", "missing.path", "deep"], $set: { "tags.2": "w" } }),
|
|
950
958
|
{ id: "a", n: 1, tags: [null, "z", "w"], meta: { kind: "k2" }, note: null },
|
|
951
959
|
"patch() that removes an element of an array, a missing path and a field, and adds an element",
|
|
@@ -961,9 +969,9 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
961
969
|
["a path past the end of an array", { $set: { "tags.7": "v" } }],
|
|
962
970
|
];
|
|
963
971
|
for (const [what, fields] of wrong) await rejects(() => storage.patch!("a", fields as never), `patch() with ${what}`);
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
972
|
+
assert.deepStrictEqual(await storage.get("a"), after, "get() after the patches that were not correct");
|
|
973
|
+
assert.deepStrictEqual(await storage.patch!("missing", { $set: { n: 1 } }), undefined, "patch() with $set of a missing id");
|
|
974
|
+
assert.deepStrictEqual(await storage.has("missing"), false, "has() of a missing id after patch() with $set");
|
|
967
975
|
}),
|
|
968
976
|
);
|
|
969
977
|
|
|
@@ -972,12 +980,12 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
972
980
|
if (lacks(typed, "patch", context)) return;
|
|
973
981
|
const storage = typed as unknown as JsonStorage;
|
|
974
982
|
await storage.set("a", { id: "a", tags: ["a", "b", "c", "d"], rows: [{ n: 1, keys: ["x"] }, { n: 2, keys: ["y"] }], meta: { kind: "k1" } });
|
|
975
|
-
|
|
983
|
+
assert.deepStrictEqual(
|
|
976
984
|
await storage.patch!("a", { $remove: ["tags.1", "tags.3", "meta.kind"], $insert: { "rows.1.keys.0": "w" } }),
|
|
977
985
|
{ id: "a", tags: ["a", "c"], rows: [{ n: 1, keys: ["x"] }, { n: 2, keys: ["w", "y"] }], meta: {} },
|
|
978
986
|
"patch() with $remove and $insert",
|
|
979
987
|
);
|
|
980
|
-
|
|
988
|
+
assert.deepStrictEqual(
|
|
981
989
|
await storage.patch!("a", { $insert: { "tags.0": "z", "tags.2": "e" }, $remove: ["rows.0", "missing.0"], $set: { "rows.1.n": 3 } }),
|
|
982
990
|
{ id: "a", tags: ["z", "a", "c", "e"], rows: [{ n: 3, keys: ["w", "y"] }], meta: {} },
|
|
983
991
|
"patch() that inserts at the start and at the end, and removes a missing path",
|
|
@@ -992,7 +1000,7 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
992
1000
|
["an $insert into a value that is not an array", { $insert: { "meta.0": "q" } }],
|
|
993
1001
|
];
|
|
994
1002
|
for (const [what, fields] of wrong) await rejects(() => storage.patch!("a", fields as never), `patch() with ${what}`);
|
|
995
|
-
|
|
1003
|
+
assert.deepStrictEqual(await storage.get("a"), after, "get() after the patches that were not correct");
|
|
996
1004
|
}),
|
|
997
1005
|
);
|
|
998
1006
|
|
|
@@ -1005,7 +1013,7 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
1005
1013
|
await Promise.all(Array.from({ length: count }, (_, n) => storage.patch!("a", { [`f${n}`]: n })));
|
|
1006
1014
|
const expected: Record<string, unknown> = { id: "a" };
|
|
1007
1015
|
for (let n = 0; n < count; n++) expected[`f${n}`] = n;
|
|
1008
|
-
|
|
1016
|
+
assert.deepStrictEqual(await storage.get("a"), expected, `get() after ${count} patches at the same time`);
|
|
1009
1017
|
}),
|
|
1010
1018
|
);
|
|
1011
1019
|
|
|
@@ -1017,7 +1025,7 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
1017
1025
|
await storage.patch!("a", { n: 10 });
|
|
1018
1026
|
await storage.patch!("missing", { n: 3 });
|
|
1019
1027
|
const delta = await storage.getChangesSince!(start.version);
|
|
1020
|
-
|
|
1028
|
+
assert.deepStrictEqual({ full: delta.full, changed: delta.changed, deleted: delta.deleted }, { full: false, changed: [["a", item("a", 10)]], deleted: [] }, "the changes after a patch");
|
|
1021
1029
|
}),
|
|
1022
1030
|
);
|
|
1023
1031
|
|
|
@@ -1031,14 +1039,14 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
1031
1039
|
const batches: unknown[][] = [];
|
|
1032
1040
|
const stop = storage.observe!((batch) => batches.push(plainBatch(batch)));
|
|
1033
1041
|
await storage.set("a", item("a", 1));
|
|
1034
|
-
|
|
1042
|
+
assert.deepStrictEqual(batches.length, 1, "the batches when set() resolves");
|
|
1035
1043
|
await storage.setBatch([["b", item("b", 2)], ["c", item("c", 3)]]);
|
|
1036
1044
|
await storage.delete("a");
|
|
1037
1045
|
await storage.deleteBatch(["b", "missing"]);
|
|
1038
1046
|
if (storage.patch) await storage.patch("c", { n: 30 });
|
|
1039
1047
|
await storage.clear();
|
|
1040
1048
|
stop();
|
|
1041
|
-
|
|
1049
|
+
assert.deepStrictEqual(
|
|
1042
1050
|
batches,
|
|
1043
1051
|
[
|
|
1044
1052
|
[{ type: "set", id: "a", value: item("a", 1) }],
|
|
@@ -1067,7 +1075,7 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
1067
1075
|
await storage.setBatch([]);
|
|
1068
1076
|
await storage.clear();
|
|
1069
1077
|
if (storage.patch) await storage.patch("missing", { n: 1 });
|
|
1070
|
-
|
|
1078
|
+
assert.deepStrictEqual(batches, [], "the batches of the writes that change nothing");
|
|
1071
1079
|
await storage.set("a", item("a", 1), { origin: "sync" });
|
|
1072
1080
|
await storage.setBatch([["b", item("b", 2)]], { origin: "app" });
|
|
1073
1081
|
await storage.delete("a", { origin: "sync" });
|
|
@@ -1075,7 +1083,7 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
1075
1083
|
await storage.set("c", item("c", 3), {});
|
|
1076
1084
|
await storage.clear({ origin: "reset" });
|
|
1077
1085
|
stop();
|
|
1078
|
-
|
|
1086
|
+
assert.deepStrictEqual(
|
|
1079
1087
|
batches,
|
|
1080
1088
|
[
|
|
1081
1089
|
[{ type: "set", id: "a", value: item("a", 1), origin: "sync" }],
|
|
@@ -1103,8 +1111,8 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
1103
1111
|
await storage.delete("a");
|
|
1104
1112
|
stopSecond();
|
|
1105
1113
|
await storage.set("b", item("b", 2));
|
|
1106
|
-
|
|
1107
|
-
|
|
1114
|
+
assert.deepStrictEqual(first, ["set"], "the changes of the first observer");
|
|
1115
|
+
assert.deepStrictEqual(second, ["set", "delete"], "the changes of the second observer");
|
|
1108
1116
|
}),
|
|
1109
1117
|
);
|
|
1110
1118
|
|
|
@@ -1114,7 +1122,7 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
1114
1122
|
await storage.flush!();
|
|
1115
1123
|
await storage.set("a", item("a", 1));
|
|
1116
1124
|
await storage.flush!();
|
|
1117
|
-
|
|
1125
|
+
assert.deepStrictEqual(await storage.get("a"), item("a", 1), "get() after flush()");
|
|
1118
1126
|
}),
|
|
1119
1127
|
);
|
|
1120
1128
|
|
|
@@ -1123,8 +1131,8 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
1123
1131
|
if (lacks(storage, "getMetadata", context)) return;
|
|
1124
1132
|
await storage.setBatch([["a", item("a", 1)], ["b", item("b", 2)]]);
|
|
1125
1133
|
const metadata = await storage.getMetadata!();
|
|
1126
|
-
|
|
1127
|
-
|
|
1134
|
+
assert.ok(["memory", "api", "hybrid", "synced", "custom"].includes(metadata.type), `getMetadata() gave the unknown type ${show(metadata.type)}`);
|
|
1135
|
+
assert.deepStrictEqual(metadata.size, 2, "the size of getMetadata()");
|
|
1128
1136
|
}),
|
|
1129
1137
|
);
|
|
1130
1138
|
|
|
@@ -1158,7 +1166,7 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
1158
1166
|
const check = async (when: string): Promise<void> => {
|
|
1159
1167
|
for (const [name, start] of operations) {
|
|
1160
1168
|
const error = await rejects(start, `${name}() ${when}`);
|
|
1161
|
-
|
|
1169
|
+
assert.ok(
|
|
1162
1170
|
error instanceof Error && /closed/i.test(error.message),
|
|
1163
1171
|
`${name}() ${when} must reject with an Error whose message says "closed", not ${show(error instanceof Error ? error.message : error)}`,
|
|
1164
1172
|
);
|
|
@@ -1172,7 +1180,7 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
1172
1180
|
} catch (thrown) {
|
|
1173
1181
|
error = thrown;
|
|
1174
1182
|
}
|
|
1175
|
-
|
|
1183
|
+
assert.ok(error instanceof Error && /closed/i.test(error.message), `observe() ${when} must throw an Error whose message says "closed", not ${show(error instanceof Error ? error.message : error)}`);
|
|
1176
1184
|
};
|
|
1177
1185
|
await check("after close()");
|
|
1178
1186
|
checkObserve("after close()");
|
|
@@ -1194,9 +1202,9 @@ export function collectionStorageContract(options: CollectionStorageContractOpti
|
|
|
1194
1202
|
*/
|
|
1195
1203
|
function conflictsOfRejection(error: unknown, what: string): readonly VersionConflict[] {
|
|
1196
1204
|
const code = (error as { code?: unknown } | null)?.code;
|
|
1197
|
-
|
|
1205
|
+
assert.ok(code === "VERSION_CONFLICT", `${what}: the error must have the code VERSION_CONFLICT, not ${show(code)} (${show(error instanceof Error ? error.message : error)})`);
|
|
1198
1206
|
const conflicts = versionConflictsOf(error);
|
|
1199
|
-
|
|
1207
|
+
assert.ok(conflicts !== undefined, `${what}: the error must have its conflicts`);
|
|
1200
1208
|
return conflicts!;
|
|
1201
1209
|
}
|
|
1202
1210
|
|
|
@@ -1212,10 +1220,10 @@ function addPreconditionCases(
|
|
|
1212
1220
|
await rejects(() => storage.set("a", item("a", 2), { expectedVersion: "v1" }), "set() with expectedVersion on a storage without versions");
|
|
1213
1221
|
await rejects(() => storage.delete("a", { expectedVersion: "v1" }), "delete() with expectedVersion on a storage without versions");
|
|
1214
1222
|
await rejects(() => storage.setBatch([["a", item("a", 3)]], { expectedVersions: [["a", "v1"]] }), "setBatch() with expectedVersions on a storage without versions");
|
|
1215
|
-
|
|
1223
|
+
assert.deepStrictEqual(await storage.get("a"), item("a", 1), "the item after the refused writes");
|
|
1216
1224
|
// A write without a precondition is the write of before
|
|
1217
1225
|
await storage.set("a", item("a", 4));
|
|
1218
|
-
|
|
1226
|
+
assert.deepStrictEqual(await storage.get("a"), item("a", 4), "a write without expectedVersion");
|
|
1219
1227
|
}),
|
|
1220
1228
|
);
|
|
1221
1229
|
|
|
@@ -1226,11 +1234,11 @@ function addPreconditionCases(
|
|
|
1226
1234
|
withStorage(versioned, async (storage) => {
|
|
1227
1235
|
await storage.set("a", item("a", 1));
|
|
1228
1236
|
await storage.set("a", item("a", 2), { expectedVersion: "v1" });
|
|
1229
|
-
|
|
1237
|
+
assert.deepStrictEqual(await storage.get("a"), item("a", 2), "the item after set() with the expected version");
|
|
1230
1238
|
await storage.set("a", item("a", 3));
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1239
|
+
assert.deepStrictEqual(await storage.get("a"), item("a", 3), "the item after set() without expectedVersion");
|
|
1240
|
+
assert.deepStrictEqual(await storage.delete("a", { expectedVersion: "v3" }), true, "delete() with the expected version");
|
|
1241
|
+
assert.deepStrictEqual(await storage.get("a"), undefined, "the item after delete() with the expected version");
|
|
1234
1242
|
}),
|
|
1235
1243
|
);
|
|
1236
1244
|
|
|
@@ -1244,15 +1252,15 @@ function addPreconditionCases(
|
|
|
1244
1252
|
const before = storage.getChangesSince ? (await storage.getChangesSince()).version : undefined;
|
|
1245
1253
|
const expected: VersionConflict = { id: "a", expected: "v2", found: true, value: item("a", 1), version: "v1" };
|
|
1246
1254
|
const setError = await rejects(() => storage.set("a", item("a", 3), { expectedVersion: "v2" }), "set() with a stale version");
|
|
1247
|
-
|
|
1255
|
+
assert.deepStrictEqual(conflictsOfRejection(setError, "set() with a stale version"), [expected], "the conflicts of set() with a stale version");
|
|
1248
1256
|
const deleteError = await rejects(() => storage.delete("a", { expectedVersion: "v2" }), "delete() with a stale version");
|
|
1249
|
-
|
|
1250
|
-
|
|
1257
|
+
assert.deepStrictEqual(conflictsOfRejection(deleteError, "delete() with a stale version"), [expected], "the conflicts of delete() with a stale version");
|
|
1258
|
+
assert.deepStrictEqual(await storage.get("a"), item("a", 1), "the item after the refused writes");
|
|
1251
1259
|
stop?.();
|
|
1252
|
-
|
|
1260
|
+
assert.deepStrictEqual(batches, 0, "the batches of observe() for the refused writes");
|
|
1253
1261
|
if (storage.getChangesSince && before !== undefined) {
|
|
1254
1262
|
const after = await storage.getChangesSince(before);
|
|
1255
|
-
|
|
1263
|
+
assert.deepStrictEqual({ changed: after.changed, deleted: after.deleted }, { changed: [], deleted: [] }, "the changes after the refused writes");
|
|
1256
1264
|
}
|
|
1257
1265
|
}),
|
|
1258
1266
|
);
|
|
@@ -1260,13 +1268,13 @@ function addPreconditionCases(
|
|
|
1260
1268
|
add("the expected version null expects no item", () =>
|
|
1261
1269
|
withStorage(versioned, async (storage) => {
|
|
1262
1270
|
await storage.set("b", item("b", 1), { expectedVersion: null });
|
|
1263
|
-
|
|
1271
|
+
assert.deepStrictEqual(await storage.get("b"), item("b", 1), "set() with expectedVersion null of a missing id");
|
|
1264
1272
|
const exists = await rejects(() => storage.set("b", item("b", 2), { expectedVersion: null }), "set() with expectedVersion null of an existing id");
|
|
1265
|
-
|
|
1273
|
+
assert.deepStrictEqual(conflictsOfRejection(exists, "set() with expectedVersion null of an existing id"), [{ id: "b", expected: null, found: true, value: item("b", 1), version: "v1" }], "the conflict of an existing id");
|
|
1266
1274
|
const missing = await rejects(() => storage.set("c", item("c", 1), { expectedVersion: "v1" }), "set() with a version of a missing id");
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1275
|
+
assert.deepStrictEqual(conflictsOfRejection(missing, "set() with a version of a missing id"), [{ id: "c", expected: "v1", found: false, version: null }], "the conflict of a missing id");
|
|
1276
|
+
assert.deepStrictEqual(await storage.has("c"), false, "the missing id after the refused set()");
|
|
1277
|
+
assert.deepStrictEqual(await storage.delete("x", { expectedVersion: null }), false, "delete() with expectedVersion null of a missing id");
|
|
1270
1278
|
}),
|
|
1271
1279
|
);
|
|
1272
1280
|
|
|
@@ -1277,11 +1285,11 @@ function addPreconditionCases(
|
|
|
1277
1285
|
() => storage.setBatch([["a", item("a", 2)], ["b", item("b", 2)], ["c", item("c", 1)]], { expectedVersions: [["a", "v1"], ["b", "v9"]] }),
|
|
1278
1286
|
"setBatch() with one stale version",
|
|
1279
1287
|
);
|
|
1280
|
-
|
|
1281
|
-
|
|
1288
|
+
assert.deepStrictEqual(conflictsOfRejection(error, "setBatch() with one stale version"), [{ id: "b", expected: "v9", found: true, value: item("b", 1), version: "v1" }], "the conflicts of setBatch()");
|
|
1289
|
+
assert.deepStrictEqual(await storage.getBatch(["a", "b", "c"]), new Map([["a", item("a", 1)], ["b", item("b", 1)]]), "the items after the refused batch");
|
|
1282
1290
|
// An id without an expected version has no precondition
|
|
1283
1291
|
await storage.setBatch([["a", item("a", 2)], ["b", item("b", 2)], ["c", item("c", 1)]], { expectedVersions: [["a", "v1"], ["b", "v1"]] });
|
|
1284
|
-
|
|
1292
|
+
assert.deepStrictEqual(await storage.getBatch(["a", "b", "c"]), new Map([["a", item("a", 2)], ["b", item("b", 2)], ["c", item("c", 1)]]), "the items after the batch");
|
|
1285
1293
|
}),
|
|
1286
1294
|
);
|
|
1287
1295
|
|
|
@@ -1293,11 +1301,11 @@ function addPreconditionCases(
|
|
|
1293
1301
|
storage.set("a", item("a", 3), { expectedVersion: "v1" }),
|
|
1294
1302
|
]);
|
|
1295
1303
|
const won = results.flatMap((result, index) => (result.status === "fulfilled" ? [index] : []));
|
|
1296
|
-
|
|
1304
|
+
assert.deepStrictEqual(won.length, 1, "the number of writes that won");
|
|
1297
1305
|
const lost = results.find((result): result is PromiseRejectedResult => result.status === "rejected")!;
|
|
1298
1306
|
const conflicts = conflictsOfRejection(lost.reason, "the write that lost");
|
|
1299
|
-
|
|
1300
|
-
|
|
1307
|
+
assert.deepStrictEqual(conflicts.map((conflict) => conflict.id), ["a"], "the conflict of the write that lost");
|
|
1308
|
+
assert.deepStrictEqual(await storage.get("a"), item("a", won[0] === 0 ? 2 : 3), "the item of the write that won");
|
|
1301
1309
|
}),
|
|
1302
1310
|
);
|
|
1303
1311
|
}
|