@procedures/client 0.24.1 → 0.26.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.
Files changed (80) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/README.md +15 -0
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/adapters/http/server/handler.js +7 -2
  5. package/dist/adapters/http/server/handler.js.map +1 -1
  6. package/dist/adapters/http/server/ndjson.d.ts +10 -13
  7. package/dist/adapters/http/server/ndjson.d.ts.map +1 -1
  8. package/dist/adapters/http/server/ndjson.js +12 -13
  9. package/dist/adapters/http/server/ndjson.js.map +1 -1
  10. package/dist/adapters/websocket/shared/rpc.d.ts +5 -8
  11. package/dist/adapters/websocket/shared/rpc.d.ts.map +1 -1
  12. package/dist/adapters/websocket/shared/rpc.js +11 -14
  13. package/dist/adapters/websocket/shared/rpc.js.map +1 -1
  14. package/dist/browser.d.ts +3 -3
  15. package/dist/browser.d.ts.map +1 -1
  16. package/dist/browser.js +4 -1
  17. package/dist/browser.js.map +1 -1
  18. package/dist/client/client.d.ts +7 -1
  19. package/dist/client/client.d.ts.map +1 -1
  20. package/dist/client/client.js +30 -19
  21. package/dist/client/client.js.map +1 -1
  22. package/dist/client/middleware/retry.d.ts.map +1 -1
  23. package/dist/client/middleware/retry.js +3 -1
  24. package/dist/client/middleware/retry.js.map +1 -1
  25. package/dist/procedures/core/index.d.ts +19 -3
  26. package/dist/procedures/core/index.d.ts.map +1 -1
  27. package/dist/procedures/core/index.js +53 -178
  28. package/dist/procedures/core/index.js.map +1 -1
  29. package/dist/procedures/core/operands.d.ts +12 -13
  30. package/dist/procedures/core/operands.d.ts.map +1 -1
  31. package/dist/procedures/core/operands.js +22 -38
  32. package/dist/procedures/core/operands.js.map +1 -1
  33. package/dist/procedures/define-procedure.d.ts.map +1 -1
  34. package/dist/procedures/define-procedure.js +6 -6
  35. package/dist/procedures/define-procedure.js.map +1 -1
  36. package/dist/procedures/envelope.d.ts +24 -8
  37. package/dist/procedures/envelope.d.ts.map +1 -1
  38. package/dist/procedures/envelope.js +51 -20
  39. package/dist/procedures/envelope.js.map +1 -1
  40. package/dist/procedures/idempotency.d.ts +5 -0
  41. package/dist/procedures/idempotency.d.ts.map +1 -1
  42. package/dist/procedures/idempotency.js +8 -0
  43. package/dist/procedures/idempotency.js.map +1 -1
  44. package/dist/procedures/index.d.ts +3 -1
  45. package/dist/procedures/index.d.ts.map +1 -1
  46. package/dist/procedures/index.js +3 -1
  47. package/dist/procedures/index.js.map +1 -1
  48. package/dist/procedures/program.d.ts +251 -0
  49. package/dist/procedures/program.d.ts.map +1 -0
  50. package/dist/procedures/program.js +1120 -0
  51. package/dist/procedures/program.js.map +1 -0
  52. package/dist/procedures/ref.d.ts +8 -91
  53. package/dist/procedures/ref.d.ts.map +1 -1
  54. package/dist/procedures/ref.js +16 -279
  55. package/dist/procedures/ref.js.map +1 -1
  56. package/dist/protocol.d.ts +12 -0
  57. package/dist/protocol.d.ts.map +1 -1
  58. package/dist/protocol.js +20 -0
  59. package/dist/protocol.js.map +1 -1
  60. package/dist/server/procedure-server.d.ts +3 -6
  61. package/dist/server/procedure-server.d.ts.map +1 -1
  62. package/dist/server/procedure-server.js +33 -14
  63. package/dist/server/procedure-server.js.map +1 -1
  64. package/package.json +2 -1
  65. package/src/adapters/http/server/handler.ts +8 -2
  66. package/src/adapters/http/server/ndjson.ts +17 -18
  67. package/src/adapters/websocket/shared/rpc.ts +11 -14
  68. package/src/browser.ts +25 -1
  69. package/src/client/client.ts +35 -23
  70. package/src/client/middleware/retry.ts +3 -1
  71. package/src/procedures/core/index.ts +68 -212
  72. package/src/procedures/core/operands.ts +24 -44
  73. package/src/procedures/define-procedure.ts +5 -7
  74. package/src/procedures/envelope.ts +64 -21
  75. package/src/procedures/idempotency.ts +8 -0
  76. package/src/procedures/index.ts +34 -4
  77. package/src/procedures/program.ts +1371 -0
  78. package/src/procedures/ref.ts +20 -354
  79. package/src/protocol.ts +19 -0
  80. package/src/server/procedure-server.ts +32 -13
