@adonis-agora/durable 0.54.0 → 0.54.2

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 (66) hide show
  1. package/CHANGELOG.md +90 -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/buffered-child-signals.d.ts +20 -4
  6. package/dist/src/buffered-child-signals.d.ts.map +1 -1
  7. package/dist/src/buffered-child-signals.js +207 -63
  8. package/dist/src/buffered-child-signals.js.map +1 -1
  9. package/dist/src/codec-state-store.d.ts +10 -1
  10. package/dist/src/codec-state-store.d.ts.map +1 -1
  11. package/dist/src/codec-state-store.js +32 -0
  12. package/dist/src/codec-state-store.js.map +1 -1
  13. package/dist/src/commands/worker.d.ts +2 -1
  14. package/dist/src/commands/worker.d.ts.map +1 -1
  15. package/dist/src/commands/worker.js +44 -3
  16. package/dist/src/commands/worker.js.map +1 -1
  17. package/dist/src/dashboard/index.d.ts +1 -1
  18. package/dist/src/dashboard/index.js +1 -1
  19. package/dist/src/engine.d.ts +69 -8
  20. package/dist/src/engine.d.ts.map +1 -1
  21. package/dist/src/engine.js +248 -53
  22. package/dist/src/engine.js.map +1 -1
  23. package/dist/src/index.d.ts +1 -1
  24. package/dist/src/index.js +1 -1
  25. package/dist/src/interfaces.d.ts +47 -0
  26. package/dist/src/interfaces.d.ts.map +1 -1
  27. package/dist/src/interfaces.js.map +1 -1
  28. package/dist/src/stores/lucid-mappers.d.ts +6 -1
  29. package/dist/src/stores/lucid-mappers.d.ts.map +1 -1
  30. package/dist/src/stores/lucid-mappers.js +41 -0
  31. package/dist/src/stores/lucid-mappers.js.map +1 -1
  32. package/dist/src/stores/lucid.d.ts +4 -1
  33. package/dist/src/stores/lucid.d.ts.map +1 -1
  34. package/dist/src/stores/lucid.js +148 -37
  35. package/dist/src/stores/lucid.js.map +1 -1
  36. package/dist/src/testing/in-memory-state-store.d.ts +4 -1
  37. package/dist/src/testing/in-memory-state-store.d.ts.map +1 -1
  38. package/dist/src/testing/in-memory-state-store.js +29 -0
  39. package/dist/src/testing/in-memory-state-store.js.map +1 -1
  40. package/dist/src/testing-kit/run-dispatch-contract.d.ts.map +1 -1
  41. package/dist/src/testing-kit/run-dispatch-contract.js +12 -3
  42. package/dist/src/testing-kit/run-dispatch-contract.js.map +1 -1
  43. package/dist/src/testing-kit/state-store-conformance.d.ts +6 -0
  44. package/dist/src/testing-kit/state-store-conformance.d.ts.map +1 -1
  45. package/dist/src/testing-kit/state-store-conformance.js +230 -0
  46. package/dist/src/testing-kit/state-store-conformance.js.map +1 -1
  47. package/dist/src/transports/adaptive-concurrency.d.ts +2 -0
  48. package/dist/src/transports/adaptive-concurrency.d.ts.map +1 -1
  49. package/dist/src/transports/adaptive-concurrency.js +39 -9
  50. package/dist/src/transports/adaptive-concurrency.js.map +1 -1
  51. package/dist/src/transports/bullmq/bullmq-transport.d.ts.map +1 -1
  52. package/dist/src/transports/bullmq/bullmq-transport.js +8 -4
  53. package/dist/src/transports/bullmq/bullmq-transport.js.map +1 -1
  54. package/dist/src/transports/bullmq/deps.d.ts.map +1 -1
  55. package/dist/src/transports/bullmq/deps.js +18 -1
  56. package/dist/src/transports/bullmq/deps.js.map +1 -1
  57. package/dist/src/transports/db.js +4 -4
  58. package/dist/src/transports/db.js.map +1 -1
  59. package/dist/src/transports/queue.d.ts.map +1 -1
  60. package/dist/src/transports/queue.js +8 -4
  61. package/dist/src/transports/queue.js.map +1 -1
  62. package/package.json +2 -2
  63. package/skills/durable-reliability/SKILL.md +2 -1
  64. package/skills/durable-transports-stores/SKILL.md +15 -2
  65. package/dist/assets/spa/assets/index-BEk3-95l.js +0 -16
  66. package/dist/assets/spa/assets/index-BEk3-95l.js.map +0 -1
