@graffiti-garden/api 0.6.1 → 0.6.3
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/dist/index.cjs +1 -1
- package/dist/index.cjs.map +3 -3
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +3 -3
- package/dist/src/1-api.d.ts +11 -7
- package/dist/src/1-api.d.ts.map +1 -1
- package/dist/src/3-errors.d.ts +2 -2
- package/dist/tests/discover.d.ts +1 -1
- package/dist/tests/discover.d.ts.map +1 -1
- package/dist/tests/orphans.d.ts +1 -1
- package/dist/tests/orphans.d.ts.map +1 -1
- package/dist/tests/utils.d.ts +2 -1
- package/dist/tests/utils.d.ts.map +1 -1
- package/dist/tests.mjs +241 -188
- package/dist/tests.mjs.map +2 -2
- package/package.json +1 -1
- package/src/1-api.ts +11 -7
- package/src/3-errors.ts +5 -5
- package/tests/discover.ts +223 -183
- package/tests/orphans.ts +71 -55
- package/tests/utils.ts +20 -0
package/tests/discover.ts
CHANGED
|
@@ -2,14 +2,21 @@ import { it, expect, describe, assert, beforeAll } from "vitest";
|
|
|
2
2
|
import type {
|
|
3
3
|
Graffiti,
|
|
4
4
|
GraffitiObjectBase,
|
|
5
|
-
GraffitiObjectStreamEntry,
|
|
6
5
|
GraffitiSession,
|
|
7
6
|
JSONSchema,
|
|
8
7
|
} from "@graffiti-garden/api";
|
|
9
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
randomString,
|
|
10
|
+
nextStreamValue,
|
|
11
|
+
randomPutObject,
|
|
12
|
+
continueStream,
|
|
13
|
+
} from "./utils";
|
|
10
14
|
|
|
11
15
|
export const graffitiDiscoverTests = (
|
|
12
|
-
useGraffiti: () => Pick<
|
|
16
|
+
useGraffiti: () => Pick<
|
|
17
|
+
Graffiti,
|
|
18
|
+
"discover" | "put" | "delete" | "patch" | "continueObjectStream"
|
|
19
|
+
>,
|
|
13
20
|
useSession1: () => GraffitiSession | Promise<GraffitiSession>,
|
|
14
21
|
useSession2: () => GraffitiSession | Promise<GraffitiSession>,
|
|
15
22
|
) => {
|
|
@@ -469,197 +476,230 @@ export const graffitiDiscoverTests = (
|
|
|
469
476
|
expect(counts.get("other")).toBe(1);
|
|
470
477
|
});
|
|
471
478
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
479
|
+
for (const continueType of ["cursor", "continue"] as const) {
|
|
480
|
+
describe(`continue discover with ${continueType}`, () => {
|
|
481
|
+
it("discover for deleted content", async () => {
|
|
482
|
+
const object = randomPutObject();
|
|
483
|
+
|
|
484
|
+
const putted = await graffiti.put<{}>(object, session);
|
|
485
|
+
|
|
486
|
+
const iterator1 = graffiti.discover<{}>(object.channels, {});
|
|
487
|
+
const value1 = await nextStreamValue<{}>(iterator1);
|
|
488
|
+
expect(value1.value).toEqual(object.value);
|
|
489
|
+
const returnValue = await iterator1.next();
|
|
490
|
+
assert(returnValue.done, "value2 is not done");
|
|
491
|
+
|
|
492
|
+
const deleted = await graffiti.delete(putted, session);
|
|
493
|
+
|
|
494
|
+
const iterator = graffiti.discover(object.channels, {});
|
|
495
|
+
await expect(iterator.next()).resolves.toHaveProperty("done", true);
|
|
496
|
+
|
|
497
|
+
const tombIterator = continueStream<{}>(
|
|
498
|
+
graffiti,
|
|
499
|
+
returnValue.value,
|
|
500
|
+
continueType,
|
|
501
|
+
);
|
|
502
|
+
const value = await tombIterator.next();
|
|
503
|
+
assert(!value.done && !value.value.error, "value is done");
|
|
504
|
+
assert(value.value.tombstone, "value is not tombstone");
|
|
505
|
+
expect(value.value.object.url).toEqual(putted.url);
|
|
506
|
+
await expect(tombIterator.next()).resolves.toHaveProperty(
|
|
507
|
+
"done",
|
|
508
|
+
true,
|
|
509
|
+
);
|
|
510
|
+
});
|
|
487
511
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
512
|
+
it("discover for replaced channels", async () => {
|
|
513
|
+
// Do this a bunch to check for concurrency issues
|
|
514
|
+
async function runTest() {
|
|
515
|
+
const object1 = randomPutObject();
|
|
516
|
+
const putted = await graffiti.put<{}>(object1, session);
|
|
517
|
+
|
|
518
|
+
const iterator3 = graffiti.discover<{}>(object1.channels, {});
|
|
519
|
+
const value3 = await nextStreamValue<{}>(iterator3);
|
|
520
|
+
expect(value3.value).toEqual(object1.value);
|
|
521
|
+
const returnValue = await iterator3.next();
|
|
522
|
+
assert(returnValue.done, "value2 is not done");
|
|
523
|
+
|
|
524
|
+
const object2 = randomPutObject();
|
|
525
|
+
const replaced = await graffiti.put<{}>(
|
|
526
|
+
{
|
|
527
|
+
...object2,
|
|
528
|
+
url: putted.url,
|
|
529
|
+
},
|
|
530
|
+
session,
|
|
531
|
+
);
|
|
532
|
+
|
|
533
|
+
const iterator1 = graffiti.discover<{}>(object1.channels, {});
|
|
534
|
+
const iterator2 = graffiti.discover<{}>(object2.channels, {});
|
|
535
|
+
const tombIterator = continueStream<{}>(
|
|
536
|
+
graffiti,
|
|
537
|
+
returnValue.value,
|
|
538
|
+
continueType,
|
|
539
|
+
);
|
|
540
|
+
|
|
541
|
+
if (putted.lastModified === replaced.lastModified) {
|
|
542
|
+
const value1 = await iterator1.next();
|
|
543
|
+
const value2 = await iterator2.next();
|
|
544
|
+
const value3 = await tombIterator.next();
|
|
545
|
+
|
|
546
|
+
// Only one should be done
|
|
547
|
+
expect(value1.done || value2.done).toBe(true);
|
|
548
|
+
expect(value1.done && value2.done).toBe(false);
|
|
549
|
+
|
|
550
|
+
assert(!value3.done && !value3.value.error, "value is done");
|
|
551
|
+
expect(value3.value.tombstone || value2.done).toBe(true);
|
|
552
|
+
expect(value3.value.tombstone && value2.done).toBe(false);
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
495
555
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
556
|
+
// Otherwise 1 should be done and 2 should not
|
|
557
|
+
const value5 = await iterator1.next();
|
|
558
|
+
assert(value5.done, "value5 is not done");
|
|
559
|
+
|
|
560
|
+
const value4 = await tombIterator.next();
|
|
561
|
+
assert(!value4.done && !value4.value.error, "value is done");
|
|
562
|
+
|
|
563
|
+
assert(value4.value.tombstone, "value is not tombstone");
|
|
564
|
+
expect(value4.value.object.url).toEqual(putted.url);
|
|
565
|
+
expect(value4.value.object.lastModified).toEqual(
|
|
566
|
+
replaced.lastModified,
|
|
567
|
+
);
|
|
568
|
+
|
|
569
|
+
const value2 = await nextStreamValue<{}>(iterator2);
|
|
570
|
+
await expect(iterator2.next()).resolves.toHaveProperty(
|
|
571
|
+
"done",
|
|
572
|
+
true,
|
|
573
|
+
);
|
|
574
|
+
|
|
575
|
+
expect(value2.value).toEqual(object2.value);
|
|
576
|
+
expect(value2.channels).toEqual(object2.channels);
|
|
577
|
+
expect(value2.lastModified).toEqual(replaced.lastModified);
|
|
578
|
+
|
|
579
|
+
// Replace the channels back
|
|
580
|
+
const patched = await graffiti.patch(
|
|
581
|
+
{
|
|
582
|
+
channels: [
|
|
583
|
+
{ op: "replace", path: "", value: object1.channels },
|
|
584
|
+
],
|
|
585
|
+
},
|
|
586
|
+
replaced,
|
|
587
|
+
session,
|
|
588
|
+
);
|
|
589
|
+
|
|
590
|
+
const tombIterator2 = continueStream<{}>(
|
|
591
|
+
graffiti,
|
|
592
|
+
value5.value,
|
|
593
|
+
continueType,
|
|
594
|
+
);
|
|
595
|
+
|
|
596
|
+
let result:
|
|
597
|
+
| {
|
|
598
|
+
tombstone: true;
|
|
599
|
+
object: {
|
|
600
|
+
url: string;
|
|
601
|
+
lastModified: number;
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
| {
|
|
605
|
+
tombstone?: undefined;
|
|
606
|
+
object: GraffitiObjectBase;
|
|
607
|
+
}
|
|
608
|
+
| undefined;
|
|
609
|
+
for await (const value of tombIterator2) {
|
|
610
|
+
if (value.error) continue;
|
|
611
|
+
if (!result) {
|
|
612
|
+
result = value;
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
if (
|
|
616
|
+
value.object.lastModified > result.object.lastModified ||
|
|
617
|
+
(value.object.lastModified === result.object.lastModified &&
|
|
618
|
+
!value.tombstone &&
|
|
619
|
+
result.tombstone)
|
|
620
|
+
) {
|
|
621
|
+
result = value;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
501
624
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
625
|
+
assert(result, "result is not defined");
|
|
626
|
+
assert(!result.tombstone, "result is tombstone");
|
|
627
|
+
expect(result.object.url).toEqual(replaced.url);
|
|
628
|
+
expect(result.object.lastModified).toEqual(patched.lastModified);
|
|
629
|
+
expect(result.object.channels).toEqual(object1.channels);
|
|
630
|
+
expect(result.object.value).toEqual(object2.value);
|
|
631
|
+
}
|
|
507
632
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
...object2,
|
|
512
|
-
url: putted.url,
|
|
513
|
-
},
|
|
514
|
-
session,
|
|
515
|
-
);
|
|
516
|
-
|
|
517
|
-
const iterator1 = graffiti.discover(object1.channels, {});
|
|
518
|
-
const iterator2 = graffiti.discover<{}>(object2.channels, {});
|
|
519
|
-
const tombIterator = returnValue.value.continue();
|
|
520
|
-
|
|
521
|
-
if (putted.lastModified === replaced.lastModified) {
|
|
522
|
-
const value1 = await iterator1.next();
|
|
523
|
-
const value2 = await iterator2.next();
|
|
524
|
-
const value3 = await tombIterator.next();
|
|
525
|
-
|
|
526
|
-
// Only one should be done
|
|
527
|
-
expect(value1.done || value2.done).toBe(true);
|
|
528
|
-
expect(value1.done && value2.done).toBe(false);
|
|
529
|
-
|
|
530
|
-
assert(!value3.done && !value3.value.error, "value is done");
|
|
531
|
-
expect(value3.value.tombstone || value2.done).toBe(true);
|
|
532
|
-
expect(value3.value.tombstone && value2.done).toBe(false);
|
|
533
|
-
continue;
|
|
534
|
-
}
|
|
633
|
+
// Run the test 20 times in parallel to check for concurrency issues
|
|
634
|
+
await Promise.allSettled(Array.from({ length: 20 }, () => runTest()));
|
|
635
|
+
});
|
|
535
636
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
637
|
+
it("discover for patched allowed", async () => {
|
|
638
|
+
const object = randomPutObject();
|
|
639
|
+
const putted = await graffiti.put<{}>(object, session);
|
|
539
640
|
|
|
540
|
-
|
|
541
|
-
|
|
641
|
+
const iterator1 = graffiti.discover<{}>(object.channels, {});
|
|
642
|
+
const value1 = await nextStreamValue<{}>(iterator1);
|
|
643
|
+
expect(value1.value).toEqual(object.value);
|
|
644
|
+
const returnValue = await iterator1.next();
|
|
645
|
+
assert(returnValue.done, "value2 is not done");
|
|
542
646
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
647
|
+
await graffiti.patch(
|
|
648
|
+
{
|
|
649
|
+
allowed: [{ op: "add", path: "", value: [] }],
|
|
650
|
+
},
|
|
651
|
+
putted,
|
|
652
|
+
session,
|
|
653
|
+
);
|
|
654
|
+
const iterator2 = graffiti.discover(object.channels, {});
|
|
655
|
+
expect(await iterator2.next()).toHaveProperty("done", true);
|
|
656
|
+
|
|
657
|
+
const iterator = continueStream<{}>(
|
|
658
|
+
graffiti,
|
|
659
|
+
returnValue.value,
|
|
660
|
+
continueType,
|
|
661
|
+
);
|
|
662
|
+
const value = await iterator.next();
|
|
663
|
+
assert(!value.done && !value.value.error, "value is done");
|
|
664
|
+
assert(value.value.tombstone, "value is not tombstone");
|
|
665
|
+
expect(value.value.object.url).toEqual(putted.url);
|
|
666
|
+
await expect(iterator.next()).resolves.toHaveProperty("done", true);
|
|
667
|
+
});
|
|
546
668
|
|
|
547
|
-
|
|
548
|
-
|
|
669
|
+
it("put concurrently and discover one", async () => {
|
|
670
|
+
const object = randomPutObject();
|
|
549
671
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
expect(value2.lastModified).toEqual(replaced.lastModified);
|
|
672
|
+
// Put a first one to get a URI
|
|
673
|
+
const putted = await graffiti.put<{}>(object, session);
|
|
553
674
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
675
|
+
const putPromises = Array(99)
|
|
676
|
+
.fill(0)
|
|
677
|
+
.map(() =>
|
|
678
|
+
graffiti.put<{}>(
|
|
679
|
+
{
|
|
680
|
+
...object,
|
|
681
|
+
url: putted.url,
|
|
682
|
+
},
|
|
683
|
+
session,
|
|
684
|
+
),
|
|
685
|
+
);
|
|
686
|
+
await Promise.all(putPromises);
|
|
687
|
+
|
|
688
|
+
const iterator = graffiti.discover(object.channels, {});
|
|
689
|
+
let tombstoneCount = 0;
|
|
690
|
+
let valueCount = 0;
|
|
691
|
+
for await (const result of iterator) {
|
|
692
|
+
assert(!result.error, "result has error");
|
|
693
|
+
if (result.tombstone) {
|
|
694
|
+
tombstoneCount++;
|
|
695
|
+
} else {
|
|
696
|
+
valueCount++;
|
|
576
697
|
}
|
|
577
|
-
| undefined;
|
|
578
|
-
for await (const value of tombIterator2) {
|
|
579
|
-
if (value.error) continue;
|
|
580
|
-
if (!result) {
|
|
581
|
-
result = value;
|
|
582
|
-
continue;
|
|
583
|
-
}
|
|
584
|
-
if (
|
|
585
|
-
value.object.lastModified > result.object.lastModified ||
|
|
586
|
-
(value.object.lastModified === result.object.lastModified &&
|
|
587
|
-
!value.tombstone &&
|
|
588
|
-
result.tombstone)
|
|
589
|
-
) {
|
|
590
|
-
result = value;
|
|
591
698
|
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
expect(result.object.lastModified).toEqual(patched.lastModified);
|
|
598
|
-
expect(result.object.channels).toEqual(object1.channels);
|
|
599
|
-
expect(result.object.value).toEqual(object2.value);
|
|
600
|
-
}
|
|
601
|
-
});
|
|
602
|
-
|
|
603
|
-
it("discover for patched allowed", async () => {
|
|
604
|
-
const object = randomPutObject();
|
|
605
|
-
const putted = await graffiti.put<{}>(object, session);
|
|
606
|
-
|
|
607
|
-
const iterator1 = graffiti.discover<{}>(object.channels, {});
|
|
608
|
-
const value1 = await nextStreamValue<{}>(iterator1);
|
|
609
|
-
expect(value1.value).toEqual(object.value);
|
|
610
|
-
const returnValue = await iterator1.next();
|
|
611
|
-
assert(returnValue.done, "value2 is not done");
|
|
612
|
-
|
|
613
|
-
await graffiti.patch(
|
|
614
|
-
{
|
|
615
|
-
allowed: [{ op: "add", path: "", value: [] }],
|
|
616
|
-
},
|
|
617
|
-
putted,
|
|
618
|
-
session,
|
|
619
|
-
);
|
|
620
|
-
const iterator2 = graffiti.discover(object.channels, {});
|
|
621
|
-
expect(await iterator2.next()).toHaveProperty("done", true);
|
|
622
|
-
|
|
623
|
-
const iterator = returnValue.value.continue();
|
|
624
|
-
const value = await iterator.next();
|
|
625
|
-
assert(!value.done && !value.value.error, "value is done");
|
|
626
|
-
assert(value.value.tombstone, "value is not tombstone");
|
|
627
|
-
expect(value.value.object.url).toEqual(putted.url);
|
|
628
|
-
await expect(iterator.next()).resolves.toHaveProperty("done", true);
|
|
629
|
-
});
|
|
630
|
-
|
|
631
|
-
it("put concurrently and discover one", async () => {
|
|
632
|
-
const object = randomPutObject();
|
|
633
|
-
|
|
634
|
-
// Put a first one to get a URI
|
|
635
|
-
const putted = await graffiti.put<{}>(object, session);
|
|
636
|
-
|
|
637
|
-
const putPromises = Array(99)
|
|
638
|
-
.fill(0)
|
|
639
|
-
.map(() =>
|
|
640
|
-
graffiti.put<{}>(
|
|
641
|
-
{
|
|
642
|
-
...object,
|
|
643
|
-
url: putted.url,
|
|
644
|
-
},
|
|
645
|
-
session,
|
|
646
|
-
),
|
|
647
|
-
);
|
|
648
|
-
await Promise.all(putPromises);
|
|
649
|
-
|
|
650
|
-
const iterator = graffiti.discover(object.channels, {});
|
|
651
|
-
let tombstoneCount = 0;
|
|
652
|
-
let valueCount = 0;
|
|
653
|
-
for await (const result of iterator) {
|
|
654
|
-
assert(!result.error, "result has error");
|
|
655
|
-
if (result.tombstone) {
|
|
656
|
-
tombstoneCount++;
|
|
657
|
-
} else {
|
|
658
|
-
valueCount++;
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
expect(tombstoneCount).toBe(0);
|
|
662
|
-
expect(valueCount).toBe(1);
|
|
663
|
-
});
|
|
699
|
+
expect(tombstoneCount).toBe(0);
|
|
700
|
+
expect(valueCount).toBe(1);
|
|
701
|
+
});
|
|
702
|
+
});
|
|
703
|
+
}
|
|
664
704
|
});
|
|
665
705
|
};
|
package/tests/orphans.ts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { it, expect, describe, assert, beforeAll } from "vitest";
|
|
2
2
|
import type { Graffiti, GraffitiSession } from "@graffiti-garden/api";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
randomPutObject,
|
|
5
|
+
randomString,
|
|
6
|
+
nextStreamValue,
|
|
7
|
+
continueStream,
|
|
8
|
+
} from "./utils";
|
|
4
9
|
|
|
5
10
|
export const graffitiOrphanTests = (
|
|
6
11
|
useGraffiti: () => Pick<
|
|
7
12
|
Graffiti,
|
|
8
|
-
"recoverOrphans" | "put" | "delete" | "patch"
|
|
13
|
+
"recoverOrphans" | "put" | "delete" | "patch" | "continueObjectStream"
|
|
9
14
|
>,
|
|
10
15
|
useSession1: () => GraffitiSession | Promise<GraffitiSession>,
|
|
11
16
|
useSession2: () => GraffitiSession | Promise<GraffitiSession>,
|
|
@@ -46,67 +51,78 @@ export const graffitiOrphanTests = (
|
|
|
46
51
|
expect(numResults).toBe(1);
|
|
47
52
|
});
|
|
48
53
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
54
|
+
for (const continueType of ["continue", "cursor"] as const) {
|
|
55
|
+
describe(`continue orphans with ${continueType}`, () => {
|
|
56
|
+
it("replaced orphan, no longer", async () => {
|
|
57
|
+
const object = randomPutObject();
|
|
58
|
+
object.channels = [];
|
|
59
|
+
const putOrphan = await graffiti.put<{}>(object, session);
|
|
53
60
|
|
|
54
|
-
|
|
55
|
-
|
|
61
|
+
// Wait for the put to be processed
|
|
62
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
56
63
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
properties: {
|
|
62
|
-
value: {
|
|
64
|
+
expect(Object.keys(object.value).length).toBeGreaterThanOrEqual(1);
|
|
65
|
+
expect(Object.keys(object.value)[0]).toBeTypeOf("string");
|
|
66
|
+
const iterator1 = graffiti.recoverOrphans<{}>(
|
|
67
|
+
{
|
|
63
68
|
properties: {
|
|
64
|
-
|
|
65
|
-
|
|
69
|
+
value: {
|
|
70
|
+
properties: {
|
|
71
|
+
[Object.keys(object.value)[0]]: {
|
|
72
|
+
type: "string",
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
required: [Object.keys(object.value)[0]],
|
|
66
76
|
},
|
|
67
77
|
},
|
|
68
|
-
required: [Object.keys(object.value)[0]],
|
|
69
78
|
},
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const returnValue = await iterator1.next();
|
|
77
|
-
assert(returnValue.done, "value2 is not done");
|
|
79
|
+
session,
|
|
80
|
+
);
|
|
81
|
+
const value1 = await nextStreamValue<{}>(iterator1);
|
|
82
|
+
expect(value1.value).toEqual(object.value);
|
|
83
|
+
const returnValue = await iterator1.next();
|
|
84
|
+
assert(returnValue.done, "value2 is not done");
|
|
78
85
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
const putNotOrphan = await graffiti.put<{}>(
|
|
87
|
+
{
|
|
88
|
+
...putOrphan,
|
|
89
|
+
...object,
|
|
90
|
+
channels: [randomString()],
|
|
91
|
+
},
|
|
92
|
+
session,
|
|
93
|
+
);
|
|
94
|
+
expect(putNotOrphan.url).toBe(putOrphan.url);
|
|
95
|
+
expect(putNotOrphan.lastModified).toBeGreaterThan(
|
|
96
|
+
putOrphan.lastModified,
|
|
97
|
+
);
|
|
89
98
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
99
|
+
// The tombstone will not appear to a fresh iterator
|
|
100
|
+
const orphanIterator = graffiti.recoverOrphans({}, session);
|
|
101
|
+
let numResults = 0;
|
|
102
|
+
for await (const orphan of orphanIterator) {
|
|
103
|
+
if (orphan.error) continue;
|
|
104
|
+
if (orphan.object.url === putOrphan.url) {
|
|
105
|
+
numResults++;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
expect(numResults).toBe(0);
|
|
100
109
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
110
|
+
const iterator2 = continueStream<{}>(
|
|
111
|
+
graffiti,
|
|
112
|
+
returnValue.value,
|
|
113
|
+
continueType,
|
|
114
|
+
session,
|
|
115
|
+
);
|
|
116
|
+
const value2 = await iterator2.next();
|
|
117
|
+
assert(
|
|
118
|
+
!value2.done && !value2.value.error,
|
|
119
|
+
"value2 is done or has error",
|
|
120
|
+
);
|
|
121
|
+
assert(value2.value.tombstone, "value2 is not tombstone");
|
|
122
|
+
expect(value2.value.object.url).toBe(putOrphan.url);
|
|
123
|
+
await expect(iterator2.next()).resolves.toHaveProperty("done", true);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
}
|
|
111
127
|
});
|
|
112
128
|
};
|
package/tests/utils.ts
CHANGED
|
@@ -4,6 +4,10 @@ import type {
|
|
|
4
4
|
GraffitiObjectStream,
|
|
5
5
|
JSONSchema,
|
|
6
6
|
GraffitiObject,
|
|
7
|
+
GraffitiObjectStreamReturn,
|
|
8
|
+
GraffitiObjectStreamContinue,
|
|
9
|
+
Graffiti,
|
|
10
|
+
GraffitiSession,
|
|
7
11
|
} from "@graffiti-garden/api";
|
|
8
12
|
|
|
9
13
|
export function randomString(): string {
|
|
@@ -38,3 +42,19 @@ export async function nextStreamValue<Schema extends JSONSchema>(
|
|
|
38
42
|
assert(!result.value.tombstone, "result has been deleted!");
|
|
39
43
|
return result.value.object;
|
|
40
44
|
}
|
|
45
|
+
|
|
46
|
+
export function continueStream<Schema extends JSONSchema>(
|
|
47
|
+
graffiti: Pick<Graffiti, "continueObjectStream">,
|
|
48
|
+
streamReturn: GraffitiObjectStreamReturn<Schema>,
|
|
49
|
+
type: "cursor" | "continue",
|
|
50
|
+
session?: GraffitiSession | null,
|
|
51
|
+
): GraffitiObjectStreamContinue<Schema> {
|
|
52
|
+
if (type === "cursor") {
|
|
53
|
+
return graffiti.continueObjectStream(
|
|
54
|
+
streamReturn.cursor,
|
|
55
|
+
session,
|
|
56
|
+
) as unknown as GraffitiObjectStreamContinue<Schema>;
|
|
57
|
+
} else {
|
|
58
|
+
return streamReturn.continue();
|
|
59
|
+
}
|
|
60
|
+
}
|