@@ -7,25 +7,22 @@ import type { ProcedureContext, ProcedurePath } from "../types.js";
7
7
  import { PROCEDURE_REGISTRY } from "../registry.js";
8
8
  import {
9
9
  isAnyProcedureRef,
10
- getRefWhen,
11
- WHEN_NEVER,
12
- normalizeRef,
13
- hydrateInput,
14
- createRefScope,
15
10
  isOutputRef,
16
11
  lookupOutputRef,
17
12
  rawInputRule,
18
- withScope,
13
+ scopeOf,
19
14
  RUNS_REFS_TAG,
20
15
  type AnyProcedureRef,
21
16
  type RefScope,
22
17
  } from "../ref.js";
18
+ import { hydrateInput, runControl, runOperand } from "../program.js";
23
19
 
24
20
  /**
25
- * The operands of a control-flow procedure arrive raw: the procedure runs them itself, with the
26
- * same rules as `exec()` (one interpreter, `hydrateInput`). The input of a control-flow procedure
27
- * carries the scope of its caller (`withScope`), so an operand can read the names of an outer
28
- * chain, `map` or `reduce` (deep dive CORE-13).
21
+ * The operands of a control-flow procedure arrive raw: the procedure runs them itself. Its logic
22
+ * is a runner of the coeval kernel (`program.ts`): the handler gives its input to the kernel as a
23
+ * document of its kind. The input of a control-flow procedure carries the scope of its caller
24
+ * (`withScope`), so an operand can read the names of an outer chain, `map` or `reduce` (deep dive
25
+ * CORE-13).
29
26
  */
30
27
 
31
28
  /** The raw-input rule of the caller's registry: the procedure at a path decides (deep dive CORE-5). */
@@ -33,6 +30,19 @@ export function rawRule(ctx: ProcedureContext): (path: ProcedurePath) => boolean
33
30
  return rawInputRule(ctx.registry ?? PROCEDURE_REGISTRY);
34
31
  }
35
32
 
33
+ /** The call function of a context, as the executor of a run. */
34
+ function executorOf(ctx: ProcedureContext): <TIn, TOut>(path: ProcedurePath, input: TIn) => Promise<TOut> {
35
+ return <TIn, TOut>(path: ProcedurePath, input: TIn): Promise<TOut> => ctx.client.call<TIn, TOut>(path, input);
36
+ }
37
+
38
+ /**
39
+ * This function runs a control-flow procedure on the kernel: the runner of its kind runs the
40
+ * operands of the input, in the scope of the caller.
41
+ */
42
+ export function runControlFlow(name: string, input: unknown, ctx: ProcedureContext): Promise<unknown> {
43
+ return runControl(name, input, { executor: executorOf(ctx), rawInput: rawRule(ctx), onRef: ctx.onRef, scope: scopeOf(input) });
44
+ }
45
+
36
46
  /**
37
47
  * Without a client, a control-flow procedure cannot run refs. This function gives a value with
38
48
  * its known `$ref` values resolved. A name that the scope does not have stays as it is.
@@ -59,38 +69,23 @@ export function substituteRefs(value: unknown, scope: RefScope): unknown {
59
69
  * in `scope` and runs its nested refs, as `exec()` does for an input.
60
70
  */
