@smthrs/flow 1.0.0-rc.6 → 1.0.0-rc.7

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.
@@ -68,11 +68,19 @@ fails to compile the same way a missing action implementation does.
68
68
  The tier tells the engine what retrying this action means. It is part of the step
69
69
  key, so it is not a hint.
70
70
 
71
- | Tier | Choose it when | What the engine does |
72
- | -------------- | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
73
- | `sealed` | The result is a pure function of the declared inputs. | Records the result and replays it. With the right declarations, reuses it across runs. |
74
- | `compensable` | The body changes the workspace. | Takes a pre-image before each attempt and restores it before the next one, so attempt two does not trip over attempt one's half-written files. Requires a `FlowEngine.SnapshotBoundary` in context, which [`@smthrs/engine-store`](/api/engine-store) supplies. |
75
- | `irreversible` | The body changes the world outside the workspace. | Refuses to retry without a declared `idempotencyKey`, dying with `IrreversibleRetryRequiresIdempotencyKey`. |
71
+ | Tier | Choose it when | What the engine does |
72
+ | -------------- | ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
73
+ | `sealed` | The result is a pure function of the declared inputs. | Records the result and replays it. With the right declarations, reuses it across runs. |
74
+ | `compensable` | The body changes the workspace. | Takes a pre-image before each attempt and restores it before the next one, so attempt two does not trip over attempt one's half-written files. Requires a `FlowEngine.SnapshotBoundary` in context, which [`@smthrs/engine-store`](/api/engine-store) supplies, and a workspace that is a jj repository. |
75
+ | `irreversible` | The body changes the world outside the workspace. | Refuses to retry without a declared `idempotencyKey`, dying with `IrreversibleRetryRequiresIdempotencyKey`. |
76
+
77
+ The `compensable` pre-image is a jj snapshot of the workspace, so the
78
+ workspace must be a jj repository: run `jj git init` in it first. Without one,
79
+ the step dies before its first attempt runs:
80
+
81
+ ```text
82
+ @smthrs/jj/JjError: jj snapshot: Error: There is no jj repo in "."
83
+ ```
76
84
 
77
85
  `sealed` is the default, and it is a claim about the body: this result is worth
78
86
  replaying. Add `nondeterministic: true` when several legitimate results may race
@@ -11,15 +11,57 @@ tool to reach for when a step key changed unexpectedly, when a branch took the
11
11
  arm you did not expect, or when you want to show an operator what a run is about
12
12
  to do.
13
13
 
14
+ ## Before you start
15
+
16
+ The examples run with `node <file>.ts` on Node.js 26.4.0 or later and need no
17
+ engine, database, or jj: planning reads declarations only. They import
18
+ `@smthrs/flow` and `@smthrs/plan`. In a project that installed `@smthrs/flows`,
19
+ add both at the version of `@smthrs/flows` you have:
20
+
21
+ ```bash
22
+ npm install @smthrs/flow@1.0.0-rc.7 @smthrs/plan@1.0.0-rc.7
23
+ ```
24
+
25
+ `@smthrs/flows` re-exports the same modules, so `import { Graph } from "@smthrs/flows"`
26
+ and its `Plan.Node` work too. The outputs below are real output from 1.0.0-rc.6.
27
+
14
28
  ## Build the graph
15
29
 
30
+ Create `inspect.ts`. The flow runs two checks in parallel, joins them into a
31
+ report, and then ships or explains:
32
+
16
33
  ```ts
17
- import { Graph } from "@smthrs/flow"
34
+ import { Action, Flow, Graph } from "@smthrs/flow"
35
+ import { Node } from "@smthrs/plan"
36
+ import * as Schema from "effect/Schema"
37
+
38
+ const Lint = Action.make("release/Lint", { payload: { target: Schema.String }, success: Schema.String })
39
+ const Types = Action.make("release/Types", { payload: { target: Schema.String }, success: Schema.String })
40
+ const Report = Action.make("release/Report", {
41
+ payload: { lint: Schema.String, types: Schema.String },
42
+ success: Schema.String
43
+ })
44
+ const Ship = Action.make("release/Ship", { payload: { report: Schema.String }, success: Schema.String })
45
+ const Explain = Action.make("release/Explain", { payload: { reason: Schema.String }, success: Schema.String })
46
+
47
+ const Release = Flow.make("release/Release", {
48
+ payload: { target: Schema.String },
49
+ success: Schema.String,
50
+ body: ({ target }) =>
51
+ Node.all({ lint: Lint.call({ target }), types: Types.call({ target }) }).pipe(
52
+ Node.bindPlanned((checks) => Report.call({ lint: checks.lint, types: checks.types })),
53
+ Node.branch({
54
+ if: (report) => report.includes("clean"),
55
+ then: (report) => Ship.call({ report }),
56
+ else: (reason) => Explain.call({ reason })
57
+ })
58
+ )
59
+ })
18
60
 
19
61
  const graph = Graph.build(Release, { target: "web" })
20
62
 
21
63
  for (const node of Graph.nodes(graph)) {
22
- console.log(node.id, node.kind, node.dependencies)
64
+ console.log(`${node.id} ${node.kind} [${node.dependencies.join(", ")}]`)
23
65
  }
24
66
 
25
67
  for (const edge of Graph.edges(graph)) {
@@ -27,6 +69,33 @@ for (const edge of Graph.edges(graph)) {
27
69
  }
28
70
  ```
29
71
 
