@entifix/workflow 0.15.0 → 0.17.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.
@@ -1 +1 @@
1
- {"version":3,"file":"make-flow-engine.d.ts","sourceRoot":"","sources":["../../src/engine/make-flow-engine.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,UAAU,EACf,aAAa,EAGd,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAW,MAAM,EAAE,KAAK,EAAU,MAAM,QAAQ,CAAC;AACxD,OAAO,EAAmB,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAElE,OAAO,EACL,KAAK,kBAAkB,EAIxB,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAEpE;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,sFAoGzB,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAAI,SAAS,kBAAkB,gVAGxD,CAAC;AAEJ;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,GAAI,SAAS,kBAAkB,gTACS,CAAC"}
1
+ {"version":3,"file":"make-flow-engine.d.ts","sourceRoot":"","sources":["../../src/engine/make-flow-engine.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,UAAU,EACf,aAAa,EAGd,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAW,MAAM,EAAE,KAAK,EAAU,MAAM,QAAQ,CAAC;AACxD,OAAO,EAAmB,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAElE,OAAO,EACL,KAAK,kBAAkB,EAIxB,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAEpE;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,sFAkHzB,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAAI,SAAS,kBAAkB,gVAGxD,CAAC;AAEJ;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,GAAI,SAAS,kBAAkB,gTACS,CAAC"}
@@ -46,6 +46,8 @@ import { FlowStatusStoreTag } from '../status/flow-status-store.js';
46
46
  waitingFor: token
47
47
  }, record.correlation === undefined ? {} : {
48
48
  correlation: record.correlation
49
+ }, record.accepts === undefined ? {} : {
50
+ accepts: record.accepts
49
51
  })).pipe(Effect.ignore)));
50
52
  });
51
53
  const engine = {
@@ -74,12 +76,20 @@ import { FlowStatusStoreTag } from '../status/flow-status-store.js';
74
76
  step: record.step
75
77
  })),
76
78
  signal: (flowId, token, outcome)=>Effect.flatMap(known(flowId), complete(token, outcome)),
