@adonis-agora/durable 0.54.1 → 0.54.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/dist/assets/spa/assets/index-9kO8tWjg.js +16 -0
  3. package/dist/assets/spa/assets/index-9kO8tWjg.js.map +1 -0
  4. package/dist/assets/spa/index.html +1 -1
  5. package/dist/src/codec-state-store.d.ts +12 -1
  6. package/dist/src/codec-state-store.d.ts.map +1 -1
  7. package/dist/src/codec-state-store.js +38 -0
  8. package/dist/src/codec-state-store.js.map +1 -1
  9. package/dist/src/dashboard/index.d.ts +1 -1
  10. package/dist/src/dashboard/index.js +1 -1
  11. package/dist/src/engine.d.ts +59 -3
  12. package/dist/src/engine.d.ts.map +1 -1
  13. package/dist/src/engine.js +232 -53
  14. package/dist/src/engine.js.map +1 -1
  15. package/dist/src/index.d.ts +1 -1
  16. package/dist/src/index.js +1 -1
  17. package/dist/src/interfaces.d.ts +60 -2
  18. package/dist/src/interfaces.d.ts.map +1 -1
  19. package/dist/src/interfaces.js.map +1 -1
  20. package/dist/src/stores/lucid-mappers.d.ts +6 -1
  21. package/dist/src/stores/lucid-mappers.d.ts.map +1 -1
  22. package/dist/src/stores/lucid-mappers.js +41 -0
  23. package/dist/src/stores/lucid-mappers.js.map +1 -1
  24. package/dist/src/stores/lucid.d.ts +6 -1
  25. package/dist/src/stores/lucid.d.ts.map +1 -1
  26. package/dist/src/stores/lucid.js +163 -37
  27. package/dist/src/stores/lucid.js.map +1 -1
  28. package/dist/src/testing/in-memory-state-store.d.ts +5 -1
  29. package/dist/src/testing/in-memory-state-store.d.ts.map +1 -1
  30. package/dist/src/testing/in-memory-state-store.js +39 -0
  31. package/dist/src/testing/in-memory-state-store.js.map +1 -1
  32. package/dist/src/testing-kit/run-dispatch-contract.d.ts.map +1 -1
  33. package/dist/src/testing-kit/run-dispatch-contract.js +12 -3
  34. package/dist/src/testing-kit/run-dispatch-contract.js.map +1 -1
  35. package/dist/src/testing-kit/state-store-conformance.d.ts.map +1 -1
  36. package/dist/src/testing-kit/state-store-conformance.js +220 -0
  37. package/dist/src/testing-kit/state-store-conformance.js.map +1 -1
  38. package/dist/src/transports/adaptive-concurrency.d.ts +2 -0
  39. package/dist/src/transports/adaptive-concurrency.d.ts.map +1 -1
  40. package/dist/src/transports/adaptive-concurrency.js +39 -9
  41. package/dist/src/transports/adaptive-concurrency.js.map +1 -1
  42. package/dist/src/transports/bullmq/bullmq-transport.d.ts.map +1 -1
  43. package/dist/src/transports/bullmq/bullmq-transport.js +8 -4
  44. package/dist/src/transports/bullmq/bullmq-transport.js.map +1 -1
  45. package/dist/src/transports/bullmq/deps.d.ts.map +1 -1
  46. package/dist/src/transports/bullmq/deps.js +18 -1
  47. package/dist/src/transports/bullmq/deps.js.map +1 -1
  48. package/dist/src/transports/db.js +4 -4
  49. package/dist/src/transports/db.js.map +1 -1
  50. package/dist/src/transports/queue.d.ts.map +1 -1
  51. package/dist/src/transports/queue.js +8 -4
  52. package/dist/src/transports/queue.js.map +1 -1
  53. package/package.json +2 -2
  54. package/skills/durable-reliability/SKILL.md +2 -1
  55. package/skills/durable-transports-stores/SKILL.md +22 -7
  56. package/dist/assets/spa/assets/index-BEk3-95l.js +0 -16
  57. package/dist/assets/spa/assets/index-BEk3-95l.js.map +0 -1