72
+ ```bash
73
+ node inspect.ts
74
+ ```
75
+
76
+ ```text
77
+ root.flow.branch.andThen.all.lint ActionCall []
78
+ root.flow.branch.andThen.all.types ActionCall []
79
+ root.flow.branch.andThen All [root.flow.branch.andThen.all.lint, root.flow.branch.andThen.all.types]
80
+ root.flow.branch.then ActionCall [root.flow.branch.andThen]
81
+ root.flow.branch AndThen [root.flow.branch.andThen, root.flow.branch.then]
82
+ root.flow.then ActionCall [root.flow.branch]
83
+ root.flow.else ActionCall [root.flow.branch]
84
+ root.flow Branch [root.flow.branch, root.flow.then, root.flow.else]
85
+ root FlowCall [root.flow]
86
+ root.flow.branch.andThen.all.lint -> root.flow.branch.andThen (value)
87
+ root.flow.branch.andThen.all.types -> root.flow.branch.andThen (value)
88
+ root.flow.branch.andThen -> root.flow.branch (value)
89
+ root.flow.branch.andThen -> root.flow.branch.then (continuation)
90
+ root.flow.branch.then -> root.flow.branch (value)
91
+ root.flow.branch -> root.flow (value)
92
+ root.flow.branch -> root.flow.then (continuation)
93
+ root.flow.then -> root.flow (value)
94
+ root.flow.branch -> root.flow.else (continuation)
95
+ root.flow.else -> root.flow (value)
96
+ root.flow -> root (value)
97
+ ```
98
+
30
99
  `Graph.build` takes a flow declaration or a bare node, the payload, and options.
31
100
  Nothing runs. It refuses a nesting depth past its bound and a duplicate node id,
32
101
  because a node id is durable dispatch identity and two nodes answering to one
@@ -79,9 +148,14 @@ partial drafts. Check diagnostics before asking for drafts:
79
148
  const issues = Graph.diagnostics(graph)
80
149
  if (issues.length === 0) {
81
150
  const drafts = Graph.drafts(graph) // ready for Plan.compile or Plan.append
151
+ console.log(`${drafts.length} drafts`)
82
152
  }
83
153
  ```
84
154
 
155
+ Appended to `inspect.ts`, it prints `9 drafts`: one per node above.
156
+ [Diff two plans](../../../plan/docs/guides/diff-two-plans.md) compiles drafts like
157
+ these and compares two versions of a flow.
158
+
85
159
  ## Resolve layers while building
86
160
 
87
161
  `BuildOptions.resolveLayers` is invoked once per node and is told a
@@ -103,17 +177,22 @@ that read the world would make a plan a function of more than its declarations.
103
177
  ## Drive one interpretation
104
178
 
105
179
  `Interpreter.interpret` builds the graph and walks it against real values,
106
- without registering the flow. It answers an `Interpretation`:
180
+ without registering the flow. It is the walk `Interpreter.layer` runs for each
181
+ round of an execution, and it requires the `FlowInstance` the runtime provides
182
+ to an executing round, so a program reaches it through `execute` rather than by
183
+ calling it at top level. It answers an `Interpretation`:
107
184
 
108
185
  ```ts
109
- import { Interpreter } from "@smthrs/flow"
110
-
111
- const interpretation = yield* Interpreter.interpret(Release, { target: "web" })
112
-
113
- interpretation.value // the root's value
114
- interpretation.settled // ReadonlyMap<string, unknown>: every node that settled
115
- interpretation.failed // the typed failures observed before a catch recovered them
116
- interpretation.skipped // the nodes a branch went the other way on
186
+ interface Interpretation {
187
+ /** The root's value. */
188
+ readonly value: unknown
189
+ /** Every node that settled. */
190
+ readonly settled: ReadonlyMap<string, unknown>
191
+ /** The typed failures observed before a catch recovered them. */
192
+ readonly failed: ReadonlyMap<string, unknown>
193
+ /** The nodes a branch went the other way on. */
194
+ readonly skipped: ReadonlyArray<string>
195
+ }
117
196
  ```
118
197
 
119
198
  `skipped` is the field that answers "why did nothing happen here". The walk is
@@ -147,6 +226,8 @@ implementation layers instead of provided under them.
147
226
 
148
227
  ## Related pages
149
228
 
229
+ - [Diff two plans](../../../plan/docs/guides/diff-two-plans.md): compile two
230
+ versions of a flow and see which steps were added, removed, or re-keyed.
150
231
  - [Bodies are plans](../concepts/bodies-and-plans.md): why the build is pure and
151
232
  what it refuses.
152
233
  - [Execution identity](../concepts/execution-identity.md): how a node id becomes
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@smthrs/flow",
3
3
  "type": "module",
4
- "version": "1.0.0-rc.6",
4
+ "version": "1.0.0-rc.7",
5
5
  "license": "MIT",
6
6
  "smthrs": {
7
7
  "group": "engine"
@@ -289,11 +289,11 @@
289
289
  "tag": "next"
290
290
  },
291
291
  "dependencies": {
292
- "@smthrs/crypto": "1.0.0-rc.6",
293
- "@smthrs/keys": "1.0.0-rc.6",
294
- "@smthrs/plan": "1.0.0-rc.6",
295
- "@smthrs/canonical": "1.0.0-rc.6",
296
- "@smthrs/capability": "1.0.0-rc.6"
292
+ "@smthrs/crypto": "1.0.0-rc.7",
293
+ "@smthrs/keys": "1.0.0-rc.7",
294
+ "@smthrs/plan": "1.0.0-rc.7",
295
+ "@smthrs/canonical": "1.0.0-rc.7",
296
+ "@smthrs/capability": "1.0.0-rc.7"
297
297
  },
298
298
  "devDependencies": {
299
299
  "@effect/language-service": "0.87.2",