77
- signalCorrelated: (correlation, token, outcome)=>Effect.gen(function*() {
79
+ signalCorrelated: (correlation, signal)=>Effect.gen(function*() {
80
+ // Only a parked flow carries its correlation: one still on its way to
81
+ // the wait has none yet, and is not found — the sender retries.
78
82
  const found = yield* store.findByCorrelation(correlation);
79
- if (Option.isNone(found)) return yield* Effect.fail(new FlowNotFound({
83
+ if (Option.isNone(found) || found.value.waitingFor === undefined) return yield* Effect.fail(new FlowNotFound({
80
84
  flowId: correlation
81
85
  }));
82
- return yield* complete(token, outcome)(found.value);
86
+ const record = found.value;
87
+ const token = record.waitingFor;
88
+ if (record.accepts !== undefined && !record.accepts.includes(signal.outcome)) return yield* Effect.fail(new FlowSignalRejected({
89
+ flowId: record.flowId,
90
+ token
91
+ }));
92
+ return yield* complete(token, signal)(record);
83
93
  }),
84
94
  interrupt: (flowId)=>Effect.gen(function*() {
85
95
  const record = yield* known(flowId);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/engine/make-flow-engine.ts"],"sourcesContent":["import {\n type FlowEngine,\n FlowEngineTag,\n FlowNotFound,\n FlowSignalRejected,\n} from '@entifix/transactions';\nimport { Context, Effect, Layer, Option } from 'effect';\nimport { DurableDeferred, WorkflowEngine } from 'effect/workflow';\n\nimport {\n type InterpreterOptions,\n SlipWorkflow,\n SlipWorkflowLayer,\n waitFor,\n} from '../interpreter/slip-workflow.js';\nimport type { FlowRecord } from '../status/flow-status-store.js';\nimport { FlowStatusStoreTag } from '../status/flow-status-store.js';\n\n/**\n * The flow engine port over Effect Workflow\n * ([ADR 0016](../../../../../docs/adr/0016-a-flow-engine-port-and-a-durable-workflow-adapter.md)).\n * A start records the flow under its key and executes the slip detached — it\n * answers the flow id, never the outcome, which arrives as `flow.*` events and\n * in the status record. A signal is accepted only by a flow parked on exactly\n * that wait.\n */\nexport const makeFlowEngine = Effect.gen(function* () {\n const context = yield* Effect.context<\n WorkflowEngine.WorkflowEngine | FlowStatusStoreTag\n >();\n const store = Context.get(context, FlowStatusStoreTag);\n const within = <A, E>(\n effect: Effect.Effect<A, E, WorkflowEngine.WorkflowEngine>,\n ) => Effect.provideContext(effect, context);\n\n const known = (flowId: string) =>\n Effect.flatMap(store.find(flowId), found =>\n Option.match(found, {\n onNone: () => Effect.fail(new FlowNotFound({ flowId })),\n onSome: Effect.succeed,\n }),\n );\n\n /** Completes the wait `record` is parked on, if `token` names it. */\n const complete = (token: string, outcome: unknown) => (record: FlowRecord) =>\n Effect.gen(function* () {\n // Claimed before the wait is resumed, never after: the resumed flow\n // records its own states, and a `running` written behind them would put\n // a finished flow back to running with nothing left to correct it. The\n // claim is conditional, so a second signal racing this one is refused.\n const claimed = yield* store.claimWait(record.flowId, token);\n if (!claimed)\n return yield* Effect.fail(\n new FlowSignalRejected({ flowId: record.flowId, token }),\n );\n const deferred = waitFor(token);\n yield* within(\n DurableDeferred.succeed(deferred, {\n token: DurableDeferred.tokenFromExecutionId(deferred, {\n workflow: SlipWorkflow,\n executionId: record.executionId,\n }),\n value: outcome,\n }),\n ).pipe(\n // The wait was not resumed, so it is parked again and a retried signal\n // can claim it.\n Effect.onError(() =>\n store\n .update(record.flowId, {\n state: 'waiting',\n step: token,\n waitingFor: token,\n ...(record.correlation === undefined\n ? {}\n : { correlation: record.correlation }),\n })\n .pipe(Effect.ignore),\n ),\n );\n });\n\n const engine: FlowEngine = {\n start: (slip, idempotencyKey) =>\n Effect.gen(function* () {\n const payload = { key: idempotencyKey, slip };\n const executionId = yield* SlipWorkflow.executionId(payload);\n const record = yield* store.register(idempotencyKey, {\n flowId: slip.flowId,\n executionId,\n state: 'running',\n });\n if (record.executionId === executionId && record.flowId === slip.flowId)\n yield* within(SlipWorkflow.execute(payload, { discard: true }));\n return { flowId: record.flowId };\n }),\n\n status: flowId =>\n Effect.map(known(flowId), record => ({\n flowId: record.flowId,\n state: record.state,\n ...(record.step === undefined ? {} : { step: record.step }),\n })),\n\n signal: (flowId, token, outcome) =>\n Effect.flatMap(known(flowId), complete(token, outcome)),\n\n signalCorrelated: (correlation, token, outcome) =>\n Effect.gen(function* () {\n const found = yield* store.findByCorrelation(correlation);\n if (Option.isNone(found))\n return yield* Effect.fail(new FlowNotFound({ flowId: correlation }));\n return yield* complete(token, outcome)(found.value);\n }),\n\n interrupt: flowId =>\n Effect.gen(function* () {\n const record = yield* known(flowId);\n yield* within(SlipWorkflow.interrupt(record.executionId));\n yield* store.update(flowId, {\n state: 'interrupted',\n ...(record.step === undefined ? {} : { step: record.step }),\n });\n }),\n };\n return engine;\n});\n\n/**\n * The flow engine with the interpreter registered. Needs a workflow engine —\n * the in-memory one, or the cluster engine over Postgres from\n * `@entifix/workflow/postgres` — a status store, a participant client, the\n * outbox its events go through, and the breaker, bulkhead and retry budget\n * every call runs under.\n */\nexport const FlowEngineLayer = (options: InterpreterOptions) =>\n Layer.effect(FlowEngineTag, makeFlowEngine).pipe(\n Layer.provideMerge(SlipWorkflowLayer(options)),\n );\n\n/**\n * The flow engine over Effect's in-memory workflow engine: every flow lives and\n * dies with the process. For a single-process host — an example, a test, a\n * tool — whose flows need the interpreter's semantics but not its durability;\n * a fleet takes `@entifix/workflow/postgres`'s engines instead, and nothing\n * else in its composition changes.\n */\nexport const MemoryFlowEngineLayer = (options: InterpreterOptions) =>\n FlowEngineLayer(options).pipe(Layer.provide(WorkflowEngine.layerMemory));\n"],"names":["FlowEngineTag","FlowNotFound","FlowSignalRejected","Context","Effect","Layer","Option","DurableDeferred","WorkflowEngine","SlipWorkflow","SlipWorkflowLayer","waitFor","FlowStatusStoreTag","makeFlowEngine","gen","context","store","get","within","effect","provideContext","known","flowId","flatMap","find","found","match","onNone","fail","onSome","succeed","complete","token","outcome","record","claimed","claimWait","deferred","tokenFromExecutionId","workflow","executionId","value","pipe","onError","update","state","step","waitingFor","correlation","undefined","ignore","engine","start","slip","idempotencyKey","payload","key","register","execute","discard","status","map","signal","signalCorrelated","findByCorrelation","isNone","interrupt","FlowEngineLayer","options","provideMerge","MemoryFlowEngineLayer","provide","layerMemory"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/engine/make-flow-engine.ts"],"sourcesContent":["import {\n type FlowEngine,\n FlowEngineTag,\n FlowNotFound,\n FlowSignalRejected,\n} from '@entifix/transactions';\nimport { Context, Effect, Layer, Option } from 'effect';\nimport { DurableDeferred, WorkflowEngine } from 'effect/workflow';\n\nimport {\n type InterpreterOptions,\n SlipWorkflow,\n SlipWorkflowLayer,\n waitFor,\n} from '../interpreter/slip-workflow.js';\nimport type { FlowRecord } from '../status/flow-status-store.js';\nimport { FlowStatusStoreTag } from '../status/flow-status-store.js';\n\n/**\n * The flow engine port over Effect Workflow\n * ([ADR 0016](../../../../../docs/adr/0016-a-flow-engine-port-and-a-durable-workflow-adapter.md)).\n * A start records the flow under its key and executes the slip detached — it\n * answers the flow id, never the outcome, which arrives as `flow.*` events and\n * in the status record. A signal is accepted only by a flow parked on exactly\n * that wait.\n */\nexport const makeFlowEngine = Effect.gen(function* () {\n const context = yield* Effect.context<\n WorkflowEngine.WorkflowEngine | FlowStatusStoreTag\n >();\n const store = Context.get(context, FlowStatusStoreTag);\n const within = <A, E>(\n effect: Effect.Effect<A, E, WorkflowEngine.WorkflowEngine>,\n ) => Effect.provideContext(effect, context);\n\n const known = (flowId: string) =>\n Effect.flatMap(store.find(flowId), found =>\n Option.match(found, {\n onNone: () => Effect.fail(new FlowNotFound({ flowId })),\n onSome: Effect.succeed,\n }),\n );\n\n /** Completes the wait `record` is parked on, if `token` names it. */\n const complete = (token: string, outcome: unknown) => (record: FlowRecord) =>\n Effect.gen(function* () {\n // Claimed before the wait is resumed, never after: the resumed flow\n // records its own states, and a `running` written behind them would put\n // a finished flow back to running with nothing left to correct it. The\n // claim is conditional, so a second signal racing this one is refused.\n const claimed = yield* store.claimWait(record.flowId, token);\n if (!claimed)\n return yield* Effect.fail(\n new FlowSignalRejected({ flowId: record.flowId, token }),\n );\n const deferred = waitFor(token);\n yield* within(\n DurableDeferred.succeed(deferred, {\n token: DurableDeferred.tokenFromExecutionId(deferred, {\n workflow: SlipWorkflow,\n executionId: record.executionId,\n }),\n value: outcome,\n }),\n ).pipe(\n // The wait was not resumed, so it is parked again and a retried signal\n // can claim it.\n Effect.onError(() =>\n store\n .update(record.flowId, {\n state: 'waiting',\n step: token,\n waitingFor: token,\n ...(record.correlation === undefined\n ? {}\n : { correlation: record.correlation }),\n ...(record.accepts === undefined\n ? {}\n : { accepts: record.accepts }),\n })\n .pipe(Effect.ignore),\n ),\n );\n });\n\n const engine: FlowEngine = {\n start: (slip, idempotencyKey) =>\n Effect.gen(function* () {\n const payload = { key: idempotencyKey, slip };\n const executionId = yield* SlipWorkflow.executionId(payload);\n const record = yield* store.register(idempotencyKey, {\n flowId: slip.flowId,\n executionId,\n state: 'running',\n });\n if (record.executionId === executionId && record.flowId === slip.flowId)\n yield* within(SlipWorkflow.execute(payload, { discard: true }));\n return { flowId: record.flowId };\n }),\n\n status: flowId =>\n Effect.map(known(flowId), record => ({\n flowId: record.flowId,\n state: record.state,\n ...(record.step === undefined ? {} : { step: record.step }),\n })),\n\n signal: (flowId, token, outcome) =>\n Effect.flatMap(known(flowId), complete(token, outcome)),\n\n signalCorrelated: (correlation, signal) =>\n Effect.gen(function* () {\n // Only a parked flow carries its correlation: one still on its way to\n // the wait has none yet, and is not found — the sender retries.\n const found = yield* store.findByCorrelation(correlation);\n if (Option.isNone(found) || found.value.waitingFor === undefined)\n return yield* Effect.fail(new FlowNotFound({ flowId: correlation }));\n const record = found.value;\n const token = record.waitingFor as string;\n if (\n record.accepts !== undefined &&\n !record.accepts.includes(signal.outcome)\n )\n return yield* Effect.fail(\n new FlowSignalRejected({ flowId: record.flowId, token }),\n );\n return yield* complete(token, signal)(record);\n }),\n\n interrupt: flowId =>\n Effect.gen(function* () {\n const record = yield* known(flowId);\n yield* within(SlipWorkflow.interrupt(record.executionId));\n yield* store.update(flowId, {\n state: 'interrupted',\n ...(record.step === undefined ? {} : { step: record.step }),\n });\n }),\n };\n return engine;\n});\n\n/**\n * The flow engine with the interpreter registered. Needs a workflow engine —\n * the in-memory one, or the cluster engine over Postgres from\n * `@entifix/workflow/postgres` — a status store, a participant client, the\n * outbox its events go through, and the breaker, bulkhead and retry budget\n * every call runs under.\n */\nexport const FlowEngineLayer = (options: InterpreterOptions) =>\n Layer.effect(FlowEngineTag, makeFlowEngine).pipe(\n Layer.provideMerge(SlipWorkflowLayer(options)),\n );\n\n/**\n * The flow engine over Effect's in-memory workflow engine: every flow lives and\n * dies with the process. For a single-process host — an example, a test, a\n * tool — whose flows need the interpreter's semantics but not its durability;\n * a fleet takes `@entifix/workflow/postgres`'s engines instead, and nothing\n * else in its composition changes.\n */\nexport const MemoryFlowEngineLayer = (options: InterpreterOptions) =>\n FlowEngineLayer(options).pipe(Layer.provide(WorkflowEngine.layerMemory));\n"],"names":["FlowEngineTag","FlowNotFound","FlowSignalRejected","Context","Effect","Layer","Option","DurableDeferred","WorkflowEngine","SlipWorkflow","SlipWorkflowLayer","waitFor","FlowStatusStoreTag","makeFlowEngine","gen","context","store","get","within","effect","provideContext","known","flowId","flatMap","find","found","match","onNone","fail","onSome","succeed","complete","token","outcome","record","claimed","claimWait","deferred","tokenFromExecutionId","workflow","executionId","value","pipe","onError","update","state","step","waitingFor","correlation","undefined","accepts","ignore","engine","start","slip","idempotencyKey","payload","key","register","execute","discard","status","map","signal","signalCorrelated","findByCorrelation","isNone","includes","interrupt","FlowEngineLayer","options","provideMerge","MemoryFlowEngineLayer","provide","layerMemory"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { type FragmentCatalog, Slip } from '@entifix/business/flow';
2
2
  import { type EntifixFailure } from '@entifix/core';
3
3
  import { BulkheadTag, CircuitBreakerTag, OutboxTag, ParticipantClientTag } from '@entifix/transactions';
4
- import { Context, Effect, Schema } from 'effect';
4
+ import { Cause, Context, Effect, Schema } from 'effect';
5
5
  import { DurableDeferred, Workflow } from 'effect/workflow';
6
6
  import { FlowStatusStoreTag } from '../status/flow-status-store.js';
7
7
  /**
@@ -13,12 +13,13 @@ export declare const JournaledFailure: Schema.Codec<EntifixFailure, unknown>;
13
13
  /**
14
14
  * How a flow ended, as the workflow's own result: every step ran, or a
15
15
  * refusal before the commit point was undone, or an undoing itself failed and
16
- * the flow stopped where it was — which a person has to look at.
16
+ * the flow stopped where it was — which a person has to look at — or a wait
17
+ * that strands on its timeout ran out, which a person has to look at too.
17
18
  */
18
- export declare const FlowOutcome: Schema.Literals<readonly ["completed", "compensated", "failed"]>;
19
+ export declare const FlowOutcome: Schema.Literals<readonly ["completed", "compensated", "failed", "stranded"]>;
19
20
  declare const FlowWaitTimedOut_base: Schema.Class<FlowWaitTimedOut, Schema.TaggedStruct<"FlowWaitTimedOut", {
20
21
  readonly step: Schema.String;
21
- }>, import("effect/Cause").YieldableError & import("@entifix/core").FailureFacts<"flowWaitTimedOut", "timeout">>;
22
+ }>, Cause.YieldableError & import("@entifix/core").FailureFacts<"flowWaitTimedOut", "timeout">>;
22
23
  /** A durable wait that outlived its timeout before its signal came. */
23
24
  export declare class FlowWaitTimedOut extends FlowWaitTimedOut_base {
24
25
  }
@@ -64,7 +65,7 @@ export declare class InterpreterOptionsTag extends InterpreterOptionsTag_base {
64
65
  export declare const SlipWorkflow: Workflow.Workflow<"entifix/slip", Schema.Struct<{
65
66
  key: Schema.String;
66
67
  slip: Schema.Struct<{
67
- readonly slipVersion: Schema.Literal<1>;
68
+ readonly slipVersion: Schema.Literals<readonly [1, 2]>;
68
69
  readonly flowId: Schema.String;
69
70
  readonly recipe: Schema.String;
70
71
  readonly requiresCommitPoint: Schema.optionalKey<Schema.Boolean>;
@@ -90,6 +91,10 @@ export declare const SlipWorkflow: Workflow.Workflow<"entifix/slip", Schema.Stru
90
91
  readonly path: Schema.String;
91
92
  }>>;
92
93
  readonly parameters: Schema.optionalKey<Schema.$Record<Schema.String, Schema.Unknown>>;
94
+ readonly guard: Schema.optionalKey<Schema.Struct<{
95
+ readonly wait: Schema.String;
96
+ readonly outcomes: Schema.$Array<Schema.String>;
97
+ }>>;
93
98
  }>>;
94
99
  }>>;
95
100
  readonly resolution: Schema.$Array<Schema.Struct<{
@@ -104,12 +109,12 @@ export declare const SlipWorkflow: Workflow.Workflow<"entifix/slip", Schema.Stru
104
109
  readonly digest: Schema.optionalKey<Schema.String>;
105
110
  }>>>;
106
111
  }>;
107
- }>, Schema.Literals<readonly ["completed", "compensated", "failed"]>, Schema.Codec<EntifixFailure<string, string, import("@entifix/core").FailureCategory>, unknown, never, never>>;
112
+ }>, Schema.Literals<readonly ["completed", "compensated", "failed", "stranded"]>, Schema.Codec<EntifixFailure<string, string, import("@entifix/core").FailureCategory>, unknown, never, never>>;
108
113
  /** The durable wait a `wait` step parks on, addressed by the step's name. */
109
114
  export declare const waitFor: (step: string) => DurableDeferred.DurableDeferred<Schema.Unknown, Schema.Never>;
110
115
  /** Every `slipVersion` the interpreter walks — each must have a journal. */
111
116
  export declare const interpretedSlipVersions: readonly number[];
112
- export declare const interpretSlip: (slip: Slip) => Effect.Effect<"completed" | "compensated" | "failed", EntifixFailure<string, string, import("@entifix/core").FailureCategory>, OutboxTag | BulkheadTag | CircuitBreakerTag | ParticipantClientTag | import("@entifix/transactions").RetryBudgetTag | FlowStatusStoreTag | InterpreterOptionsTag | import("effect/workflow/WorkflowEngine").WorkflowInstance | import("effect/workflow/WorkflowEngine").WorkflowEngine>;
117
+ export declare const interpretSlip: (slip: Slip) => Effect.Effect<"completed" | "compensated" | "failed" | "stranded", EntifixFailure<string, string, import("@entifix/core").FailureCategory>, OutboxTag | BulkheadTag | CircuitBreakerTag | ParticipantClientTag | import("@entifix/transactions").RetryBudgetTag | FlowStatusStoreTag | InterpreterOptionsTag | import("effect/workflow/WorkflowEngine").WorkflowInstance | import("effect/workflow/WorkflowEngine").WorkflowEngine>;
113
118
  /** The interpreter registered with the workflow engine, with its options. */
114
119
  export declare const SlipWorkflowLayer: (options: InterpreterOptions) => import("effect/Layer").Layer<never, never, OutboxTag | BulkheadTag | CircuitBreakerTag | ParticipantClientTag | import("@entifix/transactions").RetryBudgetTag | FlowStatusStoreTag | import("effect/workflow/WorkflowEngine").WorkflowEngine>;
115
120
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"slip-workflow.d.ts","sourceRoot":"","sources":["../../src/interpreter/slip-workflow.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,eAAe,EACpB,IAAI,EAGL,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAEL,KAAK,cAAc,EAMpB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,WAAW,EACX,iBAAiB,EAIjB,SAAS,EACT,oBAAoB,EACrB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,OAAO,EACP,MAAM,EAIN,MAAM,EAEP,MAAM,QAAQ,CAAC;AAChB,OAAO,EAGL,eAAe,EACf,QAAQ,EACT,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAGL,kBAAkB,EACnB,MAAM,gCAAgC,CAAC;AAGxC;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,KAAK,CAAC,cAAc,EAAE,OAAO,CAShE,CAAC;AAEJ;;;;GAIG;AACH,eAAO,MAAM,WAAW,kEAItB,CAAC;;;;AAEH,uEAAuE;AACvE,qBAAa,gBAAiB,SAAQ,qBAKrC;CAAG;AAEJ,6EAA6E;AAC7E,MAAM,WAAW,SAAS;IACxB,6BAA6B;IAC7B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,0DAA0D;IAC1D,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,yCAAyC;IACzC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,eAAO,MAAM,kBAAkB,EAAE,SAIhC,CAAC;AAEF,oFAAoF;AACpF,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,wDAAwD;IACxD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;IACpC;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAClB,WAAW,EAAE,MAAM,KAChB,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC,GAAG,SAAS,CAAC;CAC1D;;AAED;;;GAGG;AACH,qBAAa,qBAAsB,SAAQ,0BAGA;CAAG;AAE9C;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mLAYvB,CAAC;AAEH,6EAA6E;AAC7E,eAAO,MAAM,OAAO,GAAI,MAAM,MAAM,kEAC+B,CAAC;AA4TpE,4EAA4E;AAC5E,eAAO,MAAM,uBAAuB,EAAE,SAAS,MAAM,EACd,CAAC;AAExC,eAAO,MAAM,aAAa,GAAI,MAAM,IAAI,2ZACF,CAAC;AAEvC,6EAA6E;AAC7E,eAAO,MAAM,iBAAiB,GAAI,SAAS,kBAAkB,mPAK1D,CAAC"}
1
+ {"version":3,"file":"slip-workflow.d.ts","sourceRoot":"","sources":["../../src/interpreter/slip-workflow.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,eAAe,EACpB,IAAI,EAGL,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAEL,KAAK,cAAc,EAMpB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,WAAW,EACX,iBAAiB,EAIjB,SAAS,EACT,oBAAoB,EACrB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,KAAK,EACL,OAAO,EACP,MAAM,EAIN,MAAM,EAEP,MAAM,QAAQ,CAAC;AAChB,OAAO,EAGL,eAAe,EACf,QAAQ,EACT,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAGL,kBAAkB,EACnB,MAAM,gCAAgC,CAAC;AAGxC;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,KAAK,CAAC,cAAc,EAAE,OAAO,CAShE,CAAC;AAEJ;;;;;GAKG;AACH,eAAO,MAAM,WAAW,8EAKtB,CAAC;;;;AAqCH,uEAAuE;AACvE,qBAAa,gBAAiB,SAAQ,qBAKrC;CAAG;AAEJ,6EAA6E;AAC7E,MAAM,WAAW,SAAS;IACxB,6BAA6B;IAC7B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,0DAA0D;IAC1D,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,yCAAyC;IACzC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,eAAO,MAAM,kBAAkB,EAAE,SAIhC,CAAC;AAEF,oFAAoF;AACpF,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,wDAAwD;IACxD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;IACpC;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAClB,WAAW,EAAE,MAAM,KAChB,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC,GAAG,SAAS,CAAC;CAC1D;;AAED;;;GAGG;AACH,qBAAa,qBAAsB,SAAQ,0BAGA;CAAG;AAE9C;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+LAYvB,CAAC;AAEH,6EAA6E;AAC7E,eAAO,MAAM,OAAO,GAAI,MAAM,MAAM,kEAC+B,CAAC;AA+XpE,4EAA4E;AAC5E,eAAO,MAAM,uBAAuB,EAAE,SAAS,MAAM,EACd,CAAC;AAExC,eAAO,MAAM,aAAa,GAAI,MAAM,IAAI,waACF,CAAC;AAEvC,6EAA6E;AAC7E,eAAO,MAAM,iBAAiB,GAAI,SAAS,kBAAkB,mPAK1D,CAAC"}
@@ -2,7 +2,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
2
2
  import { Slip, slipSteps } from '@entifix/business/flow';
3
3
  import { decodeProblem, Failure, isFailure, JsonBody, problemOf, Timeout } from '@entifix/core';
4
4
  import { BulkheadTag, CircuitBreakerTag, flowEvent, On, OutboxTag, ParticipantClientTag } from '@entifix/transactions';
5
- import { Context, Effect, Exit, Metric, Schedule, Schema, SchemaTransformation } from 'effect';
5
+ import { Cause, Context, Effect, Exit, Metric, Schedule, Schema, SchemaTransformation } from 'effect';
6
6
  import { Activity, DurableClock, DurableDeferred, Workflow } from 'effect/workflow';
7
7
  import { flowStates, FlowStatusStoreTag } from '../status/flow-status-store.js';
8
8
  import { makeBindings } from './bindings.js';
@@ -17,12 +17,33 @@ import { makeBindings } from './bindings.js';
17
17
  /**
18
18
  * How a flow ended, as the workflow's own result: every step ran, or a
19
19
  * refusal before the commit point was undone, or an undoing itself failed and
20
- * the flow stopped where it was — which a person has to look at.
20
+ * the flow stopped where it was — which a person has to look at — or a wait
21
+ * that strands on its timeout ran out, which a person has to look at too.
21
22
  */ export const FlowOutcome = Schema.Literals([
22
23
  'completed',
23
24
  'compensated',
24
- 'failed'
25
+ 'failed',
26
+ 'stranded'
25
27
  ]);
28
+ const onTimeoutOf = (step)=>{
29
+ var _step_parameters;
30
+ const named = (_step_parameters = step.parameters) == null ? void 0 : _step_parameters['onTimeout'];
31
+ if (named === 'strand') return {
32
+ kind: 'strand'
33
+ };
34
+ const outcome = typeof named === 'object' && named !== null ? named.outcome : undefined;
35
+ return typeof outcome === 'string' ? {
36
+ kind: 'outcome',
37
+ outcome
38
+ } : {
39
+ kind: 'fail'
40
+ };
41
+ };
42
+ /** The outcome a wait was answered with: its signal's `outcome`, when a string. */ const outcomeOf = (answer)=>{
43
+ const named = typeof answer === 'object' && answer !== null ? answer.outcome : undefined;
44
+ return typeof named === 'string' ? named : undefined;
45
+ };
46
+ /** The race's answer when a wait that strands on its timeout ran out. */ const STRANDED = Symbol.for('@entifix/workflow/stranded');
26
47
  /** A durable wait that outlived its timeout before its signal came. */ export class FlowWaitTimedOut extends Failure.timeout()('FlowWaitTimedOut', 'flowWaitTimedOut', {
27
48
  step: Schema.String
28
49
  }, {
@@ -93,32 +114,7 @@ const segmentOf = (step)=>step.segment === undefined ? undefined : [
93
114
  ...groups.values()
94
115
  ];
95
116
  };
96
- /**
97
- * Interprets a pinned slip of grammar version 1
98
- * ([ADR 0016](../../../../../docs/adr/0016-a-flow-engine-port-and-a-durable-workflow-adapter.md)):
99
- * each step is an Activity named after it and keyed `<flowId>:<step>`, so a
100
- * resumed flow replays what already ran and calls a participant at most once
101
- * per step. Inside the Activity the call runs under the participant's
102
- * breaker and bulkhead and the retry budget, each attempt bounded and the
103
- * step as a whole bounded; nothing retries outside it.
104
- *
105
- * - Before and at the commit point, a failure — a breaker already open
106
- * included, which fails fast — compensates what succeeded, in reverse, and
107
- * the flow ends `compensated`.
108
- * - After it, a failure suspends the flow at that step: the money is taken,
109
- * so the flow rolls forward when the participant comes back, never back.
110
- * - A `wait` step parks the flow on a durable deferred its signal completes,
111
- * raced with a durable clock when it names `timeoutMs`; it may name a
112
- * `correlation`, so a sender that knows the order rather than the flow can
113
- * complete it.
114
- * - The segments of a stage run at once, bounded, and join before the next.
115
- *
116
- * Every transition is announced as a `flow.*` event through the outbox, by an
117
- * Activity, so it is written once whatever replays. Compensation is explicit
118
- * rather than a workflow finalizer, so the record says `compensated` only
119
- * after the last undoing has run — and an undoing that fails stops the flow
120
- * `failed` at that step instead of pretending.
121
- */ const interpretV1 = (slip)=>Effect.gen(function*() {
117
+ const walkSlip = (slip, grammar)=>Effect.gen(function*() {
122
118
  const options = yield* InterpreterOptionsTag;
123
119
  const dials = _extends({}, DEFAULT_FLOW_DIALS, options.dials);
124
120
  const { catalog } = options;
@@ -130,6 +126,9 @@ const segmentOf = (step)=>step.segment === undefined ? undefined : [
130
126
  });
131
127
  const bindings = makeBindings();
132
128
  const done = [];
129
+ /** What each wait was answered with, by step name — what a guard reads. */ const answered = new Map();
130
+ /** Steps a guard passed over; a step guarded on one of them is skipped too. */ const skipped = new Set();
131
+ /** The wait whose timeout stranded the flow, once one has. */ let stranded;
133
132
  const record = (name, patch)=>Activity.make({
134
133
  name,
135
134
  execute: Effect.flatMap(FlowStatusStoreTag, (store)=>store.update(slip.flowId, patch)).pipe(Effect.andThen(Metric.update(Metric.withAttributes(flowStates, {
@@ -204,15 +203,24 @@ const segmentOf = (step)=>step.segment === undefined ? undefined : [
204
203
  })))
205
204
  });
206
205
  const wait = (step)=>Effect.gen(function*() {
207
- var _step_parameters, _step_parameters1;
206
+ var _step_parameters, _catalog_get, _step_parameters1;
208
207
  const correlation = (_step_parameters = step.parameters) == null ? void 0 : _step_parameters['correlation'];
208
+ const accepts = (_catalog_get = catalog.get(step.ref)) == null ? void 0 : _catalog_get.outcomes;
209
+ const onTimeout = onTimeoutOf(step);
209
210
  yield* record(`status:${step.step}:waiting`, _extends({
210
211
  state: 'waiting',
211
212
  step: step.step,
212
213
  waitingFor: step.step
213
214
  }, typeof correlation === 'string' ? {
214
215
  correlation
215
- } : {}));
216
+ } : {}, accepts === undefined ? {} : {
217
+ accepts
218
+ }));
219
+ // Past the commit point the caller's write has landed, and the wait is
220
+ // for someone else — a courier, a person — so the flow says so and a
221
+ // browser holding the write pending can let it go. Before it, nothing
222
+ // has landed, and the flow stays silent.
223
+ if (grammar.announcesCommittedWaits && commitPoint !== -1 && steps.indexOf(step) > commitPoint) yield* announce('flow.waiting', step.step, ordinalOf(step));
216
224
  const signalled = DurableDeferred.await(waitFor(step.step));
217
225
  const timeoutMs = (_step_parameters1 = step.parameters) == null ? void 0 : _step_parameters1['timeoutMs'];
218
226
  // A durable race, so a replay answers the branch that won rather than
@@ -226,11 +234,21 @@ const segmentOf = (step)=>step.segment === undefined ? undefined : [
226
234
  DurableClock.sleep({
227
235
  name: `timeout:${step.step}`,
228
236
  duration: timeoutMs
229
- }).pipe(Effect.andThen(Effect.fail(new FlowWaitTimedOut({
237
+ }).pipe(Effect.andThen(// An outcome answers as a signal would; a strand and a
238
+ // plain failure both fail the race, and are told apart
239
+ // below — so the journaled race is the same either way.
240
+ onTimeout.kind === 'outcome' ? Effect.succeed({
241
+ outcome: onTimeout.outcome
242
+ }) : Effect.fail(new FlowWaitTimedOut({
230
243
  step: step.step
231
244
  }))))
232
245
  ]
233
- }) : yield* signalled;
246
+ }).pipe(Effect.catchTag('FlowWaitTimedOut', (timedOut)=>onTimeout.kind === 'strand' ? Effect.succeed(STRANDED) : Effect.fail(timedOut))) : yield* signalled;
247
+ if (answer === STRANDED) {
248
+ stranded = step;
249
+ return;
250
+ }
251
+ answered.set(step.step, outcomeOf(answer));
234
252
  bindings.record(step, catalog.get(step.ref), answer);
235
253
  yield* record(`status:${step.step}:resumed`, {
236
254
  state: 'running',
@@ -239,6 +257,16 @@ const segmentOf = (step)=>step.segment === undefined ? undefined : [
239
257
  });
240
258
  /** One step, its failure tagged with where it happened. */ const run = (step)=>Effect.gen(function*() {
241
259
  var _catalog_get, _catalog_get1;
260
+ // A stranded flow runs nothing more, in any group of the stage.
261
+ if (stranded !== undefined) return;
262
+ if (step.guard !== undefined) {
263
+ const { wait: guardedOn, outcomes } = step.guard;
264
+ const outcome = answered.get(guardedOn);
265
+ if (skipped.has(guardedOn) || outcome === undefined || !outcomes.includes(outcome)) {
266
+ skipped.add(step.step);
267
+ return;
268
+ }
269
+ }
242
270
  if (((_catalog_get = catalog.get(step.ref)) == null ? void 0 : _catalog_get.kind) === 'wait') return yield* wait(step).pipe(Effect.mapError((failure)=>({
243
271
  step,
244
272
  failure
@@ -262,12 +290,17 @@ const segmentOf = (step)=>step.segment === undefined ? undefined : [
262
290
  concurrency: dials.concurrency
263
291
  });
264
292
  // The earliest step that failed decides; a group's failure is its own.
265
- const [first] = exits.flatMap((exit)=>Exit.isFailure(exit) ? exit.cause.reasons.flatMap((reason)=>// A durable race fails with its losers' interruptions beside
266
- // the failure that won, so the failure is looked for.
267
- reason._tag === 'Fail' ? [
268
- reason.error
269
- ] : []) : []).sort((a, b)=>ordinalOf(a.step) - ordinalOf(b.step));
270
- if (first === undefined) continue;
293
+ const [first] = exits.flatMap((exit)=>Exit.isFailure(exit) ? // the failure that won, so the failure is looked for.
294
+ exit.cause.reasons.filter(Cause.isFailReason).map((reason)=>reason.error) : []).sort((a, b)=>ordinalOf(a.step) - ordinalOf(b.step));
295
+ if (first === undefined) {
296
+ if (stranded === undefined) continue;
297
+ yield* record(`status:${stranded.step}:stranded`, {
298
+ state: 'stranded',
299
+ step: stranded.step
300
+ });
301
+ yield* announce('flow.stranded', stranded.step, ordinalOf(stranded));
302
+ return 'stranded';
303
+ }
271
304
  const { step, failure } = first;
272
305
  if (commitPoint === -1 || steps.indexOf(step) <= commitPoint) {
273
306
  for (const undone of [
@@ -310,7 +343,12 @@ const segmentOf = (step)=>step.segment === undefined ? undefined : [
310
343
  * edit to an existing one; the journal replay holds every entry to its
311
344
  * recorded command sequence.
312
345
  */ const INTERPRETERS = {
313
- 1: interpretV1
346
+ 1: (slip)=>walkSlip(slip, {
347
+ announcesCommittedWaits: false
348
+ }),
349
+ 2: (slip)=>walkSlip(slip, {
350
+ announcesCommittedWaits: true
351
+ })
314
352
  };
315
353
  /** Every `slipVersion` the interpreter walks — each must have a journal. */ export const interpretedSlipVersions = Object.keys(INTERPRETERS).map(Number);
316
354
  export const interpretSlip = (slip)=>INTERPRETERS[slip.slipVersion](slip);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/interpreter/slip-workflow.ts"],"sourcesContent":["import {\n type FragmentCatalog,\n Slip,\n type SlipStep,\n slipSteps,\n} from '@entifix/business/flow';\nimport {\n decodeProblem,\n type EntifixFailure,\n Failure,\n isFailure,\n JsonBody,\n problemOf,\n Timeout,\n} from '@entifix/core';\nimport {\n BulkheadTag,\n CircuitBreakerTag,\n flowEvent,\n type FlowEventName,\n On,\n OutboxTag,\n ParticipantClientTag,\n} from '@entifix/transactions';\nimport {\n Context,\n Effect,\n Exit,\n Metric,\n Schedule,\n Schema,\n SchemaTransformation,\n} from 'effect';\nimport {\n Activity,\n DurableClock,\n DurableDeferred,\n Workflow,\n} from 'effect/workflow';\n\nimport {\n type FlowRecordPatch,\n flowStates,\n FlowStatusStoreTag,\n} from '../status/flow-status-store.js';\nimport { makeBindings } from './bindings.js';\n\n/**\n * A failure as an Activity journals it: the problem body on the way in, the\n * failure its code names on the way out, so a resumed flow sees the typed\n * refusal a participant sent rather than a cast.\n */\nexport const JournaledFailure: Schema.Codec<EntifixFailure, unknown> =\n Schema.Unknown.pipe(\n Schema.decodeTo(\n Schema.declare((value): value is EntifixFailure => isFailure(value)),\n SchemaTransformation.transform({\n decode: body => decodeProblem(body),\n encode: failure => problemOf(failure),\n }),\n ),\n );\n\n/**\n * How a flow ended, as the workflow's own result: every step ran, or a\n * refusal before the commit point was undone, or an undoing itself failed and\n * the flow stopped where it was — which a person has to look at.\n */\nexport const FlowOutcome = Schema.Literals([\n 'completed',\n 'compensated',\n 'failed',\n]);\n\n/** A durable wait that outlived its timeout before its signal came. */\nexport class FlowWaitTimedOut extends Failure.timeout<FlowWaitTimedOut>()(\n 'FlowWaitTimedOut',\n 'flowWaitTimedOut',\n { step: Schema.String },\n { message: ({ step }) => `the wait at '${step}' timed out` },\n) {}\n\n/** How long a flow gives each participant call, and each step as a whole. */\nexport interface FlowDials {\n /** One attempt at a call. */\n readonly attemptTimeoutMs: number;\n /** Every attempt at a step together, retries included. */\n readonly stepTimeoutMs: number;\n /** Segments of one stage run at once. */\n readonly concurrency: number;\n}\n\nexport const DEFAULT_FLOW_DIALS: FlowDials = {\n attemptTimeoutMs: 10_000,\n stepTimeoutMs: 60_000,\n concurrency: 4,\n};\n\n/** What the interpreter runs a slip with: the catalog, who announces, the dials. */\nexport interface InterpreterOptions {\n readonly catalog: FragmentCatalog;\n /** The slice the `flow.*` events are announced from. */\n readonly source: string;\n readonly dials?: Partial<FlowDials>;\n /**\n * One participant's own attempt and step bounds, over `dials` — the same\n * shape the breaker's `dialsFor` and the bulkhead's `permitsFor` take, so a\n * participant's dials are one row set however many guards read them.\n */\n readonly dialsFor?: (\n participant: string,\n ) => Partial<Omit<FlowDials, 'concurrency'>> | undefined;\n}\n\n/**\n * The interpreter's options, provided to the workflow it is registered with —\n * the payload is the slip; the catalog and dials are the deployment's.\n */\nexport class InterpreterOptionsTag extends Context.Service<\n InterpreterOptionsTag,\n InterpreterOptions\n>()('@entifix/workflow/InterpreterOptions') {}\n\n/**\n * The one workflow every flow runs as: its payload is the pinned slip and the\n * idempotency key that started it\n * ([ADR 0016](../../../../../docs/adr/0016-a-flow-engine-port-and-a-durable-workflow-adapter.md)).\n * The execution id derives from the key, so the same key is the same flow.\n *\n * A flow suspended past its commit point is resumed on the retry schedule:\n * a participant that comes back gets the step again, idempotently.\n */\nexport const SlipWorkflow = Workflow.make('entifix/slip', {\n payload: { key: Schema.String, slip: Slip },\n idempotencyKey: payload => payload.key,\n success: FlowOutcome,\n // Its failure is what suspends it past the commit point; it is never the\n // flow's result.\n error: JournaledFailure,\n // Doubling from a second, never more than a minute apart.\n suspendedRetrySchedule: Schedule.min([\n Schedule.exponential('1 second'),\n Schedule.spaced('1 minute'),\n ]),\n});\n\n/** The durable wait a `wait` step parks on, addressed by the step's name. */\nexport const waitFor = (step: string) =>\n DurableDeferred.make(`wait:${step}`, { success: Schema.Unknown });\n\n/** A step's failure, with the step it happened at. */\ninterface StepFailed {\n readonly step: SlipStep;\n readonly failure: EntifixFailure;\n}\n\nconst segmentOf = (step: SlipStep) =>\n step.segment === undefined\n ? undefined\n : [step.segment.vendor, step.segment.paymentMethod]\n .filter(Boolean)\n .join('/');\n\n/**\n * A stage's steps as the groups that run at once: one group per segment when\n * every step names one — a basket's vendors, joined before the shared pivot —\n * and otherwise one group, in order.\n */\nconst groupsOf = (steps: readonly SlipStep[]): readonly SlipStep[][] => {\n if (steps.some(step => segmentOf(step) === undefined)) return [[...steps]];\n const groups = new Map<string, SlipStep[]>();\n for (const step of steps) {\n const key = segmentOf(step) as string;\n groups.set(key, [...(groups.get(key) ?? []), step]);\n }\n return [...groups.values()];\n};\n\n/**\n * Interprets a pinned slip of grammar version 1\n * ([ADR 0016](../../../../../docs/adr/0016-a-flow-engine-port-and-a-durable-workflow-adapter.md)):\n * each step is an Activity named after it and keyed `<flowId>:<step>`, so a\n * resumed flow replays what already ran and calls a participant at most once\n * per step. Inside the Activity the call runs under the participant's\n * breaker and bulkhead and the retry budget, each attempt bounded and the\n * step as a whole bounded; nothing retries outside it.\n *\n * - Before and at the commit point, a failure — a breaker already open\n * included, which fails fast — compensates what succeeded, in reverse, and\n * the flow ends `compensated`.\n * - After it, a failure suspends the flow at that step: the money is taken,\n * so the flow rolls forward when the participant comes back, never back.\n * - A `wait` step parks the flow on a durable deferred its signal completes,\n * raced with a durable clock when it names `timeoutMs`; it may name a\n * `correlation`, so a sender that knows the order rather than the flow can\n * complete it.\n * - The segments of a stage run at once, bounded, and join before the next.\n *\n * Every transition is announced as a `flow.*` event through the outbox, by an\n * Activity, so it is written once whatever replays. Compensation is explicit\n * rather than a workflow finalizer, so the record says `compensated` only\n * after the last undoing has run — and an undoing that fails stops the flow\n * `failed` at that step instead of pretending.\n */\nconst interpretV1 = (slip: Slip) =>\n Effect.gen(function* () {\n const options = yield* InterpreterOptionsTag;\n const dials = { ...DEFAULT_FLOW_DIALS, ...options.dials };\n const { catalog } = options;\n const steps = slipSteps(slip);\n const ordinalOf = (step: SlipStep) => steps.indexOf(step) + 1;\n const commitPoint = steps.findIndex(\n step => catalog.get(step.ref)?.kind === 'pivot',\n );\n const bindings = makeBindings();\n const done: SlipStep[] = [];\n\n const record = (name: string, patch: FlowRecordPatch) =>\n Activity.make({\n name,\n execute: Effect.flatMap(FlowStatusStoreTag, store =>\n store.update(slip.flowId, patch),\n ).pipe(\n Effect.andThen(\n Metric.update(\n Metric.withAttributes(flowStates, {\n state: patch.state,\n source: options.source,\n }),\n 1,\n ),\n ),\n Effect.orDie,\n ),\n });\n\n const announce = (\n name: FlowEventName,\n step: string,\n ordinal: number,\n failure?: EntifixFailure,\n ) =>\n Activity.make({\n name: `announce:${name}:${step}`,\n execute: Effect.flatMap(OutboxTag, outbox =>\n outbox.enqueue(\n flowEvent({\n name,\n source: options.source,\n at: new Date().toISOString(),\n step: `${name}:${step}`,\n ordinal,\n audience: [\n { kind: 'flow', id: slip.flowId },\n ...(slip.audience ?? []),\n ],\n data: {\n flowId: slip.flowId,\n definition: slip.recipe,\n slipVersion: slip.slipVersion,\n step,\n ...(failure === undefined\n ? {}\n : {\n failure: {\n code: failure.code,\n category: failure.category,\n },\n }),\n },\n }),\n ),\n ).pipe(Effect.orDie),\n });\n\n /** One call, bounded, under the participant's breaker, bulkhead and budget. */\n const guarded = (\n step: SlipStep,\n attempt: Effect.Effect<unknown, EntifixFailure>,\n ) =>\n Effect.gen(function* () {\n const key = step.participant ?? 'participant';\n const bounds = { ...dials, ...options.dialsFor?.(key) };\n const breaker = yield* CircuitBreakerTag;\n const bulkhead = yield* BulkheadTag;\n const timedOut = () => Effect.fail(new Timeout({ dependency: key }));\n return yield* breaker\n .guard(key)(\n bulkhead.withPermit(key)(\n attempt.pipe(\n Effect.timeoutOrElse({\n duration: bounds.attemptTimeoutMs,\n orElse: timedOut,\n }),\n ),\n ),\n )\n .pipe(\n On(Failure.isFamily('technical')).retry({\n key: { dependency: key },\n idempotent: true,\n }),\n Effect.timeoutOrElse({\n duration: bounds.stepTimeoutMs,\n orElse: timedOut,\n }),\n );\n });\n\n const call = (step: SlipStep, kind: 'action' | 'compensation') =>\n Activity.make({\n name: `${step.step}:${kind}`,\n // Whatever the participant answered, as JSON — `undefined` included,\n // which a journal cannot otherwise hold.\n success: JsonBody,\n error: JournaledFailure,\n execute: Effect.flatMap(ParticipantClientTag, participant =>\n guarded(\n step,\n participant.call({\n step,\n kind,\n idempotencyKey:\n kind === 'action'\n ? `${slip.flowId}:${step.step}`\n : `${slip.flowId}:${step.step}:compensation`,\n inputs: bindings.inputsFor(step, catalog.get(step.ref), kind),\n }),\n ),\n ),\n });\n\n const wait = (step: SlipStep) =>\n Effect.gen(function* () {\n const correlation = step.parameters?.['correlation'];\n yield* record(`status:${step.step}:waiting`, {\n state: 'waiting',\n step: step.step,\n waitingFor: step.step,\n ...(typeof correlation === 'string' ? { correlation } : {}),\n });\n const signalled = DurableDeferred.await(waitFor(step.step));\n const timeoutMs = step.parameters?.['timeoutMs'];\n // A durable race, so a replay answers the branch that won rather than\n // racing again.\n const answer =\n typeof timeoutMs === 'number'\n ? yield* DurableDeferred.raceAll({\n name: `race:${step.step}`,\n success: Schema.Unknown,\n error: FlowWaitTimedOut,\n effects: [\n signalled,\n DurableClock.sleep({\n name: `timeout:${step.step}`,\n duration: timeoutMs,\n }).pipe(\n Effect.andThen(\n Effect.fail(new FlowWaitTimedOut({ step: step.step })),\n ),\n ),\n ],\n })\n : yield* signalled;\n bindings.record(step, catalog.get(step.ref), answer);\n yield* record(`status:${step.step}:resumed`, {\n state: 'running',\n step: step.step,\n });\n });\n\n /** One step, its failure tagged with where it happened. */\n const run = (step: SlipStep) =>\n Effect.gen(function* () {\n if (catalog.get(step.ref)?.kind === 'wait')\n return yield* wait(step).pipe(\n Effect.mapError((failure): StepFailed => ({ step, failure })),\n );\n yield* record(`status:${step.step}:running`, {\n state: 'running',\n step: step.step,\n });\n const answer = yield* call(step, 'action').pipe(\n Effect.mapError((failure): StepFailed => ({ step, failure })),\n );\n bindings.record(step, catalog.get(step.ref), answer);\n if (catalog.get(step.ref)?.kind === 'compensatable') done.push(step);\n });\n\n yield* announce('flow.started', 'started', 0);\n\n for (const stage of slip.stages) {\n const exits = yield* Effect.forEach(\n groupsOf(stage.steps),\n group => Effect.exit(Effect.forEach(group, run, { discard: true })),\n { concurrency: dials.concurrency },\n );\n // The earliest step that failed decides; a group's failure is its own.\n const [first] = exits\n .flatMap(exit =>\n Exit.isFailure(exit)\n ? exit.cause.reasons.flatMap(reason =>\n // A durable race fails with its losers' interruptions beside\n // the failure that won, so the failure is looked for.\n reason._tag === 'Fail' ? [reason.error] : [],\n )\n : [],\n )\n .sort((a, b) => ordinalOf(a.step) - ordinalOf(b.step));\n if (first === undefined) continue;\n\n const { step, failure } = first;\n if (commitPoint === -1 || steps.indexOf(step) <= commitPoint) {\n for (const undone of [...done].reverse()) {\n const undoing = yield* Effect.exit(call(undone, 'compensation'));\n if (Exit.isFailure(undoing)) {\n yield* record(`status:${undone.step}:failed`, {\n state: 'failed',\n step: undone.step,\n });\n yield* announce(\n 'flow.failed',\n undone.step,\n ordinalOf(undone),\n failure,\n );\n return 'failed' as const;\n }\n }\n yield* record(`status:${step.step}:compensated`, {\n state: 'compensated',\n step: step.step,\n });\n yield* announce('flow.failed', step.step, ordinalOf(step), failure);\n return 'compensated' as const;\n }\n\n yield* record(`status:${step.step}:suspended`, {\n state: 'suspended',\n step: step.step,\n });\n yield* announce('flow.suspended', step.step, ordinalOf(step), failure);\n return yield* Effect.fail(failure).pipe(\n Effect.provideService(Workflow.SuspendOnFailure, true),\n );\n }\n\n yield* record('status:completed', { state: 'completed' });\n yield* announce('flow.completed', 'completed', steps.length + 1);\n return 'completed' as const;\n });\n\n/**\n * One interpreter per grammar version a slip can be written in. A flow runs\n * to its end under the grammar it started with, so a change to how steps are\n * walked — their order, their Activity names — is a new entry here, never an\n * edit to an existing one; the journal replay holds every entry to its\n * recorded command sequence.\n */\nconst INTERPRETERS = { 1: interpretV1 } as const satisfies Record<\n Slip['slipVersion'],\n (slip: Slip) => unknown\n>;\n\n/** Every `slipVersion` the interpreter walks — each must have a journal. */\nexport const interpretedSlipVersions: readonly number[] =\n Object.keys(INTERPRETERS).map(Number);\n\nexport const interpretSlip = (slip: Slip) =>\n INTERPRETERS[slip.slipVersion](slip);\n\n/** The interpreter registered with the workflow engine, with its options. */\nexport const SlipWorkflowLayer = (options: InterpreterOptions) =>\n SlipWorkflow.toLayer(payload =>\n interpretSlip(payload.slip).pipe(\n Effect.provideService(InterpreterOptionsTag, options),\n ),\n );\n"],"names":["Slip","slipSteps","decodeProblem","Failure","isFailure","JsonBody","problemOf","Timeout","BulkheadTag","CircuitBreakerTag","flowEvent","On","OutboxTag","ParticipantClientTag","Context","Effect","Exit","Metric","Schedule","Schema","SchemaTransformation","Activity","DurableClock","DurableDeferred","Workflow","flowStates","FlowStatusStoreTag","makeBindings","JournaledFailure","Unknown","pipe","decodeTo","declare","value","transform","decode","body","encode","failure","FlowOutcome","Literals","FlowWaitTimedOut","timeout","step","String","message","DEFAULT_FLOW_DIALS","attemptTimeoutMs","stepTimeoutMs","concurrency","InterpreterOptionsTag","Service","SlipWorkflow","make","payload","key","slip","idempotencyKey","success","error","suspendedRetrySchedule","min","exponential","spaced","waitFor","segmentOf","segment","undefined","vendor","paymentMethod","filter","Boolean","join","groupsOf","steps","some","groups","Map","set","get","values","interpretV1","gen","options","dials","catalog","ordinalOf","indexOf","commitPoint","findIndex","ref","kind","bindings","done","record","name","patch","execute","flatMap","store","update","flowId","andThen","withAttributes","state","source","orDie","announce","ordinal","outbox","enqueue","at","Date","toISOString","audience","id","data","definition","recipe","slipVersion","code","category","guarded","attempt","participant","bounds","dialsFor","breaker","bulkhead","timedOut","fail","dependency","guard","withPermit","timeoutOrElse","duration","orElse","isFamily","retry","idempotent","call","inputs","inputsFor","wait","correlation","parameters","waitingFor","signalled","await","timeoutMs","answer","raceAll","effects","sleep","run","mapError","push","stage","stages","exits","forEach","group","exit","discard","first","cause","reasons","reason","_tag","sort","a","b","undone","reverse","undoing","provideService","SuspendOnFailure","length","INTERPRETERS","interpretedSlipVersions","Object","keys","map","Number","interpretSlip","SlipWorkflowLayer","toLayer"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/interpreter/slip-workflow.ts"],"sourcesContent":["import {\n type FragmentCatalog,\n Slip,\n type SlipStep,\n slipSteps,\n} from '@entifix/business/flow';\nimport {\n decodeProblem,\n type EntifixFailure,\n Failure,\n isFailure,\n JsonBody,\n problemOf,\n Timeout,\n} from '@entifix/core';\nimport {\n BulkheadTag,\n CircuitBreakerTag,\n flowEvent,\n type FlowEventName,\n On,\n OutboxTag,\n ParticipantClientTag,\n} from '@entifix/transactions';\nimport {\n Cause,\n Context,\n Effect,\n Exit,\n Metric,\n Schedule,\n Schema,\n SchemaTransformation,\n} from 'effect';\nimport {\n Activity,\n DurableClock,\n DurableDeferred,\n Workflow,\n} from 'effect/workflow';\n\nimport {\n type FlowRecordPatch,\n flowStates,\n FlowStatusStoreTag,\n} from '../status/flow-status-store.js';\nimport { makeBindings } from './bindings.js';\n\n/**\n * A failure as an Activity journals it: the problem body on the way in, the\n * failure its code names on the way out, so a resumed flow sees the typed\n * refusal a participant sent rather than a cast.\n */\nexport const JournaledFailure: Schema.Codec<EntifixFailure, unknown> =\n Schema.Unknown.pipe(\n Schema.decodeTo(\n Schema.declare((value): value is EntifixFailure => isFailure(value)),\n SchemaTransformation.transform({\n decode: body => decodeProblem(body),\n encode: failure => problemOf(failure),\n }),\n ),\n );\n\n/**\n * How a flow ended, as the workflow's own result: every step ran, or a\n * refusal before the commit point was undone, or an undoing itself failed and\n * the flow stopped where it was — which a person has to look at — or a wait\n * that strands on its timeout ran out, which a person has to look at too.\n */\nexport const FlowOutcome = Schema.Literals([\n 'completed',\n 'compensated',\n 'failed',\n 'stranded',\n]);\n\n/**\n * What a wait does when its `timeoutMs` runs out, from its `onTimeout`\n * parameter: answer as if signalled with an outcome a later guard branches\n * on, strand the flow for an operator, or — named neither — fail as any step\n * does, which past the commit point suspends it.\n */\ntype OnTimeout =\n | { readonly kind: 'outcome'; readonly outcome: string }\n | { readonly kind: 'strand' }\n | { readonly kind: 'fail' };\n\nconst onTimeoutOf = (step: SlipStep): OnTimeout => {\n const named = step.parameters?.['onTimeout'];\n if (named === 'strand') return { kind: 'strand' };\n const outcome =\n typeof named === 'object' && named !== null\n ? (named as { readonly outcome?: unknown }).outcome\n : undefined;\n return typeof outcome === 'string'\n ? { kind: 'outcome', outcome }\n : { kind: 'fail' };\n};\n\n/** The outcome a wait was answered with: its signal's `outcome`, when a string. */\nconst outcomeOf = (answer: unknown): string | undefined => {\n const named =\n typeof answer === 'object' && answer !== null\n ? (answer as { readonly outcome?: unknown }).outcome\n : undefined;\n return typeof named === 'string' ? named : undefined;\n};\n\n/** The race's answer when a wait that strands on its timeout ran out. */\nconst STRANDED = Symbol.for('@entifix/workflow/stranded');\n\n/** A durable wait that outlived its timeout before its signal came. */\nexport class FlowWaitTimedOut extends Failure.timeout<FlowWaitTimedOut>()(\n 'FlowWaitTimedOut',\n 'flowWaitTimedOut',\n { step: Schema.String },\n { message: ({ step }) => `the wait at '${step}' timed out` },\n) {}\n\n/** How long a flow gives each participant call, and each step as a whole. */\nexport interface FlowDials {\n /** One attempt at a call. */\n readonly attemptTimeoutMs: number;\n /** Every attempt at a step together, retries included. */\n readonly stepTimeoutMs: number;\n /** Segments of one stage run at once. */\n readonly concurrency: number;\n}\n\nexport const DEFAULT_FLOW_DIALS: FlowDials = {\n attemptTimeoutMs: 10_000,\n stepTimeoutMs: 60_000,\n concurrency: 4,\n};\n\n/** What the interpreter runs a slip with: the catalog, who announces, the dials. */\nexport interface InterpreterOptions {\n readonly catalog: FragmentCatalog;\n /** The slice the `flow.*` events are announced from. */\n readonly source: string;\n readonly dials?: Partial<FlowDials>;\n /**\n * One participant's own attempt and step bounds, over `dials` — the same\n * shape the breaker's `dialsFor` and the bulkhead's `permitsFor` take, so a\n * participant's dials are one row set however many guards read them.\n */\n readonly dialsFor?: (\n participant: string,\n ) => Partial<Omit<FlowDials, 'concurrency'>> | undefined;\n}\n\n/**\n * The interpreter's options, provided to the workflow it is registered with —\n * the payload is the slip; the catalog and dials are the deployment's.\n */\nexport class InterpreterOptionsTag extends Context.Service<\n InterpreterOptionsTag,\n InterpreterOptions\n>()('@entifix/workflow/InterpreterOptions') {}\n\n/**\n * The one workflow every flow runs as: its payload is the pinned slip and the\n * idempotency key that started it\n * ([ADR 0016](../../../../../docs/adr/0016-a-flow-engine-port-and-a-durable-workflow-adapter.md)).\n * The execution id derives from the key, so the same key is the same flow.\n *\n * A flow suspended past its commit point is resumed on the retry schedule:\n * a participant that comes back gets the step again, idempotently.\n */\nexport const SlipWorkflow = Workflow.make('entifix/slip', {\n payload: { key: Schema.String, slip: Slip },\n idempotencyKey: payload => payload.key,\n success: FlowOutcome,\n // Its failure is what suspends it past the commit point; it is never the\n // flow's result.\n error: JournaledFailure,\n // Doubling from a second, never more than a minute apart.\n suspendedRetrySchedule: Schedule.min([\n Schedule.exponential('1 second'),\n Schedule.spaced('1 minute'),\n ]),\n});\n\n/** The durable wait a `wait` step parks on, addressed by the step's name. */\nexport const waitFor = (step: string) =>\n DurableDeferred.make(`wait:${step}`, { success: Schema.Unknown });\n\n/** A step's failure, with the step it happened at. */\ninterface StepFailed {\n readonly step: SlipStep;\n readonly failure: EntifixFailure;\n}\n\nconst segmentOf = (step: SlipStep) =>\n step.segment === undefined\n ? undefined\n : [step.segment.vendor, step.segment.paymentMethod]\n .filter(Boolean)\n .join('/');\n\n/**\n * A stage's steps as the groups that run at once: one group per segment when\n * every step names one — a basket's vendors, joined before the shared pivot —\n * and otherwise one group, in order.\n */\nconst groupsOf = (steps: readonly SlipStep[]): readonly SlipStep[][] => {\n if (steps.some(step => segmentOf(step) === undefined)) return [[...steps]];\n const groups = new Map<string, SlipStep[]>();\n for (const step of steps) {\n const key = segmentOf(step) as string;\n groups.set(key, [...(groups.get(key) ?? []), step]);\n }\n return [...groups.values()];\n};\n\n/**\n * Interprets a pinned slip of grammar version 1\n * ([ADR 0016](../../../../../docs/adr/0016-a-flow-engine-port-and-a-durable-workflow-adapter.md)):\n * each step is an Activity named after it and keyed `<flowId>:<step>`, so a\n * resumed flow replays what already ran and calls a participant at most once\n * per step. Inside the Activity the call runs under the participant's\n * breaker and bulkhead and the retry budget, each attempt bounded and the\n * step as a whole bounded; nothing retries outside it.\n *\n * - Before and at the commit point, a failure — a breaker already open\n * included, which fails fast — compensates what succeeded, in reverse, and\n * the flow ends `compensated`.\n * - After it, a failure suspends the flow at that step: the money is taken,\n * so the flow rolls forward when the participant comes back, never back.\n * - A `wait` step parks the flow on a durable deferred its signal completes,\n * raced with a durable clock when it names `timeoutMs`; it may name a\n * `correlation`, so a sender that knows the order rather than the flow can\n * complete it.\n * - The segments of a stage run at once, bounded, and join before the next.\n *\n * Every transition is announced as a `flow.*` event through the outbox, by an\n * Activity, so it is written once whatever replays. Compensation is explicit\n * rather than a workflow finalizer, so the record says `compensated` only\n * after the last undoing has run — and an undoing that fails stops the flow\n * `failed` at that step instead of pretending.\n */\n/** What differs between grammar versions as the walk is interpreted. */\ninterface Grammar {\n /** Whether a wait past the commit point announces `flow.waiting` (v2). */\n readonly announcesCommittedWaits: boolean;\n}\n\nconst walkSlip = (slip: Slip, grammar: Grammar) =>\n Effect.gen(function* () {\n const options = yield* InterpreterOptionsTag;\n const dials = { ...DEFAULT_FLOW_DIALS, ...options.dials };\n const { catalog } = options;\n const steps = slipSteps(slip);\n const ordinalOf = (step: SlipStep) => steps.indexOf(step) + 1;\n const commitPoint = steps.findIndex(\n step => catalog.get(step.ref)?.kind === 'pivot',\n );\n const bindings = makeBindings();\n const done: SlipStep[] = [];\n /** What each wait was answered with, by step name — what a guard reads. */\n const answered = new Map<string, string | undefined>();\n /** Steps a guard passed over; a step guarded on one of them is skipped too. */\n const skipped = new Set<string>();\n /** The wait whose timeout stranded the flow, once one has. */\n let stranded: SlipStep | undefined;\n\n const record = (name: string, patch: FlowRecordPatch) =>\n Activity.make({\n name,\n execute: Effect.flatMap(FlowStatusStoreTag, store =>\n store.update(slip.flowId, patch),\n ).pipe(\n Effect.andThen(\n Metric.update(\n Metric.withAttributes(flowStates, {\n state: patch.state,\n source: options.source,\n }),\n 1,\n ),\n ),\n Effect.orDie,\n ),\n });\n\n const announce = (\n name: FlowEventName,\n step: string,\n ordinal: number,\n failure?: EntifixFailure,\n ) =>\n Activity.make({\n name: `announce:${name}:${step}`,\n execute: Effect.flatMap(OutboxTag, outbox =>\n outbox.enqueue(\n flowEvent({\n name,\n source: options.source,\n at: new Date().toISOString(),\n step: `${name}:${step}`,\n ordinal,\n audience: [\n { kind: 'flow', id: slip.flowId },\n ...(slip.audience ?? []),\n ],\n data: {\n flowId: slip.flowId,\n definition: slip.recipe,\n slipVersion: slip.slipVersion,\n step,\n ...(failure === undefined\n ? {}\n : {\n failure: {\n code: failure.code,\n category: failure.category,\n },\n }),\n },\n }),\n ),\n ).pipe(Effect.orDie),\n });\n\n /** One call, bounded, under the participant's breaker, bulkhead and budget. */\n const guarded = (\n step: SlipStep,\n attempt: Effect.Effect<unknown, EntifixFailure>,\n ) =>\n Effect.gen(function* () {\n const key = step.participant ?? 'participant';\n const bounds = { ...dials, ...options.dialsFor?.(key) };\n const breaker = yield* CircuitBreakerTag;\n const bulkhead = yield* BulkheadTag;\n const timedOut = () => Effect.fail(new Timeout({ dependency: key }));\n return yield* breaker\n .guard(key)(\n bulkhead.withPermit(key)(\n attempt.pipe(\n Effect.timeoutOrElse({\n duration: bounds.attemptTimeoutMs,\n orElse: timedOut,\n }),\n ),\n ),\n )\n .pipe(\n On(Failure.isFamily('technical')).retry({\n key: { dependency: key },\n idempotent: true,\n }),\n Effect.timeoutOrElse({\n duration: bounds.stepTimeoutMs,\n orElse: timedOut,\n }),\n );\n });\n\n const call = (step: SlipStep, kind: 'action' | 'compensation') =>\n Activity.make({\n name: `${step.step}:${kind}`,\n // Whatever the participant answered, as JSON — `undefined` included,\n // which a journal cannot otherwise hold.\n success: JsonBody,\n error: JournaledFailure,\n execute: Effect.flatMap(ParticipantClientTag, participant =>\n guarded(\n step,\n participant.call({\n step,\n kind,\n idempotencyKey:\n kind === 'action'\n ? `${slip.flowId}:${step.step}`\n : `${slip.flowId}:${step.step}:compensation`,\n inputs: bindings.inputsFor(step, catalog.get(step.ref), kind),\n }),\n ),\n ),\n });\n\n const wait = (step: SlipStep) =>\n Effect.gen(function* () {\n const correlation = step.parameters?.['correlation'];\n const accepts = catalog.get(step.ref)?.outcomes;\n const onTimeout = onTimeoutOf(step);\n yield* record(`status:${step.step}:waiting`, {\n state: 'waiting',\n step: step.step,\n waitingFor: step.step,\n ...(typeof correlation === 'string' ? { correlation } : {}),\n ...(accepts === undefined ? {} : { accepts }),\n });\n // Past the commit point the caller's write has landed, and the wait is\n // for someone else — a courier, a person — so the flow says so and a\n // browser holding the write pending can let it go. Before it, nothing\n // has landed, and the flow stays silent.\n if (\n grammar.announcesCommittedWaits &&\n commitPoint !== -1 &&\n steps.indexOf(step) > commitPoint\n )\n yield* announce('flow.waiting', step.step, ordinalOf(step));\n const signalled = DurableDeferred.await(waitFor(step.step));\n const timeoutMs = step.parameters?.['timeoutMs'];\n // A durable race, so a replay answers the branch that won rather than\n // racing again.\n const answer =\n typeof timeoutMs === 'number'\n ? yield* DurableDeferred.raceAll({\n name: `race:${step.step}`,\n success: Schema.Unknown,\n error: FlowWaitTimedOut,\n effects: [\n signalled,\n DurableClock.sleep({\n name: `timeout:${step.step}`,\n duration: timeoutMs,\n }).pipe(\n Effect.andThen(\n // An outcome answers as a signal would; a strand and a\n // plain failure both fail the race, and are told apart\n // below — so the journaled race is the same either way.\n onTimeout.kind === 'outcome'\n ? Effect.succeed<unknown>({\n outcome: onTimeout.outcome,\n })\n : Effect.fail(\n new FlowWaitTimedOut({ step: step.step }),\n ),\n ),\n ),\n ],\n }).pipe(\n Effect.catchTag('FlowWaitTimedOut', timedOut =>\n onTimeout.kind === 'strand'\n ? Effect.succeed<unknown>(STRANDED)\n : Effect.fail(timedOut),\n ),\n )\n : yield* signalled;\n if (answer === STRANDED) {\n stranded = step;\n return;\n }\n answered.set(step.step, outcomeOf(answer));\n bindings.record(step, catalog.get(step.ref), answer);\n yield* record(`status:${step.step}:resumed`, {\n state: 'running',\n step: step.step,\n });\n });\n\n /** One step, its failure tagged with where it happened. */\n const run = (step: SlipStep) =>\n Effect.gen(function* () {\n // A stranded flow runs nothing more, in any group of the stage.\n if (stranded !== undefined) return;\n if (step.guard !== undefined) {\n const { wait: guardedOn, outcomes } = step.guard;\n const outcome = answered.get(guardedOn);\n if (\n skipped.has(guardedOn) ||\n outcome === undefined ||\n !outcomes.includes(outcome)\n ) {\n skipped.add(step.step);\n return;\n }\n }\n if (catalog.get(step.ref)?.kind === 'wait')\n return yield* wait(step).pipe(\n Effect.mapError((failure): StepFailed => ({ step, failure })),\n );\n yield* record(`status:${step.step}:running`, {\n state: 'running',\n step: step.step,\n });\n const answer = yield* call(step, 'action').pipe(\n Effect.mapError((failure): StepFailed => ({ step, failure })),\n );\n bindings.record(step, catalog.get(step.ref), answer);\n if (catalog.get(step.ref)?.kind === 'compensatable') done.push(step);\n });\n\n yield* announce('flow.started', 'started', 0);\n\n for (const stage of slip.stages) {\n const exits = yield* Effect.forEach(\n groupsOf(stage.steps),\n group => Effect.exit(Effect.forEach(group, run, { discard: true })),\n { concurrency: dials.concurrency },\n );\n // The earliest step that failed decides; a group's failure is its own.\n const [first] = exits\n .flatMap(exit =>\n Exit.isFailure(exit)\n ? // A durable race fails with its losers' interruptions beside\n // the failure that won, so the failure is looked for.\n exit.cause.reasons\n .filter(Cause.isFailReason)\n .map(reason => reason.error)\n : [],\n )\n .sort((a, b) => ordinalOf(a.step) - ordinalOf(b.step));\n if (first === undefined) {\n if (stranded === undefined) continue;\n yield* record(`status:${stranded.step}:stranded`, {\n state: 'stranded',\n step: stranded.step,\n });\n yield* announce('flow.stranded', stranded.step, ordinalOf(stranded));\n return 'stranded' as const;\n }\n\n const { step, failure } = first;\n if (commitPoint === -1 || steps.indexOf(step) <= commitPoint) {\n for (const undone of [...done].reverse()) {\n const undoing = yield* Effect.exit(call(undone, 'compensation'));\n if (Exit.isFailure(undoing)) {\n yield* record(`status:${undone.step}:failed`, {\n state: 'failed',\n step: undone.step,\n });\n yield* announce(\n 'flow.failed',\n undone.step,\n ordinalOf(undone),\n failure,\n );\n return 'failed' as const;\n }\n }\n yield* record(`status:${step.step}:compensated`, {\n state: 'compensated',\n step: step.step,\n });\n yield* announce('flow.failed', step.step, ordinalOf(step), failure);\n return 'compensated' as const;\n }\n\n yield* record(`status:${step.step}:suspended`, {\n state: 'suspended',\n step: step.step,\n });\n yield* announce('flow.suspended', step.step, ordinalOf(step), failure);\n return yield* Effect.fail(failure).pipe(\n Effect.provideService(Workflow.SuspendOnFailure, true),\n );\n }\n\n yield* record('status:completed', { state: 'completed' });\n yield* announce('flow.completed', 'completed', steps.length + 1);\n return 'completed' as const;\n });\n\n/**\n * One interpreter per grammar version a slip can be written in. A flow runs\n * to its end under the grammar it started with, so a change to how steps are\n * walked — their order, their Activity names — is a new entry here, never an\n * edit to an existing one; the journal replay holds every entry to its\n * recorded command sequence.\n */\nconst INTERPRETERS = {\n 1: (slip: Slip) => walkSlip(slip, { announcesCommittedWaits: false }),\n 2: (slip: Slip) => walkSlip(slip, { announcesCommittedWaits: true }),\n} as const satisfies Record<Slip['slipVersion'], (slip: Slip) => unknown>;\n\n/** Every `slipVersion` the interpreter walks — each must have a journal. */\nexport const interpretedSlipVersions: readonly number[] =\n Object.keys(INTERPRETERS).map(Number);\n\nexport const interpretSlip = (slip: Slip) =>\n INTERPRETERS[slip.slipVersion](slip);\n\n/** The interpreter registered with the workflow engine, with its options. */\nexport const SlipWorkflowLayer = (options: InterpreterOptions) =>\n SlipWorkflow.toLayer(payload =>\n interpretSlip(payload.slip).pipe(\n Effect.provideService(InterpreterOptionsTag, options),\n ),\n );\n"],"names":["Slip","slipSteps","decodeProblem","Failure","isFailure","JsonBody","problemOf","Timeout","BulkheadTag","CircuitBreakerTag","flowEvent","On","OutboxTag","ParticipantClientTag","Cause","Context","Effect","Exit","Metric","Schedule","Schema","SchemaTransformation","Activity","DurableClock","DurableDeferred","Workflow","flowStates","FlowStatusStoreTag","makeBindings","JournaledFailure","Unknown","pipe","decodeTo","declare","value","transform","decode","body","encode","failure","FlowOutcome","Literals","onTimeoutOf","step","named","parameters","kind","outcome","undefined","outcomeOf","answer","STRANDED","Symbol","for","FlowWaitTimedOut","timeout","String","message","DEFAULT_FLOW_DIALS","attemptTimeoutMs","stepTimeoutMs","concurrency","InterpreterOptionsTag","Service","SlipWorkflow","make","payload","key","slip","idempotencyKey","success","error","suspendedRetrySchedule","min","exponential","spaced","waitFor","segmentOf","segment","vendor","paymentMethod","filter","Boolean","join","groupsOf","steps","some","groups","Map","set","get","values","walkSlip","grammar","gen","options","dials","catalog","ordinalOf","indexOf","commitPoint","findIndex","ref","bindings","done","answered","skipped","Set","stranded","record","name","patch","execute","flatMap","store","update","flowId","andThen","withAttributes","state","source","orDie","announce","ordinal","outbox","enqueue","at","Date","toISOString","audience","id","data","definition","recipe","slipVersion","code","category","guarded","attempt","participant","bounds","dialsFor","breaker","bulkhead","timedOut","fail","dependency","guard","withPermit","timeoutOrElse","duration","orElse","isFamily","retry","idempotent","call","inputs","inputsFor","wait","correlation","accepts","outcomes","onTimeout","waitingFor","announcesCommittedWaits","signalled","await","timeoutMs","raceAll","effects","sleep","succeed","catchTag","run","guardedOn","has","includes","add","mapError","push","stage","stages","exits","forEach","group","exit","discard","first","cause","reasons","isFailReason","map","reason","sort","a","b","undone","reverse","undoing","provideService","SuspendOnFailure","length","INTERPRETERS","interpretedSlipVersions","Object","keys","Number","interpretSlip","SlipWorkflowLayer","toLayer"],"mappings":"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"}
@@ -1,7 +1,10 @@
1
1
  import { Effect, Layer } from 'effect';
2
2
  import { SqlClient } from 'effect/sql';
3
3
  import { type FlowStatusStore, FlowStatusStoreTag } from '../status/flow-status-store.js';
4
- /** Creates the flow status table if it is missing. Idempotent. */
4
+ /**
5
+ * Creates the flow status table if it is missing, and adds a column a table
6
+ * created by an earlier release lacks. Idempotent.
7
+ */
5
8
  export declare const ensureFlowStatusTable: (sql: SqlClient.SqlClient) => Effect.Effect<void, import("@entifix/core").StoreFailure, never>;
6
9
  /**
7
10
  * The flow status store over SQL, beside the workflow engine's own tables. A
@@ -1 +1 @@
1
- {"version":3,"file":"sql-flow-status-store.d.ts","sourceRoot":"","sources":["../../src/postgres/sql-flow-status-store.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAU,MAAM,QAAQ,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,OAAO,EAEL,KAAK,eAAe,EACpB,kBAAkB,EACnB,MAAM,gCAAgC,CAAC;AAwBxC,kEAAkE;AAClE,eAAO,MAAM,qBAAqB,GAAI,KAAK,SAAS,CAAC,SAAS,qEAUZ,CAAC;AAEnD;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,GACjC,KAAK,SAAS,CAAC,SAAS,KACvB,eAqDD,CAAC;AAEH,mEAAmE;AACnE,eAAO,MAAM,uBAAuB,4FAOnC,CAAC"}
1
+ {"version":3,"file":"sql-flow-status-store.d.ts","sourceRoot":"","sources":["../../src/postgres/sql-flow-status-store.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAU,MAAM,QAAQ,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,OAAO,EAEL,KAAK,eAAe,EACpB,kBAAkB,EACnB,MAAM,gCAAgC,CAAC;AAgCxC;;;GAGG;AACH,eAAO,MAAM,qBAAqB,GAAI,KAAK,SAAS,CAAC,SAAS,qEAe7B,CAAC;AAElC;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,GACjC,KAAK,SAAS,CAAC,SAAS,KACvB,eAyDD,CAAC;AAEH,mEAAmE;AACnE,eAAO,MAAM,uBAAuB,4FAOnC,CAAC"}
@@ -3,6 +3,10 @@ import { translateSqlError } from '@entifix/sql';
3
3
  import { Effect, Layer, Option } from 'effect';
4
4
  import { SqlClient } from 'effect/sql';
5
5
  import { FlowStatusStoreTag } from '../status/flow-status-store.js';
6
+ const acceptsOf = (text)=>{
7
+ const parsed = JSON.parse(text);
8
+ return Array.isArray(parsed) ? parsed.map(String) : [];
9
+ };
6
10
  const recordOf = (row)=>_extends({
7
11
  flowId: row.flow_id,
8
12
  executionId: row.execution_id,
@@ -13,18 +17,28 @@ const recordOf = (row)=>_extends({
13
17
  waitingFor: row.waiting_for
14
18
  }, row.correlation === null ? {} : {
15
19
  correlation: row.correlation
20
+ }, row.accepts === null ? {} : {
21
+ accepts: acceptsOf(row.accepts)
16
22
  });
17
23
  const fault = (error)=>translateSqlError(error);
18
- /** Creates the flow status table if it is missing. Idempotent. */ export const ensureFlowStatusTable = (sql)=>sql`
19
- CREATE TABLE IF NOT EXISTS entifix_flow_status (
20
- flow_id text PRIMARY KEY,
21
- idempotency_key text NOT NULL UNIQUE,
22
- execution_id text NOT NULL,
23
- state text NOT NULL,
24
- step text,
25
- waiting_for text,
26
- correlation text
27
- )`.pipe(Effect.asVoid, Effect.mapError(fault));
24
+ /**
25
+ * Creates the flow status table if it is missing, and adds a column a table
26
+ * created by an earlier release lacks. Idempotent.
27
+ */ export const ensureFlowStatusTable = (sql)=>Effect.gen(function*() {
28
+ yield* sql`
29
+ CREATE TABLE IF NOT EXISTS entifix_flow_status (
30
+ flow_id text PRIMARY KEY,
31
+ idempotency_key text NOT NULL UNIQUE,
32
+ execution_id text NOT NULL,
33
+ state text NOT NULL,
34
+ step text,
35
+ waiting_for text,
36
+ correlation text,
37
+ accepts text
38
+ )`;
39
+ yield* sql`
40
+ ALTER TABLE entifix_flow_status ADD COLUMN IF NOT EXISTS accepts text`;
41
+ }).pipe(Effect.mapError(fault));
28
42
  /**
29
43
  * The flow status store over SQL, beside the workflow engine's own tables. A
30
44
  * registration inserts under the key or answers the flow already there, in one
@@ -55,12 +69,14 @@ const fault = (error)=>translateSqlError(error);
55
69
  UPDATE entifix_flow_status
56
70
  SET state = ${patch.state}, step = ${(_patch_step = patch.step) != null ? _patch_step : null},
57
71
  waiting_for = ${(_patch_waitingFor = patch.waitingFor) != null ? _patch_waitingFor : null},
58
- correlation = ${(_patch_correlation = patch.correlation) != null ? _patch_correlation : null}
72
+ correlation = ${(_patch_correlation = patch.correlation) != null ? _patch_correlation : null},
73
+ accepts = ${patch.accepts === undefined ? null : JSON.stringify(patch.accepts)}
59
74
  WHERE flow_id = ${flowId}`.pipe(Effect.asVoid, Effect.mapError(fault));
60
75
  },
61
76
  claimWait: (flowId, token)=>sql`
62
77
  UPDATE entifix_flow_status
63
- SET state = 'running', step = ${token}, waiting_for = NULL
78
+ SET state = 'running', step = ${token}, waiting_for = NULL,
79
+ accepts = NULL
64
80
  WHERE flow_id = ${flowId} AND state = 'waiting' AND waiting_for = ${token}
65
81
  RETURNING flow_id`.pipe(Effect.map((rows)=>rows.length > 0), Effect.mapError(fault))
66
82
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/postgres/sql-flow-status-store.ts"],"sourcesContent":["import { translateSqlError } from '@entifix/sql';\nimport type { FlowState } from '@entifix/transactions';\nimport { Effect, Layer, Option } from 'effect';\nimport { SqlClient } from 'effect/sql';\n\nimport {\n type FlowRecord,\n type FlowStatusStore,\n FlowStatusStoreTag,\n} from '../status/flow-status-store.js';\n\n// A type alias, not an interface: a row must be assignable to the record a\n// driver hands back, and an interface has no index signature.\ntype FlowStatusRow = {\n readonly flow_id: string;\n readonly execution_id: string;\n readonly state: FlowState;\n readonly step: string | null;\n readonly waiting_for: string | null;\n readonly correlation: string | null;\n};\n\nconst recordOf = (row: FlowStatusRow): FlowRecord => ({\n flowId: row.flow_id,\n executionId: row.execution_id,\n state: row.state,\n ...(row.step === null ? {} : { step: row.step }),\n ...(row.waiting_for === null ? {} : { waitingFor: row.waiting_for }),\n ...(row.correlation === null ? {} : { correlation: row.correlation }),\n});\n\nconst fault = (error: unknown) => translateSqlError(error);\n\n/** Creates the flow status table if it is missing. Idempotent. */\nexport const ensureFlowStatusTable = (sql: SqlClient.SqlClient) =>\n sql`\n CREATE TABLE IF NOT EXISTS entifix_flow_status (\n flow_id text PRIMARY KEY,\n idempotency_key text NOT NULL UNIQUE,\n execution_id text NOT NULL,\n state text NOT NULL,\n step text,\n waiting_for text,\n correlation text\n )`.pipe(Effect.asVoid, Effect.mapError(fault));\n\n/**\n * The flow status store over SQL, beside the workflow engine's own tables. A\n * registration inserts under the key or answers the flow already there, in one\n * statement and a read.\n */\nexport const makeSqlFlowStatusStore = (\n sql: SqlClient.SqlClient,\n): FlowStatusStore => ({\n register: (key, record) =>\n Effect.gen(function* () {\n const [inserted] = yield* sql<FlowStatusRow>`\n INSERT INTO entifix_flow_status\n (flow_id, idempotency_key, execution_id, state)\n VALUES (${record.flowId}, ${key}, ${record.executionId}, ${record.state})\n ON CONFLICT DO NOTHING\n RETURNING *`;\n if (inserted !== undefined) return recordOf(inserted);\n const [existing] = yield* sql<FlowStatusRow>`\n SELECT * FROM entifix_flow_status\n WHERE idempotency_key = ${key} OR flow_id = ${record.flowId}`;\n // Gone between the two statements cannot happen: nothing deletes a flow.\n return recordOf(existing as FlowStatusRow);\n }).pipe(Effect.mapError(fault)),\n\n find: flowId =>\n sql<FlowStatusRow>`\n SELECT * FROM entifix_flow_status WHERE flow_id = ${flowId}`.pipe(\n Effect.map(([row]) =>\n row === undefined ? Option.none() : Option.some(recordOf(row)),\n ),\n Effect.mapError(fault),\n ),\n\n findByCorrelation: correlation =>\n sql<FlowStatusRow>`\n SELECT * FROM entifix_flow_status\n WHERE correlation = ${correlation}`.pipe(\n Effect.map(([row]) =>\n row === undefined ? Option.none() : Option.some(recordOf(row)),\n ),\n Effect.mapError(fault),\n ),\n\n update: (flowId, patch) =>\n sql`\n UPDATE entifix_flow_status\n SET state = ${patch.state}, step = ${patch.step ?? null},\n waiting_for = ${patch.waitingFor ?? null},\n correlation = ${patch.correlation ?? null}\n WHERE flow_id = ${flowId}`.pipe(Effect.asVoid, Effect.mapError(fault)),\n\n claimWait: (flowId, token) =>\n sql<{ readonly flow_id: string }>`\n UPDATE entifix_flow_status\n SET state = 'running', step = ${token}, waiting_for = NULL\n WHERE flow_id = ${flowId} AND state = 'waiting' AND waiting_for = ${token}\n RETURNING flow_id`.pipe(\n Effect.map(rows => rows.length > 0),\n Effect.mapError(fault),\n ),\n});\n\n/** The SQL status store, its table ensured as the layer builds. */\nexport const SqlFlowStatusStoreLayer = Layer.effect(\n FlowStatusStoreTag,\n Effect.gen(function* () {\n const sql = yield* SqlClient.SqlClient;\n yield* ensureFlowStatusTable(sql);\n return makeSqlFlowStatusStore(sql);\n }),\n);\n"],"names":["translateSqlError","Effect","Layer","Option","SqlClient","FlowStatusStoreTag","recordOf","row","flowId","flow_id","executionId","execution_id","state","step","waiting_for","waitingFor","correlation","fault","error","ensureFlowStatusTable","sql","pipe","asVoid","mapError","makeSqlFlowStatusStore","register","key","record","gen","inserted","undefined","existing","find","map","none","some","findByCorrelation","update","patch","claimWait","token","rows","length","SqlFlowStatusStoreLayer","effect"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/postgres/sql-flow-status-store.ts"],"sourcesContent":["import { translateSqlError } from '@entifix/sql';\nimport type { FlowState } from '@entifix/transactions';\nimport { Effect, Layer, Option } from 'effect';\nimport { SqlClient } from 'effect/sql';\n\nimport {\n type FlowRecord,\n type FlowStatusStore,\n FlowStatusStoreTag,\n} from '../status/flow-status-store.js';\n\n// A type alias, not an interface: a row must be assignable to the record a\n// driver hands back, and an interface has no index signature.\ntype FlowStatusRow = {\n readonly flow_id: string;\n readonly execution_id: string;\n readonly state: FlowState;\n readonly step: string | null;\n readonly waiting_for: string | null;\n readonly correlation: string | null;\n /** The parked wait's outcomes, as a JSON array. */\n readonly accepts: string | null;\n};\n\nconst acceptsOf = (text: string): readonly string[] => {\n const parsed: unknown = JSON.parse(text);\n return Array.isArray(parsed) ? parsed.map(String) : [];\n};\n\nconst recordOf = (row: FlowStatusRow): FlowRecord => ({\n flowId: row.flow_id,\n executionId: row.execution_id,\n state: row.state,\n ...(row.step === null ? {} : { step: row.step }),\n ...(row.waiting_for === null ? {} : { waitingFor: row.waiting_for }),\n ...(row.correlation === null ? {} : { correlation: row.correlation }),\n ...(row.accepts === null ? {} : { accepts: acceptsOf(row.accepts) }),\n});\n\nconst fault = (error: unknown) => translateSqlError(error);\n\n/**\n * Creates the flow status table if it is missing, and adds a column a table\n * created by an earlier release lacks. Idempotent.\n */\nexport const ensureFlowStatusTable = (sql: SqlClient.SqlClient) =>\n Effect.gen(function* () {\n yield* sql`\n CREATE TABLE IF NOT EXISTS entifix_flow_status (\n flow_id text PRIMARY KEY,\n idempotency_key text NOT NULL UNIQUE,\n execution_id text NOT NULL,\n state text NOT NULL,\n step text,\n waiting_for text,\n correlation text,\n accepts text\n )`;\n yield* sql`\n ALTER TABLE entifix_flow_status ADD COLUMN IF NOT EXISTS accepts text`;\n }).pipe(Effect.mapError(fault));\n\n/**\n * The flow status store over SQL, beside the workflow engine's own tables. A\n * registration inserts under the key or answers the flow already there, in one\n * statement and a read.\n */\nexport const makeSqlFlowStatusStore = (\n sql: SqlClient.SqlClient,\n): FlowStatusStore => ({\n register: (key, record) =>\n Effect.gen(function* () {\n const [inserted] = yield* sql<FlowStatusRow>`\n INSERT INTO entifix_flow_status\n (flow_id, idempotency_key, execution_id, state)\n VALUES (${record.flowId}, ${key}, ${record.executionId}, ${record.state})\n ON CONFLICT DO NOTHING\n RETURNING *`;\n if (inserted !== undefined) return recordOf(inserted);\n const [existing] = yield* sql<FlowStatusRow>`\n SELECT * FROM entifix_flow_status\n WHERE idempotency_key = ${key} OR flow_id = ${record.flowId}`;\n // Gone between the two statements cannot happen: nothing deletes a flow.\n return recordOf(existing as FlowStatusRow);\n }).pipe(Effect.mapError(fault)),\n\n find: flowId =>\n sql<FlowStatusRow>`\n SELECT * FROM entifix_flow_status WHERE flow_id = ${flowId}`.pipe(\n Effect.map(([row]) =>\n row === undefined ? Option.none() : Option.some(recordOf(row)),\n ),\n Effect.mapError(fault),\n ),\n\n findByCorrelation: correlation =>\n sql<FlowStatusRow>`\n SELECT * FROM entifix_flow_status\n WHERE correlation = ${correlation}`.pipe(\n Effect.map(([row]) =>\n row === undefined ? Option.none() : Option.some(recordOf(row)),\n ),\n Effect.mapError(fault),\n ),\n\n update: (flowId, patch) =>\n sql`\n UPDATE entifix_flow_status\n SET state = ${patch.state}, step = ${patch.step ?? null},\n waiting_for = ${patch.waitingFor ?? null},\n correlation = ${patch.correlation ?? null},\n accepts = ${\n patch.accepts === undefined ? null : JSON.stringify(patch.accepts)\n }\n WHERE flow_id = ${flowId}`.pipe(Effect.asVoid, Effect.mapError(fault)),\n\n claimWait: (flowId, token) =>\n sql<{ readonly flow_id: string }>`\n UPDATE entifix_flow_status\n SET state = 'running', step = ${token}, waiting_for = NULL,\n accepts = NULL\n WHERE flow_id = ${flowId} AND state = 'waiting' AND waiting_for = ${token}\n RETURNING flow_id`.pipe(\n Effect.map(rows => rows.length > 0),\n Effect.mapError(fault),\n ),\n});\n\n/** The SQL status store, its table ensured as the layer builds. */\nexport const SqlFlowStatusStoreLayer = Layer.effect(\n FlowStatusStoreTag,\n Effect.gen(function* () {\n const sql = yield* SqlClient.SqlClient;\n yield* ensureFlowStatusTable(sql);\n return makeSqlFlowStatusStore(sql);\n }),\n);\n"],"names":["translateSqlError","Effect","Layer","Option","SqlClient","FlowStatusStoreTag","acceptsOf","text","parsed","JSON","parse","Array","isArray","map","String","recordOf","row","flowId","flow_id","executionId","execution_id","state","step","waiting_for","waitingFor","correlation","accepts","fault","error","ensureFlowStatusTable","sql","gen","pipe","mapError","makeSqlFlowStatusStore","register","key","record","inserted","undefined","existing","find","none","some","findByCorrelation","update","patch","stringify","asVoid","claimWait","token","rows","length","SqlFlowStatusStoreLayer","effect"],"mappings":"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"}
@@ -19,6 +19,11 @@ export interface FlowRecord {
19
19
  readonly waitingFor?: string;
20
20
  /** What that wait is correlated with — the order a delivery flow follows. */
21
21
  readonly correlation?: string;
22
+ /**
23
+ * The outcomes that wait is answered with, when it declares them: a
24
+ * correlated signal naming another is refused rather than taken.
25
+ */
26
+ readonly accepts?: readonly string[];
22
27
  }
23
28
  /** A change to a flow's record: its new state, and where it now stands. */
24
29
  export interface FlowRecordPatch {
@@ -26,6 +31,7 @@ export interface FlowRecordPatch {
26
31
  readonly step?: string;
27
32
  readonly waitingFor?: string;
28
33
  readonly correlation?: string;
34
+ readonly accepts?: readonly string[];
29
35
  }
30
36
  export interface FlowStatusStore {
31
37
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"flow-status-store.d.ts","sourceRoot":"","sources":["../../src/status/flow-status-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEzD;;;;;;;GAOG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,+CAA+C;IAC/C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,6EAA6E;IAC7E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,2EAA2E;AAC3E,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,CACjB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,UAAU,KACf,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;IAC7C,QAAQ,CAAC,IAAI,EAAE,CACb,MAAM,EAAE,MAAM,KACX,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,YAAY,CAAC,CAAC;IAC5D,0EAA0E;IAC1E,QAAQ,CAAC,iBAAiB,EAAE,CAC1B,WAAW,EAAE,MAAM,KAChB,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,YAAY,CAAC,CAAC;IAC5D,QAAQ,CAAC,MAAM,EAAE,CACf,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,eAAe,KACnB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IACvC;;;;;;OAMG;IACH,QAAQ,CAAC,SAAS,EAAE,CAClB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,KACV,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;CAC3C;AAED;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,wBAErB,CAAC;;AAEH,qBAAa,kBAAmB,SAAQ,uBAGA;CAAG;AAE3C;;;GAGG;AACH,eAAO,MAAM,2BAA2B,QAAO,eAwD9C,CAAC"}
1
+ {"version":3,"file":"flow-status-store.d.ts","sourceRoot":"","sources":["../../src/status/flow-status-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEzD;;;;;;;GAOG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,+CAA+C;IAC/C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,6EAA6E;IAC7E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AAED,2EAA2E;AAC3E,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AAED,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,CACjB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,UAAU,KACf,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;IAC7C,QAAQ,CAAC,IAAI,EAAE,CACb,MAAM,EAAE,MAAM,KACX,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,YAAY,CAAC,CAAC;IAC5D,0EAA0E;IAC1E,QAAQ,CAAC,iBAAiB,EAAE,CAC1B,WAAW,EAAE,MAAM,KAChB,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,YAAY,CAAC,CAAC;IAC5D,QAAQ,CAAC,MAAM,EAAE,CACf,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,eAAe,KACnB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IACvC;;;;;;OAMG;IACH,QAAQ,CAAC,SAAS,EAAE,CAClB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,KACV,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;CAC3C;AAED;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,wBAErB,CAAC;;AAEH,qBAAa,kBAAmB,SAAQ,uBAGA;CAAG;AAE3C;;;GAGG;AACH,eAAO,MAAM,2BAA2B,QAAO,eAyD9C,CAAC"}
@@ -42,6 +42,8 @@ export class FlowStatusStoreTag extends Context.Service()('@entifix/workflow/Flo
42
42
  waitingFor: patch.waitingFor
43
43
  }, patch.correlation === undefined ? {} : {
44
44
  correlation: patch.correlation
45
+ }, patch.accepts === undefined ? {} : {
46
+ accepts: patch.accepts
45
47
  }));
46
48
  }),
47
49
  claimWait: (flowId, token)=>Effect.sync(()=>{
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/status/flow-status-store.ts"],"sourcesContent":["import type { StoreFailure } from '@entifix/core';\nimport type { FlowState } from '@entifix/transactions';\nimport { Context, Effect, Metric, Option } from 'effect';\n\n/**\n * What the engine knows about one flow beyond its journal: the state a reader\n * is answered with, the step it is at, and the wait it is parked on.\n *\n * Kept beside the journal because the journal answers \"did it finish\" and\n * nothing finer — a flow parked on a signal and one mid-call look the same to\n * it, and a signal must be refused unless the flow is waiting for exactly it.\n */\nexport interface FlowRecord {\n readonly flowId: string;\n /** The workflow execution the flow runs as. */\n readonly executionId: string;\n readonly state: FlowState;\n readonly step?: string;\n /** The step whose signal the flow waits for, while `state` is `waiting`. */\n readonly waitingFor?: string;\n /** What that wait is correlated with — the order a delivery flow follows. */\n readonly correlation?: string;\n}\n\n/** A change to a flow's record: its new state, and where it now stands. */\nexport interface FlowRecordPatch {\n readonly state: FlowState;\n readonly step?: string;\n readonly waitingFor?: string;\n readonly correlation?: string;\n}\n\nexport interface FlowStatusStore {\n /**\n * Records a flow under its idempotency key, unless the key already names\n * one — then that flow is answered and nothing is written, so a repeated\n * start finds the flow the first one started.\n */\n readonly register: (\n key: string,\n record: FlowRecord,\n ) => Effect.Effect<FlowRecord, StoreFailure>;\n readonly find: (\n flowId: string,\n ) => Effect.Effect<Option.Option<FlowRecord>, StoreFailure>;\n /** The flow parked on a wait correlated with `correlation`, if one is. */\n readonly findByCorrelation: (\n correlation: string,\n ) => Effect.Effect<Option.Option<FlowRecord>, StoreFailure>;\n readonly update: (\n flowId: string,\n patch: FlowRecordPatch,\n ) => Effect.Effect<void, StoreFailure>;\n /**\n * Moves a flow parked on `token` to `running`, and answers whether it was\n * parked on it. The one conditional write a signal makes, and it is made\n * **before** the wait is resumed: written after, it could land behind the\n * resumed flow's own writes and put a finished flow back to `running` for\n * good. Two signals racing one wait claim it once.\n */\n readonly claimWait: (\n flowId: string,\n token: string,\n ) => Effect.Effect<boolean, StoreFailure>;\n}\n\n/**\n * Every state the interpreter journals a flow into, by state and the slice it\n * runs for. Counted inside the Activity that records it, so a replay — which\n * reads the journal rather than executing again — never counts a state twice.\n * `suspended` is the series to alert on: a flow past its commit point that\n * cannot go on, which a person has to look at.\n */\nexport const flowStates = Metric.counter('entifix_flow_states_total', {\n description: 'Flow states the interpreter journaled, by state and source.',\n});\n\nexport class FlowStatusStoreTag extends Context.Service<\n FlowStatusStoreTag,\n FlowStatusStore\n>()('@entifix/workflow/FlowStatusStore') {}\n\n/**\n * A status store in memory, for a single process that keeps no flow past its\n * life — specs, and the in-memory workflow engine.\n */\nexport const makeInMemoryFlowStatusStore = (): FlowStatusStore => {\n const byKey = new Map<string, string>();\n const records = new Map<string, FlowRecord>();\n return {\n register: (key, record) =>\n Effect.sync(() => {\n const known = byKey.get(key);\n if (known !== undefined) return records.get(known) as FlowRecord;\n byKey.set(key, record.flowId);\n records.set(record.flowId, record);\n return record;\n }),\n find: flowId =>\n Effect.sync(() => Option.fromNullishOr(records.get(flowId))),\n findByCorrelation: correlation =>\n Effect.sync(() =>\n Option.fromNullishOr(\n [...records.values()].find(\n record => record.correlation === correlation,\n ),\n ),\n ),\n update: (flowId, patch) =>\n Effect.sync(() => {\n const current = records.get(flowId);\n if (current === undefined) return;\n records.set(flowId, {\n flowId: current.flowId,\n executionId: current.executionId,\n state: patch.state,\n ...(patch.step === undefined ? {} : { step: patch.step }),\n ...(patch.waitingFor === undefined\n ? {}\n : { waitingFor: patch.waitingFor }),\n ...(patch.correlation === undefined\n ? {}\n : { correlation: patch.correlation }),\n });\n }),\n claimWait: (flowId, token) =>\n Effect.sync(() => {\n const current = records.get(flowId);\n if (current?.state !== 'waiting' || current.waitingFor !== token)\n return false;\n records.set(flowId, {\n flowId: current.flowId,\n executionId: current.executionId,\n state: 'running',\n step: token,\n ...(current.correlation === undefined\n ? {}\n : { correlation: current.correlation }),\n });\n return true;\n }),\n };\n};\n"],"names":["Context","Effect","Metric","Option","flowStates","counter","description","FlowStatusStoreTag","Service","makeInMemoryFlowStatusStore","byKey","Map","records","register","key","record","sync","known","get","undefined","set","flowId","find","fromNullishOr","findByCorrelation","correlation","values","update","patch","current","executionId","state","step","waitingFor","claimWait","token"],"mappings":";AAEA,SAASA,OAAO,EAAEC,MAAM,EAAEC,MAAM,EAAEC,MAAM,QAAQ,SAAS;AAgEzD;;;;;;CAMC,GACD,OAAO,MAAMC,aAAaF,OAAOG,OAAO,CAAC,6BAA6B;IACpEC,aAAa;AACf,GAAG;AAEH,OAAO,MAAMC,2BAA2BP,QAAQQ,OAAO,GAGnD;AAAsC;AAE1C;;;CAGC,GACD,OAAO,MAAMC,8BAA8B;IACzC,MAAMC,QAAQ,IAAIC;IAClB,MAAMC,UAAU,IAAID;IACpB,OAAO;QACLE,UAAU,CAACC,KAAKC,SACdd,OAAOe,IAAI,CAAC;gBACV,MAAMC,QAAQP,MAAMQ,GAAG,CAACJ;gBACxB,IAAIG,UAAUE,WAAW,OAAOP,QAAQM,GAAG,CAACD;gBAC5CP,MAAMU,GAAG,CAACN,KAAKC,OAAOM,MAAM;gBAC5BT,QAAQQ,GAAG,CAACL,OAAOM,MAAM,EAAEN;gBAC3B,OAAOA;YACT;QACFO,MAAMD,CAAAA,SACJpB,OAAOe,IAAI,CAAC,IAAMb,OAAOoB,aAAa,CAACX,QAAQM,GAAG,CAACG;QACrDG,mBAAmBC,CAAAA,cACjBxB,OAAOe,IAAI,CAAC,IACVb,OAAOoB,aAAa,CAClB;uBAAIX,QAAQc,MAAM;iBAAG,CAACJ,IAAI,CACxBP,CAAAA,SAAUA,OAAOU,WAAW,KAAKA;QAIzCE,QAAQ,CAACN,QAAQO,QACf3B,OAAOe,IAAI,CAAC;gBACV,MAAMa,UAAUjB,QAAQM,GAAG,CAACG;gBAC5B,IAAIQ,YAAYV,WAAW;gBAC3BP,QAAQQ,GAAG,CAACC,QAAQ;oBAClBA,QAAQQ,QAAQR,MAAM;oBACtBS,aAAaD,QAAQC,WAAW;oBAChCC,OAAOH,MAAMG,KAAK;mBACdH,MAAMI,IAAI,KAAKb,YAAY,CAAC,IAAI;oBAAEa,MAAMJ,MAAMI,IAAI;gBAAC,GACnDJ,MAAMK,UAAU,KAAKd,YACrB,CAAC,IACD;oBAAEc,YAAYL,MAAMK,UAAU;gBAAC,GAC/BL,MAAMH,WAAW,KAAKN,YACtB,CAAC,IACD;oBAAEM,aAAaG,MAAMH,WAAW;gBAAC;YAEzC;QACFS,WAAW,CAACb,QAAQc,QAClBlC,OAAOe,IAAI,CAAC;gBACV,MAAMa,UAAUjB,QAAQM,GAAG,CAACG;gBAC5B,IAAIQ,CAAAA,2BAAAA,QAASE,KAAK,MAAK,aAAaF,QAAQI,UAAU,KAAKE,OACzD,OAAO;gBACTvB,QAAQQ,GAAG,CAACC,QAAQ;oBAClBA,QAAQQ,QAAQR,MAAM;oBACtBS,aAAaD,QAAQC,WAAW;oBAChCC,OAAO;oBACPC,MAAMG;mBACFN,QAAQJ,WAAW,KAAKN,YACxB,CAAC,IACD;oBAAEM,aAAaI,QAAQJ,WAAW;gBAAC;gBAEzC,OAAO;YACT;IACJ;AACF,EAAE"}
1
+ {"version":3,"sources":["../../src/status/flow-status-store.ts"],"sourcesContent":["import type { StoreFailure } from '@entifix/core';\nimport type { FlowState } from '@entifix/transactions';\nimport { Context, Effect, Metric, Option } from 'effect';\n\n/**\n * What the engine knows about one flow beyond its journal: the state a reader\n * is answered with, the step it is at, and the wait it is parked on.\n *\n * Kept beside the journal because the journal answers \"did it finish\" and\n * nothing finer — a flow parked on a signal and one mid-call look the same to\n * it, and a signal must be refused unless the flow is waiting for exactly it.\n */\nexport interface FlowRecord {\n readonly flowId: string;\n /** The workflow execution the flow runs as. */\n readonly executionId: string;\n readonly state: FlowState;\n readonly step?: string;\n /** The step whose signal the flow waits for, while `state` is `waiting`. */\n readonly waitingFor?: string;\n /** What that wait is correlated with — the order a delivery flow follows. */\n readonly correlation?: string;\n /**\n * The outcomes that wait is answered with, when it declares them: a\n * correlated signal naming another is refused rather than taken.\n */\n readonly accepts?: readonly string[];\n}\n\n/** A change to a flow's record: its new state, and where it now stands. */\nexport interface FlowRecordPatch {\n readonly state: FlowState;\n readonly step?: string;\n readonly waitingFor?: string;\n readonly correlation?: string;\n readonly accepts?: readonly string[];\n}\n\nexport interface FlowStatusStore {\n /**\n * Records a flow under its idempotency key, unless the key already names\n * one — then that flow is answered and nothing is written, so a repeated\n * start finds the flow the first one started.\n */\n readonly register: (\n key: string,\n record: FlowRecord,\n ) => Effect.Effect<FlowRecord, StoreFailure>;\n readonly find: (\n flowId: string,\n ) => Effect.Effect<Option.Option<FlowRecord>, StoreFailure>;\n /** The flow parked on a wait correlated with `correlation`, if one is. */\n readonly findByCorrelation: (\n correlation: string,\n ) => Effect.Effect<Option.Option<FlowRecord>, StoreFailure>;\n readonly update: (\n flowId: string,\n patch: FlowRecordPatch,\n ) => Effect.Effect<void, StoreFailure>;\n /**\n * Moves a flow parked on `token` to `running`, and answers whether it was\n * parked on it. The one conditional write a signal makes, and it is made\n * **before** the wait is resumed: written after, it could land behind the\n * resumed flow's own writes and put a finished flow back to `running` for\n * good. Two signals racing one wait claim it once.\n */\n readonly claimWait: (\n flowId: string,\n token: string,\n ) => Effect.Effect<boolean, StoreFailure>;\n}\n\n/**\n * Every state the interpreter journals a flow into, by state and the slice it\n * runs for. Counted inside the Activity that records it, so a replay — which\n * reads the journal rather than executing again — never counts a state twice.\n * `suspended` is the series to alert on: a flow past its commit point that\n * cannot go on, which a person has to look at.\n */\nexport const flowStates = Metric.counter('entifix_flow_states_total', {\n description: 'Flow states the interpreter journaled, by state and source.',\n});\n\nexport class FlowStatusStoreTag extends Context.Service<\n FlowStatusStoreTag,\n FlowStatusStore\n>()('@entifix/workflow/FlowStatusStore') {}\n\n/**\n * A status store in memory, for a single process that keeps no flow past its\n * life — specs, and the in-memory workflow engine.\n */\nexport const makeInMemoryFlowStatusStore = (): FlowStatusStore => {\n const byKey = new Map<string, string>();\n const records = new Map<string, FlowRecord>();\n return {\n register: (key, record) =>\n Effect.sync(() => {\n const known = byKey.get(key);\n if (known !== undefined) return records.get(known) as FlowRecord;\n byKey.set(key, record.flowId);\n records.set(record.flowId, record);\n return record;\n }),\n find: flowId =>\n Effect.sync(() => Option.fromNullishOr(records.get(flowId))),\n findByCorrelation: correlation =>\n Effect.sync(() =>\n Option.fromNullishOr(\n [...records.values()].find(\n record => record.correlation === correlation,\n ),\n ),\n ),\n update: (flowId, patch) =>\n Effect.sync(() => {\n const current = records.get(flowId);\n if (current === undefined) return;\n records.set(flowId, {\n flowId: current.flowId,\n executionId: current.executionId,\n state: patch.state,\n ...(patch.step === undefined ? {} : { step: patch.step }),\n ...(patch.waitingFor === undefined\n ? {}\n : { waitingFor: patch.waitingFor }),\n ...(patch.correlation === undefined\n ? {}\n : { correlation: patch.correlation }),\n ...(patch.accepts === undefined ? {} : { accepts: patch.accepts }),\n });\n }),\n claimWait: (flowId, token) =>\n Effect.sync(() => {\n const current = records.get(flowId);\n if (current?.state !== 'waiting' || current.waitingFor !== token)\n return false;\n records.set(flowId, {\n flowId: current.flowId,\n executionId: current.executionId,\n state: 'running',\n step: token,\n ...(current.correlation === undefined\n ? {}\n : { correlation: current.correlation }),\n });\n return true;\n }),\n };\n};\n"],"names":["Context","Effect","Metric","Option","flowStates","counter","description","FlowStatusStoreTag","Service","makeInMemoryFlowStatusStore","byKey","Map","records","register","key","record","sync","known","get","undefined","set","flowId","find","fromNullishOr","findByCorrelation","correlation","values","update","patch","current","executionId","state","step","waitingFor","accepts","claimWait","token"],"mappings":";AAEA,SAASA,OAAO,EAAEC,MAAM,EAAEC,MAAM,EAAEC,MAAM,QAAQ,SAAS;AAsEzD;;;;;;CAMC,GACD,OAAO,MAAMC,aAAaF,OAAOG,OAAO,CAAC,6BAA6B;IACpEC,aAAa;AACf,GAAG;AAEH,OAAO,MAAMC,2BAA2BP,QAAQQ,OAAO,GAGnD;AAAsC;AAE1C;;;CAGC,GACD,OAAO,MAAMC,8BAA8B;IACzC,MAAMC,QAAQ,IAAIC;IAClB,MAAMC,UAAU,IAAID;IACpB,OAAO;QACLE,UAAU,CAACC,KAAKC,SACdd,OAAOe,IAAI,CAAC;gBACV,MAAMC,QAAQP,MAAMQ,GAAG,CAACJ;gBACxB,IAAIG,UAAUE,WAAW,OAAOP,QAAQM,GAAG,CAACD;gBAC5CP,MAAMU,GAAG,CAACN,KAAKC,OAAOM,MAAM;gBAC5BT,QAAQQ,GAAG,CAACL,OAAOM,MAAM,EAAEN;gBAC3B,OAAOA;YACT;QACFO,MAAMD,CAAAA,SACJpB,OAAOe,IAAI,CAAC,IAAMb,OAAOoB,aAAa,CAACX,QAAQM,GAAG,CAACG;QACrDG,mBAAmBC,CAAAA,cACjBxB,OAAOe,IAAI,CAAC,IACVb,OAAOoB,aAAa,CAClB;uBAAIX,QAAQc,MAAM;iBAAG,CAACJ,IAAI,CACxBP,CAAAA,SAAUA,OAAOU,WAAW,KAAKA;QAIzCE,QAAQ,CAACN,QAAQO,QACf3B,OAAOe,IAAI,CAAC;gBACV,MAAMa,UAAUjB,QAAQM,GAAG,CAACG;gBAC5B,IAAIQ,YAAYV,WAAW;gBAC3BP,QAAQQ,GAAG,CAACC,QAAQ;oBAClBA,QAAQQ,QAAQR,MAAM;oBACtBS,aAAaD,QAAQC,WAAW;oBAChCC,OAAOH,MAAMG,KAAK;mBACdH,MAAMI,IAAI,KAAKb,YAAY,CAAC,IAAI;oBAAEa,MAAMJ,MAAMI,IAAI;gBAAC,GACnDJ,MAAMK,UAAU,KAAKd,YACrB,CAAC,IACD;oBAAEc,YAAYL,MAAMK,UAAU;gBAAC,GAC/BL,MAAMH,WAAW,KAAKN,YACtB,CAAC,IACD;oBAAEM,aAAaG,MAAMH,WAAW;gBAAC,GACjCG,MAAMM,OAAO,KAAKf,YAAY,CAAC,IAAI;oBAAEe,SAASN,MAAMM,OAAO;gBAAC;YAEpE;QACFC,WAAW,CAACd,QAAQe,QAClBnC,OAAOe,IAAI,CAAC;gBACV,MAAMa,UAAUjB,QAAQM,GAAG,CAACG;gBAC5B,IAAIQ,CAAAA,2BAAAA,QAASE,KAAK,MAAK,aAAaF,QAAQI,UAAU,KAAKG,OACzD,OAAO;gBACTxB,QAAQQ,GAAG,CAACC,QAAQ;oBAClBA,QAAQQ,QAAQR,MAAM;oBACtBS,aAAaD,QAAQC,WAAW;oBAChCC,OAAO;oBACPC,MAAMI;mBACFP,QAAQJ,WAAW,KAAKN,YACxB,CAAC,IACD;oBAAEM,aAAaI,QAAQJ,WAAW;gBAAC;gBAEzC,OAAO;YACT;IACJ;AACF,EAAE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@entifix/workflow",
3
- "version": "0.15.0",
3
+ "version": "0.17.0",
4
4
  "description": "Durable flows: the slip interpreter on Effect Workflow, behind the flow engine port, with Postgres storage and socket runners.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -89,16 +89,16 @@
89
89
  },
90
90
  "dependencies": {
91
91
  "@swc/helpers": "0.5.23",
92
- "@entifix/core": "0.15.0",
93
- "@entifix/transactions": "0.15.0",
94
- "@entifix/sql": "0.15.0",
95
- "@entifix/business": "0.15.0"
92
+ "@entifix/business": "0.17.0",
93
+ "@entifix/core": "0.17.0",
94
+ "@entifix/sql": "0.17.0",
95
+ "@entifix/transactions": "0.17.0"
96
96
  },
97
97
  "devDependencies": {
98
98
  "@effect/platform-node": "4.0.7",
99
99
  "@effect/sql-pg": "4.0.7",
100
100
  "effect": "4.0.7",
101
- "@entifix/testing-unit": "0.15.0"
101
+ "@entifix/testing-unit": "0.17.0"
102
102
  },
103
103
  "peerDependencies": {
104
104
  "@effect/platform-node": "^4.0.7",