@@ -567,6 +567,37 @@ export function runStateStoreContract(name, makeStore) {
567
567
  expect(await store.countCheckpoints('r1', 5, 9, ['pending'])).toBe(0);
568
568
  expect(await store.countCheckpoints('r1', 0, 4, [])).toBe(0);
569
569
  });
570
+ t('hasCheckpoints answers whether a seq range holds a status', async () => {
571
+ // Optional on the interface (the engine falls back to countCheckpoints), but a conformant store
572
+ // MUST provide it — assert presence, then exercise the fast path.
573
+ expect(store.hasCheckpoints).toBeDefined();
574
+ if (!store.hasCheckpoints)
575
+ return;
576
+ await store.createRun(run());
577
+ for (const [seq, status] of [
578
+ [0, 'completed'],
579
+ [1, 'pending'],
580
+ [2, 'completed'],
581
+ [3, 'failed'],
582
+ [4, 'completed'],
583
+ ]) {
584
+ await store.saveCheckpoint(checkpoint({ seq, status, stepId: `r1:${seq}` }));
585
+ }
586
+ await store.createRun(run({ id: 'other' }));
587
+ await store.saveCheckpoint(checkpoint({ runId: 'other', seq: 4, status: 'pending', stepId: 'other:4' }));
588
+ expect(await store.hasCheckpoints('r1', 0, 4, ['pending'])).toBe(true);
589
+ // Both bounds are inclusive.
590
+ expect(await store.hasCheckpoints('r1', 1, 1, ['pending'])).toBe(true);
591
+ expect(await store.hasCheckpoints('r1', 0, 1, ['pending'])).toBe(true);
592
+ expect(await store.hasCheckpoints('r1', 1, 2, ['pending'])).toBe(true);
593
+ expect(await store.hasCheckpoints('r1', 2, 4, ['pending'])).toBe(false);
594
+ expect(await store.hasCheckpoints('r1', 2, 4, ['pending', 'failed'])).toBe(true);
595
+ // `other` has a pending row at seq 4: the answer is scoped to its run.
596
+ expect(await store.hasCheckpoints('r1', 4, 4, ['pending'])).toBe(false);
597
+ expect(await store.hasCheckpoints('other', 0, 9, ['pending'])).toBe(true);
598
+ expect(await store.hasCheckpoints('r1', 5, 9, ['completed'])).toBe(false);
599
+ expect(await store.hasCheckpoints('r1', 0, 4, [])).toBe(false);
600
+ });
570
601
  t('saveCheckpointIfPending writes over a pending row only', async () => {
571
602
  // Optional on the interface (the engine falls back to a blind save, which can clobber a settled
572
603
  // row), but a conformant store MUST provide it — assert presence, then exercise it.
@@ -661,6 +692,195 @@ export function runStateStoreContract(name, makeStore) {
661
692
  ])).toEqual([true, true]);
662
693
  expect((await store.getCheckpoint('r1', 3))?.output).toEqual({ n: 3 });
663
694
  });