61
71
  export async function hydrateOperand(value: unknown, ctx: ProcedureContext, scope?: RefScope): Promise<unknown> {
62
- return hydrateInput(value, (path, input) => ctx.client.call(path, input), { scope, rawInput: rawRule(ctx), onRef: ctx.onRef });
72
+ return hydrateInput(value, executorOf(ctx), { scope, rawInput: rawRule(ctx), onRef: ctx.onRef });
63
73
  }
64
74
 
65
75
  /**
66
- * This function runs an operand ref of a control-flow procedure.
76
+ * This function runs an operand ref of a control-flow procedure (`runOperand()`).
67
77
  *
68
78
  * It resolves the `$ref` values of the ref's input in `scope`, runs the nested refs of the
69
79
  * input and adds `extra` (the parent context, for example `cwd`). Then it calls the procedure.
70
80
  * A procedure that takes its input raw (control flow) gets the input unchanged, with the scope:
71
- * that procedure runs its own operands.
81
+ * that procedure runs its own operands. A `$never` operand is data with its full input.
72
82
  */
73
83
  export async function callOperand(
74
84
  ref: AnyProcedureRef,
75
85
  ctx: ProcedureContext,
76
86
  options: { scope?: RefScope | undefined; extra?: Record<string, unknown> | undefined } = {},
77
87
  ): Promise<unknown> {
78
- // A $never operand is data with its full input, as a $never call anywhere (roadmap R4: before,
79
- // only a chain step obeyed it, and a $never branch, task or fn started)
80
- if (getRefWhen(ref) === WHEN_NEVER) {
81
- return ref;
82
- }
83
- const normalized = normalizeRef(ref);
84
- const raw = rawRule(ctx)(normalized.path);
85
- let input: unknown = raw ? normalized.input : await hydrateOperand(normalized.input, ctx, options.scope);
86
- const extra = options.extra ?? {};
87
- if (Object.keys(extra).length > 0) {
88
- input = { ...(typeof input === "object" && input !== null && !Array.isArray(input) ? input : {}), ...extra };
89
- }
90
- if (raw) {
91
- input = withScope(input, options.scope);
92
- }
93
- return ctx.client.call(normalized.path, input);
88
+ return runOperand(ref, { executor: executorOf(ctx), rawInput: rawRule(ctx), onRef: ctx.onRef, scope: options.scope, extra: options.extra });
94
89
  }
95
90
 
96
91
  /** This function gives the value of any operand: a ref runs, another value is hydrated. */
@@ -105,21 +100,6 @@ export async function evaluateOperand(
105
100
  return hydrateOperand(value, ctx, options.scope);
106
101
  }
107
102
 
108
- /** The parent context that the control-flow procedures give to their operands. */
109
- export function parentContext(input: { cwd?: string | undefined; node?: unknown }): Record<string, unknown> {
110
- return {
111
- ...(input.cwd ? { cwd: input.cwd } : {}),
112
- ...(input.node ? { node: input.node } : {}),
113
- };
114
- }
115
-
116
- /** A child scope of `parent` with the given `$ref` names, for one call of `fn`. */
117
- export function scopeWith(values: Record<string, unknown>, parent?: RefScope): RefScope {
118
- const scope = createRefScope(parent);
119
- for (const [name, value] of Object.entries(values)) scope.outputs.set(name, value);
120
- return scope;
121
- }
122
-
123
103
  /** The tags of a control-flow procedure: hydration gives it its input raw. */
