@jarenjs/flow 0.67.0 → 0.72.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 +2 -0
- package/docs/FLOW-FORMAT.md +15 -9
- package/package.json +3 -3
- package/schemas/jaren-dag.authoring.schema.json +212 -0
- package/schemas/jaren-dag.draft-07.schema.json +13 -6
- package/schemas/jaren-dag.schema.json +13 -6
- package/schemas/jaren-fsm.authoring.schema.json +130 -0
- package/schemas/jaren-fsm.draft-07.schema.json +5 -2
- package/schemas/jaren-fsm.schema.json +49 -13
- package/src/dag.js +12 -6
package/README.md
CHANGED
|
@@ -270,8 +270,10 @@ Every subpath a consumer can import, derived from the manifest by
|
|
|
270
270
|
| Import | Kind | Declarations |
|
|
271
271
|
|---|---|---|
|
|
272
272
|
| `@jarenjs/flow` | JavaScript | declared |
|
|
273
|
+
| `@jarenjs/flow/schemas/jaren-dag.authoring.schema.json` | schema | — |
|
|
273
274
|
| `@jarenjs/flow/schemas/jaren-dag.draft-07.schema.json` | schema | — |
|
|
274
275
|
| `@jarenjs/flow/schemas/jaren-dag.schema.json` | schema | — |
|
|
276
|
+
| `@jarenjs/flow/schemas/jaren-fsm.authoring.schema.json` | schema | — |
|
|
275
277
|
| `@jarenjs/flow/schemas/jaren-fsm.draft-07.schema.json` | schema | — |
|
|
276
278
|
| `@jarenjs/flow/schemas/jaren-fsm.schema.json` | schema | — |
|
|
277
279
|
| `@jarenjs/flow/package.json` | metadata | — |
|
package/docs/FLOW-FORMAT.md
CHANGED
|
@@ -425,19 +425,21 @@ await dag.run(input, { runId: 'run-42' });
|
|
|
425
425
|
runs twice after a crash is the caller's bug, bluntly. The
|
|
426
426
|
mitigation is an idempotency key threaded through the node's
|
|
427
427
|
`with` props and honoured by the effectful system itself.
|
|
428
|
-
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
428
|
+
- A standalone checkpoint store owns run identity: it must bind the run
|
|
429
|
+
id to the workflow, input and `taskVersions` before returning saved
|
|
430
|
+
values. `compileDag` validates the registry but cannot infer the
|
|
431
|
+
provenance of an arbitrary host store. Use the `@jarenjs/db` DAG job
|
|
432
|
+
runner for persisted identity comparison before node-value loading. Values recorded for node ids the current document does not
|
|
432
433
|
declare (or no longer declares `checkpoint`) are ignored.
|
|
433
434
|
|
|
434
435
|
### §7.8 Declared task versions
|
|
435
436
|
|
|
436
437
|
A checkpointed node's value is REPLAYED on a later run instead of being
|
|
437
438
|
recomputed. That is sound only while the handler that produced it is the
|
|
438
|
-
same handler.
|
|
439
|
-
declare `version`: a
|
|
440
|
-
implementation
|
|
439
|
+
same handler. Every `task` in a workflow containing any checkpoint must
|
|
440
|
+
declare `version`: a nonblank string naming its implementation identity.
|
|
441
|
+
This includes recomputed tasks: an upstream implementation can affect a
|
|
442
|
+
downstream checkpoint even when its own result is never saved.
|
|
441
443
|
|
|
442
444
|
```jsonc
|
|
443
445
|
{ "kind": "task", "run": "summarise", "version": "2026-09-05", "checkpoint": true }
|
|
@@ -463,9 +465,13 @@ declared identity it depends on, keyed by node id and SORTED, so two
|
|
|
463
465
|
compiles of the same document produce the same map — byte for byte —
|
|
464
466
|
whatever order the declarations were written in. A handler that is itself
|
|
465
467
|
a compiled workflow may expose its own `taskVersions`; those compose under
|
|
466
|
-
the node's path (`outer`, `outer/inner`),
|
|
468
|
+
the node's path (`outer`, `outer/inner`), with each node-id segment escaped
|
|
469
|
+
as JSON Pointer (`~` becomes `~0`, `/` becomes `~1`), so a composed run has one
|
|
467
470
|
identity rather than two. Every task id is retained as an own member,
|
|
468
|
-
including names inherited by ordinary JavaScript objects.
|
|
471
|
+
including names inherited by ordinary JavaScript objects. Registry nested
|
|
472
|
+
maps must contain nonblank string versions and valid escaped paths.
|
|
473
|
+
A literal `outer/inner` node is `outer~1inner`, distinct from a nested
|
|
474
|
+
`inner` task under `outer`.
|
|
469
475
|
|
|
470
476
|
```js
|
|
471
477
|
compileDag(doc, { tasks: { summarise: { version: '2026-09-05', run: handler } } })
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jarenjs/flow",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.72.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
7
|
"types": "./dist/types/index.d.ts",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"prepack": "npm run build:types"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@jarenjs/core": "^0.
|
|
53
|
-
"@jarenjs/json": "^0.
|
|
52
|
+
"@jarenjs/core": "^0.72.0",
|
|
53
|
+
"@jarenjs/json": "^0.72.0"
|
|
54
54
|
}
|
|
55
55
|
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://jarenjs.dev/schemas/jaren-dag/0.1/authoring",
|
|
4
|
+
"title": "Jaren dataflow graph 0.1 (authoring profile)",
|
|
5
|
+
"description": "Authoring profile of the canonical grammar: the document SHAPE only, with queryDocument, stylesheetDocument left open. Constrains decoding on small models, where the full grammar does not decode at all. It is deliberately WEAKER than the canonical schema - a document valid here may be nonsense - so the engine compiler is the gate that makes it safe, and generated documents must be compiled before use.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": [
|
|
8
|
+
"$dag",
|
|
9
|
+
"nodes",
|
|
10
|
+
"edges"
|
|
11
|
+
],
|
|
12
|
+
"properties": {
|
|
13
|
+
"$dag": {
|
|
14
|
+
"description": "The dag format version; required.",
|
|
15
|
+
"const": "0.1"
|
|
16
|
+
},
|
|
17
|
+
"nodes": {
|
|
18
|
+
"description": "Node declarations, id to declaration. The id is what edges reference.",
|
|
19
|
+
"type": "object",
|
|
20
|
+
"additionalProperties": {
|
|
21
|
+
"$ref": "#/$defs/node"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"edges": {
|
|
25
|
+
"description": "The wiring: data flows from a node's result to a consumer's input scope, in document order for port-object assembly.",
|
|
26
|
+
"type": "array",
|
|
27
|
+
"items": {
|
|
28
|
+
"$ref": "#/$defs/edge"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"$defs": {
|
|
33
|
+
"node": {
|
|
34
|
+
"anyOf": [
|
|
35
|
+
{
|
|
36
|
+
"description": "Yields the run's input value (null when absent). Accepts no inbound edge.",
|
|
37
|
+
"type": "object",
|
|
38
|
+
"properties": {
|
|
39
|
+
"kind": {
|
|
40
|
+
"const": "input"
|
|
41
|
+
},
|
|
42
|
+
"checkpoint": {
|
|
43
|
+
"type": "boolean",
|
|
44
|
+
"description": "Opt-in durable checkpointing (FLOW-FORMAT §7.6): asserts this node's output is JSON-serializable; recorded values are seeded on resume instead of re-evaluated."
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"required": [
|
|
48
|
+
"kind"
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"description": "The run's result is this node's input scope value. Exactly one per document; no outbound edge.",
|
|
53
|
+
"type": "object",
|
|
54
|
+
"properties": {
|
|
55
|
+
"kind": {
|
|
56
|
+
"const": "output"
|
|
57
|
+
},
|
|
58
|
+
"checkpoint": {
|
|
59
|
+
"type": "boolean",
|
|
60
|
+
"description": "Opt-in durable checkpointing (FLOW-FORMAT §7.6): asserts this node's output is JSON-serializable; recorded values are seeded on resume instead of re-evaluated."
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"required": [
|
|
64
|
+
"kind"
|
|
65
|
+
]
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"description": "Yields its literal value. Accepts no inbound edge.",
|
|
69
|
+
"type": "object",
|
|
70
|
+
"properties": {
|
|
71
|
+
"kind": {
|
|
72
|
+
"const": "const"
|
|
73
|
+
},
|
|
74
|
+
"value": {
|
|
75
|
+
"description": "Any JSON value, delivered by reference."
|
|
76
|
+
},
|
|
77
|
+
"checkpoint": {
|
|
78
|
+
"type": "boolean",
|
|
79
|
+
"description": "Opt-in durable checkpointing (FLOW-FORMAT §7.6): asserts this node's output is JSON-serializable; recorded values are seeded on resume instead of re-evaluated."
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"required": [
|
|
83
|
+
"kind",
|
|
84
|
+
"value"
|
|
85
|
+
]
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"description": "A Jaren JSON Query evaluated with $ bound to the node's input scope; an empty result yields null.",
|
|
89
|
+
"type": "object",
|
|
90
|
+
"properties": {
|
|
91
|
+
"kind": {
|
|
92
|
+
"const": "query"
|
|
93
|
+
},
|
|
94
|
+
"query": {
|
|
95
|
+
"description": "The query document.",
|
|
96
|
+
"allOf": [
|
|
97
|
+
{
|
|
98
|
+
"$ref": "#/$defs/queryDocument"
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
},
|
|
102
|
+
"checkpoint": {
|
|
103
|
+
"type": "boolean",
|
|
104
|
+
"description": "Opt-in durable checkpointing (FLOW-FORMAT §7.6): asserts this node's output is JSON-serializable; recorded values are seeded on resume instead of re-evaluated."
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
"required": [
|
|
108
|
+
"kind",
|
|
109
|
+
"query"
|
|
110
|
+
]
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"description": "A JSLT stylesheet transforming the node's input scope; an empty result yields null.",
|
|
114
|
+
"type": "object",
|
|
115
|
+
"properties": {
|
|
116
|
+
"kind": {
|
|
117
|
+
"const": "jslt"
|
|
118
|
+
},
|
|
119
|
+
"stylesheet": {
|
|
120
|
+
"description": "The stylesheet document (bare rule array or envelope form).",
|
|
121
|
+
"allOf": [
|
|
122
|
+
{
|
|
123
|
+
"$ref": "#/$defs/stylesheetDocument"
|
|
124
|
+
}
|
|
125
|
+
]
|
|
126
|
+
},
|
|
127
|
+
"checkpoint": {
|
|
128
|
+
"type": "boolean",
|
|
129
|
+
"description": "Opt-in durable checkpointing (FLOW-FORMAT §7.6): asserts this node's output is JSON-serializable; recorded values are seeded on resume instead of re-evaluated."
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
"required": [
|
|
133
|
+
"kind",
|
|
134
|
+
"stylesheet"
|
|
135
|
+
]
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"description": "A registered async handler, called as handler({ with, input }, signal); the handler must honor the run's shared AbortSignal.",
|
|
139
|
+
"type": "object",
|
|
140
|
+
"properties": {
|
|
141
|
+
"kind": {
|
|
142
|
+
"const": "task"
|
|
143
|
+
},
|
|
144
|
+
"run": {
|
|
145
|
+
"description": "The compile-time registry handler name.",
|
|
146
|
+
"type": "string",
|
|
147
|
+
"minLength": 1
|
|
148
|
+
},
|
|
149
|
+
"version": {
|
|
150
|
+
"description": "Declared nonblank handler identity (FLOW-FORMAT section 7.8). The compiler requires it on every task in a workflow containing checkpoints, including recomputed tasks that can affect saved downstream results. The registry must supply the same token; identity is never derived from source.",
|
|
151
|
+
"type": "string",
|
|
152
|
+
"minLength": 1,
|
|
153
|
+
"pattern": "\\S"
|
|
154
|
+
},
|
|
155
|
+
"with": {
|
|
156
|
+
"description": "Handler props: a query resolved against the node's input scope (null when absent or empty).",
|
|
157
|
+
"allOf": [
|
|
158
|
+
{
|
|
159
|
+
"$ref": "#/$defs/queryDocument"
|
|
160
|
+
}
|
|
161
|
+
]
|
|
162
|
+
},
|
|
163
|
+
"checkpoint": {
|
|
164
|
+
"type": "boolean",
|
|
165
|
+
"description": "Opt-in durable checkpointing (FLOW-FORMAT §7.6): asserts this node's output is JSON-serializable; recorded values are seeded on resume instead of re-evaluated."
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
"required": [
|
|
169
|
+
"kind",
|
|
170
|
+
"run"
|
|
171
|
+
]
|
|
172
|
+
}
|
|
173
|
+
]
|
|
174
|
+
},
|
|
175
|
+
"edge": {
|
|
176
|
+
"type": "object",
|
|
177
|
+
"properties": {
|
|
178
|
+
"from": {
|
|
179
|
+
"description": "The producing node id.",
|
|
180
|
+
"type": "string"
|
|
181
|
+
},
|
|
182
|
+
"to": {
|
|
183
|
+
"description": "The consuming node id.",
|
|
184
|
+
"type": "string"
|
|
185
|
+
},
|
|
186
|
+
"port": {
|
|
187
|
+
"description": "Names this delivery in the consumer's input-scope object; when any inbound edge of a node is ported, all must be, uniquely.",
|
|
188
|
+
"type": "string",
|
|
189
|
+
"minLength": 1
|
|
190
|
+
},
|
|
191
|
+
"select": {
|
|
192
|
+
"description": "A query applied to the source value before delivery; an empty result delivers null.",
|
|
193
|
+
"allOf": [
|
|
194
|
+
{
|
|
195
|
+
"$ref": "#/$defs/queryDocument"
|
|
196
|
+
}
|
|
197
|
+
]
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
"required": [
|
|
201
|
+
"from",
|
|
202
|
+
"to"
|
|
203
|
+
]
|
|
204
|
+
},
|
|
205
|
+
"queryDocument": {
|
|
206
|
+
"description": "A Jaren query expression; validate against the full grammar and compile locally."
|
|
207
|
+
},
|
|
208
|
+
"stylesheetDocument": {
|
|
209
|
+
"description": "A JSLT stylesheet; validate and compile locally."
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
"description": "The query document.",
|
|
96
96
|
"allOf": [
|
|
97
97
|
{
|
|
98
|
-
"$ref": "
|
|
98
|
+
"$ref": "#/definitions/queryDocument"
|
|
99
99
|
}
|
|
100
100
|
]
|
|
101
101
|
},
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
"description": "The stylesheet document (bare rule array or envelope form).",
|
|
121
121
|
"allOf": [
|
|
122
122
|
{
|
|
123
|
-
"$ref": "
|
|
123
|
+
"$ref": "#/definitions/stylesheetDocument"
|
|
124
124
|
}
|
|
125
125
|
]
|
|
126
126
|
},
|
|
@@ -147,15 +147,16 @@
|
|
|
147
147
|
"minLength": 1
|
|
148
148
|
},
|
|
149
149
|
"version": {
|
|
150
|
-
"description": "
|
|
150
|
+
"description": "Declared nonblank handler identity (FLOW-FORMAT section 7.8). The compiler requires it on every task in a workflow containing checkpoints, including recomputed tasks that can affect saved downstream results. The registry must supply the same token; identity is never derived from source.",
|
|
151
151
|
"type": "string",
|
|
152
|
-
"minLength": 1
|
|
152
|
+
"minLength": 1,
|
|
153
|
+
"pattern": "\\S"
|
|
153
154
|
},
|
|
154
155
|
"with": {
|
|
155
156
|
"description": "Handler props: a query resolved against the node's input scope (null when absent or empty).",
|
|
156
157
|
"allOf": [
|
|
157
158
|
{
|
|
158
|
-
"$ref": "
|
|
159
|
+
"$ref": "#/definitions/queryDocument"
|
|
159
160
|
}
|
|
160
161
|
]
|
|
161
162
|
},
|
|
@@ -191,7 +192,7 @@
|
|
|
191
192
|
"description": "A query applied to the source value before delivery; an empty result delivers null.",
|
|
192
193
|
"allOf": [
|
|
193
194
|
{
|
|
194
|
-
"$ref": "
|
|
195
|
+
"$ref": "#/definitions/queryDocument"
|
|
195
196
|
}
|
|
196
197
|
]
|
|
197
198
|
}
|
|
@@ -200,6 +201,12 @@
|
|
|
200
201
|
"from",
|
|
201
202
|
"to"
|
|
202
203
|
]
|
|
204
|
+
},
|
|
205
|
+
"queryDocument": {
|
|
206
|
+
"$ref": "https://jarenjs.dev/schemas/jaren-query/0.1/draft-07"
|
|
207
|
+
},
|
|
208
|
+
"stylesheetDocument": {
|
|
209
|
+
"$ref": "https://jarenjs.dev/schemas/jaren-jslt/0.1/draft-07"
|
|
203
210
|
}
|
|
204
211
|
}
|
|
205
212
|
}
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
"description": "The query document.",
|
|
96
96
|
"allOf": [
|
|
97
97
|
{
|
|
98
|
-
"$ref": "
|
|
98
|
+
"$ref": "#/$defs/queryDocument"
|
|
99
99
|
}
|
|
100
100
|
]
|
|
101
101
|
},
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
"description": "The stylesheet document (bare rule array or envelope form).",
|
|
121
121
|
"allOf": [
|
|
122
122
|
{
|
|
123
|
-
"$ref": "
|
|
123
|
+
"$ref": "#/$defs/stylesheetDocument"
|
|
124
124
|
}
|
|
125
125
|
]
|
|
126
126
|
},
|
|
@@ -147,15 +147,16 @@
|
|
|
147
147
|
"minLength": 1
|
|
148
148
|
},
|
|
149
149
|
"version": {
|
|
150
|
-
"description": "
|
|
150
|
+
"description": "Declared nonblank handler identity (FLOW-FORMAT section 7.8). The compiler requires it on every task in a workflow containing checkpoints, including recomputed tasks that can affect saved downstream results. The registry must supply the same token; identity is never derived from source.",
|
|
151
151
|
"type": "string",
|
|
152
|
-
"minLength": 1
|
|
152
|
+
"minLength": 1,
|
|
153
|
+
"pattern": "\\S"
|
|
153
154
|
},
|
|
154
155
|
"with": {
|
|
155
156
|
"description": "Handler props: a query resolved against the node's input scope (null when absent or empty).",
|
|
156
157
|
"allOf": [
|
|
157
158
|
{
|
|
158
|
-
"$ref": "
|
|
159
|
+
"$ref": "#/$defs/queryDocument"
|
|
159
160
|
}
|
|
160
161
|
]
|
|
161
162
|
},
|
|
@@ -191,7 +192,7 @@
|
|
|
191
192
|
"description": "A query applied to the source value before delivery; an empty result delivers null.",
|
|
192
193
|
"allOf": [
|
|
193
194
|
{
|
|
194
|
-
"$ref": "
|
|
195
|
+
"$ref": "#/$defs/queryDocument"
|
|
195
196
|
}
|
|
196
197
|
]
|
|
197
198
|
}
|
|
@@ -200,6 +201,12 @@
|
|
|
200
201
|
"from",
|
|
201
202
|
"to"
|
|
202
203
|
]
|
|
204
|
+
},
|
|
205
|
+
"queryDocument": {
|
|
206
|
+
"$ref": "https://jarenjs.dev/schemas/jaren-query/0.1"
|
|
207
|
+
},
|
|
208
|
+
"stylesheetDocument": {
|
|
209
|
+
"$ref": "https://jarenjs.dev/schemas/jaren-jslt/0.1"
|
|
203
210
|
}
|
|
204
211
|
}
|
|
205
212
|
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://jarenjs.dev/schemas/jaren-fsm/0.1/authoring",
|
|
4
|
+
"title": "Jaren finite state machine 0.1 (authoring profile)",
|
|
5
|
+
"description": "Authoring profile of the canonical grammar: the document SHAPE only, with queryDocument left open. Constrains decoding on small models, where the full grammar does not decode at all. It is deliberately WEAKER than the canonical schema - a document valid here may be nonsense - so the engine compiler is the gate that makes it safe, and generated documents must be compiled before use.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"$fsm": {
|
|
9
|
+
"description": "The fsm format version; absent implies 0.1.",
|
|
10
|
+
"const": "0.1"
|
|
11
|
+
},
|
|
12
|
+
"initial": {
|
|
13
|
+
"description": "The id of the machine's initial state, or null when the document does not choose one (a session must then be started with an explicit state).",
|
|
14
|
+
"type": [
|
|
15
|
+
"string",
|
|
16
|
+
"null"
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
"states": {
|
|
20
|
+
"description": "Declared states, in document order. A string is shorthand for { id }.",
|
|
21
|
+
"type": "array",
|
|
22
|
+
"items": {
|
|
23
|
+
"anyOf": [
|
|
24
|
+
{
|
|
25
|
+
"type": "string"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"type": "object",
|
|
29
|
+
"properties": {
|
|
30
|
+
"id": {
|
|
31
|
+
"description": "The state id transitions refer to.",
|
|
32
|
+
"type": "string"
|
|
33
|
+
},
|
|
34
|
+
"entry": {
|
|
35
|
+
"$ref": "#/$defs/effects"
|
|
36
|
+
},
|
|
37
|
+
"exit": {
|
|
38
|
+
"$ref": "#/$defs/effects"
|
|
39
|
+
},
|
|
40
|
+
"final": {
|
|
41
|
+
"description": "Marks a terminal state; a session reports done here.",
|
|
42
|
+
"type": "boolean"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"required": [
|
|
46
|
+
"id"
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"transitions": {
|
|
53
|
+
"description": "The transition table. Document order IS the selection order: the first entry whose from-state, event (a null or absent event matches any name) and guard all match fires.",
|
|
54
|
+
"type": "array",
|
|
55
|
+
"items": {
|
|
56
|
+
"type": "object",
|
|
57
|
+
"properties": {
|
|
58
|
+
"from": {
|
|
59
|
+
"description": "The state this transition leaves.",
|
|
60
|
+
"type": "string"
|
|
61
|
+
},
|
|
62
|
+
"event": {
|
|
63
|
+
"description": "The event name this transition answers; null or absent matches any event (a wildcard). A transition never fires spontaneously.",
|
|
64
|
+
"type": [
|
|
65
|
+
"string",
|
|
66
|
+
"null"
|
|
67
|
+
]
|
|
68
|
+
},
|
|
69
|
+
"guard": {
|
|
70
|
+
"description": "A Jaren JSON Query document asserted by effective boolean value against the evaluation scope { state, event, payload, context }; null or absent means no guard. A plain string not starting with '$' is a literal and therefore always true — projection-produced display guards are vacuous by design.",
|
|
71
|
+
"anyOf": [
|
|
72
|
+
{
|
|
73
|
+
"type": "null"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"$ref": "#/$defs/queryDocument"
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
},
|
|
80
|
+
"to": {
|
|
81
|
+
"description": "The state this transition enters.",
|
|
82
|
+
"type": "string"
|
|
83
|
+
},
|
|
84
|
+
"effects": {
|
|
85
|
+
"$ref": "#/$defs/effects"
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"required": [
|
|
89
|
+
"from",
|
|
90
|
+
"to"
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
"required": [
|
|
96
|
+
"initial",
|
|
97
|
+
"states",
|
|
98
|
+
"transitions"
|
|
99
|
+
],
|
|
100
|
+
"$defs": {
|
|
101
|
+
"effects": {
|
|
102
|
+
"description": "Effect descriptors, returned as resolved data when their state or transition fires — the engine never executes them.",
|
|
103
|
+
"type": "array",
|
|
104
|
+
"items": {
|
|
105
|
+
"type": "object",
|
|
106
|
+
"properties": {
|
|
107
|
+
"run": {
|
|
108
|
+
"description": "The host-registered handler name.",
|
|
109
|
+
"type": "string",
|
|
110
|
+
"minLength": 1
|
|
111
|
+
},
|
|
112
|
+
"with": {
|
|
113
|
+
"description": "Handler props: a Jaren JSON Query document evaluated against the scope at fire time; an empty result omits the member.",
|
|
114
|
+
"allOf": [
|
|
115
|
+
{
|
|
116
|
+
"$ref": "#/$defs/queryDocument"
|
|
117
|
+
}
|
|
118
|
+
]
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
"required": [
|
|
122
|
+
"run"
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
"queryDocument": {
|
|
127
|
+
"description": "A Jaren query expression; validate against the full grammar and compile locally."
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"type": "null"
|
|
74
74
|
},
|
|
75
75
|
{
|
|
76
|
-
"$ref": "
|
|
76
|
+
"$ref": "#/definitions/queryDocument"
|
|
77
77
|
}
|
|
78
78
|
]
|
|
79
79
|
},
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
"description": "Handler props: a Jaren JSON Query document evaluated against the scope at fire time; an empty result omits the member.",
|
|
114
114
|
"allOf": [
|
|
115
115
|
{
|
|
116
|
-
"$ref": "
|
|
116
|
+
"$ref": "#/definitions/queryDocument"
|
|
117
117
|
}
|
|
118
118
|
]
|
|
119
119
|
}
|
|
@@ -122,6 +122,9 @@
|
|
|
122
122
|
"run"
|
|
123
123
|
]
|
|
124
124
|
}
|
|
125
|
+
},
|
|
126
|
+
"queryDocument": {
|
|
127
|
+
"$ref": "https://jarenjs.dev/schemas/jaren-query/0.1/draft-07"
|
|
125
128
|
}
|
|
126
129
|
}
|
|
127
130
|
}
|
|
@@ -11,14 +11,19 @@
|
|
|
11
11
|
},
|
|
12
12
|
"initial": {
|
|
13
13
|
"description": "The id of the machine's initial state, or null when the document does not choose one (a session must then be started with an explicit state).",
|
|
14
|
-
"type": [
|
|
14
|
+
"type": [
|
|
15
|
+
"string",
|
|
16
|
+
"null"
|
|
17
|
+
]
|
|
15
18
|
},
|
|
16
19
|
"states": {
|
|
17
20
|
"description": "Declared states, in document order. A string is shorthand for { id }.",
|
|
18
21
|
"type": "array",
|
|
19
22
|
"items": {
|
|
20
23
|
"anyOf": [
|
|
21
|
-
{
|
|
24
|
+
{
|
|
25
|
+
"type": "string"
|
|
26
|
+
},
|
|
22
27
|
{
|
|
23
28
|
"type": "object",
|
|
24
29
|
"properties": {
|
|
@@ -26,14 +31,20 @@
|
|
|
26
31
|
"description": "The state id transitions refer to.",
|
|
27
32
|
"type": "string"
|
|
28
33
|
},
|
|
29
|
-
"entry": {
|
|
30
|
-
|
|
34
|
+
"entry": {
|
|
35
|
+
"$ref": "#/$defs/effects"
|
|
36
|
+
},
|
|
37
|
+
"exit": {
|
|
38
|
+
"$ref": "#/$defs/effects"
|
|
39
|
+
},
|
|
31
40
|
"final": {
|
|
32
41
|
"description": "Marks a terminal state; a session reports done here.",
|
|
33
42
|
"type": "boolean"
|
|
34
43
|
}
|
|
35
44
|
},
|
|
36
|
-
"required": [
|
|
45
|
+
"required": [
|
|
46
|
+
"id"
|
|
47
|
+
]
|
|
37
48
|
}
|
|
38
49
|
]
|
|
39
50
|
}
|
|
@@ -50,26 +61,42 @@
|
|
|
50
61
|
},
|
|
51
62
|
"event": {
|
|
52
63
|
"description": "The event name this transition answers; null or absent matches any event (a wildcard). A transition never fires spontaneously.",
|
|
53
|
-
"type": [
|
|
64
|
+
"type": [
|
|
65
|
+
"string",
|
|
66
|
+
"null"
|
|
67
|
+
]
|
|
54
68
|
},
|
|
55
69
|
"guard": {
|
|
56
70
|
"description": "A Jaren JSON Query document asserted by effective boolean value against the evaluation scope { state, event, payload, context }; null or absent means no guard. A plain string not starting with '$' is a literal and therefore always true — projection-produced display guards are vacuous by design.",
|
|
57
71
|
"anyOf": [
|
|
58
|
-
{
|
|
59
|
-
|
|
72
|
+
{
|
|
73
|
+
"type": "null"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"$ref": "#/$defs/queryDocument"
|
|
77
|
+
}
|
|
60
78
|
]
|
|
61
79
|
},
|
|
62
80
|
"to": {
|
|
63
81
|
"description": "The state this transition enters.",
|
|
64
82
|
"type": "string"
|
|
65
83
|
},
|
|
66
|
-
"effects": {
|
|
84
|
+
"effects": {
|
|
85
|
+
"$ref": "#/$defs/effects"
|
|
86
|
+
}
|
|
67
87
|
},
|
|
68
|
-
"required": [
|
|
88
|
+
"required": [
|
|
89
|
+
"from",
|
|
90
|
+
"to"
|
|
91
|
+
]
|
|
69
92
|
}
|
|
70
93
|
}
|
|
71
94
|
},
|
|
72
|
-
"required": [
|
|
95
|
+
"required": [
|
|
96
|
+
"initial",
|
|
97
|
+
"states",
|
|
98
|
+
"transitions"
|
|
99
|
+
],
|
|
73
100
|
"$defs": {
|
|
74
101
|
"effects": {
|
|
75
102
|
"description": "Effect descriptors, returned as resolved data when their state or transition fires — the engine never executes them.",
|
|
@@ -84,11 +111,20 @@
|
|
|
84
111
|
},
|
|
85
112
|
"with": {
|
|
86
113
|
"description": "Handler props: a Jaren JSON Query document evaluated against the scope at fire time; an empty result omits the member.",
|
|
87
|
-
"allOf": [
|
|
114
|
+
"allOf": [
|
|
115
|
+
{
|
|
116
|
+
"$ref": "#/$defs/queryDocument"
|
|
117
|
+
}
|
|
118
|
+
]
|
|
88
119
|
}
|
|
89
120
|
},
|
|
90
|
-
"required": [
|
|
121
|
+
"required": [
|
|
122
|
+
"run"
|
|
123
|
+
]
|
|
91
124
|
}
|
|
125
|
+
},
|
|
126
|
+
"queryDocument": {
|
|
127
|
+
"$ref": "https://jarenjs.dev/schemas/jaren-query/0.1"
|
|
92
128
|
}
|
|
93
129
|
}
|
|
94
130
|
}
|
package/src/dag.js
CHANGED
|
@@ -95,7 +95,7 @@ function normalizeTaskEntry(entry, name) {
|
|
|
95
95
|
`compileDag: the registered handler '${name}' is not a function, nor { run, version }`);
|
|
96
96
|
}
|
|
97
97
|
if (entry.version !== undefined
|
|
98
|
-
&& (typeof entry.version !== 'string' || entry.version === '')) {
|
|
98
|
+
&& (typeof entry.version !== 'string' || entry.version.trim() === '')) {
|
|
99
99
|
throw new TypeError(
|
|
100
100
|
`compileDag: the registered handler '${name}' has a version that is not a non-empty string`);
|
|
101
101
|
}
|
|
@@ -103,6 +103,10 @@ function normalizeTaskEntry(entry, name) {
|
|
|
103
103
|
throw new TypeError(
|
|
104
104
|
`compileDag: the registered handler '${name}' has a taskVersions that is not an object`);
|
|
105
105
|
}
|
|
106
|
+
if (entry.taskVersions !== undefined && Object.entries(entry.taskVersions).some(([path, version]) =>
|
|
107
|
+
/~(?:[^01]|$)/.test(path) || typeof version !== 'string' || version.trim() === '')) {
|
|
108
|
+
throw new TypeError(`compileDag: the registered handler '${name}' has invalid taskVersions paths or versions`);
|
|
109
|
+
}
|
|
106
110
|
return {
|
|
107
111
|
run: entry.run,
|
|
108
112
|
version: entry.version ?? null,
|
|
@@ -156,6 +160,7 @@ export function compileDag(doc, options) {
|
|
|
156
160
|
/** @type {Map<string, any>} */
|
|
157
161
|
const nodes = new Map();
|
|
158
162
|
const order = Object.keys(doc.nodes);
|
|
163
|
+
const durable = order.some((id) => doc.nodes[id]?.checkpoint === true);
|
|
159
164
|
for (const id of order) {
|
|
160
165
|
const decl = doc.nodes[id];
|
|
161
166
|
const base = `/nodes/${encodeJSONPointerSegment(id)}`;
|
|
@@ -202,7 +207,7 @@ export function compileDag(doc, options) {
|
|
|
202
207
|
`task node '${id}' must carry a non-empty string "run"`, `${base}/run`);
|
|
203
208
|
}
|
|
204
209
|
if (decl.version !== undefined
|
|
205
|
-
&& (typeof decl.version !== 'string' || decl.version === '')) {
|
|
210
|
+
&& (typeof decl.version !== 'string' || decl.version.trim() === '')) {
|
|
206
211
|
throw new FlowCompileError('JF0011',
|
|
207
212
|
`task node '${id}' has a "version" member that is not a non-empty string`,
|
|
208
213
|
`${base}/version`);
|
|
@@ -212,9 +217,9 @@ export function compileDag(doc, options) {
|
|
|
212
217
|
// same implementation. That identity is declared, never derived:
|
|
213
218
|
// hashing a closure's source would call a reformat a new task and
|
|
214
219
|
// a changed dependency the same one
|
|
215
|
-
if (
|
|
220
|
+
if (durable && decl.version === undefined) {
|
|
216
221
|
throw new FlowCompileError('JF0011',
|
|
217
|
-
`task node '${id}'
|
|
222
|
+
`task node '${id}' belongs to a workflow with checkpoints, so it must also declare a "version" — `
|
|
218
223
|
+ 'a checkpointed result is replayed only while the handler that produced it is '
|
|
219
224
|
+ 'the same one, and that identity has to be stated', `${base}/version`);
|
|
220
225
|
}
|
|
@@ -631,10 +636,11 @@ export function compileDag(doc, options) {
|
|
|
631
636
|
const versions = {};
|
|
632
637
|
for (const node of nodes.values()) {
|
|
633
638
|
if (node.kind !== 'task' || node.version === null) continue;
|
|
634
|
-
|
|
639
|
+
const pathPrefix = encodeJSONPointerSegment(node.id);
|
|
640
|
+
setObjectMember(versions, pathPrefix, node.version);
|
|
635
641
|
if (node.nestedVersions === null) continue;
|
|
636
642
|
for (const [path, version] of Object.entries(node.nestedVersions)) {
|
|
637
|
-
setObjectMember(versions, `${
|
|
643
|
+
setObjectMember(versions, `${pathPrefix}/${path}`, version);
|
|
638
644
|
}
|
|
639
645
|
}
|
|
640
646
|
const taskVersions = Object.freeze(Object.fromEntries(
|