@smthrs/flow 1.0.0-rc.12 → 1.0.0-rc.14
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 +3 -3
- package/dist/cjs/Flow/make.d.ts +14 -0
- package/dist/cjs/Flow/make.d.ts.map +1 -1
- package/dist/cjs/Flow/make.js.map +2 -2
- package/dist/cjs/HumanTask.d.ts.map +1 -1
- package/dist/cjs/HumanTask.js +10 -1
- package/dist/cjs/HumanTask.js.map +2 -2
- package/dist/cjs/Interpreter.d.ts.map +1 -1
- package/dist/cjs/Interpreter.js +14 -2
- package/dist/cjs/Interpreter.js.map +2 -2
- package/dist/cjs/internal/RecordedPlan.d.ts +19 -0
- package/dist/cjs/internal/RecordedPlan.d.ts.map +1 -0
- package/dist/cjs/internal/RecordedPlan.js +55 -0
- package/dist/cjs/internal/RecordedPlan.js.map +7 -0
- package/dist/esm/Flow/make.d.ts +14 -0
- package/dist/esm/Flow/make.d.ts.map +1 -1
- package/dist/esm/Flow/make.js.map +1 -1
- package/dist/esm/HumanTask.d.ts.map +1 -1
- package/dist/esm/HumanTask.js +13 -1
- package/dist/esm/HumanTask.js.map +1 -1
- package/dist/esm/Interpreter.d.ts.map +1 -1
- package/dist/esm/Interpreter.js +15 -2
- package/dist/esm/Interpreter.js.map +1 -1
- package/dist/esm/internal/RecordedPlan.d.ts +19 -0
- package/dist/esm/internal/RecordedPlan.d.ts.map +1 -0
- package/dist/esm/internal/RecordedPlan.js +45 -0
- package/dist/esm/internal/RecordedPlan.js.map +1 -0
- package/docs/guides/inspect-the-plan.md +1 -1
- package/docs/installation.md +3 -3
- package/docs/quickstart.md +1 -1
- package/package.json +10 -10
- package/src/Flow/make.ts +14 -0
- package/src/HumanTask.ts +15 -1
- package/src/Interpreter.ts +18 -2
- package/src/internal/RecordedPlan.ts +50 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resume compatibility is weaker than result reuse: unchanged callback source
|
|
3
|
+
* may rebuild a recorded plan, but an opaque closure still cannot earn a cache
|
|
4
|
+
* hit. Only the interpreter's PlanRecorded guard uses this projection. Approval
|
|
5
|
+
* digests and dispatch keys keep the original entropy-bearing material.
|
|
6
|
+
*
|
|
7
|
+
* @since 1.0.0
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
import { isRecord } from "@smthrs/canonical/Record";
|
|
11
|
+
import * as Graph from "../Graph.js";
|
|
12
|
+
/** Missing source metadata retains the old identity and fails closed. */
|
|
13
|
+
const callback = (value) => isRecord(value) && value._tag === "FunctionIdentity" && value.algorithm === "sha256-source-ephemeral/v4" &&
|
|
14
|
+
typeof value.sourceDigest === "string"
|
|
15
|
+
? { _tag: value._tag, algorithm: value.algorithm, digest: value.sourceDigest }
|
|
16
|
+
: value;
|
|
17
|
+
/** Project only callback slots, never identity-shaped application data. */
|
|
18
|
+
const body = (value) => {
|
|
19
|
+
if (!isRecord(value))
|
|
20
|
+
return value;
|
|
21
|
+
switch (value._tag) {
|
|
22
|
+
case "FlowCall":
|
|
23
|
+
return isRecord(value.declaration)
|
|
24
|
+
? { ...value, declaration: { ...value.declaration, body: callback(value.declaration.body) } }
|
|
25
|
+
: value;
|
|
26
|
+
case "Map":
|
|
27
|
+
return { ...value, mapper: callback(value.mapper) };
|
|
28
|
+
case "AndThen":
|
|
29
|
+
return { ...value, continuation: callback(value.continuation) };
|
|
30
|
+
case "Branch":
|
|
31
|
+
return { ...value, predicate: callback(value.predicate) };
|
|
32
|
+
case "Catch":
|
|
33
|
+
return { ...value, filterIdentity: callback(value.filterIdentity) };
|
|
34
|
+
default:
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* The same drafts, with only callback identities made restart-comparable.
|
|
40
|
+
*
|
|
41
|
+
* @since 1.0.0
|
|
42
|
+
* @private
|
|
43
|
+
*/
|
|
44
|
+
export const recordedPlanDrafts = (graph) => Graph.drafts(graph).map((draft) => ({ ...draft, material: { ...draft.material, body: body(draft.material.body) } }));
|
|
45
|
+
//# sourceMappingURL=RecordedPlan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RecordedPlan.js","sourceRoot":"","sources":["../../../src/internal/RecordedPlan.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AAEnD,OAAO,KAAK,KAAK,MAAM,aAAa,CAAA;AAEpC,yEAAyE;AACzE,MAAM,QAAQ,GAAG,CAAC,KAAc,EAAW,EAAE,CAC3C,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,kBAAkB,IAAI,KAAK,CAAC,SAAS,KAAK,4BAA4B;IACtG,OAAO,KAAK,CAAC,YAAY,KAAK,QAAQ;IACtC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,YAAY,EAAE;IAC9E,CAAC,CAAC,KAAK,CAAA;AAEX,2EAA2E;AAC3E,MAAM,IAAI,GAAG,CAAC,KAAc,EAAW,EAAE;IACvC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IAClC,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;QACnB,KAAK,UAAU;YACb,OAAO,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC;gBAChC,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,WAAW,EAAE,EAAE,GAAG,KAAK,CAAC,WAAW,EAAE,IAAI,EAAE,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,EAAE;gBAC7F,CAAC,CAAC,KAAK,CAAA;QACX,KAAK,KAAK;YACR,OAAO,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAA;QACrD,KAAK,SAAS;YACZ,OAAO,EAAE,GAAG,KAAK,EAAE,YAAY,EAAE,QAAQ,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,CAAA;QACjE,KAAK,QAAQ;YACX,OAAO,EAAE,GAAG,KAAK,EAAE,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAA;QAC3D,KAAK,OAAO;YACV,OAAO,EAAE,GAAG,KAAK,EAAE,cAAc,EAAE,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,EAAE,CAAA;QACrE;YACE,OAAO,KAAK,CAAA;IAChB,CAAC;AACH,CAAC,CAAA;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,KAAkB,EAAiC,EAAE,CACtF,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,QAAQ,EAAE,EAAE,GAAG,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,CAAA","sourcesContent":["/**\n * Resume compatibility is weaker than result reuse: unchanged callback source\n * may rebuild a recorded plan, but an opaque closure still cannot earn a cache\n * hit. Only the interpreter's PlanRecorded guard uses this projection. Approval\n * digests and dispatch keys keep the original entropy-bearing material.\n *\n * @since 1.0.0\n * @internal\n */\n\nimport { isRecord } from \"@smthrs/canonical/Record\"\nimport type * as Plan from \"@smthrs/plan/Plan\"\nimport * as Graph from \"../Graph.ts\"\n\n/** Missing source metadata retains the old identity and fails closed. */\nconst callback = (value: unknown): unknown =>\n isRecord(value) && value._tag === \"FunctionIdentity\" && value.algorithm === \"sha256-source-ephemeral/v4\" &&\n typeof value.sourceDigest === \"string\"\n ? { _tag: value._tag, algorithm: value.algorithm, digest: value.sourceDigest }\n : value\n\n/** Project only callback slots, never identity-shaped application data. */\nconst body = (value: unknown): unknown => {\n if (!isRecord(value)) return value\n switch (value._tag) {\n case \"FlowCall\":\n return isRecord(value.declaration)\n ? { ...value, declaration: { ...value.declaration, body: callback(value.declaration.body) } }\n : value\n case \"Map\":\n return { ...value, mapper: callback(value.mapper) }\n case \"AndThen\":\n return { ...value, continuation: callback(value.continuation) }\n case \"Branch\":\n return { ...value, predicate: callback(value.predicate) }\n case \"Catch\":\n return { ...value, filterIdentity: callback(value.filterIdentity) }\n default:\n return value\n }\n}\n\n/**\n * The same drafts, with only callback identities made restart-comparable.\n *\n * @since 1.0.0\n * @private\n */\nexport const recordedPlanDrafts = (graph: Graph.Graph): ReadonlyArray<Plan.NodeDraft> =>\n Graph.drafts(graph).map((draft) => ({ ...draft, material: { ...draft.material, body: body(draft.material.body) } }))\n"]}
|
|
@@ -19,7 +19,7 @@ engine, database, or jj: planning reads declarations only. They import
|
|
|
19
19
|
add both at the version of `@smthrs/flows` you have:
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
npm install @smthrs/flow@1.0.0-rc.
|
|
22
|
+
npm install @smthrs/flow@1.0.0-rc.14 @smthrs/plan@1.0.0-rc.14
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
`@smthrs/flows` re-exports the same modules, so `import { Graph } from "@smthrs/flows"`
|
package/docs/installation.md
CHANGED
|
@@ -13,8 +13,8 @@ npm install @smthrs/flow@next effect
|
|
|
13
13
|
|
|
14
14
|
The `next` tag carries the 1.0 release candidates.
|
|
15
15
|
|
|
16
|
-
`effect` is a peer dependency of this package,
|
|
17
|
-
`effect`
|
|
16
|
+
`effect` is a peer dependency of this package, at exactly `4.0.5`. Add one
|
|
17
|
+
`effect` at that version to your own dependencies: your declarations import
|
|
18
18
|
`effect/Schema` and your implementations import `effect/Effect` directly, and two
|
|
19
19
|
copies of `effect` in one program are two sets of service tags.
|
|
20
20
|
|
|
@@ -28,7 +28,7 @@ from, and the canonical JSON checks applied to values.
|
|
|
28
28
|
## Use the libraries
|
|
29
29
|
|
|
30
30
|
Every `@smthrs` library installs from npm the same way. Name each package your
|
|
31
|
-
code imports with the `next` tag, and add one `effect` in the
|
|
31
|
+
code imports with the `next` tag, and add one `effect` in the `4.0.5` range
|
|
32
32
|
the libraries peer. The [Quickstart](./quickstart.md) program needs these:
|
|
33
33
|
|
|
34
34
|
```bash
|
package/docs/quickstart.md
CHANGED
|
@@ -10,7 +10,7 @@ touches a database or the network, and the result arrives as a typed value
|
|
|
10
10
|
decoded by the schema you declared.
|
|
11
11
|
|
|
12
12
|
A runnable copy of this program is published in the Smithers examples,
|
|
13
|
-
[`01-define-and-run.ts`](https://github.com/smithersai/smithers/blob/main/examples/
|
|
13
|
+
[`01-define-and-run.ts`](https://github.com/smithersai/smithers/blob/main/packages/smithers/flows/engine/examples/01-define-and-run.ts).
|
|
14
14
|
|
|
15
15
|
## Prerequisites
|
|
16
16
|
|
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.
|
|
4
|
+
"version": "1.0.0-rc.14",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"smthrs": {
|
|
7
7
|
"group": "engine"
|
|
@@ -289,21 +289,21 @@
|
|
|
289
289
|
"tag": "next"
|
|
290
290
|
},
|
|
291
291
|
"dependencies": {
|
|
292
|
-
"@smthrs/crypto": "1.0.0-rc.
|
|
293
|
-
"@smthrs/keys": "1.0.0-rc.
|
|
294
|
-
"@smthrs/plan": "1.0.0-rc.
|
|
295
|
-
"@smthrs/canonical": "1.0.0-rc.
|
|
296
|
-
"@smthrs/capability": "1.0.0-rc.
|
|
292
|
+
"@smthrs/crypto": "1.0.0-rc.14",
|
|
293
|
+
"@smthrs/keys": "1.0.0-rc.14",
|
|
294
|
+
"@smthrs/plan": "1.0.0-rc.14",
|
|
295
|
+
"@smthrs/canonical": "1.0.0-rc.14",
|
|
296
|
+
"@smthrs/capability": "1.0.0-rc.14"
|
|
297
297
|
},
|
|
298
298
|
"devDependencies": {
|
|
299
299
|
"@effect/language-service": "0.87.2",
|
|
300
|
-
"@effect/platform-node": "4.0.
|
|
301
|
-
"@effect/vitest": "4.0.
|
|
300
|
+
"@effect/platform-node": "4.0.5",
|
|
301
|
+
"@effect/vitest": "4.0.5",
|
|
302
302
|
"@eslint/js": "9.39.5",
|
|
303
303
|
"@types/node": "26.4.1",
|
|
304
304
|
"@vitest/coverage-v8": "5.0.0",
|
|
305
305
|
"dprint": "0.57.1",
|
|
306
|
-
"effect": "4.0.
|
|
306
|
+
"effect": "4.0.5",
|
|
307
307
|
"esbuild": "0.28.2",
|
|
308
308
|
"eslint": "9.39.5",
|
|
309
309
|
"eslint-import-resolver-typescript": "4.4.5",
|
|
@@ -316,7 +316,7 @@
|
|
|
316
316
|
"vitest": "5.0.0"
|
|
317
317
|
},
|
|
318
318
|
"peerDependencies": {
|
|
319
|
-
"effect": "
|
|
319
|
+
"effect": "4.0.5"
|
|
320
320
|
},
|
|
321
321
|
"scripts": {
|
|
322
322
|
"lint": "eslint src examples --max-warnings=0 && dprint check",
|
package/src/Flow/make.ts
CHANGED
|
@@ -84,6 +84,20 @@ interface MakeOptions<
|
|
|
84
84
|
* reach.
|
|
85
85
|
*/
|
|
86
86
|
readonly modelInvocable?: boolean | undefined
|
|
87
|
+
/**
|
|
88
|
+
* The tokens, milliseconds and dollars one run asks a control plane to
|
|
89
|
+
* approve, and what exceeding them does, as a literal.
|
|
90
|
+
*
|
|
91
|
+
* Discovery reads it from the source text onto the descriptor's budget,
|
|
92
|
+
* exactly as Markdown frontmatter's `budget` is read; the control plane, not
|
|
93
|
+
* this flow, enforces it.
|
|
94
|
+
*/
|
|
95
|
+
readonly budget?: {
|
|
96
|
+
readonly tokens?: number | undefined
|
|
97
|
+
readonly milliseconds?: number | undefined
|
|
98
|
+
readonly usd?: number | undefined
|
|
99
|
+
readonly onExceeded?: "fail" | "warn" | "skip-remaining" | "park" | undefined
|
|
100
|
+
} | undefined
|
|
87
101
|
readonly idempotencyKey?:
|
|
88
102
|
| ((
|
|
89
103
|
payload: Payload extends Schema.Struct.Fields ? Schema.Struct.Type<Payload>
|
package/src/HumanTask.ts
CHANGED
|
@@ -1004,4 +1004,18 @@ export const answer = (options: {
|
|
|
1004
1004
|
export const decode =
|
|
1005
1005
|
<S extends Schema.ConstraintDecoder<unknown>>(schema: S) =>
|
|
1006
1006
|
<E, R>(self: Node.Node<typeof Schema.Json.Type, E, R>): Node.Node<S["Type"], E, R> =>
|
|
1007
|
-
Node.map(self,
|
|
1007
|
+
Node.map(self, answerDecoder(schema))
|
|
1008
|
+
|
|
1009
|
+
// Schema decoders are opaque callbacks. Reuse one per immutable schema so a
|
|
1010
|
+
// same-process rebuild preserves its identity without claiming portable
|
|
1011
|
+
// identity for arbitrary schema transformations or checks.
|
|
1012
|
+
const answerDecoders = new WeakMap<object, (input: unknown) => unknown>()
|
|
1013
|
+
|
|
1014
|
+
const answerDecoder = <S extends Schema.ConstraintDecoder<unknown>>(schema: S): (input: unknown) => S["Type"] => {
|
|
1015
|
+
let decoder = answerDecoders.get(schema)
|
|
1016
|
+
if (decoder === undefined) {
|
|
1017
|
+
decoder = Schema.decodeUnknownSync(schema)
|
|
1018
|
+
answerDecoders.set(schema, decoder)
|
|
1019
|
+
}
|
|
1020
|
+
return decoder
|
|
1021
|
+
}
|
package/src/Interpreter.ts
CHANGED
|
@@ -80,6 +80,7 @@ import * as HumanTask from "./HumanTask.ts"
|
|
|
80
80
|
import * as BoundedJson from "./internal/BoundedJson.ts"
|
|
81
81
|
import { ExecutionMiddleware } from "./internal/ExecutionMiddleware.ts"
|
|
82
82
|
import { OutcomeValueTypeId } from "./internal/OutcomeMarker.ts"
|
|
83
|
+
import { recordedPlanDrafts } from "./internal/RecordedPlan.ts"
|
|
83
84
|
import * as Sleep from "./Sleep.ts"
|
|
84
85
|
import * as WaitFor from "./WaitFor.ts"
|
|
85
86
|
|
|
@@ -272,7 +273,7 @@ const planPages = (
|
|
|
272
273
|
> =>
|
|
273
274
|
Effect.gen(function*() {
|
|
274
275
|
const identity = yield* StepKey.content({
|
|
275
|
-
body: { flow, nodes:
|
|
276
|
+
body: { flow, nodes: recordedPlanDrafts(graph), edges: Graph.edges(graph) },
|
|
276
277
|
inputs: {},
|
|
277
278
|
layers: [],
|
|
278
279
|
capabilities: {}
|
|
@@ -1486,12 +1487,27 @@ export const layer = <
|
|
|
1486
1487
|
|
|
1487
1488
|
// These layers only register handlers at construction. Crypto is supplied by
|
|
1488
1489
|
// the executing interpreter (Services), not required to register a flow.
|
|
1489
|
-
const
|
|
1490
|
+
const systemHandlers = Layer.mergeAll(HumanTask.layer, Sleep.layer, WaitFor.layer) as Layer.Layer<
|
|
1490
1491
|
never,
|
|
1491
1492
|
never,
|
|
1492
1493
|
FlowRuntime
|
|
1493
1494
|
>
|
|
1494
1495
|
|
|
1496
|
+
// Independent module/refresh layer builds can share one implementation table.
|
|
1497
|
+
// Memoize the built-ins by that owner, with Layer retaining each live scope's
|
|
1498
|
+
// reference, rather than registering a second handler or overriding the first.
|
|
1499
|
+
const systemMemoMaps = new WeakMap<Implementations["Service"], Layer.MemoMap>()
|
|
1500
|
+
const systemActions = Layer.fromBuild((currentMemoMap, scope) =>
|
|
1501
|
+
Effect.flatMap(Implementations, (table) => {
|
|
1502
|
+
let memoMap = systemMemoMaps.get(table)
|
|
1503
|
+
if (memoMap === undefined) {
|
|
1504
|
+
memoMap = currentMemoMap
|
|
1505
|
+
systemMemoMaps.set(table, memoMap)
|
|
1506
|
+
}
|
|
1507
|
+
return Layer.buildWithMemoMap(systemHandlers, memoMap, scope)
|
|
1508
|
+
})
|
|
1509
|
+
)
|
|
1510
|
+
|
|
1495
1511
|
/** Registers the interpreter and reachable child flows under one policy. @private */
|
|
1496
1512
|
const makeLayer = <
|
|
1497
1513
|
Tag extends string,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resume compatibility is weaker than result reuse: unchanged callback source
|
|
3
|
+
* may rebuild a recorded plan, but an opaque closure still cannot earn a cache
|
|
4
|
+
* hit. Only the interpreter's PlanRecorded guard uses this projection. Approval
|
|
5
|
+
* digests and dispatch keys keep the original entropy-bearing material.
|
|
6
|
+
*
|
|
7
|
+
* @since 1.0.0
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { isRecord } from "@smthrs/canonical/Record"
|
|
12
|
+
import type * as Plan from "@smthrs/plan/Plan"
|
|
13
|
+
import * as Graph from "../Graph.ts"
|
|
14
|
+
|
|
15
|
+
/** Missing source metadata retains the old identity and fails closed. */
|
|
16
|
+
const callback = (value: unknown): unknown =>
|
|
17
|
+
isRecord(value) && value._tag === "FunctionIdentity" && value.algorithm === "sha256-source-ephemeral/v4" &&
|
|
18
|
+
typeof value.sourceDigest === "string"
|
|
19
|
+
? { _tag: value._tag, algorithm: value.algorithm, digest: value.sourceDigest }
|
|
20
|
+
: value
|
|
21
|
+
|
|
22
|
+
/** Project only callback slots, never identity-shaped application data. */
|
|
23
|
+
const body = (value: unknown): unknown => {
|
|
24
|
+
if (!isRecord(value)) return value
|
|
25
|
+
switch (value._tag) {
|
|
26
|
+
case "FlowCall":
|
|
27
|
+
return isRecord(value.declaration)
|
|
28
|
+
? { ...value, declaration: { ...value.declaration, body: callback(value.declaration.body) } }
|
|
29
|
+
: value
|
|
30
|
+
case "Map":
|
|
31
|
+
return { ...value, mapper: callback(value.mapper) }
|
|
32
|
+
case "AndThen":
|
|
33
|
+
return { ...value, continuation: callback(value.continuation) }
|
|
34
|
+
case "Branch":
|
|
35
|
+
return { ...value, predicate: callback(value.predicate) }
|
|
36
|
+
case "Catch":
|
|
37
|
+
return { ...value, filterIdentity: callback(value.filterIdentity) }
|
|
38
|
+
default:
|
|
39
|
+
return value
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The same drafts, with only callback identities made restart-comparable.
|
|
45
|
+
*
|
|
46
|
+
* @since 1.0.0
|
|
47
|
+
* @private
|
|
48
|
+
*/
|
|
49
|
+
export const recordedPlanDrafts = (graph: Graph.Graph): ReadonlyArray<Plan.NodeDraft> =>
|
|
50
|
+
Graph.drafts(graph).map((draft) => ({ ...draft, material: { ...draft.material, body: body(draft.material.body) } }))
|