124
104
  export function controlFlowTags(...tags: string[]): string[] {
125
105
  return ["core", ...tags, RUNS_REFS_TAG];
@@ -41,13 +41,12 @@ import { anySchema, zodAdapter } from "./core/schemas.js";
41
41
  import { keyToPath, pathToKey } from "./types.js";
42
42
  import {
43
43
  createRefScope,
44
- hydrateInput,
45
44
  isAnyProcedureRef,
46
45
  markDataDriven,
47
- prepareRootCall,
48
46
  rawInputRule,
49
47
  RUNS_REFS_TAG,
50
48
  } from "./ref.js";
49
+ import { hydrateInput, runProgram } from "./program.js";
51
50
  import { INFERRED_EFFECTS_METADATA_KEY, PURE_EFFECTS, REGISTRY_READ_EFFECTS, effectsValue, withEffects } from "./effects.js";
52
51
  import { MANIFEST_FORMAT, describeProcedure as describeEntry, describeRegistry, type ProcedureManifest } from "./manifest.js";
53
52
  import type { JsonSchema } from "./schema.js";
@@ -192,9 +191,9 @@ const DeleteOutputSchema = z.looseObject({
192
191
  /**
193
192
  * Create a handler that executes an aggregation definition.
194
193
  *
195
- * The body runs exactly as `exec()` runs the same program: `prepareRootCall()` gives the path
196
- * and the input of the root call (a `$when` on the root call does not apply, a list of calls in
197
- * the root input is an implicit chain), then the body starts the root call. The scope of the
194
+ * The body runs exactly as `exec()` runs the same program: one run of the kernel (`runProgram()`),
195
+ * whose root document is the root call (a `$when` on the root call does not apply, a list of calls
196
+ * in the root input is an implicit chain). The scope of the
198
197
  * body has one name, `input`: the input of the procedure. So `{ $ref: "input.dir" }` reads a
199
198
  * field of the input, and `$last`, the step names, `item`, `acc` and `index` keep their meaning
200
199
  * inside the body. (Deep dive CORE-4: before, a first pass replaced every `$ref` with a field of
@@ -213,8 +212,7 @@ function createAggregationHandler(
213
212
  const executor = <TIn, TOut>(path: ProcedurePath, value: TIn): Promise<TOut> => ctx.client.call<TIn, TOut>(path, value);
214
213
  const options = { scope, rawInput: rawInputRule(ctx.registry ?? PROCEDURE_REGISTRY), onRef: ctx.onRef };
215
214
  if (isAnyProcedureRef(aggregation)) {
216
- const root = await prepareRootCall(aggregation, executor, options);
217
- return ctx.client.call(root.path, root.input);
215
+ return runProgram(aggregation, { ...options, executor });
218
216
  }
219
217
  // A body that is not one call (an older stored program): hydrate it as a root value
220
218
  return hydrateInput(aggregation, executor, options);
@@ -1,20 +1,26 @@
1
1
  /**
2
- * The envelope form of a program (coeval, decisions D6, D9 and D10, roadmap item M7).
2
+ * The envelope form of a program (coeval, decisions D6, D9, D10 and D12, roadmap item M7).
3
3
  *
4
4
  * A document of coeval is an envelope: `{ type, value }`. The header can also have `name` and
5
5
  * `own`. In the envelope form of a program:
6
6
  *
7
7
  * - A call is `{ type: path, value: input }`. The type is the path of a procedure, as an array of
8
8
  * keys. A ref into the world, `{ type: "ref", value: ["world", ...path] }`, is also a type.
9
+ * The arrays `["ref"]` and `["lit"]` are kinds of the kernel, not paths. A procedure with the
10
+ * path `["ref"]` or `["lit"]` has a ref into the world as its type.
9
11
  * - A ref is `{ type: "ref", value: ["names", name, ...fields] }`. The name `last` reads the result
10
12
  * of the step before.
11
- * - A literal is `{ type: "literal", value }`. Nothing in its value runs.
13
+ * - A literal is `{ type: "literal", value }`. Nothing in its value runs. The type `"lit"` (the kind
14
+ * of the kernel, coeval D12) is an alias: `{ type: "lit", value }` is also a literal.
12
15
  *
13
- * The envelope form is a surface. `fromEnvelopeProgram()` changes it into the `$proc` form, and
14
- * `hydrateInput()` starts that form. In the input of a `$proc` program, an envelope is data. Thus
15
- * the root of a program decides its form, and the old programs do not change.
16
+ * The functions of this module read the envelopes with the helpers of `@mark1russell7/coeval`
17
+ * (`isTypeRef`, `typePath`, `ref`). `fromEnvelopeProgram()` changes the envelope form into the
18
+ * `$proc` form, and `client.exec()` then runs the program on the kernel (`program.ts`). In the
19
+ * input of a `$proc` program, an envelope is data. Thus the root of a program decides its form,
20
+ * and the old programs do not change.
16
21
  */
17
22
 
23
+ import { ENVELOPE_KEYS, isTypeRef, ref as refDocument, typePath } from "@mark1russell7/coeval";
18
24
  import {
19
25
  LITERAL_KEY,
20
26
  MAX_DATA_DEPTH,
@@ -50,7 +56,10 @@ export interface EnvelopeRef {
50
56
  readonly value: EnvelopePath;
51
57
  }
52
58
 
53
- /** This document gives its value as data. Nothing in the value runs. */
59
+ /**
60
+ * This document gives its value as data. Nothing in the value runs. The functions of this module
61
+ * write the type `"literal"`, and they also read the alias `"lit"` (coeval D12: the kind `lit`).
62
+ */
54
63
  export interface EnvelopeLiteral {
55
64
  readonly type: "literal";
56
65
  readonly value: unknown;
@@ -61,7 +70,14 @@ export interface EnvelopeLiteral {
61
70
  * procedure, or a ref into the world. The value is the input. A value of `null` gives no input.
62
71
  */
63
72
  export interface EnvelopeProgram {
64
- /** The path of the procedure, or a ref into the world: `{ type: "ref", value: ["world", ...path] }`. */
73
+ /**
74
+ * The path of the procedure, or a ref into the world: `{ type: "ref", value: ["world", ...path] }`.
75
+ *
76
+ * The types `["ref"]` and `["lit"]` (arrays of one key) are the kinds `ref` and `lit` of the
77
+ * kernel (D26). Before 0.26.0, they were calls of the procedures with these paths. A ref into
78
+ * the world is always a call: it is the type of these procedures, for example
79
+ * `{ type: { type: "ref", value: ["world", "ref"] }, value }`.
80
+ */
65
81
  readonly type: readonly string[] | EnvelopeRef;
66
82
  /** The input of the call. `null` gives a call without input. */
67
83
  readonly value: unknown;
@@ -76,7 +92,7 @@ export interface EnvelopeProgram {
76
92
  // =============================================================================
77
93
 
78
94
  /** The keys of an envelope (coeval D6). */
79
- const HEADER_KEYS: ReadonlySet<string> = new Set(["type", "value", "name", "own"]);
95
+ const HEADER_KEYS: ReadonlySet<string> = new Set<string>(ENVELOPE_KEYS);
80
96
 
81
97
  /** The keys that make an object a construct of the `$proc` form. */
82
98
  const NATIVE_KEYS: readonly string[] = [PROCEDURE_JSON_KEY, OUTPUT_REF_KEY, LITERAL_KEY];
@@ -88,7 +104,10 @@ const CALL_KEYS: ReadonlySet<string> = new Set([PROCEDURE_JSON_KEY, "input", PRO
88
104
  const RUNTIME_CALL_KEYS: ReadonlySet<string> = new Set(["path", "input", PROCEDURE_NAME_KEY, PROCEDURE_WHEN_KEY]);
89
105
 
90
106
  const REF_TYPE = "ref";
107
+ /** The type of a literal that this module writes (the form of 0.23.0). */
91
108
  const LITERAL_TYPE = "literal";
109
+ /** The alias of the literal type: the kind `lit` of the kernel (coeval D12). */
110
+ const LIT_ALIAS = "lit";
92
111
  const NAMES_ROOT = "names";
93
112
  const WORLD_ROOT = "world";
94
113
  /** The name of the result of the step before, in the envelope form. */
@@ -102,7 +121,10 @@ type EnvelopeShape = Record<string, unknown> & { type: unknown; value: unknown;
102
121
  /** The kind of an envelope. `bad-type` is an envelope whose type is a ref that is not into the world. */
103
122
  type EnvelopeKind = "call" | "ref" | "literal" | "bad-type";
104
123
 
105
- /** This function gives true if a value is a plain object with `type`, `value` and only header keys. */
124
+ /**
125
+ * This function gives true if a value is a plain object with `type`, `value` and only header keys.
126
+ * The test is looser than `isEnvelope()` of coeval: a bad header gives an error, not data.
127
+ */
106
128
  function isEnvelopeShape(value: unknown): value is EnvelopeShape {
107
129
  return (
108
130
  isPlainObject(value) &&
@@ -117,22 +139,39 @@ function isProcedurePath(value: unknown): value is readonly string[] {
117
139
  return Array.isArray(value) && value.length > 0 && value.every((key) => typeof key === "string");
118
140
  }
119
141
 
120
- /** This function gives the procedure path of the type of a call, or undefined for another type. */
142
+ /** The keys of the type of an envelope (coeval `typePath()`), or undefined for a value that is not a type. */
143
+ function keysOfType(type: unknown): readonly (string | number)[] | undefined {
144
+ // A ref into the world as a type has only the keys `type` and `value`
145
+ if (isPlainObject(type) && Object.keys(type).length !== 2) return undefined;
146
+ return isTypeRef(type) ? typePath(type) : undefined;
147
+ }
148
+
149
+ /** This function gives true if a procedure path is also a kind of the kernel as an array: `["ref"]` or `["lit"]`. */
150
+ function isKindPath(keys: readonly unknown[]): boolean {
151
+ return keys.length === 1 && (keys[0] === REF_TYPE || keys[0] === LIT_ALIAS);
152
+ }
153
+
154
+ /**
155
+ * This function gives the procedure path of the type of a call, or undefined for another type. The
156
+ * type of a call is a list of keys, or a ref into the world. A text is a kind of the kernel or data.
157
+ * The arrays `["ref"]` and `["lit"]` are kinds. A ref into the world is always a call, so it is the
158
+ * type of the procedures `["ref"]` and `["lit"]`.
159
+ */
121
160
  function callPath(type: unknown): ProcedurePath | undefined {
122
- if (isProcedurePath(type)) return [...type];
123
- if (!isEnvelopeShape(type) || type.type !== REF_TYPE || Object.keys(type).length !== 2) return undefined;
124
- const path = type.value;
125
- if (!Array.isArray(path) || path[0] !== WORLD_ROOT) return undefined;
126
- const steps: unknown[] = path.slice(1);
127
- return isProcedurePath(steps) ? [...steps] : undefined;
161
+ if (typeof type === "string") return undefined;
162
+ const keys = keysOfType(type);
163
+ if (!isProcedurePath(keys) || (Array.isArray(type) && isKindPath(keys))) return undefined;
164
+ return [...keys];
128
165
  }
129
166
 
130
167
  /** This function gives the kind of an envelope, or undefined for data. */
131
168
  function envelopeKind(value: unknown): EnvelopeKind | undefined {
132
169
  if (!isEnvelopeShape(value)) return undefined;
133
170
  if (callPath(value.type)) return "call";
134
- if (value.type === REF_TYPE) return "ref";
135
- if (value.type === LITERAL_TYPE) return "literal";
171
+ const keys = keysOfType(value.type);
172
+ if (keys?.length === 1 && keys[0] === REF_TYPE) return "ref";
173
+ // The text "literal" (an array ["literal"] is a call, as in 0.23.0), or the alias "lit"
174
+ if (value.type === LITERAL_TYPE || (keys?.length === 1 && keys[0] === LIT_ALIAS)) return "literal";
136
175
  if (isPlainObject(value.type) && value.type["type"] === REF_TYPE) return "bad-type";
137
176
  return undefined;
138
177
  }
@@ -332,7 +371,8 @@ function fromValue(value: unknown, path: Set<object>, depth: number): unknown {
332
371
  *
333
372
  * The conversion:
334
373
  * - A call gives `{ type: path, value: input }`. A call without input gives the value `null`.
335
- * `$name` gives the header `name`.
374
+ * `$name` gives the header `name`. The path `["ref"]` or `["lit"]` gives a ref into the world as
375
+ * the type, because these arrays are kinds of the kernel.
336
376
  * - `{ $ref: "$last" }` gives `["names", "last"]`. `{ $ref: "a.b" }` gives `["names", "a", "b"]`.
337
377
  * - `{ $literal: value }` gives a literal.
338
378
  * - Data that the envelope form reads in a different way gives a literal. This data is an object
@@ -396,7 +436,10 @@ function toCall(call: AnyProcedureRef, path: Set<object>, depth: number): Envelo
396
436
  : input === null
397
437
  ? { type: LITERAL_TYPE, value: null }
398
438
  : toValue(input, path, depth + 1);
399
- return { type: [...procedurePath], value, ...($name !== undefined ? { name: $name } : {}) };
439
+ const type: EnvelopeProgram["type"] = isKindPath(procedurePath)
440
+ ? (refDocument(WORLD_ROOT, ...procedurePath) as unknown as EnvelopeRef)
441
+ : [...procedurePath];
442
+ return { type, value, ...($name !== undefined ? { name: $name } : {}) };
400
443
  }
401
444
 
402
445
  /** This function changes a `$ref` object into a ref envelope. */
@@ -414,7 +457,7 @@ function toRef(ref: OutputRef): EnvelopeRef {
414
457
  `["names", "last"] reads the result of the step before: give the step a different name`
415
458
  );
416
459
  }
417
- return { type: REF_TYPE, value: [NAMES_ROOT, name === NATIVE_LAST ? LAST_NAME : name!, ...fields] };
460
+ return refDocument(NAMES_ROOT, name === NATIVE_LAST ? LAST_NAME : name!, ...fields) as unknown as EnvelopeRef;
418
461
  }
419
462
 
420
463
  /** This function gives true if a value of the `$proc` form is a call, a `$ref` or a `$literal`. */
@@ -136,6 +136,14 @@ export class IdempotencyCache<T> {
136
136
  return { result: entry.done, first: true };
137
137
  }
138
138
 
139
+ /**
140
+ * This method removes the result of a key, so the next call with the key runs again. It removes
141
+ * the entry only when `result` is its result: an entry of a newer call with the key stays.
142
+ */
143
+ forget(key: string, result: Promise<T>): void {
144
+ if (this.entries.get(key)?.done === result) this.entries.delete(key);
145
+ }
146
+
139
147
  /** This function removes the expired results, and the oldest finished results above the limit. */
140
148
  private evict(): void {
141
149
  const now = this.now();
@@ -247,9 +247,6 @@ export {
247
247
  toJson,
248
248
  normalizeRef,
249
249
  // Hydration
250
- hydrateInput,
251
- prepareRootCall,
252
- observeHydration,
253
250
  isControlFlowPath,
254
251
  isDataDriven,
255
252
  markDataDriven,
@@ -266,7 +263,6 @@ export {
266
263
  resolveOutputRef,
267
264
  withScope,
268
265
  scopeOf,
269
- executeRef,
270
266
  // Templates
271
267
  extractTemplate,
272
268
  parseProcedureJson,
@@ -287,6 +283,40 @@ export type {
287
283
  RefScope,
288
284
  } from "./ref.js";
289
285
 
286
+ // Programs on the coeval kernel (coeval D13, roadmap item M14): the run documents and their machine
287
+ export {
288
+ hydrateInput,
289
+ prepareRootCall,
290
+ observeHydration,
291
+ executeRef,
292
+ runProgram,
293
+ runDocument,
294
+ runControl,
295
+ runOperand,
296
+ programMachine,
297
+ PROGRAM_WORLD,
298
+ desugarProgram,
299
+ desugarInput,
300
+ desugarControlValue,
301
+ originOf,
302
+ namesOfScope,
303
+ scopeOfNames,
304
+ nameKey,
305
+ nameOfKey,
306
+ documentAt,
307
+ } from "./program.js";
308
+ export type {
309
+ ProgramDocument,
310
+ DocumentOrigin,
311
+ DesugarOptions,
312
+ ProgramHost,
313
+ CallEffect,
314
+ CallPosition,
315
+ RunDocumentOptions,
316
+ RunProgramOptions,
317
+ RunOperandOptions,
318
+ } from "./program.js";
319
+
290
320
  // The envelope form of a program (coeval D6, D9 and D10, roadmap item M7)
291
321
  export { isEnvelopeProgram, fromEnvelopeProgram, toEnvelopeProgram } from "./envelope.js";
292
322
  export type { EnvelopeProgram, EnvelopeRef, EnvelopeLiteral, EnvelopePath } from "./envelope.js";