695
+ t('settleCheckpointsIfPending writes only the result columns, row by row (when implemented)', async () => {
696
+ if (!store.settleCheckpointsIfPending)
697
+ return;
698
+ expect(await store.settleCheckpointsIfPending([])).toEqual([]);
699
+ await store.createRun(run());
700
+ await store.createRun(run({ id: 'r2' }));
701
+ const pending = (runId, seq) => checkpoint({
702
+ runId,
703
+ seq,
704
+ stepId: `${runId}:${seq}`,
705
+ status: 'pending',
706
+ input: { prompt: `${runId}:${seq}` },
707
+ output: undefined,
708
+ attempts: 2,
709
+ workerGroup: 'g',
710
+ parallelGroup: 'fan:0:4',
711
+ wakeAt: 5_000,
712
+ });
713
+ for (const seq of [0, 1, 2, 3])
714
+ await store.saveCheckpoint(pending('r1', seq));
715
+ await store.saveCheckpoint(pending('r2', 0));
716
+ await store.saveCheckpoint({
717
+ ...pending('r1', 1),
718
+ status: 'completed',
719
+ output: { first: 1 },
720
+ });
721
+ const startedAt = new Date('2026-05-01T00:00:01.000Z');
722
+ const finishedAt = new Date('2026-05-01T00:00:02.000Z');
723
+ const done = (runId, seq, output) => ({
724
+ runId,
725
+ seq,
726
+ status: 'completed',
727
+ output,
728
+ events: [{ at: 1, level: 'info', message: `done ${seq}` }],
729
+ startedAt,
730
+ finishedAt,
731
+ });
732
+ const wrote = await store.settleCheckpointsIfPending([
733
+ done('r1', 0, { n: 0 }),
734
+ // Already settled: the result is refused.
735
+ done('r1', 1, { n: 1 }),
736
+ // A second copy of a row settled earlier in the call: refused.
737
+ done('r1', 0, { n: 'dup' }),
738
+ {
739
+ runId: 'r1',
740
+ seq: 2,
741
+ status: 'failed',
742
+ error: { message: 'boom' },
743
+ startedAt,
744
+ finishedAt,
745
+ },
746
+ // No such row: nothing is written.
747
+ done('r1', 9, { n: 9 }),
748
+ done('r2', 0, { n: 'r2' }),
749
+ ]);
750
+ expect(wrote).toEqual([true, false, false, true, false, true]);
751
+ const r1 = await store.listCheckpoints('r1');
752
+ expect(r1.map((c) => [c.seq, c.status, c.output])).toEqual([
753
+ [0, 'completed', { n: 0 }],
754
+ [1, 'completed', { first: 1 }],
755
+ [2, 'failed', undefined],
756
+ [3, 'pending', undefined],
757
+ ]);
758
+ const settled = r1[0];
759
+ expect(settled.events).toEqual([{ at: 1, level: 'info', message: 'done 0' }]);
760
+ expect(settled.startedAt.getTime()).toBe(startedAt.getTime());
761
+ expect(settled.finishedAt.getTime()).toBe(finishedAt.getTime());
762
+ // Every column the result does not carry is left as it was.
763
+ expect(settled.input).toEqual({ prompt: 'r1:0' });
764
+ expect(settled.attempts).toBe(2);
765
+ expect(settled.workerGroup).toBe('g');
766
+ expect(settled.parallelGroup).toBe('fan:0:4');
767
+ expect(settled.wakeAt).toBe(5_000);
768
+ expect(settled.stepId).toBe('r1:0');
769
+ expect(r1[2]?.error).toEqual({ message: 'boom' });
770
+ expect(r1[2]?.input).toEqual({ prompt: 'r1:2' });
771
+ expect(await store.getCheckpoint('r1', 9)).toBeNull();
772
+ expect((await store.getCheckpoint('r2', 0))?.output).toEqual({ n: 'r2' });
773
+ // One entry alone is the same compare-and-set.
774
+ expect(await store.settleCheckpointsIfPending([done('r1', 3, { n: 3 })])).toEqual([true]);
775
+ expect(await store.settleCheckpointsIfPending([done('r1', 3, { n: 'again' })])).toEqual([
776
+ false,
777
+ ]);
778
+ const third = await store.getCheckpoint('r1', 3);
779
+ expect(third?.output).toEqual({ n: 3 });
780
+ expect(third?.input).toEqual({ prompt: 'r1:3' });
781
+ });
782
+ t('settleCheckpointsIfPending settles a batch wider than one statement (when implemented)', async () => {
783
+ if (!store.settleCheckpointsIfPending)
784
+ return;
785
+ await store.createRun(run());
786
+ const rows = Array.from({ length: 600 }, (_, seq) => checkpoint({
787
+ seq,
788
+ stepId: `r1:${seq}`,
789
+ status: 'pending',
790
+ input: { seq },
791
+ output: undefined,
792
+ }));
793
+ if (store.saveCheckpoints)
794
+ await store.saveCheckpoints(rows);
795
+ else
796
+ for (const row of rows)
797
+ await store.saveCheckpoint(row);
798
+ const at = new Date('2026-05-01T00:00:03.000Z');
799
+ const wrote = await store.settleCheckpointsIfPending(rows.map(({ seq }) => ({
800
+ runId: 'r1',
801
+ seq,
802
+ status: 'completed',
803
+ output: seq * 2,
804
+ startedAt: at,
805
+ finishedAt: at,
806
+ })));
807
+ expect(wrote.every((w) => w)).toBe(true);
808
+ const all = await store.listCheckpoints('r1');
809
+ expect(all.every((c, i) => c.status === 'completed' && c.output === i * 2)).toBe(true);
810
+ expect(all.every((c, i) => c.input.seq === i)).toBe(true);
811
+ }, 30_000);
812
+ t('getCheckpointsForSettle reads the rows without their input (when implemented)', async () => {
813
+ if (!store.getCheckpointsForSettle)
814
+ return;
815
+ expect(await store.getCheckpointsForSettle('r1', [])).toEqual([]);
816
+ await store.createRun(run());
817
+ for (const seq of [0, 1, 2]) {
818
+ await store.saveCheckpoint(checkpoint({
819
+ seq,
820
+ stepId: `r1:${seq}`,
821
+ status: seq === 0 ? 'completed' : 'pending',
822
+ input: { prompt: `p${seq}` },
823
+ output: seq === 0 ? { a: 0 } : undefined,
824
+ wakeAt: 5_000 + seq,
825
+ }));
826
+ }
827
+ const full = (await store.getCheckpoints?.('r1', [2, 0, 9])) ?? [
828
+ (await store.getCheckpoint('r1', 0)),
829
+ (await store.getCheckpoint('r1', 2)),
830
+ ];
831
+ const read = await store.getCheckpointsForSettle('r1', [2, 0, 9]);
832
+ expect(read.map((c) => [c.seq, c.input])).toEqual([
833
+ [0, undefined],
834
+ [2, undefined],
835
+ ]);
836
+ const withoutInput = ({ input: _input, ...rest }) => rest;
837
+ expect(read.map(withoutInput)).toEqual(full.map(withoutInput));
838
+ expect((await store.getCheckpoint('r1', 2))?.input).toEqual({ prompt: 'p2' });
839
+ });
840
+ t("listCheckpointsForReplay leaves out only a completed row's input (when implemented)", async () => {
841
+ if (!store.listCheckpointsForReplay)
842
+ return;
843
+ await store.createRun(run());
844
+ const input = (seq) => ({ prompt: `p${seq}` });
845
+ await store.saveCheckpoint(checkpoint({ seq: 0, stepId: 'r1:0', input: input(0), output: { a: 0 } }));
846
+ await store.saveCheckpoint(checkpoint({
847
+ seq: 1,
848
+ stepId: 'r1:1',
849
+ status: 'pending',
850
+ input: input(1),
851
+ output: undefined,
852
+ }));
853
+ await store.saveCheckpoint(checkpoint({
854
+ seq: 2,
855
+ stepId: 'r1:2',
856
+ status: 'failed',
857
+ input: input(2),
858
+ output: undefined,
859
+ error: { message: 'boom' },
860
+ wakeAt: 7_000,
861
+ }));
862
+ await store.saveCheckpoint(checkpoint({
863
+ seq: 3,
864
+ stepId: 'r1:3',
865
+ status: 'running',
866
+ input: input(3),
867
+ output: undefined,
868
+ }));
869
+ const full = await store.listCheckpoints('r1');
870
+ const replay = await store.listCheckpointsForReplay('r1');
871
+ expect(replay.map((c) => [c.seq, c.status, c.input])).toEqual([
872
+ [0, 'completed', undefined],
873
+ [1, 'pending', input(1)],
874
+ [2, 'failed', input(2)],
875
+ [3, 'running', input(3)],
876
+ ]);
877
+ // Every other field reads as listCheckpoints reads it.
878
+ const withoutInput = ({ input: _input, ...rest }) => rest;
879
+ expect(replay.map(withoutInput)).toEqual(full.map(withoutInput));
880
+ // The full read still carries every input.
881
+ expect(full.map((c) => c.input)).toEqual([input(0), input(1), input(2), input(3)]);
882
+ expect(await store.listCheckpointsForReplay('nope')).toEqual([]);
883
+ });
664
884
  t('saveCheckpoints writes many rows and getCheckpoints reads them back (when implemented)', async () => {
665
885
  if (!store.saveCheckpoints || !store.getCheckpoints)
666
886
  return;