@jarenjs/flow 0.72.2 → 0.73.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -3
- package/dist/types/dag.d.ts +6 -4
- package/dist/types/errors.d.ts +10 -0
- package/dist/types/fsm.d.ts +24 -0
- package/dist/types/index.d.ts +18 -0
- package/dist/types/statechart.d.ts +82 -0
- package/dist/types/workflow.d.ts +97 -0
- package/docs/APP-INTEGRATION.md +8 -0
- package/docs/FLOW-FORMAT.md +67 -11
- package/docs/STATECHART-FORMAT.md +186 -0
- package/docs/WORKFLOW-FORMAT.md +260 -0
- package/package.json +4 -4
- package/schemas/jaren-statechart-state.draft-07.schema.json +72 -0
- package/schemas/jaren-statechart-state.schema.json +72 -0
- package/schemas/jaren-statechart.authoring.schema.json +216 -0
- package/schemas/jaren-statechart.draft-07.schema.json +216 -0
- package/schemas/jaren-statechart.schema.json +216 -0
- package/schemas/jaren-workflow.authoring.schema.json +282 -0
- package/schemas/jaren-workflow.draft-07.schema.json +288 -0
- package/schemas/jaren-workflow.schema.json +288 -0
- package/src/dag.js +52 -24
- package/src/errors.js +10 -0
- package/src/fsm.js +2 -2
- package/src/index.js +11 -0
- package/src/statechart.js +420 -0
- package/src/workflow.js +461 -0
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# @jarenjs/flow
|
|
2
2
|
|
|
3
|
-
Executable
|
|
3
|
+
Executable JSON workflows: flat FSMs, statecharts, DAGs, and a composition
|
|
4
|
+
compiler that combines long-lived control with concurrent work regions.
|
|
5
|
+
The **jaren-fsm 0.1 format**
|
|
4
6
|
is a finite state machine as one JSON value — declared states, an
|
|
5
7
|
initial state, and a document-ordered transition table whose guards and
|
|
6
8
|
effect props are [Jaren JSON Query](../json/docs/QUERY-FORMAT.md)
|
|
@@ -10,6 +12,12 @@ own engines — query documents, JSLT stylesheets, registered async
|
|
|
10
12
|
tasks — wired by edges that carry data and compiled into a
|
|
11
13
|
run-to-completion executor.
|
|
12
14
|
|
|
15
|
+
`compileStatechart` adds compound states, parallel regions, shallow/deep
|
|
16
|
+
history and explicit-time delayed transitions in jaren-fsm 0.2.
|
|
17
|
+
`compileWorkflow` lowers one composed document onto statechart control and
|
|
18
|
+
DAG work, with choices, bounded loops, nested flows, saved waits and
|
|
19
|
+
generation-fenced checkpoints. Existing 0.1 APIs keep their behavior.
|
|
20
|
+
|
|
13
21
|
It is the executable half of a round trip the suite already ships: a
|
|
14
22
|
`stateDiagram-v2` parsed by [`@jarenjs/mermaid`](../../components/mermaid)
|
|
15
23
|
projects (via a JSLT stylesheet) into exactly this shape, and this
|
|
@@ -20,10 +28,51 @@ The grammar is published as JSON Schema in
|
|
|
20
28
|
[`schemas/jaren-fsm.schema.json`](schemas/jaren-fsm.schema.json) (with a
|
|
21
29
|
mechanically derived draft-07 twin for providers pinned to older
|
|
22
30
|
drafts) — hand it to a constrained decoder and a language model cannot
|
|
23
|
-
emit a machine with
|
|
31
|
+
emit a machine with a structurally malformed guard. The
|
|
24
32
|
normative contract is [docs/FLOW-FORMAT.md](docs/FLOW-FORMAT.md). Zero
|
|
25
33
|
dependencies outside the suite; no `eval`, CSP-safe; the only runtime
|
|
26
|
-
|
|
34
|
+
dependencies are `@jarenjs/core` and `@jarenjs/json`.
|
|
35
|
+
|
|
36
|
+
## Composed workflows and statecharts
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
import { compileWorkflow } from '@jarenjs/flow';
|
|
40
|
+
|
|
41
|
+
const workflow = compileWorkflow({
|
|
42
|
+
$workflow: '0.2', revision: 'review/1', initial: 'prepare',
|
|
43
|
+
states: {
|
|
44
|
+
prepare: { work: { task: 'prepare', version: '1' }, then: 'review' },
|
|
45
|
+
review: { on: [{ event: 'approve', to: 'done' }] },
|
|
46
|
+
done: { final: true },
|
|
47
|
+
},
|
|
48
|
+
}, { tasks: { prepare: { version: '1', run: ({ input }) => ({ ...input, ready: true }) } } });
|
|
49
|
+
|
|
50
|
+
const first = await workflow.run({ title: 'Draft' }, { runId: 'review-7' });
|
|
51
|
+
const done = await workflow.run({ title: 'Draft' }, {
|
|
52
|
+
runId: 'review-7', snapshot: first.snapshot, event: { type: 'approve' },
|
|
53
|
+
});
|
|
54
|
+
// done.result = { title: 'Draft', ready: true }
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Use `work: {dag: document}` for concurrent branches, `choose` for guarded
|
|
58
|
+
switches, `flow` for nested control, and `limit` to bound a loop. An optional
|
|
59
|
+
CAS store persists node values and control before advancing. The compiler
|
|
60
|
+
rejects a resume whose document, input or task identities changed.
|
|
61
|
+
[WORKFLOW-FORMAT.md](docs/WORKFLOW-FORMAT.md) defines lowering, scope,
|
|
62
|
+
idempotency keys, crash windows and the benchmark command that consumes it.
|
|
63
|
+
|
|
64
|
+
For statecharts directly, `compileStatechart(doc).start({now})` returns the
|
|
65
|
+
initial control snapshot and entry-effect descriptors. `step(state,event)`
|
|
66
|
+
handles an event; `advance(state,now)` processes deadlines supplied by the
|
|
67
|
+
host. Snapshots include active leaves, history and pending timers.
|
|
68
|
+
`createStatechartSession` provides synchronous save-before-advance sessions.
|
|
69
|
+
[STATECHART-FORMAT.md](docs/STATECHART-FORMAT.md) specifies the semantics.
|
|
70
|
+
Neither API silently upgrades a 0.1 FSM or its string snapshots.
|
|
71
|
+
|
|
72
|
+
Both formats publish canonical, draft-07 and derived authoring schemas.
|
|
73
|
+
`createGrammarAuthor` accepts `statechart` and `workflow` with their full
|
|
74
|
+
schema/compiler gates. The existing LINQ flow pen continues to author the
|
|
75
|
+
0.1 FSM and DAG documents; its DAG documents can be embedded as work regions.
|
|
27
76
|
|
|
28
77
|
## The format in one glance
|
|
29
78
|
|
|
@@ -280,6 +329,14 @@ Every subpath a consumer can import, derived from the manifest by
|
|
|
280
329
|
| `@jarenjs/flow/schemas/jaren-fsm.authoring.schema.json` | schema | — |
|
|
281
330
|
| `@jarenjs/flow/schemas/jaren-fsm.draft-07.schema.json` | schema | — |
|
|
282
331
|
| `@jarenjs/flow/schemas/jaren-fsm.schema.json` | schema | — |
|
|
332
|
+
| `@jarenjs/flow/schemas/jaren-statechart-state.draft-07.schema.json` | schema | — |
|
|
333
|
+
| `@jarenjs/flow/schemas/jaren-statechart-state.schema.json` | schema | — |
|
|
334
|
+
| `@jarenjs/flow/schemas/jaren-statechart.authoring.schema.json` | schema | — |
|
|
335
|
+
| `@jarenjs/flow/schemas/jaren-statechart.draft-07.schema.json` | schema | — |
|
|
336
|
+
| `@jarenjs/flow/schemas/jaren-statechart.schema.json` | schema | — |
|
|
337
|
+
| `@jarenjs/flow/schemas/jaren-workflow.authoring.schema.json` | schema | — |
|
|
338
|
+
| `@jarenjs/flow/schemas/jaren-workflow.draft-07.schema.json` | schema | — |
|
|
339
|
+
| `@jarenjs/flow/schemas/jaren-workflow.schema.json` | schema | — |
|
|
283
340
|
| `@jarenjs/flow/package.json` | metadata | — |
|
|
284
341
|
<!--/fact-->
|
|
285
342
|
|
package/dist/types/dag.d.ts
CHANGED
|
@@ -18,9 +18,9 @@ export type DagNodeRecord = {
|
|
|
18
18
|
ms: number;
|
|
19
19
|
};
|
|
20
20
|
export type DagCheckpointStore = {
|
|
21
|
-
load: (runId: string) => any;
|
|
22
|
-
save: (runId: string, nodeId: string, value: any) => any;
|
|
23
|
-
complete: (runId: string, result: any) => any;
|
|
21
|
+
load: (runId: string, identity?: any) => any;
|
|
22
|
+
save: (runId: string, nodeId: string, value: any, identity?: any) => any;
|
|
23
|
+
complete: (runId: string, result: any, identity?: any) => any;
|
|
24
24
|
};
|
|
25
25
|
export type CompiledDag = {
|
|
26
26
|
/**
|
|
@@ -46,6 +46,7 @@ export type CompiledDag = {
|
|
|
46
46
|
signal?: AbortSignal;
|
|
47
47
|
onNode?: (record: DagNodeRecord) => void;
|
|
48
48
|
runId?: string;
|
|
49
|
+
drainOnAbort?: boolean;
|
|
49
50
|
}) => Promise<any>;
|
|
50
51
|
};
|
|
51
52
|
/**
|
|
@@ -58,7 +59,7 @@ export type CompiledDag = {
|
|
|
58
59
|
* @param {{ tasks?: Record<string, ((props: { with: any, input: any }, signal: AbortSignal) => any)
|
|
59
60
|
* | { run: (props: { with: any, input: any }, signal: AbortSignal) => any, version?: string,
|
|
60
61
|
* taskVersions?: Record<string, string> }>,
|
|
61
|
-
* checkpoint?: DagCheckpointStore }} [options]
|
|
62
|
+
* checkpoint?: DagCheckpointStore, revision?: string }} [options]
|
|
62
63
|
* @returns {CompiledDag}
|
|
63
64
|
* @throws {FlowCompileError} when the document violates the format (JF0xxx)
|
|
64
65
|
* @throws {TypeError} when the options are malformed (a registry that is
|
|
@@ -78,4 +79,5 @@ export declare function compileDag(doc: any, options?: {
|
|
|
78
79
|
taskVersions?: Record<string, string>;
|
|
79
80
|
}>;
|
|
80
81
|
checkpoint?: DagCheckpointStore;
|
|
82
|
+
revision?: string;
|
|
81
83
|
}): CompiledDag;
|
package/dist/types/errors.d.ts
CHANGED
|
@@ -34,6 +34,9 @@ export declare const FLOW_CODES: Readonly<{
|
|
|
34
34
|
JF0017: "the document does not declare exactly one output node";
|
|
35
35
|
JF0018: "a task node names a handler the registry does not provide";
|
|
36
36
|
JF0019: "a task node and its registered handler disagree about the handler version";
|
|
37
|
+
JF0020: "a statechart declaration violates hierarchy or transition rules";
|
|
38
|
+
JF0021: "a workflow declaration or reference is malformed";
|
|
39
|
+
JF0022: "an automatic workflow cycle has no declared visit bound";
|
|
37
40
|
JF2001: "a state id the machine does not declare";
|
|
38
41
|
JF2002: "step was called with a non-string event";
|
|
39
42
|
JF2003: "a guard threw while evaluating";
|
|
@@ -43,6 +46,13 @@ export declare const FLOW_CODES: Readonly<{
|
|
|
43
46
|
JF2007: "the caller signal aborted the run";
|
|
44
47
|
JF2008: "a declared checkpoint value is not JSON-serializable";
|
|
45
48
|
JF2009: "the checkpoint store failed";
|
|
49
|
+
JF2010: "a statechart snapshot is malformed or has an illegal configuration";
|
|
50
|
+
JF2011: "explicit time moved backwards or a timer exceeded its numeric range";
|
|
51
|
+
JF2012: "a statechart exceeded its microstep limit";
|
|
52
|
+
JF2013: "a checkpoint belongs to a different workflow, input or task implementation";
|
|
53
|
+
JF2014: "a workflow run is busy or its snapshot generation is stale";
|
|
54
|
+
JF2015: "a workflow state exceeded its declared visit bound";
|
|
55
|
+
JF2016: "a workflow snapshot is malformed or execution could not progress";
|
|
46
56
|
}>;
|
|
47
57
|
/**
|
|
48
58
|
* A defect in the flow document itself, raised while `compileFsm`
|
package/dist/types/fsm.d.ts
CHANGED
|
@@ -18,6 +18,30 @@ export type CompiledEffect = {
|
|
|
18
18
|
with: any;
|
|
19
19
|
docPath: string;
|
|
20
20
|
};
|
|
21
|
+
/**
|
|
22
|
+
* A compiled effect: the registered handler name, the compiled `with`
|
|
23
|
+
* query (or null) and the descriptor's docPath for runtime records.
|
|
24
|
+
* @typedef {{ run: string, with: any, docPath: string }} CompiledEffect
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Compile one effects list (`entry`, `exit` or a transition's
|
|
28
|
+
* `effects`). Absent means none; anything else must be an array of
|
|
29
|
+
* `{ run, with? }` descriptors.
|
|
30
|
+
* @param {any} list
|
|
31
|
+
* @param {string} docPath - JSON Pointer of the list member.
|
|
32
|
+
* @returns {CompiledEffect[]}
|
|
33
|
+
*/
|
|
34
|
+
export declare function compileEffectList(list: any, docPath: string): CompiledEffect[];
|
|
35
|
+
/**
|
|
36
|
+
* Resolve one compiled effect against the evaluation scope. An empty
|
|
37
|
+
* query result omits the `with` member; a throwing `with` fails closed —
|
|
38
|
+
* the effect is omitted and the failure recorded (JF2004).
|
|
39
|
+
* @param {CompiledEffect} effect
|
|
40
|
+
* @param {any} scope
|
|
41
|
+
* @param {any[]} out - resolved descriptors, appended to
|
|
42
|
+
* @param {any[]} errors - step-result error records, appended to
|
|
43
|
+
*/
|
|
44
|
+
export declare function resolveEffect(effect: CompiledEffect, scope: any, out: any[], errors: any[]): void;
|
|
21
45
|
export type FsmStepResult = {
|
|
22
46
|
/**
|
|
23
47
|
* - A transition fired. A self-transition
|
package/dist/types/index.d.ts
CHANGED
|
@@ -7,5 +7,23 @@
|
|
|
7
7
|
export { compileFsm, createFsmSession } from './fsm.js';
|
|
8
8
|
export { fsmToApp, fsmStateSchema } from './app.js';
|
|
9
9
|
export { compileDag } from './dag.js';
|
|
10
|
+
export { compileStatechart, createStatechartSession } from './statechart.js';
|
|
11
|
+
export { lowerWorkflow, compileWorkflow } from './workflow.js';
|
|
10
12
|
export { snapshotFsm, resumeFsmSession, createDurableFsmSession } from './persist.js';
|
|
11
13
|
export { FlowCompileError, FlowRuntimeError, FLOW_CODES } from './errors.js';
|
|
14
|
+
export type StatechartState = import('./statechart.js').StatechartState;
|
|
15
|
+
export type StatechartResult = import('./statechart.js').StatechartResult;
|
|
16
|
+
export type CompiledStatechart = import('./statechart.js').CompiledStatechart;
|
|
17
|
+
export type LoweredWorkflow = import('./workflow.js').LoweredWorkflow;
|
|
18
|
+
export type WorkflowSnapshot = import('./workflow.js').WorkflowSnapshot;
|
|
19
|
+
export type WorkflowStore = import('./workflow.js').WorkflowStore;
|
|
20
|
+
export type WorkflowResult = import('./workflow.js').WorkflowResult;
|
|
21
|
+
export type CompiledWorkflow = import('./workflow.js').CompiledWorkflow;
|
|
22
|
+
/** @typedef {import('./statechart.js').StatechartState} StatechartState */
|
|
23
|
+
/** @typedef {import('./statechart.js').StatechartResult} StatechartResult */
|
|
24
|
+
/** @typedef {import('./statechart.js').CompiledStatechart} CompiledStatechart */
|
|
25
|
+
/** @typedef {import('./workflow.js').LoweredWorkflow} LoweredWorkflow */
|
|
26
|
+
/** @typedef {import('./workflow.js').WorkflowSnapshot} WorkflowSnapshot */
|
|
27
|
+
/** @typedef {import('./workflow.js').WorkflowStore} WorkflowStore */
|
|
28
|
+
/** @typedef {import('./workflow.js').WorkflowResult} WorkflowResult */
|
|
29
|
+
/** @typedef {import('./workflow.js').CompiledWorkflow} CompiledWorkflow */
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export type StatechartState = Readonly<{
|
|
2
|
+
active: readonly string[];
|
|
3
|
+
history: Readonly<Record<string, readonly string[]>>;
|
|
4
|
+
timers: readonly Readonly<{
|
|
5
|
+
transition: number;
|
|
6
|
+
at: number;
|
|
7
|
+
token: number;
|
|
8
|
+
}>[];
|
|
9
|
+
serial: number;
|
|
10
|
+
time: number;
|
|
11
|
+
}>;
|
|
12
|
+
export type StatechartOptions = {
|
|
13
|
+
now?: number;
|
|
14
|
+
payload?: any;
|
|
15
|
+
context?: any;
|
|
16
|
+
one?: boolean;
|
|
17
|
+
};
|
|
18
|
+
export type StatechartResult = {
|
|
19
|
+
changed: boolean;
|
|
20
|
+
state: StatechartState;
|
|
21
|
+
final: boolean;
|
|
22
|
+
effects: any[];
|
|
23
|
+
errors: {
|
|
24
|
+
code: string;
|
|
25
|
+
docPath: string;
|
|
26
|
+
message: string;
|
|
27
|
+
}[];
|
|
28
|
+
entered: string[];
|
|
29
|
+
exited: string[];
|
|
30
|
+
transitions: number[];
|
|
31
|
+
};
|
|
32
|
+
export type CompiledStatechart = {
|
|
33
|
+
states: readonly string[];
|
|
34
|
+
start: (opts?: StatechartOptions) => StatechartResult;
|
|
35
|
+
step: (state: StatechartState, event: string, opts?: StatechartOptions) => StatechartResult;
|
|
36
|
+
advance: (state: StatechartState, now: number, opts?: StatechartOptions) => StatechartResult;
|
|
37
|
+
final: (state: StatechartState) => boolean;
|
|
38
|
+
events: (state: StatechartState) => readonly string[];
|
|
39
|
+
restore: (state: StatechartState) => StatechartState;
|
|
40
|
+
};
|
|
41
|
+
/** Compile jaren-fsm 0.2 without creating a clock, timer or effect handler.
|
|
42
|
+
* @param {any} doc
|
|
43
|
+
* @param {{ maxMicrosteps?: number }} [options]
|
|
44
|
+
* @returns {CompiledStatechart}
|
|
45
|
+
*/
|
|
46
|
+
export declare function compileStatechart(doc: any, options?: {
|
|
47
|
+
maxMicrosteps?: number;
|
|
48
|
+
}): CompiledStatechart;
|
|
49
|
+
/** A synchronous session; callers execute returned effects and supply time.
|
|
50
|
+
* @param {CompiledStatechart} chart
|
|
51
|
+
* @param {{ state?: StatechartState, now?: number, context?: any,
|
|
52
|
+
* store?: { load: () => StatechartState | null, save: (state: StatechartState) => void } }} [options]
|
|
53
|
+
*/
|
|
54
|
+
export declare function createStatechartSession(chart: CompiledStatechart, options?: {
|
|
55
|
+
state?: StatechartState;
|
|
56
|
+
now?: number;
|
|
57
|
+
context?: any;
|
|
58
|
+
store?: {
|
|
59
|
+
load: () => StatechartState | null;
|
|
60
|
+
save: (state: StatechartState) => void;
|
|
61
|
+
};
|
|
62
|
+
}): Readonly<{
|
|
63
|
+
readonly state: Readonly<{
|
|
64
|
+
active: readonly string[];
|
|
65
|
+
history: Readonly<Record<string, readonly string[]>>;
|
|
66
|
+
timers: readonly Readonly<{
|
|
67
|
+
transition: number;
|
|
68
|
+
at: number;
|
|
69
|
+
token: number;
|
|
70
|
+
}>[];
|
|
71
|
+
serial: number;
|
|
72
|
+
time: number;
|
|
73
|
+
}>;
|
|
74
|
+
readonly done: boolean;
|
|
75
|
+
initial: StatechartResult | null;
|
|
76
|
+
/** @param {string} event @param {StatechartOptions} [opts] */
|
|
77
|
+
send: (event: string, opts?: StatechartOptions) => StatechartResult;
|
|
78
|
+
/** @param {number} now @param {StatechartOptions} [opts] */
|
|
79
|
+
advance: (now: number, opts?: StatechartOptions) => StatechartResult;
|
|
80
|
+
/** @param {string} event @param {StatechartOptions} [opts] */
|
|
81
|
+
can: (event: string, opts?: StatechartOptions) => boolean;
|
|
82
|
+
}>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { compileDag } from './dag.js';
|
|
2
|
+
export type LoweredWorkflow = {
|
|
3
|
+
version: string;
|
|
4
|
+
revision: string;
|
|
5
|
+
document: any;
|
|
6
|
+
fsm: any;
|
|
7
|
+
dags: Record<string, any>;
|
|
8
|
+
sources: Record<string, string>;
|
|
9
|
+
specs: Record<string, any>;
|
|
10
|
+
};
|
|
11
|
+
export type WorkflowSnapshot = {
|
|
12
|
+
format: string;
|
|
13
|
+
runId: string;
|
|
14
|
+
identity: any;
|
|
15
|
+
generation: number;
|
|
16
|
+
control: import('./statechart.js').StatechartState;
|
|
17
|
+
context: {
|
|
18
|
+
input: any;
|
|
19
|
+
data: any;
|
|
20
|
+
event: null | {
|
|
21
|
+
type: string;
|
|
22
|
+
payload?: any;
|
|
23
|
+
};
|
|
24
|
+
results: Record<string, any>;
|
|
25
|
+
visits: Record<string, number>;
|
|
26
|
+
};
|
|
27
|
+
pending: null | {
|
|
28
|
+
state: string;
|
|
29
|
+
visit: number;
|
|
30
|
+
input: any;
|
|
31
|
+
values: Record<string, any>;
|
|
32
|
+
identity?: any;
|
|
33
|
+
result?: any;
|
|
34
|
+
};
|
|
35
|
+
status: 'running' | 'waiting' | 'done';
|
|
36
|
+
};
|
|
37
|
+
export type WorkflowResult = {
|
|
38
|
+
status: 'waiting' | 'done';
|
|
39
|
+
result: any;
|
|
40
|
+
snapshot: WorkflowSnapshot;
|
|
41
|
+
};
|
|
42
|
+
export type CompiledWorkflow = {
|
|
43
|
+
lowered: LoweredWorkflow;
|
|
44
|
+
revisions: {
|
|
45
|
+
control: string;
|
|
46
|
+
dags: Record<string, string>;
|
|
47
|
+
};
|
|
48
|
+
taskVersions: Readonly<Record<string, string>>;
|
|
49
|
+
run: (input: any, opts: WorkflowRunOptions) => Promise<WorkflowResult>;
|
|
50
|
+
};
|
|
51
|
+
/** @typedef {{version: string, revision: string, document: any, fsm: any,
|
|
52
|
+
* dags: Record<string, any>, sources: Record<string, string>, specs: Record<string, any>}} LoweredWorkflow */
|
|
53
|
+
/** @typedef {{format: string, runId: string, identity: any, generation: number,
|
|
54
|
+
* control: import('./statechart.js').StatechartState,
|
|
55
|
+
* context: {input: any, data: any, event: null | {type: string, payload?: any}, results: Record<string, any>, visits: Record<string, number>},
|
|
56
|
+
* pending: null | {state: string, visit: number, input: any, values: Record<string, any>, identity?: any, result?: any},
|
|
57
|
+
* status: 'running'|'waiting'|'done'}} WorkflowSnapshot */
|
|
58
|
+
/** @typedef {{status: 'waiting'|'done', result: any, snapshot: WorkflowSnapshot}} WorkflowResult */
|
|
59
|
+
/** @typedef {{lowered: LoweredWorkflow, revisions: {control: string, dags: Record<string, string>},
|
|
60
|
+
* taskVersions: Readonly<Record<string, string>>,
|
|
61
|
+
* run: (input: any, opts: WorkflowRunOptions) => Promise<WorkflowResult>}} CompiledWorkflow */
|
|
62
|
+
/** Lower one neutral workflow to inspectable JSON, without resolving host tasks.
|
|
63
|
+
* Work states carry work:{task,version,with?} or work:{dag}; choose/on/flow/final
|
|
64
|
+
* are the other mutually exclusive control forms. @param {any} document
|
|
65
|
+
* @returns {LoweredWorkflow} */
|
|
66
|
+
export declare function lowerWorkflow(document: any): LoweredWorkflow;
|
|
67
|
+
export type WorkflowStore = {
|
|
68
|
+
load: (runId: string) => WorkflowSnapshot | null | Promise<WorkflowSnapshot | null>;
|
|
69
|
+
save: (runId: string, snapshot: WorkflowSnapshot, expectedGeneration: number) => boolean | Promise<boolean>;
|
|
70
|
+
};
|
|
71
|
+
export type WorkflowRunOptions = {
|
|
72
|
+
runId: string;
|
|
73
|
+
snapshot?: any;
|
|
74
|
+
expectedGeneration?: number;
|
|
75
|
+
signal?: AbortSignal;
|
|
76
|
+
now?: number;
|
|
77
|
+
event?: {
|
|
78
|
+
type: string;
|
|
79
|
+
payload?: any;
|
|
80
|
+
};
|
|
81
|
+
onTrace?: (record: any) => void;
|
|
82
|
+
};
|
|
83
|
+
/** @typedef {{ load: (runId: string) => WorkflowSnapshot | null | Promise<WorkflowSnapshot | null>,
|
|
84
|
+
* save: (runId: string, snapshot: WorkflowSnapshot, expectedGeneration: number) => boolean | Promise<boolean> }} WorkflowStore */
|
|
85
|
+
/** @typedef {{ runId: string, snapshot?: any, expectedGeneration?: number,
|
|
86
|
+
* signal?: AbortSignal, now?: number, event?: {type: string, payload?: any},
|
|
87
|
+
* onTrace?: (record: any) => void }} WorkflowRunOptions */
|
|
88
|
+
/** Compile once; each run owns its control and checkpoint records.
|
|
89
|
+
* Store save MUST atomically compare expectedGeneration (0 means absent).
|
|
90
|
+
* @param {any} document
|
|
91
|
+
* @param {{tasks?: NonNullable<Parameters<typeof compileDag>[1]>['tasks'], store?: WorkflowStore}} [options]
|
|
92
|
+
* @returns {CompiledWorkflow}
|
|
93
|
+
*/
|
|
94
|
+
export declare function compileWorkflow(document: any, options?: {
|
|
95
|
+
tasks?: NonNullable<Parameters<typeof compileDag>[1]>['tasks'];
|
|
96
|
+
store?: WorkflowStore;
|
|
97
|
+
}): CompiledWorkflow;
|
package/docs/APP-INTEGRATION.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Hosting a machine in @jarenjs/app
|
|
2
2
|
|
|
3
|
+
This generator targets jaren-fsm **0.1**. Statecharts and composed workflows
|
|
4
|
+
use structured snapshots; they do not pass through `fsmToApp`. Host a composed
|
|
5
|
+
run through the same `createTaskEffect`/id-guard convention as a DAG, persist
|
|
6
|
+
its snapshot, and submit external events on subsequent calls to
|
|
7
|
+
`workflow.run(input, {runId, snapshot, event, signal})`. A returned `waiting`
|
|
8
|
+
status means control is saved and ready for an event; it is not an unresolved
|
|
9
|
+
effect promise. [WORKFLOW-FORMAT](WORKFLOW-FORMAT.md) defines that boundary.
|
|
10
|
+
|
|
3
11
|
How a jaren-fsm document drives a live `@jarenjs/app` application. The
|
|
4
12
|
shape of the convention is one sentence: **control state is a state
|
|
5
13
|
slice, and the machine's transition table becomes generated standard
|
package/docs/FLOW-FORMAT.md
CHANGED
|
@@ -9,8 +9,10 @@ authoritative for everything a structural schema cannot express, and
|
|
|
9
9
|
the compiler enforces it.
|
|
10
10
|
|
|
11
11
|
Section map: §1 scope, §2 the jaren-fsm document, §3 the evaluation
|
|
12
|
-
scope, §4 the selection rule, §5 errors
|
|
13
|
-
|
|
12
|
+
scope, §4 the selection rule, §5 errors, §6 the jaren-dag document and
|
|
13
|
+
§7 DAG execution/persistence. This document retains the 0.1 engine contracts.
|
|
14
|
+
[STATECHART-FORMAT.md](STATECHART-FORMAT.md) specifies jaren-fsm 0.2 and
|
|
15
|
+
[WORKFLOW-FORMAT.md](WORKFLOW-FORMAT.md) specifies their composed workflow.
|
|
14
16
|
|
|
15
17
|
## §1 Scope
|
|
16
18
|
|
|
@@ -42,15 +44,18 @@ them, the same boundary discipline `@jarenjs/app` keeps.
|
|
|
42
44
|
|
|
43
45
|
### §1.1 Non-goals of format 0.1
|
|
44
46
|
|
|
45
|
-
|
|
47
|
+
These remain limits of `compileFsm` and its 0.1 format. Compound states,
|
|
48
|
+
history, parallel regions, explicit-time delays, completion and eventless
|
|
49
|
+
transitions ship separately through `compileStatechart` and jaren-fsm 0.2;
|
|
50
|
+
existing string snapshots and app-generated actions retain their contracts.
|
|
46
51
|
|
|
47
52
|
- **Hierarchy.** No compound or nested states; a mermaid composite state
|
|
48
53
|
arrives flattened.
|
|
49
54
|
- **Eventless chains.** A transition only ever fires in answer to a
|
|
50
55
|
`step`/`send` call; there are no spontaneous microsteps and no
|
|
51
56
|
always-transitions that cascade.
|
|
52
|
-
- **History, parallel regions, delayed/timed transitions.**
|
|
53
|
-
|
|
57
|
+
- **History, parallel regions, delayed/timed transitions.** These require
|
|
58
|
+
the structured snapshot and explicit time of version 0.2.
|
|
54
59
|
- **Effect execution.** The engine resolves descriptors and returns
|
|
55
60
|
them; it MUST NOT invoke handlers.
|
|
56
61
|
- **Persistence.** A machine's current state is a string; storing it is
|
|
@@ -211,10 +216,13 @@ tables there and here MUST stay in sync.
|
|
|
211
216
|
| JF0017 | not exactly one `output` node |
|
|
212
217
|
| JF0018 | a `task` node names no registered handler |
|
|
213
218
|
| JF0019 | a `task` node and its registered handler disagree about the handler version |
|
|
219
|
+
| JF0020 | a statechart violates hierarchy, initial, state or transition rules (STATECHART-FORMAT) |
|
|
220
|
+
| JF0021 | a composed workflow declaration or reference is malformed (WORKFLOW-FORMAT) |
|
|
221
|
+
| JF0022 | an automatic workflow cycle has no declared visit bound |
|
|
214
222
|
|
|
215
223
|
### §5.2 Runtime: thrown vs recorded
|
|
216
224
|
|
|
217
|
-
|
|
225
|
+
In the **0.1 FSM**, only caller mistakes throw (`FlowRuntimeError`):
|
|
218
226
|
|
|
219
227
|
| code | condition |
|
|
220
228
|
|---|---|
|
|
@@ -246,6 +254,13 @@ exactly the kind of partial result D7 forbids:
|
|
|
246
254
|
| JF2007 | the caller's signal aborted the run |
|
|
247
255
|
| JF2008 | a node declared `checkpoint: true` but produced a value that is not JSON-serializable; the run rejects at save time |
|
|
248
256
|
| JF2009 | the checkpoint store threw while loading, saving or completing; the run rejects |
|
|
257
|
+
| JF2010 | an invalid statechart snapshot or illegal active/history/timer configuration |
|
|
258
|
+
| JF2011 | time moved backwards or a deadline/token exceeded its numeric range |
|
|
259
|
+
| JF2012 | a statechart exceeded its microstep limit |
|
|
260
|
+
| JF2013 | checkpoint workflow, lowering, input or task provenance disagrees or is missing |
|
|
261
|
+
| JF2014 | a workflow run is busy or a snapshot/event generation is stale |
|
|
262
|
+
| JF2015 | a workflow state exceeded its declared visit limit |
|
|
263
|
+
| JF2016 | a workflow snapshot is malformed or execution failed to progress cleanly |
|
|
249
264
|
|
|
250
265
|
## §6 The jaren-dag document
|
|
251
266
|
|
|
@@ -433,11 +448,11 @@ await dag.run(input, { runId: 'run-42' });
|
|
|
433
448
|
runs twice after a crash is the caller's bug, bluntly. The
|
|
434
449
|
mitigation is an idempotency key threaded through the node's
|
|
435
450
|
`with` props and honoured by the effectful system itself.
|
|
436
|
-
- A standalone checkpoint store owns run identity
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
451
|
+
- A legacy standalone checkpoint store owns run identity. The opt-in
|
|
452
|
+
`revision` gate (§7.9) now compares the exact document, input and
|
|
453
|
+
`taskVersions` before using saved values; the composed workflow supplies
|
|
454
|
+
this gate and a CAS store automatically. The `@jarenjs/db` DAG job runner
|
|
455
|
+
retains its own persisted identity and lease checks. Values recorded for node ids the current document does not
|
|
441
456
|
declare (or no longer declares `checkpoint`) are ignored.
|
|
442
457
|
|
|
443
458
|
### §7.8 Declared task versions
|
|
@@ -511,3 +526,44 @@ const session = createDurableFsmSession(machine, {
|
|
|
511
526
|
save: (state) => orders.put({ id: 'order-7', state }, 'order-7'),
|
|
512
527
|
});
|
|
513
528
|
```
|
|
529
|
+
|
|
530
|
+
### §7.9 Standalone DAG provenance
|
|
531
|
+
|
|
532
|
+
Pass a nonblank `revision` with a checkpoint store to enable engine-checked
|
|
533
|
+
provenance. The engine constructs an identity containing that revision, the
|
|
534
|
+
exact canonical DAG document, the canonical input and the sorted task-version
|
|
535
|
+
map. It supplies that identity to `load(runId, identity)`,
|
|
536
|
+
`save(runId, nodeId, value, identity)` and `complete(runId, result, identity)`.
|
|
537
|
+
The store persists it beside `values`:
|
|
538
|
+
|
|
539
|
+
```js
|
|
540
|
+
let record = null; // One-run example; production stores key by runId.
|
|
541
|
+
const checkpoint = {
|
|
542
|
+
load: () => record,
|
|
543
|
+
save(runId, nodeId, value, identity) {
|
|
544
|
+
record ??= { identity, values: {} };
|
|
545
|
+
Object.defineProperty(record.values, nodeId, { value, enumerable: true,
|
|
546
|
+
configurable: true, writable: true });
|
|
547
|
+
},
|
|
548
|
+
complete() {},
|
|
549
|
+
};
|
|
550
|
+
const dag = compileDag(doc, { tasks, checkpoint, revision: 'enrich/3' });
|
|
551
|
+
await dag.run(input, { runId: 'enrich-7' });
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
Any non-null loaded record with a missing or different identity is JF2013
|
|
555
|
+
**before** values enter the node memo. Equality uses exact canonical JSON,
|
|
556
|
+
not a collision-prone fingerprint. The store still owns atomic run binding,
|
|
557
|
+
concurrency and retention; the composed workflow's CAS protocol supplies those
|
|
558
|
+
checks for its runs. Supplying no `revision` preserves the legacy store API
|
|
559
|
+
and its host-owned provenance contract. Handler versions remain declarations:
|
|
560
|
+
a host MUST change them when implementation behavior changes.
|
|
561
|
+
|
|
562
|
+
Caller abort now settles a run even while a task or store load ignores the
|
|
563
|
+
signal. A task settling after failure cannot start a new checkpoint save.
|
|
564
|
+
`run(input, {drainOnAbort: true})` instead waits for started node evaluations
|
|
565
|
+
to settle before rejecting a caller abort. `createDagJobRunner` selects this
|
|
566
|
+
mode so its separately bounded stop grace reports actual unfinished handlers;
|
|
567
|
+
ordinary callers and composed workflows keep prompt rejection.
|
|
568
|
+
Already-started host writes cannot be undone; a durable multi-writer host
|
|
569
|
+
must fence them, as the composed workflow's generation protocol does.
|