@adonis-agora/durable 0.50.0 → 0.51.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 +28 -0
- package/dist/commands/work.d.ts.map +1 -1
- package/dist/commands/work.js +3 -1
- package/dist/commands/work.js.map +1 -1
- package/dist/providers/durable_provider.d.ts.map +1 -1
- package/dist/providers/durable_provider.js +3 -1
- package/dist/providers/durable_provider.js.map +1 -1
- package/dist/src/commands/worker.d.ts +4 -2
- package/dist/src/commands/worker.d.ts.map +1 -1
- package/dist/src/commands/worker.js +8 -5
- package/dist/src/commands/worker.js.map +1 -1
- package/dist/src/dashboard/index.d.ts +1 -1
- package/dist/src/dashboard/index.js +1 -1
- package/dist/src/engine.d.ts +10 -1
- package/dist/src/engine.d.ts.map +1 -1
- package/dist/src/engine.js +55 -17
- package/dist/src/engine.js.map +1 -1
- package/dist/src/errors.d.ts +15 -5
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/errors.js +11 -7
- package/dist/src/errors.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/on-error.d.ts +4 -1
- package/dist/src/on-error.d.ts.map +1 -1
- package/dist/src/on-error.js +15 -2
- package/dist/src/on-error.js.map +1 -1
- package/dist/src/scheduler.d.ts +44 -3
- package/dist/src/scheduler.d.ts.map +1 -1
- package/dist/src/scheduler.js +36 -1
- package/dist/src/scheduler.js.map +1 -1
- package/dist/src/standard-schema.d.ts +8 -5
- package/dist/src/standard-schema.d.ts.map +1 -1
- package/dist/src/standard-schema.js +9 -7
- package/dist/src/standard-schema.js.map +1 -1
- package/dist/src/step-discovery.js +2 -2
- package/dist/src/step-discovery.js.map +1 -1
- package/dist/src/workflow-ref.d.ts +4 -2
- package/dist/src/workflow-ref.d.ts.map +1 -1
- package/dist/src/workflow-ref.js.map +1 -1
- package/package.json +1 -1
- package/skills/durable-workflows/SKILL.md +15 -3
package/dist/src/engine.js
CHANGED
|
@@ -24,7 +24,7 @@ import { loadFanMembers, loadRunDetail, } from './run-detail-summary.js';
|
|
|
24
24
|
import { runFacetsOf } from './run-facets.js';
|
|
25
25
|
import { scanRunValueFacets } from './run-value-facets.js';
|
|
26
26
|
import { listSignalWaitersOfRuns } from './run-waiting.js';
|
|
27
|
-
import { scheduleWindow } from './scheduler.js';
|
|
27
|
+
import { resolveSchedule, scheduleWindow } from './scheduler.js';
|
|
28
28
|
import { ScheduleClient } from './schedules.js';
|
|
29
29
|
import { SINGLETON_RESERVED_TAGS, withoutAdmission, withSingletonTracked, } from './singleton-admission.js';
|
|
30
30
|
import { SingletonGate } from './singleton-gate.js';
|
|
@@ -547,7 +547,7 @@ export class WorkflowEngine {
|
|
|
547
547
|
* Called by `runTick`; idempotent.
|
|
548
548
|
*/
|
|
549
549
|
adoptSchedules(schedules) {
|
|
550
|
-
this.#allSchedules = schedules;
|
|
550
|
+
this.#allSchedules = schedules.map(resolveSchedule);
|
|
551
551
|
}
|
|
552
552
|
/**
|
|
553
553
|
* The runtime pause override for a schedule key: `true`/`false` when the console set one (wins
|
|
@@ -626,7 +626,7 @@ export class WorkflowEngine {
|
|
|
626
626
|
continue;
|
|
627
627
|
}
|
|
628
628
|
seen.add(schedule.key);
|
|
629
|
-
this.#discoveredSchedules.push(schedule);
|
|
629
|
+
this.#discoveredSchedules.push(resolveSchedule(schedule));
|
|
630
630
|
}
|
|
631
631
|
}
|
|
632
632
|
/** Colocated schedules discovered from `static schedule`, for the worker loop to merge with config. */
|
|
@@ -1061,11 +1061,15 @@ export class WorkflowEngine {
|
|
|
1061
1061
|
: `workflow ${name}@${pinned} is not registered`);
|
|
1062
1062
|
}
|
|
1063
1063
|
// Validate the input up front — a bad payload is rejected before any run is created. The schema
|
|
1064
|
-
// runs first and its OUTPUT (parsed/coerced)
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1064
|
+
// runs first and `validateInput` sees its OUTPUT (parsed/coerced), but the run stores the input
|
|
1065
|
+
// AS SENT: the body gets the parsed value by re-parsing it before every turn (`turnInput`), so a
|
|
1066
|
+
// value JSON can't carry (a `Date` from `z.coerce.date()`) reaches it the same way on every
|
|
1067
|
+
// turn, whichever store holds the row. Everything else here — singleton and concurrency keys
|
|
1068
|
+
// included — reads the stored input, the one value the singleton gate re-reads later.
|
|
1069
|
+
const parsed = registered.inputSchema
|
|
1070
|
+
? await validateStandard(registered.inputSchema, input, `workflow "${name}" input`, 'invalid_input')
|
|
1071
|
+
: input;
|
|
1072
|
+
await registered.validateInput?.(parsed);
|
|
1069
1073
|
// Idempotent by run id: a redelivered trigger (at-least-once queues) or a scheduler re-tick for
|
|
1070
1074
|
// the same id is a no-op, returning the existing run's state instead of starting a duplicate.
|
|
1071
1075
|
const prior = await this.store.getRun(runId);
|
|
@@ -3689,10 +3693,28 @@ export class WorkflowEngine {
|
|
|
3689
3693
|
return this.settleRun(run, await this.followHandoff(run, handoff));
|
|
3690
3694
|
}
|
|
3691
3695
|
const history = await this.remoteHistory(run.id, checkpoints);
|
|
3696
|
+
// The remote body gets the parsed input too, re-parsed here each turn like an inline one. It
|
|
3697
|
+
// travels as JSON, so a value JSON can't carry arrives as its JSON form — but coercions and
|
|
3698
|
+
// defaults do apply. Only the TASK carries it: `run` (and so the store) keeps the input as sent.
|
|
3699
|
+
let turnRun;
|
|
3700
|
+
try {
|
|
3701
|
+
turnRun = registered.inputSchema
|
|
3702
|
+
? { ...run, input: await this.turnInput(run, registered) }
|
|
3703
|
+
: run;
|
|
3704
|
+
}
|
|
3705
|
+
catch (err) {
|
|
3706
|
+
return this.settleRun(run, {
|
|
3707
|
+
kind: 'failed',
|
|
3708
|
+
error: {
|
|
3709
|
+
message: err instanceof Error ? err.message : String(err),
|
|
3710
|
+
...codeAndDetails(err),
|
|
3711
|
+
},
|
|
3712
|
+
});
|
|
3713
|
+
}
|
|
3692
3714
|
// Broker-backed executors: dispatch the turn and suspend on it. The decision is applied by
|
|
3693
3715
|
// whichever replica consumes it (completeRemoteDecision), never awaited here.
|
|
3694
3716
|
if (remote.executor.dispatch)
|
|
3695
|
-
return this.dispatchRemoteTurn(
|
|
3717
|
+
return this.dispatchRemoteTurn(turnRun, registered, history);
|
|
3696
3718
|
// INLINE advance (an executor that produces the decision in-process): await it under the lease.
|
|
3697
3719
|
const advance = remote.executor.advance;
|
|
3698
3720
|
if (!advance) {
|
|
@@ -3701,7 +3723,7 @@ export class WorkflowEngine {
|
|
|
3701
3723
|
let decision;
|
|
3702
3724
|
try {
|
|
3703
3725
|
const silenceMs = this.remoteAdvanceSilenceMs;
|
|
3704
|
-
const pending = advance.call(remote.executor,
|
|
3726
|
+
const pending = advance.call(remote.executor, turnRun, history);
|
|
3705
3727
|
decision =
|
|
3706
3728
|
silenceMs != null
|
|
3707
3729
|
? await this.awaitTurnWithSilence(run.id, pending, silenceMs)
|
|
@@ -4642,6 +4664,19 @@ export class WorkflowEngine {
|
|
|
4642
4664
|
return { runId: run.id, status: 'cancelled', error };
|
|
4643
4665
|
}
|
|
4644
4666
|
/** The run body, lease held + renewed by {@link execute}. */
|
|
4667
|
+
/**
|
|
4668
|
+
* The input a turn's body receives: the stored input parsed by the workflow's `inputSchema`, or the
|
|
4669
|
+
* stored input itself without one. Re-parsed on every turn rather than stored parsed, because the
|
|
4670
|
+
* store round-trips the row through JSON (on the SQL stores) or a structured clone (in memory): a
|
|
4671
|
+
* stored parsed value would reach the body as a `Date` in tests and a string in production. A
|
|
4672
|
+
* stored input that no longer parses — its schema changed without a version bump — throws the same
|
|
4673
|
+
* `SchemaValidationError` (`invalid_input`, never retried) that fails the run.
|
|
4674
|
+
*/
|
|
4675
|
+
async turnInput(run, registered) {
|
|
4676
|
+
if (!registered?.inputSchema)
|
|
4677
|
+
return run.input;
|
|
4678
|
+
return validateStandard(registered.inputSchema, run.input, `workflow "${run.workflow}" input`, 'invalid_input');
|
|
4679
|
+
}
|
|
4645
4680
|
async runExecution(run, fn, registered) {
|
|
4646
4681
|
// First execution of an enqueued run: mark it running and announce the start, BEFORE the singleton
|
|
4647
4682
|
// gate, so an admitted run's body never starts while it still reads `pending`. A resumed run is
|
|
@@ -4689,7 +4724,8 @@ export class WorkflowEngine {
|
|
|
4689
4724
|
// Establish the ambient ctx for the duration of this body turn so context-aware statics
|
|
4690
4725
|
// (`BaseWorkflow.start`/`dispatch`) reachable from `fn` route through this run's `ctx.child`/
|
|
4691
4726
|
// `ctx.startChild`. Re-set on every replay turn (each is its own async scope) — correct.
|
|
4692
|
-
const
|
|
4727
|
+
const input = await this.turnInput(run, registered);
|
|
4728
|
+
const output = await workflowAls.run(ctx, () => fn(ctx, input));
|
|
4693
4729
|
// A compensating cancel requested while this turn ran (or one whose resume replays straight to
|
|
4694
4730
|
// `return`, with no suspension point left) still wins: undo + settle cancelled, never completed.
|
|
4695
4731
|
if (await this.takeCancelIntent(run.id)) {
|
|
@@ -4918,7 +4954,7 @@ export class WorkflowEngine {
|
|
|
4918
4954
|
throw new Error('dispatched compensation requires a Transport');
|
|
4919
4955
|
const token = stepGroup(def, run.namespace);
|
|
4920
4956
|
const validInput = def.input
|
|
4921
|
-
? await validateStandard(def.input, args, `step "${def.name}" input
|
|
4957
|
+
? await validateStandard(def.input, args, `step "${def.name}" input`, 'invalid_input')
|
|
4922
4958
|
: args;
|
|
4923
4959
|
const id = `${run.id}:compensate:${def.name}:${attempt}`;
|
|
4924
4960
|
const name = `compensate:${def.name}`;
|
|
@@ -5087,7 +5123,7 @@ export class WorkflowEngine {
|
|
|
5087
5123
|
?.searchAttributesSchema
|
|
5088
5124
|
: undefined;
|
|
5089
5125
|
if (schema) {
|
|
5090
|
-
validateStandardSync(schema, merged, `workflow "${run?.workflow}"
|
|
5126
|
+
validateStandardSync(schema, merged, `workflow "${run?.workflow}" ctx.upsertSearchAttributes`);
|
|
5091
5127
|
}
|
|
5092
5128
|
await this.store.updateRun(runId, {
|
|
5093
5129
|
searchAttributes: merged,
|
|
@@ -5418,7 +5454,7 @@ export class WorkflowEngine {
|
|
|
5418
5454
|
// Optional runtime schema: validate only when the def carries one (a bare `@Step()` / string-name
|
|
5419
5455
|
// call carries none — the engine passes the input through untouched then).
|
|
5420
5456
|
const validInput = step.input
|
|
5421
|
-
? await validateStandard(step.input, input, `step "${step.name}" input
|
|
5457
|
+
? await validateStandard(step.input, input, `step "${step.name}" input`, 'invalid_input')
|
|
5422
5458
|
: input;
|
|
5423
5459
|
// Routing token: BY NAME (sanitized for brokers), optionally partition-suffixed. Computed once and
|
|
5424
5460
|
// used at BOTH the checkpoint's workerGroup and the dispatched task's routing `group`, so the same
|
|
@@ -5567,7 +5603,9 @@ export class WorkflowEngine {
|
|
|
5567
5603
|
async sendStepFan(runId, positions, step, inputs, batch, group, opts, runNamespace) {
|
|
5568
5604
|
const token = stepGroup(step, runNamespace);
|
|
5569
5605
|
await this.ensureRoutable(token, step.requires);
|
|
5570
|
-
const input = (i) => step.input
|
|
5606
|
+
const input = (i) => step.input
|
|
5607
|
+
? validateStandard(step.input, inputs[i], `step "${step.name}" input`, 'invalid_input')
|
|
5608
|
+
: inputs[i];
|
|
5571
5609
|
const validInputs = new Map();
|
|
5572
5610
|
for (const i of batch)
|
|
5573
5611
|
validInputs.set(i, await input(i));
|
|
@@ -5999,7 +6037,7 @@ export class WorkflowEngine {
|
|
|
5999
6037
|
if (this.pool.size === 0)
|
|
6000
6038
|
throw new Error('remote steps require a Transport');
|
|
6001
6039
|
const validInput = step.input
|
|
6002
|
-
? await validateStandard(step.input, input, `step "${step.name}" input
|
|
6040
|
+
? await validateStandard(step.input, input, `step "${step.name}" input`, 'invalid_input')
|
|
6003
6041
|
: input;
|
|
6004
6042
|
const token = stepGroup(step, runNamespace);
|
|
6005
6043
|
// Capability/protocol guard (design §7.5) — same as the durable path, before the `step.started`
|
|
@@ -6118,7 +6156,7 @@ export class WorkflowEngine {
|
|
|
6118
6156
|
/** Shared finalization for a remote resolution, however it arrived (live await or salvaged). */
|
|
6119
6157
|
async settleRemoteAttempt(runId, seq, step, input, resolution, attempt, token, enqueuedAt) {
|
|
6120
6158
|
const output = (step.output
|
|
6121
|
-
? await validateStandard(step.output, resolution.output, `step "${step.name}" output
|
|
6159
|
+
? await validateStandard(step.output, resolution.output, `step "${step.name}" output`, 'invalid_output')
|
|
6122
6160
|
: resolution.output);
|
|
6123
6161
|
// The worker reports when it actually picked the task up; fall back to dispatch time if a
|
|
6124
6162
|
// transport doesn't carry it (queue-wait then reads as zero rather than going negative).
|