cojson 0.19.19 → 0.19.21
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/dist/coValueCore/coValueCore.d.ts +9 -0
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +21 -0
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/coValues/account.d.ts.map +1 -1
- package/dist/coValues/account.js +10 -10
- package/dist/coValues/account.js.map +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +10 -0
- package/dist/config.js.map +1 -1
- package/dist/exports.d.ts +7 -1
- package/dist/exports.d.ts.map +1 -1
- package/dist/exports.js +4 -1
- package/dist/exports.js.map +1 -1
- package/dist/ids.d.ts +1 -1
- package/dist/ids.d.ts.map +1 -1
- package/dist/ids.js.map +1 -1
- package/dist/knownState.d.ts +5 -0
- package/dist/knownState.d.ts.map +1 -1
- package/dist/knownState.js +15 -0
- package/dist/knownState.js.map +1 -1
- package/dist/localNode.d.ts.map +1 -1
- package/dist/localNode.js +8 -2
- package/dist/localNode.js.map +1 -1
- package/dist/queue/IncomingMessagesQueue.d.ts +6 -7
- package/dist/queue/IncomingMessagesQueue.d.ts.map +1 -1
- package/dist/queue/IncomingMessagesQueue.js +7 -30
- package/dist/queue/IncomingMessagesQueue.js.map +1 -1
- package/dist/queue/LinkedList.d.ts +1 -1
- package/dist/queue/LinkedList.d.ts.map +1 -1
- package/dist/queue/LinkedList.js.map +1 -1
- package/dist/queue/StorageStreamingQueue.d.ts +43 -0
- package/dist/queue/StorageStreamingQueue.d.ts.map +1 -0
- package/dist/queue/StorageStreamingQueue.js +70 -0
- package/dist/queue/StorageStreamingQueue.js.map +1 -0
- package/dist/storage/knownState.d.ts +5 -0
- package/dist/storage/knownState.d.ts.map +1 -1
- package/dist/storage/knownState.js +11 -0
- package/dist/storage/knownState.js.map +1 -1
- package/dist/storage/sqlite/client.d.ts +2 -0
- package/dist/storage/sqlite/client.d.ts.map +1 -1
- package/dist/storage/sqlite/client.js +18 -0
- package/dist/storage/sqlite/client.js.map +1 -1
- package/dist/storage/sqliteAsync/client.d.ts +2 -0
- package/dist/storage/sqliteAsync/client.d.ts.map +1 -1
- package/dist/storage/sqliteAsync/client.js +20 -0
- package/dist/storage/sqliteAsync/client.js.map +1 -1
- package/dist/storage/storageAsync.d.ts +2 -0
- package/dist/storage/storageAsync.d.ts.map +1 -1
- package/dist/storage/storageAsync.js +40 -0
- package/dist/storage/storageAsync.js.map +1 -1
- package/dist/storage/storageSync.d.ts +9 -2
- package/dist/storage/storageSync.d.ts.map +1 -1
- package/dist/storage/storageSync.js +71 -44
- package/dist/storage/storageSync.js.map +1 -1
- package/dist/storage/types.d.ts +20 -0
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/sync.d.ts +34 -0
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +185 -46
- package/dist/sync.js.map +1 -1
- package/dist/tests/IncomingMessagesQueue.test.js +4 -150
- package/dist/tests/IncomingMessagesQueue.test.js.map +1 -1
- package/dist/tests/StorageApiAsync.test.js +91 -0
- package/dist/tests/StorageApiAsync.test.js.map +1 -1
- package/dist/tests/StorageApiSync.test.js +91 -0
- package/dist/tests/StorageApiSync.test.js.map +1 -1
- package/dist/tests/StorageStreamingQueue.test.d.ts +2 -0
- package/dist/tests/StorageStreamingQueue.test.d.ts.map +1 -0
- package/dist/tests/StorageStreamingQueue.test.js +213 -0
- package/dist/tests/StorageStreamingQueue.test.js.map +1 -0
- package/dist/tests/SyncManager.processQueues.test.d.ts +2 -0
- package/dist/tests/SyncManager.processQueues.test.d.ts.map +1 -0
- package/dist/tests/SyncManager.processQueues.test.js +208 -0
- package/dist/tests/SyncManager.processQueues.test.js.map +1 -0
- package/dist/tests/coValueCore.loadFromStorage.test.js +1 -0
- package/dist/tests/coValueCore.loadFromStorage.test.js.map +1 -1
- package/dist/tests/knownState.lazyLoading.test.d.ts +2 -0
- package/dist/tests/knownState.lazyLoading.test.d.ts.map +1 -0
- package/dist/tests/knownState.lazyLoading.test.js +166 -0
- package/dist/tests/knownState.lazyLoading.test.js.map +1 -0
- package/dist/tests/messagesTestUtils.d.ts +5 -2
- package/dist/tests/messagesTestUtils.d.ts.map +1 -1
- package/dist/tests/messagesTestUtils.js +4 -0
- package/dist/tests/messagesTestUtils.js.map +1 -1
- package/dist/tests/setup.d.ts +2 -0
- package/dist/tests/setup.d.ts.map +1 -0
- package/dist/tests/setup.js +4 -0
- package/dist/tests/setup.js.map +1 -0
- package/dist/tests/sync.garbageCollection.test.js.map +1 -1
- package/dist/tests/sync.load.test.js +388 -0
- package/dist/tests/sync.load.test.js.map +1 -1
- package/dist/tests/sync.mesh.test.js +23 -23
- package/dist/tests/sync.storage.test.js +176 -20
- package/dist/tests/sync.storage.test.js.map +1 -1
- package/dist/tests/sync.test.js +1 -1
- package/dist/tests/sync.test.js.map +1 -1
- package/dist/tests/testStorage.js +36 -0
- package/dist/tests/testStorage.js.map +1 -1
- package/dist/tests/testUtils.d.ts +16 -4
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +2 -2
- package/dist/tests/testUtils.js.map +1 -1
- package/package.json +4 -4
- package/src/coValueCore/coValueCore.ts +26 -0
- package/src/coValues/account.ts +12 -14
- package/src/config.ts +13 -0
- package/src/exports.ts +6 -0
- package/src/ids.ts +1 -1
- package/src/knownState.ts +24 -0
- package/src/localNode.ts +9 -2
- package/src/queue/IncomingMessagesQueue.ts +7 -39
- package/src/queue/LinkedList.ts +1 -1
- package/src/queue/StorageStreamingQueue.ts +96 -0
- package/src/storage/knownState.ts +12 -0
- package/src/storage/sqlite/client.ts +31 -0
- package/src/storage/sqliteAsync/client.ts +35 -0
- package/src/storage/storageAsync.ts +51 -0
- package/src/storage/storageSync.ts +121 -55
- package/src/storage/types.ts +29 -0
- package/src/sync.ts +210 -47
- package/src/tests/IncomingMessagesQueue.test.ts +4 -206
- package/src/tests/StorageApiAsync.test.ts +136 -0
- package/src/tests/StorageApiSync.test.ts +132 -0
- package/src/tests/StorageStreamingQueue.test.ts +276 -0
- package/src/tests/SyncManager.processQueues.test.ts +287 -0
- package/src/tests/coValueCore.loadFromStorage.test.ts +3 -0
- package/src/tests/knownState.lazyLoading.test.ts +217 -0
- package/src/tests/messagesTestUtils.ts +10 -3
- package/src/tests/setup.ts +4 -0
- package/src/tests/sync.garbageCollection.test.ts +1 -3
- package/src/tests/sync.load.test.ts +483 -1
- package/src/tests/sync.mesh.test.ts +23 -23
- package/src/tests/sync.storage.test.ts +224 -32
- package/src/tests/sync.test.ts +1 -9
- package/src/tests/testStorage.ts +38 -0
- package/src/tests/testUtils.ts +16 -4
- package/vitest.config.ts +1 -0
|
@@ -823,4 +823,140 @@ describe("StorageApiAsync", () => {
|
|
|
823
823
|
expect(() => storage.close()).not.toThrow();
|
|
824
824
|
});
|
|
825
825
|
});
|
|
826
|
+
|
|
827
|
+
describe("loadKnownState", () => {
|
|
828
|
+
test("should return cached knownState if available", async () => {
|
|
829
|
+
const { fixturesNode, dbPath } = await createFixturesNode();
|
|
830
|
+
const { storage } = await createTestNode(dbPath);
|
|
831
|
+
|
|
832
|
+
// Create a group to have data in the database
|
|
833
|
+
const group = fixturesNode.createGroup();
|
|
834
|
+
group.addMember("everyone", "reader");
|
|
835
|
+
await group.core.waitForSync();
|
|
836
|
+
|
|
837
|
+
// First call should hit the database and cache the result
|
|
838
|
+
const result1 = await new Promise<CoValueKnownState | undefined>(
|
|
839
|
+
(resolve) => {
|
|
840
|
+
storage.loadKnownState(group.id, resolve);
|
|
841
|
+
},
|
|
842
|
+
);
|
|
843
|
+
|
|
844
|
+
expect(result1).toBeDefined();
|
|
845
|
+
expect(result1?.id).toBe(group.id);
|
|
846
|
+
expect(result1?.header).toBe(true);
|
|
847
|
+
|
|
848
|
+
// Second call should return from cache
|
|
849
|
+
const result2 = await new Promise<CoValueKnownState | undefined>(
|
|
850
|
+
(resolve) => {
|
|
851
|
+
storage.loadKnownState(group.id, resolve);
|
|
852
|
+
},
|
|
853
|
+
);
|
|
854
|
+
|
|
855
|
+
expect(result2).toEqual(result1);
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
test("should return undefined for non-existent CoValue", async () => {
|
|
859
|
+
const { storage } = await createTestNode();
|
|
860
|
+
|
|
861
|
+
const result = await new Promise<CoValueKnownState | undefined>(
|
|
862
|
+
(resolve) => {
|
|
863
|
+
storage.loadKnownState("co_nonexistent" as any, resolve);
|
|
864
|
+
},
|
|
865
|
+
);
|
|
866
|
+
|
|
867
|
+
expect(result).toBeUndefined();
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
test("should deduplicate concurrent requests for the same ID", async () => {
|
|
871
|
+
const { fixturesNode, dbPath } = await createFixturesNode();
|
|
872
|
+
const { storage } = await createTestNode(dbPath);
|
|
873
|
+
|
|
874
|
+
// Create a group to have data in the database
|
|
875
|
+
const group = fixturesNode.createGroup();
|
|
876
|
+
group.addMember("everyone", "reader");
|
|
877
|
+
await group.core.waitForSync();
|
|
878
|
+
|
|
879
|
+
// Clear the cache to force database access
|
|
880
|
+
storage.knownStates.knownStates.clear();
|
|
881
|
+
|
|
882
|
+
// Spy on the database client to track how many times it's called
|
|
883
|
+
const dbClientSpy = vi.spyOn(
|
|
884
|
+
(storage as any).dbClient,
|
|
885
|
+
"getCoValueKnownState",
|
|
886
|
+
);
|
|
887
|
+
|
|
888
|
+
// Make multiple concurrent requests for the same ID
|
|
889
|
+
const promises = [
|
|
890
|
+
new Promise<CoValueKnownState | undefined>((resolve) => {
|
|
891
|
+
storage.loadKnownState(group.id, resolve);
|
|
892
|
+
}),
|
|
893
|
+
new Promise<CoValueKnownState | undefined>((resolve) => {
|
|
894
|
+
storage.loadKnownState(group.id, resolve);
|
|
895
|
+
}),
|
|
896
|
+
new Promise<CoValueKnownState | undefined>((resolve) => {
|
|
897
|
+
storage.loadKnownState(group.id, resolve);
|
|
898
|
+
}),
|
|
899
|
+
];
|
|
900
|
+
|
|
901
|
+
const results = await Promise.all(promises);
|
|
902
|
+
|
|
903
|
+
// All results should be the same
|
|
904
|
+
expect(results[0]).toEqual(results[1]);
|
|
905
|
+
expect(results[1]).toEqual(results[2]);
|
|
906
|
+
expect(results[0]?.id).toBe(group.id);
|
|
907
|
+
|
|
908
|
+
// Database should only be called once due to deduplication
|
|
909
|
+
expect(dbClientSpy).toHaveBeenCalledTimes(1);
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
test("should use cache and not query database when cache is populated", async () => {
|
|
913
|
+
const { fixturesNode, dbPath } = await createFixturesNode();
|
|
914
|
+
const { storage } = await createTestNode(dbPath);
|
|
915
|
+
|
|
916
|
+
// Create a group to have data in the database
|
|
917
|
+
const group = fixturesNode.createGroup();
|
|
918
|
+
group.addMember("everyone", "reader");
|
|
919
|
+
await group.core.waitForSync();
|
|
920
|
+
|
|
921
|
+
// Spy on the database client to track calls
|
|
922
|
+
const dbClientSpy = vi.spyOn(
|
|
923
|
+
(storage as any).dbClient,
|
|
924
|
+
"getCoValueKnownState",
|
|
925
|
+
);
|
|
926
|
+
|
|
927
|
+
// First call - should hit the database
|
|
928
|
+
const result1 = await new Promise<CoValueKnownState | undefined>(
|
|
929
|
+
(resolve) => {
|
|
930
|
+
storage.loadKnownState(group.id, resolve);
|
|
931
|
+
},
|
|
932
|
+
);
|
|
933
|
+
|
|
934
|
+
expect(result1).toBeDefined();
|
|
935
|
+
expect(dbClientSpy).toHaveBeenCalledTimes(1);
|
|
936
|
+
|
|
937
|
+
// Clear the spy to reset call count
|
|
938
|
+
dbClientSpy.mockClear();
|
|
939
|
+
|
|
940
|
+
// Second call - should use cache, not database
|
|
941
|
+
const result2 = await new Promise<CoValueKnownState | undefined>(
|
|
942
|
+
(resolve) => {
|
|
943
|
+
storage.loadKnownState(group.id, resolve);
|
|
944
|
+
},
|
|
945
|
+
);
|
|
946
|
+
|
|
947
|
+
expect(result2).toEqual(result1);
|
|
948
|
+
// Database should NOT be called since cache was hit
|
|
949
|
+
expect(dbClientSpy).toHaveBeenCalledTimes(0);
|
|
950
|
+
|
|
951
|
+
// Third call - also from cache
|
|
952
|
+
const result3 = await new Promise<CoValueKnownState | undefined>(
|
|
953
|
+
(resolve) => {
|
|
954
|
+
storage.loadKnownState(group.id, resolve);
|
|
955
|
+
},
|
|
956
|
+
);
|
|
957
|
+
|
|
958
|
+
expect(result3).toEqual(result1);
|
|
959
|
+
expect(dbClientSpy).toHaveBeenCalledTimes(0);
|
|
960
|
+
});
|
|
961
|
+
});
|
|
826
962
|
});
|
|
@@ -619,4 +619,136 @@ describe("StorageApiSync", () => {
|
|
|
619
619
|
expect(() => storage.close()).not.toThrow();
|
|
620
620
|
});
|
|
621
621
|
});
|
|
622
|
+
|
|
623
|
+
describe("loadKnownState", () => {
|
|
624
|
+
test("should return correct knownState structure for existing CoValue", async () => {
|
|
625
|
+
const { fixturesNode, dbPath } = await createFixturesNode();
|
|
626
|
+
const { storage } = await createTestNode(dbPath);
|
|
627
|
+
|
|
628
|
+
// Create a group to have data in the database
|
|
629
|
+
const group = fixturesNode.createGroup();
|
|
630
|
+
group.addMember("everyone", "reader");
|
|
631
|
+
await group.core.waitForSync();
|
|
632
|
+
|
|
633
|
+
const result = await new Promise<CoValueKnownState | undefined>(
|
|
634
|
+
(resolve) => {
|
|
635
|
+
storage.loadKnownState(group.id, resolve);
|
|
636
|
+
},
|
|
637
|
+
);
|
|
638
|
+
|
|
639
|
+
expect(result).toBeDefined();
|
|
640
|
+
expect(result?.id).toBe(group.id);
|
|
641
|
+
expect(result?.header).toBe(true);
|
|
642
|
+
expect(result?.sessions).toEqual(group.core.knownState().sessions);
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
test("should return undefined for non-existent CoValue", async () => {
|
|
646
|
+
const { storage } = await createTestNode();
|
|
647
|
+
|
|
648
|
+
const result = await new Promise<CoValueKnownState | undefined>(
|
|
649
|
+
(resolve) => {
|
|
650
|
+
storage.loadKnownState("co_nonexistent" as any, resolve);
|
|
651
|
+
},
|
|
652
|
+
);
|
|
653
|
+
|
|
654
|
+
expect(result).toBeUndefined();
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
test("should handle CoValue with no sessions (header only)", async () => {
|
|
658
|
+
const { fixturesNode, dbPath } = await createFixturesNode();
|
|
659
|
+
const { storage } = await createTestNode(dbPath);
|
|
660
|
+
|
|
661
|
+
// Create a CoValue with just a header (no transactions yet)
|
|
662
|
+
const coValue = fixturesNode.createCoValue({
|
|
663
|
+
type: "comap",
|
|
664
|
+
ruleset: { type: "unsafeAllowAll" },
|
|
665
|
+
meta: null,
|
|
666
|
+
...crypto.createdNowUnique(),
|
|
667
|
+
});
|
|
668
|
+
await coValue.waitForSync();
|
|
669
|
+
|
|
670
|
+
const result = await new Promise<CoValueKnownState | undefined>(
|
|
671
|
+
(resolve) => {
|
|
672
|
+
storage.loadKnownState(coValue.id, resolve);
|
|
673
|
+
},
|
|
674
|
+
);
|
|
675
|
+
|
|
676
|
+
expect(result).toBeDefined();
|
|
677
|
+
expect(result?.id).toBe(coValue.id);
|
|
678
|
+
expect(result?.header).toBe(true);
|
|
679
|
+
// The sessions should have one entry with lastIdx = 0 (just header)
|
|
680
|
+
expect(Object.keys(result?.sessions || {}).length).toBe(0);
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
test("should handle CoValue with multiple sessions", async () => {
|
|
684
|
+
const { fixturesNode, dbPath } = await createFixturesNode();
|
|
685
|
+
const { fixturesNode: fixturesNode2 } = await createFixturesNode(dbPath);
|
|
686
|
+
const { storage } = await createTestNode(dbPath);
|
|
687
|
+
|
|
688
|
+
// Create a CoValue and have two nodes make transactions
|
|
689
|
+
const coValue = fixturesNode.createCoValue({
|
|
690
|
+
type: "comap",
|
|
691
|
+
ruleset: { type: "unsafeAllowAll" },
|
|
692
|
+
meta: null,
|
|
693
|
+
...crypto.createdNowUnique(),
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
coValue.makeTransaction([{ key1: "value1" }], "trusting");
|
|
697
|
+
await coValue.waitForSync();
|
|
698
|
+
|
|
699
|
+
const coValueOnNode2 = await loadCoValueOrFail(
|
|
700
|
+
fixturesNode2,
|
|
701
|
+
coValue.id as CoID<RawCoMap>,
|
|
702
|
+
);
|
|
703
|
+
|
|
704
|
+
coValueOnNode2.set("key2", "value2", "trusting");
|
|
705
|
+
await coValueOnNode2.core.waitForSync();
|
|
706
|
+
|
|
707
|
+
const result = await new Promise<CoValueKnownState | undefined>(
|
|
708
|
+
(resolve) => {
|
|
709
|
+
storage.loadKnownState(coValue.id, resolve);
|
|
710
|
+
},
|
|
711
|
+
);
|
|
712
|
+
|
|
713
|
+
expect(result).toBeDefined();
|
|
714
|
+
expect(result?.id).toBe(coValue.id);
|
|
715
|
+
expect(result?.header).toBe(true);
|
|
716
|
+
// Should have two sessions
|
|
717
|
+
expect(Object.keys(result?.sessions || {}).length).toBe(2);
|
|
718
|
+
// Verify sessions match the expected state
|
|
719
|
+
expect(result?.sessions).toEqual(
|
|
720
|
+
coValueOnNode2.core.knownState().sessions,
|
|
721
|
+
);
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
test("should use cache when knownState is cached", async () => {
|
|
725
|
+
const { fixturesNode, dbPath } = await createFixturesNode();
|
|
726
|
+
const { storage } = await createTestNode(dbPath);
|
|
727
|
+
|
|
728
|
+
// Create a group to have data in the database
|
|
729
|
+
const group = fixturesNode.createGroup();
|
|
730
|
+
group.addMember("everyone", "reader");
|
|
731
|
+
await group.core.waitForSync();
|
|
732
|
+
|
|
733
|
+
// First call should hit the database and cache the result
|
|
734
|
+
const result1 = await new Promise<CoValueKnownState | undefined>(
|
|
735
|
+
(resolve) => {
|
|
736
|
+
storage.loadKnownState(group.id, resolve);
|
|
737
|
+
},
|
|
738
|
+
);
|
|
739
|
+
|
|
740
|
+
expect(result1).toBeDefined();
|
|
741
|
+
expect(result1?.id).toBe(group.id);
|
|
742
|
+
expect(result1?.header).toBe(true);
|
|
743
|
+
|
|
744
|
+
// Second call should return from cache
|
|
745
|
+
const result2 = await new Promise<CoValueKnownState | undefined>(
|
|
746
|
+
(resolve) => {
|
|
747
|
+
storage.loadKnownState(group.id, resolve);
|
|
748
|
+
},
|
|
749
|
+
);
|
|
750
|
+
|
|
751
|
+
expect(result2).toEqual(result1);
|
|
752
|
+
});
|
|
753
|
+
});
|
|
622
754
|
});
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { describe, expect, test, vi } from "vitest";
|
|
2
|
+
import { CO_VALUE_PRIORITY } from "../priority.js";
|
|
3
|
+
import { StorageStreamingQueue } from "../queue/StorageStreamingQueue.js";
|
|
4
|
+
|
|
5
|
+
describe("StorageStreamingQueue", () => {
|
|
6
|
+
describe("constructor", () => {
|
|
7
|
+
test("should initialize with empty queues", () => {
|
|
8
|
+
const queue = new StorageStreamingQueue();
|
|
9
|
+
expect(queue.isEmpty()).toBe(true);
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe("push", () => {
|
|
14
|
+
test("should add MEDIUM priority entry to queue", () => {
|
|
15
|
+
const queue = new StorageStreamingQueue();
|
|
16
|
+
|
|
17
|
+
queue.push(() => {}, CO_VALUE_PRIORITY.MEDIUM);
|
|
18
|
+
|
|
19
|
+
expect(queue.isEmpty()).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("should add LOW priority entry to queue", () => {
|
|
23
|
+
const queue = new StorageStreamingQueue();
|
|
24
|
+
|
|
25
|
+
queue.push(() => {}, CO_VALUE_PRIORITY.LOW);
|
|
26
|
+
|
|
27
|
+
expect(queue.isEmpty()).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("should add HIGH priority entry to queue", () => {
|
|
31
|
+
const queue = new StorageStreamingQueue();
|
|
32
|
+
|
|
33
|
+
queue.push(() => {}, CO_VALUE_PRIORITY.HIGH);
|
|
34
|
+
|
|
35
|
+
expect(queue.isEmpty()).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("should accept multiple entries", () => {
|
|
39
|
+
const queue = new StorageStreamingQueue();
|
|
40
|
+
const entry1 = () => {};
|
|
41
|
+
const entry2 = () => {};
|
|
42
|
+
|
|
43
|
+
queue.push(entry1, CO_VALUE_PRIORITY.MEDIUM);
|
|
44
|
+
queue.push(entry2, CO_VALUE_PRIORITY.MEDIUM);
|
|
45
|
+
|
|
46
|
+
expect(queue.pull()).toBe(entry1);
|
|
47
|
+
expect(queue.pull()).toBe(entry2);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("pull", () => {
|
|
52
|
+
test("should return undefined for empty queue", () => {
|
|
53
|
+
const queue = new StorageStreamingQueue();
|
|
54
|
+
expect(queue.pull()).toBeUndefined();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("should return and remove entry from queue", () => {
|
|
58
|
+
const queue = new StorageStreamingQueue();
|
|
59
|
+
const entry = () => {};
|
|
60
|
+
|
|
61
|
+
queue.push(entry, CO_VALUE_PRIORITY.MEDIUM);
|
|
62
|
+
const pulled = queue.pull();
|
|
63
|
+
|
|
64
|
+
expect(pulled).toBe(entry);
|
|
65
|
+
expect(queue.isEmpty()).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("should pull HIGH priority before MEDIUM and LOW", () => {
|
|
69
|
+
const queue = new StorageStreamingQueue();
|
|
70
|
+
const lowEntry = () => {};
|
|
71
|
+
const mediumEntry = () => {};
|
|
72
|
+
const highEntry = () => {};
|
|
73
|
+
|
|
74
|
+
// Push in reverse order
|
|
75
|
+
queue.push(lowEntry, CO_VALUE_PRIORITY.LOW);
|
|
76
|
+
queue.push(mediumEntry, CO_VALUE_PRIORITY.MEDIUM);
|
|
77
|
+
queue.push(highEntry, CO_VALUE_PRIORITY.HIGH);
|
|
78
|
+
|
|
79
|
+
// Should pull HIGH first, then MEDIUM, then LOW
|
|
80
|
+
expect(queue.pull()).toBe(highEntry);
|
|
81
|
+
expect(queue.pull()).toBe(mediumEntry);
|
|
82
|
+
expect(queue.pull()).toBe(lowEntry);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("should pull MEDIUM priority before LOW priority", () => {
|
|
86
|
+
const queue = new StorageStreamingQueue();
|
|
87
|
+
const lowEntry = () => {};
|
|
88
|
+
const mediumEntry = () => {};
|
|
89
|
+
|
|
90
|
+
// Push LOW first, then MEDIUM
|
|
91
|
+
queue.push(lowEntry, CO_VALUE_PRIORITY.LOW);
|
|
92
|
+
queue.push(mediumEntry, CO_VALUE_PRIORITY.MEDIUM);
|
|
93
|
+
|
|
94
|
+
// Should pull MEDIUM first
|
|
95
|
+
expect(queue.pull()).toBe(mediumEntry);
|
|
96
|
+
expect(queue.pull()).toBe(lowEntry);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("should pull entries in FIFO order within same priority", () => {
|
|
100
|
+
const queue = new StorageStreamingQueue();
|
|
101
|
+
const entry1 = () => {};
|
|
102
|
+
const entry2 = () => {};
|
|
103
|
+
const entry3 = () => {};
|
|
104
|
+
|
|
105
|
+
queue.push(entry1, CO_VALUE_PRIORITY.MEDIUM);
|
|
106
|
+
queue.push(entry2, CO_VALUE_PRIORITY.MEDIUM);
|
|
107
|
+
queue.push(entry3, CO_VALUE_PRIORITY.MEDIUM);
|
|
108
|
+
|
|
109
|
+
expect(queue.pull()).toBe(entry1);
|
|
110
|
+
expect(queue.pull()).toBe(entry2);
|
|
111
|
+
expect(queue.pull()).toBe(entry3);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("should handle interleaved priorities correctly", () => {
|
|
115
|
+
const queue = new StorageStreamingQueue();
|
|
116
|
+
const low1 = () => {};
|
|
117
|
+
const medium1 = () => {};
|
|
118
|
+
const high1 = () => {};
|
|
119
|
+
const low2 = () => {};
|
|
120
|
+
const medium2 = () => {};
|
|
121
|
+
const high2 = () => {};
|
|
122
|
+
|
|
123
|
+
queue.push(low1, CO_VALUE_PRIORITY.LOW);
|
|
124
|
+
queue.push(medium1, CO_VALUE_PRIORITY.MEDIUM);
|
|
125
|
+
queue.push(high1, CO_VALUE_PRIORITY.HIGH);
|
|
126
|
+
queue.push(low2, CO_VALUE_PRIORITY.LOW);
|
|
127
|
+
queue.push(medium2, CO_VALUE_PRIORITY.MEDIUM);
|
|
128
|
+
queue.push(high2, CO_VALUE_PRIORITY.HIGH);
|
|
129
|
+
|
|
130
|
+
// All HIGH should come first, in order
|
|
131
|
+
expect(queue.pull()).toBe(high1);
|
|
132
|
+
expect(queue.pull()).toBe(high2);
|
|
133
|
+
// Then all MEDIUM, in order
|
|
134
|
+
expect(queue.pull()).toBe(medium1);
|
|
135
|
+
expect(queue.pull()).toBe(medium2);
|
|
136
|
+
// Then all LOW, in order
|
|
137
|
+
expect(queue.pull()).toBe(low1);
|
|
138
|
+
expect(queue.pull()).toBe(low2);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe("isEmpty", () => {
|
|
143
|
+
test("should return true for empty queue", () => {
|
|
144
|
+
const queue = new StorageStreamingQueue();
|
|
145
|
+
expect(queue.isEmpty()).toBe(true);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("should return false when MEDIUM queue has entries", () => {
|
|
149
|
+
const queue = new StorageStreamingQueue();
|
|
150
|
+
queue.push(() => {}, CO_VALUE_PRIORITY.MEDIUM);
|
|
151
|
+
expect(queue.isEmpty()).toBe(false);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("should return false when LOW queue has entries", () => {
|
|
155
|
+
const queue = new StorageStreamingQueue();
|
|
156
|
+
queue.push(() => {}, CO_VALUE_PRIORITY.LOW);
|
|
157
|
+
expect(queue.isEmpty()).toBe(false);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("should return false when HIGH queue has entries", () => {
|
|
161
|
+
const queue = new StorageStreamingQueue();
|
|
162
|
+
queue.push(() => {}, CO_VALUE_PRIORITY.HIGH);
|
|
163
|
+
expect(queue.isEmpty()).toBe(false);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("should return true after all entries are pulled", () => {
|
|
167
|
+
const queue = new StorageStreamingQueue();
|
|
168
|
+
queue.push(() => {}, CO_VALUE_PRIORITY.MEDIUM);
|
|
169
|
+
queue.push(() => {}, CO_VALUE_PRIORITY.LOW);
|
|
170
|
+
|
|
171
|
+
queue.pull();
|
|
172
|
+
queue.pull();
|
|
173
|
+
|
|
174
|
+
expect(queue.isEmpty()).toBe(true);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe("callback invocation", () => {
|
|
179
|
+
test("should not invoke callback when pushed", () => {
|
|
180
|
+
const queue = new StorageStreamingQueue();
|
|
181
|
+
const callback = vi.fn();
|
|
182
|
+
|
|
183
|
+
queue.push(callback, CO_VALUE_PRIORITY.MEDIUM);
|
|
184
|
+
|
|
185
|
+
expect(callback).not.toHaveBeenCalled();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("should not invoke callback when pulled", () => {
|
|
189
|
+
const queue = new StorageStreamingQueue();
|
|
190
|
+
const callback = vi.fn();
|
|
191
|
+
|
|
192
|
+
queue.push(callback, CO_VALUE_PRIORITY.MEDIUM);
|
|
193
|
+
queue.pull();
|
|
194
|
+
|
|
195
|
+
expect(callback).not.toHaveBeenCalled();
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("should allow caller to invoke callback after pull", () => {
|
|
199
|
+
const queue = new StorageStreamingQueue();
|
|
200
|
+
const callback = vi.fn();
|
|
201
|
+
|
|
202
|
+
queue.push(callback, CO_VALUE_PRIORITY.MEDIUM);
|
|
203
|
+
const pulled = queue.pull();
|
|
204
|
+
|
|
205
|
+
expect(callback).not.toHaveBeenCalled();
|
|
206
|
+
|
|
207
|
+
pulled?.();
|
|
208
|
+
|
|
209
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
describe("setListener and emit", () => {
|
|
214
|
+
test("should call listener when emit is called", () => {
|
|
215
|
+
const queue = new StorageStreamingQueue();
|
|
216
|
+
const listener = vi.fn();
|
|
217
|
+
|
|
218
|
+
queue.setListener(listener);
|
|
219
|
+
queue.emit();
|
|
220
|
+
|
|
221
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("should not throw when emit is called without listener", () => {
|
|
225
|
+
const queue = new StorageStreamingQueue();
|
|
226
|
+
|
|
227
|
+
expect(() => queue.emit()).not.toThrow();
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test("should call listener multiple times on multiple emits", () => {
|
|
231
|
+
const queue = new StorageStreamingQueue();
|
|
232
|
+
const listener = vi.fn();
|
|
233
|
+
|
|
234
|
+
queue.setListener(listener);
|
|
235
|
+
queue.emit();
|
|
236
|
+
queue.emit();
|
|
237
|
+
queue.emit();
|
|
238
|
+
|
|
239
|
+
expect(listener).toHaveBeenCalledTimes(3);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("should use latest listener when setListener is called multiple times", () => {
|
|
243
|
+
const queue = new StorageStreamingQueue();
|
|
244
|
+
const listener1 = vi.fn();
|
|
245
|
+
const listener2 = vi.fn();
|
|
246
|
+
|
|
247
|
+
queue.setListener(listener1);
|
|
248
|
+
queue.setListener(listener2);
|
|
249
|
+
queue.emit();
|
|
250
|
+
|
|
251
|
+
expect(listener1).not.toHaveBeenCalled();
|
|
252
|
+
expect(listener2).toHaveBeenCalledTimes(1);
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
describe("edge cases", () => {
|
|
257
|
+
test("should handle alternating push and pull operations", () => {
|
|
258
|
+
const queue = new StorageStreamingQueue();
|
|
259
|
+
|
|
260
|
+
const entry1 = () => {};
|
|
261
|
+
const entry2 = () => {};
|
|
262
|
+
const entry3 = () => {};
|
|
263
|
+
|
|
264
|
+
queue.push(entry1, CO_VALUE_PRIORITY.MEDIUM);
|
|
265
|
+
expect(queue.pull()).toBe(entry1);
|
|
266
|
+
|
|
267
|
+
queue.push(entry2, CO_VALUE_PRIORITY.LOW);
|
|
268
|
+
expect(queue.pull()).toBe(entry2);
|
|
269
|
+
|
|
270
|
+
expect(queue.isEmpty()).toBe(true);
|
|
271
|
+
|
|
272
|
+
queue.push(entry3, CO_VALUE_PRIORITY.MEDIUM);
|
|
273
|
+
expect(queue.pull()).toBe(entry3);
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
});
|