@@ -59,6 +59,7 @@ export function runStateStoreContract(name, makeStore) {
59
59
  let cleanup;
60
60
  let supportsAsyncTransaction = true;
61
61
  let supportsTagFilter = true;
62
+ let statements;
62
63
  let skipReason;
63
64
  let loggedSkip = false;
64
65
  beforeEach(async () => {
@@ -68,6 +69,7 @@ export function runStateStoreContract(name, makeStore) {
68
69
  cleanup = ctx.cleanup;
69
70
  supportsAsyncTransaction = ctx.supportsAsyncTransaction ?? true;
70
71
  supportsTagFilter = ctx.supportsTagFilter ?? true;
72
+ statements = ctx.statements;
71
73
  skipReason = undefined;
72
74
  }
73
75
  catch (err) {
@@ -149,6 +151,45 @@ export function runStateStoreContract(name, makeStore) {
149
151
  expect(await store.takeBufferedSignal('child:a')).toBeNull();
150
152
  expect(await store.takeBufferedSignal('child:b')).toBeNull();
151
153
  });
154
+ t('a sweep of buffered child completions sends a few statements per page, not one per row', async () => {
155
+ if (!statements || !store.listBufferedSignals || !store.deleteBufferedSignals)
156
+ return;
157
+ if (!store.saveCheckpoints)
158
+ return;
159
+ // A wide fan's parent, still open, that has settled every child whose completion is still
160
+ // buffered: the rows must go, decided without one checkpoint read per row.
161
+ const parent = 'wide';
162
+ await store.createRun(run({ id: parent, status: 'suspended' }));
163
+ const fan = 4_000;
164
+ for (let from = 0; from < fan; from += 500) {
165
+ await store.saveCheckpoints(Array.from({ length: 500 }, (_, k) => {
166
+ const i = from + k;
167
+ return checkpoint({
168
+ runId: parent,
169
+ seq: i,
170
+ stepId: `${parent}:${i}`,
171
+ name: `signal:child:${parent}.all.0.${i}`,
172
+ kind: 'signal',
173
+ output: { ok: true, value: { i } },
174
+ parallelGroup: `all:0:n${fan}`,
175
+ });
176
+ }));
177
+ }
178
+ const rows = 500;
179
+ for (let k = 0; k < rows; k += 1) {
180
+ const i = 2_000 + k;
181
+ await store.bufferSignal(`child:${parent}.all.0.${i}`, { ok: true, value: { i } });
182
+ }
183
+ const engine = new WorkflowEngine({ store });
184
+ const before = statements();
185
+ const deleted = await engine.sweepBufferedSignals(Date.now(), Number.POSITIVE_INFINITY);
186
+ const sent = statements() - before;
187
+ expect(deleted).toBe(rows);
188
+ expect(await store.listBufferedSignals('child:', undefined, 10)).toEqual([]);
189
+ // The page, the parent's run row, its checkpoints by seq, the delete, the next (empty) page:
190
+ // a few statements each, against one per buffered row.
191
+ expect(sent).toBeLessThanOrEqual(10);
192
+ }, 60_000);
152
193
  t('a parent joining children that already finished settles them from their rows', async () => {
153
194
  const engine = new WorkflowEngine({ store, reconcileMs: 0 });
154
195
  engine.register('j-child', '1', async (_ctx, input) => ({ n: input.n }));
@@ -620,6 +661,195 @@ export function runStateStoreContract(name, makeStore) {
620
661
  ])).toEqual([true, true]);
621
662
  expect((await store.getCheckpoint('r1', 3))?.output).toEqual({ n: 3 });
622
663
  });
664
+ t('settleCheckpointsIfPending writes only the result columns, row by row (when implemented)', async () => {
665
+ if (!store.settleCheckpointsIfPending)
666
+ return;
667
+ expect(await store.settleCheckpointsIfPending([])).toEqual([]);
668
+ await store.createRun(run());
669
+ await store.createRun(run({ id: 'r2' }));
670
+ const pending = (runId, seq) => checkpoint({
671
+ runId,
672
+ seq,
673
+ stepId: `${runId}:${seq}`,
674
+ status: 'pending',
675
+ input: { prompt: `${runId}:${seq}` },
676
+ output: undefined,
677
+ attempts: 2,
678
+ workerGroup: 'g',
679
+ parallelGroup: 'fan:0:4',
680
+ wakeAt: 5_000,
681
+ });
682
+ for (const seq of [0, 1, 2, 3])
683
+ await store.saveCheckpoint(pending('r1', seq));
684
+ await store.saveCheckpoint(pending('r2', 0));
685
+ await store.saveCheckpoint({
686
+ ...pending('r1', 1),
687
+ status: 'completed',
688
+ output: { first: 1 },
689
+ });
690
+ const startedAt = new Date('2026-05-01T00:00:01.000Z');
691
+ const finishedAt = new Date('2026-05-01T00:00:02.000Z');
692
+ const done = (runId, seq, output) => ({
693
+ runId,
694
+ seq,
695
+ status: 'completed',
696
+ output,
697
+ events: [{ at: 1, level: 'info', message: `done ${seq}` }],
698
+ startedAt,
699
+ finishedAt,
700
+ });
701
+ const wrote = await store.settleCheckpointsIfPending([
702
+ done('r1', 0, { n: 0 }),
703
+ // Already settled: the result is refused.
704
+ done('r1', 1, { n: 1 }),
705
+ // A second copy of a row settled earlier in the call: refused.
706
+ done('r1', 0, { n: 'dup' }),
707
+ {
708
+ runId: 'r1',
709
+ seq: 2,
710
+ status: 'failed',
711
+ error: { message: 'boom' },
712
+ startedAt,
713
+ finishedAt,
714
+ },
715
+ // No such row: nothing is written.
716
+ done('r1', 9, { n: 9 }),
717
+ done('r2', 0, { n: 'r2' }),
718
+ ]);
719
+ expect(wrote).toEqual([true, false, false, true, false, true]);
720
+ const r1 = await store.listCheckpoints('r1');
721
+ expect(r1.map((c) => [c.seq, c.status, c.output])).toEqual([
722
+ [0, 'completed', { n: 0 }],
723
+ [1, 'completed', { first: 1 }],
724
+ [2, 'failed', undefined],
725
+ [3, 'pending', undefined],
726
+ ]);
727
+ const settled = r1[0];
728
+ expect(settled.events).toEqual([{ at: 1, level: 'info', message: 'done 0' }]);
729
+ expect(settled.startedAt.getTime()).toBe(startedAt.getTime());
730
+ expect(settled.finishedAt.getTime()).toBe(finishedAt.getTime());
731
+ // Every column the result does not carry is left as it was.
732
+ expect(settled.input).toEqual({ prompt: 'r1:0' });
733
+ expect(settled.attempts).toBe(2);
734
+ expect(settled.workerGroup).toBe('g');
735
+ expect(settled.parallelGroup).toBe('fan:0:4');
736
+ expect(settled.wakeAt).toBe(5_000);
737
+ expect(settled.stepId).toBe('r1:0');
738
+ expect(r1[2]?.error).toEqual({ message: 'boom' });
739
+ expect(r1[2]?.input).toEqual({ prompt: 'r1:2' });
740
+ expect(await store.getCheckpoint('r1', 9)).toBeNull();
741
+ expect((await store.getCheckpoint('r2', 0))?.output).toEqual({ n: 'r2' });
742
+ // One entry alone is the same compare-and-set.
743
+ expect(await store.settleCheckpointsIfPending([done('r1', 3, { n: 3 })])).toEqual([true]);
744
+ expect(await store.settleCheckpointsIfPending([done('r1', 3, { n: 'again' })])).toEqual([
745
+ false,
746
+ ]);
747
+ const third = await store.getCheckpoint('r1', 3);
748
+ expect(third?.output).toEqual({ n: 3 });
749
+ expect(third?.input).toEqual({ prompt: 'r1:3' });
750
+ });
751
+ t('settleCheckpointsIfPending settles a batch wider than one statement (when implemented)', async () => {
752
+ if (!store.settleCheckpointsIfPending)
753
+ return;
754
+ await store.createRun(run());
755
+ const rows = Array.from({ length: 600 }, (_, seq) => checkpoint({
756
+ seq,
757
+ stepId: `r1:${seq}`,
758
+ status: 'pending',
759
+ input: { seq },
760
+ output: undefined,
761
+ }));
762
+ if (store.saveCheckpoints)
763
+ await store.saveCheckpoints(rows);
764
+ else
765
+ for (const row of rows)
766
+ await store.saveCheckpoint(row);
767
+ const at = new Date('2026-05-01T00:00:03.000Z');
768
+ const wrote = await store.settleCheckpointsIfPending(rows.map(({ seq }) => ({
769
+ runId: 'r1',
770
+ seq,
771
+ status: 'completed',
772
+ output: seq * 2,
773
+ startedAt: at,
774
+ finishedAt: at,
775
+ })));
776
+ expect(wrote.every((w) => w)).toBe(true);
777
+ const all = await store.listCheckpoints('r1');
778
+ expect(all.every((c, i) => c.status === 'completed' && c.output === i * 2)).toBe(true);
779
+ expect(all.every((c, i) => c.input.seq === i)).toBe(true);
780
+ }, 30_000);
781
+ t('getCheckpointsForSettle reads the rows without their input (when implemented)', async () => {
782
+ if (!store.getCheckpointsForSettle)
783
+ return;
784
+ expect(await store.getCheckpointsForSettle('r1', [])).toEqual([]);
785
+ await store.createRun(run());
786
+ for (const seq of [0, 1, 2]) {
787
+ await store.saveCheckpoint(checkpoint({
788
+ seq,
789
+ stepId: `r1:${seq}`,
790
+ status: seq === 0 ? 'completed' : 'pending',
791
+ input: { prompt: `p${seq}` },
792
+ output: seq === 0 ? { a: 0 } : undefined,
793
+ wakeAt: 5_000 + seq,
794
+ }));
795
+ }
796
+ const full = (await store.getCheckpoints?.('r1', [2, 0, 9])) ?? [
797
+ (await store.getCheckpoint('r1', 0)),
798
+ (await store.getCheckpoint('r1', 2)),
799
+ ];
800
+ const read = await store.getCheckpointsForSettle('r1', [2, 0, 9]);
801
+ expect(read.map((c) => [c.seq, c.input])).toEqual([
802
+ [0, undefined],
803
+ [2, undefined],
804
+ ]);
805
+ const withoutInput = ({ input: _input, ...rest }) => rest;
806
+ expect(read.map(withoutInput)).toEqual(full.map(withoutInput));
807
+ expect((await store.getCheckpoint('r1', 2))?.input).toEqual({ prompt: 'p2' });
808
+ });
809
+ t("listCheckpointsForReplay leaves out only a completed row's input (when implemented)", async () => {
810
+ if (!store.listCheckpointsForReplay)
811
+ return;
812
+ await store.createRun(run());
813
+ const input = (seq) => ({ prompt: `p${seq}` });
814
+ await store.saveCheckpoint(checkpoint({ seq: 0, stepId: 'r1:0', input: input(0), output: { a: 0 } }));
815
+ await store.saveCheckpoint(checkpoint({
816
+ seq: 1,
817
+ stepId: 'r1:1',
818
+ status: 'pending',
819
+ input: input(1),
820
+ output: undefined,
821
+ }));
822
+ await store.saveCheckpoint(checkpoint({
823
+ seq: 2,
824
+ stepId: 'r1:2',
825
+ status: 'failed',
826
+ input: input(2),
827
+ output: undefined,
828
+ error: { message: 'boom' },
829
+ wakeAt: 7_000,
830
+ }));
831
+ await store.saveCheckpoint(checkpoint({
832
+ seq: 3,
833
+ stepId: 'r1:3',
834
+ status: 'running',
835
+ input: input(3),
836
+ output: undefined,
837
+ }));
838
+ const full = await store.listCheckpoints('r1');
839
+ const replay = await store.listCheckpointsForReplay('r1');
840
+ expect(replay.map((c) => [c.seq, c.status, c.input])).toEqual([
841
+ [0, 'completed', undefined],
842
+ [1, 'pending', input(1)],
843
+ [2, 'failed', input(2)],
844
+ [3, 'running', input(3)],
845
+ ]);
846
+ // Every other field reads as listCheckpoints reads it.
847
+ const withoutInput = ({ input: _input, ...rest }) => rest;
848
+ expect(replay.map(withoutInput)).toEqual(full.map(withoutInput));
849
+ // The full read still carries every input.
850
+ expect(full.map((c) => c.input)).toEqual([input(0), input(1), input(2), input(3)]);
851
+ expect(await store.listCheckpointsForReplay('nope')).toEqual([]);
852
+ });
623
853
  t('saveCheckpoints writes many rows and getCheckpoints reads them back (when implemented)', async () => {
624
854
  if (!store.saveCheckpoints || !store.getCheckpoints)
625
855
  return;