@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
@@ -0,0 +1,1120 @@
1
+ /**
2
+ * Programs on the coeval kernel (coeval decisions D9, D10 and D13, roadmap item M14).
3
+ *
4
+ * A program runs as a run document of `@mark1russell7/coeval`. This module holds the parts that
5
+ * are the program format's own:
6
+ *
7
+ * - The world of a program: a call kind for each procedure path (an open world), the kinds of
8
+ * the control-flow procedures with their parts and their names, and the kinds `lit`, `ref`,
9
+ * `program.data`, `program.record`, `program.deferred` and `program.fail`.
10
+ * - The desugarer: it changes the `$proc` form into a run document. The rules of hydration
11
+ * (implicit chains, `$when`, `$literal`, the limits) decide the shape of the document.
12
+ * - The runners of the kinds, and the boundary: each call is an effect, and the host does it
13
+ * with the client (`client.call`). Thus `invokeProcedure()` stays the authority for the
14
+ * admission, the validation, the interceptors and the exposure rules of each call.
15
+ *
16
+ * The kernel does the rest: the order of the parts, the names of a scope, the references, the
17
+ * parts at the same time, the error values and the static check.
18
+ */
19
+ import { context as kernelContext, isEnvelope, ok, prepare, walk, } from "@mark1russell7/coeval";
20
+ import { LITERAL_KEY, MAX_DATA_DEPTH, WHEN_NEVER, createRefScope, getRefWhen, isAnyProcedureRef, isControlFlowPath, isLiteral, isOutputRef, isPlainObject, normalizeRef, shouldExecuteRef, withScope, } from "./ref.js";
21
+ // =============================================================================
22
+ // Names
23
+ // =============================================================================
24
+ /**
25
+ * The name of the result of the step before, in the kernel. A chain binds it for each step after
26
+ * the first step. The `$proc` form writes it as `{ $ref: "$last" }`.
27
+ */
28
+ const LAST = "last";
29
+ /**
30
+ * This function gives the key of a `$proc` name in the names of the kernel. The kernel name
31
+ * `last` is the result of the step before, so a step with the name `last` gets the key `$last`
32
+ * (and `$last` gets `$$last`). The change is one to one, so no two names get the same key.
33
+ */
34
+ export function nameKey(name) {
35
+ return /^\$*last$/.test(name) ? `$${name}` : name;
36
+ }
37
+ /** This function gives the `$proc` name of a key of the kernel names (the inverse of `nameKey()`). */
38
+ export function nameOfKey(key) {
39
+ return /^\$+last$/.test(key) ? key.slice(1) : key;
40
+ }
41
+ /**
42
+ * This function gives the names of the kernel for a scope of the `$proc` form. The outer scopes
43
+ * come first, so an inner name wins. The innermost `last` wins too, as `lookupOutputRef()` reads it.
44
+ */
45
+ export function namesOfScope(scope) {
46
+ const chain = [];
47
+ for (let current = scope; current; current = current.parent)
48
+ chain.unshift(current);
49
+ const names = {};
50
+ for (const frame of chain) {
51
+ for (const [name, value] of frame.outputs)
52
+ setOwn(names, nameKey(name), value);
53
+ if (Object.hasOwn(frame, "last"))
54
+ setOwn(names, LAST, frame.last);
55
+ }
56
+ return names;
57
+ }
58
+ /** This function gives a scope of the `$proc` form for the names of the kernel, for `withScope()`. */
59
+ export function scopeOfNames(names) {
60
+ const scope = createRefScope();
61
+ for (const key of Object.keys(names)) {
62
+ if (key === LAST)
63
+ scope.last = names[key];
64
+ else
65
+ scope.outputs.set(nameOfKey(key), names[key]);
66
+ }
67
+ return scope;
68
+ }
69
+ function setOwn(target, key, value) {
70
+ // An assignment to "__proto__" changes the prototype: that key needs an own property
71
+ if (key === "__proto__")
72
+ Object.defineProperty(target, key, { value, enumerable: true, writable: true, configurable: true });
73
+ else
74
+ target[key] = value;
75
+ }
76
+ // =============================================================================
77
+ // The world of a program
78
+ // =============================================================================
79
+ /** The first keys of the kinds of the kernel and of this module. A procedure path that starts with one goes under `procs`. */
80
+ const RESERVED_KEYS = new Set(["lit", "ref", "program", "procs"]);
81
+ /** The kind of a call: its type is the path of the procedure, and its value is the input. Each envelope in the input is a part. */
82
+ const CALL_KIND = {
83
+ verbs: { eval: "program.call" },
84
+ parts: { shape: "deep" },
85
+ side: "run",
86
+ effects: "type",
87
+ summary: "A call of a procedure. The type is the path of the procedure, and the value is the input.",
88
+ };
89
+ /**
90
+ * The kind of a control-flow call: a call with the names that the procedure binds for its operands.
91
+ * Its value is the input. An operand is one part (a document) or data. The procedure gets its input
92
+ * raw, so any value fits.
93
+ */
94
+ function controlKind(summary, scope) {
95
+ return { ...CALL_KIND, ...(scope ? { scope } : {}), summary };
96
+ }
97
+ /** The control-flow procedures of `client`, with the parts and the names of their operands. */
98
+ const CONTROL_KINDS = {
99
+ chain: controlKind("Its steps, in order. A later step reads the result of a named step, and last reads the step before.", [
100
+ { names: { headers: true }, in: [["steps", "*"]], sequential: true },
101
+ { names: { fixed: [LAST] }, in: [["steps", "*"]], sequential: true },
102
+ ]),
103
+ parallel: controlKind("Its tasks at the same time, with a limit."),
104
+ conditional: controlKind("The condition, then only the branch that it selects."),
105
+ tryCatch: controlKind("The try operand, and after an error the catch operand. The catch operand reads error.", [
106
+ { names: { fixed: ["error"] }, in: [["catch"]] },
107
+ ]),
108
+ and: controlKind("Its values in order, until a value is falsy."),
109
+ or: controlKind("Its values in order, until a value is truthy."),
110
+ map: controlKind("The call fn for each item. fn reads item (or the name in as) and index.", [
111
+ { names: { textAt: ["as"], otherwise: "item" }, in: [["fn"]] },
112
+ { names: { fixed: ["index"] }, in: [["fn"]] },
113
+ ]),
114
+ reduce: controlKind("The call fn for each item. fn reads acc, item and index, and its result is the next acc.", [{ names: { fixed: ["acc", "item", "index"] }, in: [["fn"]] }]),
115
+ };
116
+ /** The namespace `client`: the control-flow kinds, and a call for each other procedure. */
117
+ const CLIENT_NAMESPACE = { kinds: CONTROL_KINDS, fallback: CALL_KIND };
118
+ /**
119
+ * The world of a program. It is open: each path that starts with a key that the world does not
120
+ * have is a call. A path that starts with a key of the world goes under the namespace `procs`.
121
+ */
122
+ export const PROGRAM_WORLD = {
123
+ kinds: {
124
+ lit: { verbs: { eval: "program.lit" }, summary: "A literal: its value is the result. Nothing in the value starts." },
125
+ ref: { verbs: { eval: "program.ref" }, summary: "A name of the scope and its fields: [\"names\", name, ...fields]." },
126
+ program: {
127
+ kinds: {
128
+ data: { verbs: { eval: "program.data" }, parts: { shape: "deep" }, summary: "A value with documents in it: each document gives its result in its place." },
129
+ record: {
130
+ verbs: { eval: "program.data" },
131
+ parts: { shape: "map", of: "lit" },
132
+ summary: "A record with the shape of an envelope: each field is a document or data.",
133
+ },
134
+ deferred: {
135
+ verbs: { eval: "program.deferred" },
136
+ parts: { shape: "deep" },
137
+ summary: "A call that waits for its runner: it stays data, with its input evaluated.",
138
+ },
139
+ fail: { verbs: { eval: "program.fail" }, summary: "An error of the program. It fails when a run reaches it." },
140
+ call: CALL_KIND,
141
+ },
142
+ },
143
+ procs: { kinds: {}, fallback: CALL_KIND },
144
+ client: CLIENT_NAMESPACE,
145
+ },
146
+ defaults: {},
147
+ fallback: CALL_KIND,
148
+ };
149
+ /** The type of a call document for a procedure path. */
150
+ function callType(path) {
151
+ if (!Array.isArray(path) || path.length === 0 || !path.every((key) => typeof key === "string"))
152
+ return ["program", "call"];
153
+ return RESERVED_KEYS.has(path[0]) ? ["procs", ...path] : [...path];
154
+ }
155
+ const ORIGINS = new WeakMap();
156
+ /**
157
+ * This function gives the document at a place of a run document, for example the place of an issue
158
+ * of `check()` of coeval. A part that is the whole value of its document has the same place as its
159
+ * document: the function gives the deepest document at the place.
160
+ */
161
+ export function documentAt(doc, place) {
162
+ const want = place.map(String);
163
+ let found;
164
+ walk(PROGRAM_WORLD, doc, (visit) => {
165
+ const here = visit.place.map(String);
166
+ if (here.length > want.length || here.some((key, index) => key !== want[index]))
167
+ return "skip";
168
+ if (here.length === want.length)
169
+ found = visit.doc;
170
+ return undefined;
171
+ });
172
+ return found;
173
+ }
174
+ /** This function gives the origin of a document that the desugarer made, or undefined. */
175
+ export function originOf(doc) {
176
+ return typeof doc === "object" && doc !== null ? ORIGINS.get(doc) : undefined;
177
+ }
178
+ function makeDoc(type, value, origin, name) {
179
+ const doc = name === undefined ? { type, value } : { type, value, name };
180
+ if (origin)
181
+ ORIGINS.set(doc, origin);
182
+ return doc;
183
+ }
184
+ function lit(value, name) {
185
+ return makeDoc("lit", value, undefined, name);
186
+ }
187
+ function failDoc(message, pointer) {
188
+ return makeDoc(["program", "fail"], message, { source: undefined, pointer, scoped: false });
189
+ }
190
+ /** The header name of a call: a step of a chain binds it. A name that is not a text, or the empty text, binds nothing. */
191
+ function headerName(name) {
192
+ return typeof name === "string" && name !== "" ? nameKey(name) : undefined;
193
+ }
194
+ /** This function gives the pointer of a child (RFC 6901). */
195
+ function childPointer(pointer, key) {
196
+ return `${pointer}/${String(key).replace(/~/g, "~0").replace(/\//g, "~1")}`;
197
+ }
198
+ const NO_POINTER = () => "";
199
+ function settingsOf(options) {
200
+ return {
201
+ rawInput: options.rawInput ?? isControlFlowPath,
202
+ contextStack: options.contextStack ?? [],
203
+ maxDepth: options.maxDepth ?? 10,
204
+ scoped: options.scoped ?? false,
205
+ expand: options.expand ?? false,
206
+ expanding: new Set(),
207
+ pointer: options.expand ? childPointer : NO_POINTER,
208
+ };
209
+ }
210
+ function rootWalk(settings, pointer) {
211
+ return { settings, skipImplicitChain: false, deferred: false, refDepth: 0, dataDepth: 0, ancestors: undefined, consumer: undefined, pointer };
212
+ }
213
+ /** This function tells if a value holds an object with the shape of an envelope, through its plain objects and arrays. */
214
+ function holdsEnvelopeShape(value) {
215
+ if (value === null || typeof value !== "object")
216
+ return false;
217
+ const seen = new Set();
218
+ const stack = [{ item: value, depth: 0 }];
219
+ while (stack.length > 0) {
220
+ const { item, depth } = stack.pop();
221
+ if (item === null || typeof item !== "object" || seen.has(item))
222
+ continue;
223
+ if (depth > MAX_DATA_DEPTH)
224
+ return true;
225
+ seen.add(item);
226
+ if (Array.isArray(item)) {
227
+ for (const child of item)
228
+ stack.push({ item: child, depth: depth + 1 });
229
+ }
230
+ else if (isPlainObject(item)) {
231
+ if (isEnvelope(item))
232
+ return true;
233
+ for (const key of Object.keys(item))
234
+ stack.push({ item: item[key], depth: depth + 1 });
235
+ }
236
+ }
237
+ return false;
238
+ }
239
+ /** Data that stays as it is (the same object). */
240
+ function keep(value) {
241
+ return { data: value, shaped: holdsEnvelopeShape(value) };
242
+ }
243
+ /** The form of a value in a value of the shape `deep`: data with the shape of an envelope becomes a literal. */
244
+ function embed(out) {
245
+ if ("doc" in out)
246
+ return out.doc;
247
+ if ("template" in out)
248
+ return out.template;
249
+ return out.shaped ? lit(out.data) : out.data;
250
+ }
251
+ /** The form of a value as one part: a template becomes a document of the kind `program.data`. */
252
+ function embedPart(out) {
253
+ return "template" in out ? makeDoc(["program", "data"], out.template) : embed(out);
254
+ }
255
+ /** The form of a value as one part, also for data: data becomes a literal. */
256
+ function asPart(out) {
257
+ return "data" in out ? lit(out.data) : embedPart(out);
258
+ }
259
+ /** The value of a call document for an input: `null` for a call without input, else the input. */
260
+ function inputValue(input, out) {
261
+ if (input === undefined)
262
+ return null;
263
+ if ("data" in out && out.data === null)
264
+ return lit(null);
265
+ return embed(out);
266
+ }
267
+ function child(walk, parent, key) {
268
+ return { ...walk, dataDepth: walk.dataDepth + 1, ancestors: { value: parent, up: walk.ancestors }, pointer: walk.settings.pointer(walk.pointer, key) };
269
+ }
270
+ /**
271
+ * This function desugars one value of a hydration (`hydrateValue()` of the interpreter before
272
+ * October 2026, with the same rules).
273
+ */
274
+ function desugarValue(value, walk) {
275
+ if (value === null || typeof value !== "object")
276
+ return { data: value, shaped: false };
277
+ if (walk.dataDepth > MAX_DATA_DEPTH) {
278
+ return { doc: failDoc(`Hydration failed: the input has more than ${MAX_DATA_DEPTH} levels of nesting`, walk.pointer) };
279
+ }
280
+ for (let current = walk.ancestors; current; current = current.up) {
281
+ if (current.value === value)
282
+ return { doc: failDoc("Hydration failed: the input is cyclic (an object contains itself)", walk.pointer) };
283
+ }
284
+ if (isLiteral(value))
285
+ return { doc: lit(value.$literal) };
286
+ if (isOutputRef(value))
287
+ return walk.settings.scoped ? { doc: refDoc(value, walk) } : keep(value);
288
+ if (isAnyProcedureRef(value))
289
+ return desugarRef(value, walk);
290
+ if (Array.isArray(value)) {
291
+ // An array of calls that all start now is an implicit chain (PROGRAM-FORMAT section 7)
292
+ if (!walk.skipImplicitChain &&
293
+ value.length > 0 &&
294
+ value.every((item) => isAnyProcedureRef(item) && shouldExecuteRef(item, [...walk.settings.contextStack], false))) {
295
+ const chain = { $proc: ["client", "chain"], input: { steps: value } };
296
+ return desugarRef(chain, { ...walk, skipImplicitChain: true }, true);
297
+ }
298
+ return combine(value, undefined, walk);
299
+ }
300
+ // Values that are not plain data (a Date, a Map, a typed array, an instance of a class) stay the same object
301
+ if (!isPlainObject(value))
302
+ return { data: value, shaped: false };
303
+ return combine(value, Object.keys(value), walk);
304
+ }
305
+ /** The result of an item that is not an object: the item is its own value. */
306
+ const PRIMITIVE = { data: undefined, shaped: false };
307
+ /**
308
+ * This function desugars the items of an array (no keys) or the fields of an object, and makes
309
+ * their template. A subtree without a document stays the same object.
310
+ */
311
+ function combine(original, keys, walk) {
312
+ const source = original;
313
+ const length = keys ? keys.length : original.length;
314
+ const outs = new Array(length);
315
+ let documents = false;
316
+ let shaped = false;
317
+ for (let index = 0; index < length; index += 1) {
318
+ const key = keys ? keys[index] : index;
319
+ const item = source[key];
320
+ if (item === null || typeof item !== "object") {
321
+ outs[index] = PRIMITIVE;
322
+ continue;
323
+ }
324
+ const out = desugarValue(item, child(walk, original, key));
325
+ outs[index] = out;
326
+ if (!("data" in out))
327
+ documents = true;
328
+ else if (out.shaped)
329
+ shaped = true;
330
+ }
331
+ if (!documents)
332
+ return { data: original, shaped: shaped || isEnvelope(original) };
333
+ const itemAt = (index, form) => {
334
+ const out = outs[index];
335
+ return out === PRIMITIVE ? source[keys ? keys[index] : index] : form(out);
336
+ };
337
+ if (!keys)
338
+ return { template: Array.from(outs, (_, index) => itemAt(index, embed)) };
339
+ const template = {};
340
+ keys.forEach((key, index) => setOwn(template, key, itemAt(index, embed)));
341
+ if (!isEnvelope(template))
342
+ return { template };
343
+ // An object with the shape of an envelope is a record document: each field is one part
344
+ const record = {};
345
+ keys.forEach((key, index) => setOwn(record, key, itemAt(index, embedPart)));
346
+ return { doc: makeDoc(["program", "record"], record) };
347
+ }
348
+ /** This function makes the document of a `$ref` in a scope. */
349
+ function refDoc(value, walk) {
350
+ const [first = "", ...fields] = value.$ref.split(".");
351
+ const name = first === "$last" ? LAST : nameKey(first);
352
+ // A run reads the origin only in the input of a deferred call. A check reads its pointer.
353
+ const origin = walk.deferred || walk.settings.expand ? { source: value, pointer: walk.pointer, scoped: true, deferred: walk.deferred } : undefined;
354
+ return makeDoc("ref", ["names", name, ...fields], origin);
355
+ }
356
+ /** This function desugars a call of a hydration (`hydrateRef()` of the interpreter before October 2026). */
357
+ function desugarRef(value, walk, synthesized = false) {
358
+ // $never: data, with its whole subtree
359
+ if (getRefWhen(value) === WHEN_NEVER)
360
+ return keep(value);
361
+ if (walk.refDepth >= walk.settings.maxDepth) {
362
+ return { doc: failDoc(`Hydration depth exceeded maximum of ${walk.settings.maxDepth}`, walk.pointer) };
363
+ }
364
+ const ref = normalizeRef(value);
365
+ const raw = walk.settings.rawInput(ref.path);
366
+ const inputWalk = (deferred) => ({
367
+ ...walk,
368
+ skipImplicitChain: true,
369
+ deferred,
370
+ refDepth: walk.refDepth + 1,
371
+ dataDepth: walk.dataDepth + 1,
372
+ ancestors: { value, up: walk.ancestors },
373
+ consumer: value,
374
+ pointer: walk.settings.pointer(walk.pointer, "input"),
375
+ });
376
+ const origin = {
377
+ source: value,
378
+ pointer: walk.pointer,
379
+ scoped: walk.settings.scoped,
380
+ consumer: walk.consumer,
381
+ call: ref,
382
+ ...(synthesized ? { synthesized } : {}),
383
+ };
384
+ // A call that waits for its runner ($parent, or a context name that the runner did not give) is data
385
+ if (!shouldExecuteRef(value, [...walk.settings.contextStack], false)) {
386
+ if (raw)
387
+ return keep(value);
388
+ const input = desugarValue(ref.input, inputWalk(true));
389
+ if ("data" in input)
390
+ return keep(value);
391
+ return { doc: makeDoc(["program", "deferred"], { input: embed(input) }, origin) };
392
+ }
393
+ if (raw) {
394
+ // The steps of an implicit chain are the array of the program itself
395
+ const at = synthesized ? (key) => (key === "steps" ? walk.pointer : walk.settings.pointer(walk.pointer, key)) : undefined;
396
+ return { doc: makeDoc(callType(ref.path), rawValue(ref, walk.settings, origin.pointer, at), origin, headerName(ref.$name)) };
397
+ }
398
+ const input = desugarValue(ref.input, inputWalk(false));
399
+ return { doc: makeDoc(callType(ref.path), inputValue(ref.input, input), origin, headerName(ref.$name)) };
400
+ }
401
+ /**
402
+ * The value of a call of a procedure that takes its input raw. The procedure gets the input of
403
+ * the program, so the value is data. For a check (`expand`), the operands of a control-flow
404
+ * procedure become documents, with the names that the procedure binds.
405
+ */
406
+ function rawValue(ref, settings, pointer, inputPointer = (key) => settings.pointer(settings.pointer(pointer, "input"), key)) {
407
+ if (ref.input === undefined)
408
+ return null;
409
+ const input = ref.input;
410
+ if (settings.expand && typeof input === "object" && input !== null && !settings.expanding.has(input)) {
411
+ settings.expanding.add(input);
412
+ try {
413
+ const value = isControlFlowPath(ref.path)
414
+ ? controlValue(ref.path[1], input, settings, inputPointer)
415
+ : ref.path.length === 2 && ref.path[0] === "client" && ref.path[1] === "eval"
416
+ ? evalValue(input, settings, pointer)
417
+ : undefined;
418
+ if (value !== undefined)
419
+ return value;
420
+ }
421
+ finally {
422
+ settings.expanding.delete(input);
423
+ }
424
+ }
425
+ return lit(input);
426
+ }
427
+ /**
428
+ * The value of a call of `client.eval` for a check (`expand`). When its `procedure` is a path or a
429
+ * call, `client.eval` starts one operand call in the scope of the caller: its input is a new
430
+ * hydration. The function gives that input as documents, at the pointer of the input of the operand call.
431
+ */
432
+ function evalValue(input, settings, pointer) {
433
+ if (!isPlainObject(input))
434
+ return undefined;
435
+ const procedure = input["procedure"];
436
+ const evalInput = input["input"];
437
+ let operandInput;
438
+ if (Array.isArray(procedure) && procedure.every((key) => typeof key === "string")) {
439
+ operandInput = evalInput;
440
+ }
441
+ else if (isAnyProcedureRef(procedure) && !isOutputRef(procedure)) {
442
+ // The input of eval goes over the input of the ref, for objects
443
+ const refInput = normalizeRef(procedure).input;
444
+ operandInput =
445
+ evalInput === undefined ? refInput : isPlainObject(refInput) && isPlainObject(evalInput) ? { ...refInput, ...evalInput } : evalInput;
446
+ }
447
+ else {
448
+ return undefined;
449
+ }
450
+ const value = {};
451
+ for (const key of Object.keys(input))
452
+ setOwn(value, key, embed(keep(input[key])));
453
+ // The input of the operand call is at the pointer of the input of eval
454
+ const at = childPointer(childPointer(pointer, "input"), "input");
455
+ setOwn(value, "input", embedPart(desugarValue(operandInput, rootWalk(settings, at))));
456
+ return value;
457
+ }
458
+ /**
459
+ * This function desugars an operand of a control-flow procedure (`evaluateOperand()`): a call is
460
+ * an operand call (only `$never` holds it back, and its input is a new hydration), and another
461
+ * value is a new hydration.
462
+ */
463
+ function desugarOperand(value, settings, pointer) {
464
+ if (!isAnyProcedureRef(value))
465
+ return desugarValue(value, rootWalk(settings, pointer));
466
+ const ref = normalizeRef(value);
467
+ const name = headerName(ref.$name);
468
+ if (getRefWhen(value) === WHEN_NEVER)
469
+ return { doc: lit(value, name) };
470
+ return { doc: operandCall(value, settings, pointer, name) };
471
+ }
472
+ /** This function makes the document of a call whose input is a new hydration: an operand call, or the root call of a program. */
473
+ function operandCall(value, settings, pointer, name) {
474
+ const ref = normalizeRef(value);
475
+ const origin = { source: value, pointer, scoped: settings.scoped, call: ref };
476
+ if (settings.rawInput(ref.path))
477
+ return makeDoc(callType(ref.path), rawValue(ref, settings, pointer), origin, name);
478
+ const inputPointer = settings.pointer(pointer, "input");
479
+ return makeDoc(callType(ref.path), inputValue(ref.input, desugarValue(ref.input, rootWalk(settings, inputPointer))), origin, name);
480
+ }
481
+ /** The items of `client.map` and `client.reduce`: an array gives each item as its own hydration. */
482
+ function itemsOperand(items, settings, pointer) {
483
+ if (!Array.isArray(items))
484
+ return embedPart(desugarValue(items, rootWalk(settings, pointer)));
485
+ return makeDoc(["program", "data"], items.map((item, index) => embed(desugarValue(item, rootWalk(settings, settings.pointer(pointer, index))))));
486
+ }
487
+ /**
488
+ * This function desugars the input of a control-flow procedure: its operands become documents,
489
+ * and the other fields stay data. It gives undefined when the input does not have the shape of
490
+ * the procedure. The operands of a procedure without a scope have no scope (`scopeOf()`), except
491
+ * the operands that the procedure gives names: the steps of a chain, `catch` and `fn`.
492
+ */
493
+ export function desugarControlValue(name, input, options = {}) {
494
+ const settings = settingsOf(options);
495
+ return controlValue(name, input, settings, (key) => settings.pointer("", key));
496
+ }
497
+ /**
498
+ * The input of a control-flow procedure with its operands as documents. `at` gives the pointer of
499
+ * a field of the input: for an implicit chain, the steps are the array of the program itself.
500
+ */
501
+ function controlValue(name, input, settings, at) {
502
+ if (!isPlainObject(input))
503
+ return undefined;
504
+ const outer = settings;
505
+ const inner = { ...settings, scoped: true };
506
+ const operands = new Map();
507
+ // An operand that a procedure starts as an operand: a call is an operand call (`evaluateOperand()`)
508
+ const put = (key, scope) => {
509
+ if (Object.hasOwn(input, key))
510
+ operands.set(key, embedPart(desugarOperand(input[key], scope, at(key))));
511
+ };
512
+ // An operand that a procedure hydrates as a value (`hydrateOperand()`): a call in it is a call of a hydration
513
+ const hydrated = (key, scope) => {
514
+ if (Object.hasOwn(input, key))
515
+ operands.set(key, embedPart(desugarValue(input[key], rootWalk(scope, at(key)))));
516
+ };
517
+ const list = (key, scope) => {
518
+ const items = input[key];
519
+ if (!Array.isArray(items))
520
+ return false;
521
+ // Each item of a list is one part, so the names of a sequence count each step
522
+ operands.set(key, items.map((item, index) => asPart(desugarOperand(item, scope, scope.pointer(at(key), index)))));
523
+ return true;
524
+ };
525
+ switch (name) {
526
+ case "chain":
527
+ if (!list("steps", inner))
528
+ return undefined;
529
+ hydrated("initialInput", inner);
530
+ break;
531
+ case "parallel":
532
+ if (!list("tasks", outer))
533
+ return undefined;
534
+ break;
535
+ case "conditional":
536
+ for (const key of ["condition", "then", "else"])
537
+ put(key, outer);
538
+ break;
539
+ case "tryCatch":
540
+ put("try", outer);
541
+ put("catch", inner);
542
+ break;
543
+ case "and":
544
+ case "or":
545
+ if (!list("values", outer))
546
+ return undefined;
547
+ break;
548
+ case "map":
549
+ case "reduce":
550
+ if (Object.hasOwn(input, "items"))
551
+ operands.set("items", itemsOperand(input["items"], outer, at("items")));
552
+ if (name === "reduce") {
553
+ hydrated("initial", outer);
554
+ hydrated("accumulated", outer);
555
+ }
556
+ else if (typeof input["as"] === "string") {
557
+ operands.set("as", nameKey(input["as"]));
558
+ }
559
+ put("fn", inner);
560
+ break;
561
+ default:
562
+ return undefined;
563
+ }
564
+ // The other fields are data: a field with the shape of an envelope becomes a literal
565
+ const value = {};
566
+ for (const key of Object.keys(input))
567
+ setOwn(value, key, operands.has(key) ? operands.get(key) : embed(keep(input[key])));
568
+ return value;
569
+ }
570
+ /** This function desugars a value of a hydration: the root of `hydrateInput()`. Data gives undefined. */
571
+ export function desugarInput(input, options = {}) {
572
+ const out = desugarValue(input, rootWalk(settingsOf(options), ""));
573
+ if ("data" in out)
574
+ return undefined;
575
+ return "doc" in out ? out.doc : makeDoc(["program", "data"], out.template);
576
+ }
577
+ /**
578
+ * This function desugars a program: the root call of `client.exec()` (`prepareRootCall()`). A
579
+ * `$when` on the root call does not apply: the caller starts the root call now. Its input is a new
580
+ * hydration.
581
+ */
582
+ export function desugarProgram(program, options = {}) {
583
+ return operandCall(program, settingsOf(options), "", headerName(normalizeRef(program).$name));
584
+ }
585
+ const ROOT_FRAME = { position: "root", extra: {} };
586
+ const NO_EXTRA = {};
587
+ function frameOf(self) {
588
+ return typeof self === "object" && self !== null && "position" in self ? self : undefined;
589
+ }
590
+ /** The context of the parts of a document: the parts get no frame. */
591
+ function partContext(context) {
592
+ return context.self === null ? context : { ...context, self: null };
593
+ }
594
+ /** The parent context that the control-flow procedures give to their operand calls. */
595
+ export function parentContext(input) {
596
+ return {
597
+ ...(input.cwd ? { cwd: input.cwd } : {}),
598
+ ...(input.node ? { node: input.node } : {}),
599
+ };
600
+ }
601
+ /** The value that a failed result throws at the boundary: the error of the host, or an error with the reason. */
602
+ export function thrownValue(error) {
603
+ return error.cause !== undefined ? error.cause : new Error(error.reason);
604
+ }
605
+ /** The message of a failed result, as the control-flow procedures read an error. */
606
+ function messageOf(error) {
607
+ const value = thrownValue(error);
608
+ return value instanceof Error ? value.message : String(value);
609
+ }
610
+ /** The values of the parts, one after the other or at the same time. It stops at the first failure. */
611
+ function* evaluateParts(parts, context, all, parallel) {
612
+ const values = [];
613
+ if (!parallel || parts.length < 2) {
614
+ for (const part of parts) {
615
+ const result = yield* part.evaluate(context);
616
+ if (!result.ok)
617
+ return { failure: result };
618
+ values.push(result.value);
619
+ }
620
+ return { values };
621
+ }
622
+ let first;
623
+ const results = yield* all(parts.map((part) => ({
624
+ key: part.key,
625
+ path: part.path,
626
+ *evaluate() {
627
+ const result = yield* part.evaluate(context);
628
+ if (!result.ok)
629
+ first ??= result;
630
+ return result;
631
+ },
632
+ })), { failFast: true });
633
+ if (first)
634
+ return { failure: first };
635
+ for (const result of results)
636
+ values.push(result.value);
637
+ return { values };
638
+ }
639
+ /** The truth of a condition of `client.conditional`: the `value` field of an object decides. */
640
+ function conditionTruth(condition) {
641
+ if (condition && typeof condition === "object" && "value" in condition)
642
+ return Boolean(condition.value);
643
+ return Boolean(condition);
644
+ }
645
+ /** The source of a document: the input of a control-flow procedure (the raw input that its handler got). */
646
+ function sourceOf(input) {
647
+ const source = originOf(input.doc)?.source;
648
+ return isPlainObject(source) ? source : {};
649
+ }
650
+ function operandsOf(input) {
651
+ const parts = new Set(input.parts);
652
+ const value = isPlainObject(input.value) ? input.value : {};
653
+ return {
654
+ at: (key) => (Object.hasOwn(value, key) ? value[key] : undefined),
655
+ list: (key) => (Array.isArray(value[key]) ? value[key] : []),
656
+ *run(operand, context) {
657
+ if (parts.has(operand))
658
+ return yield* operand.evaluate(context);
659
+ return ok(operand);
660
+ },
661
+ };
662
+ }
663
+ /** The context of an operand: an operand call gets a call frame, another operand no frame. */
664
+ function operandContext(context, names, source, extra, passed) {
665
+ const self = isAnyProcedureRef(source) ? { position: "operand", extra, ...(passed ? { passed } : {}) } : null;
666
+ return { ...context, names, self };
667
+ }
668
+ /** The context of `fn` of `client.map` and `client.reduce`: the names of the item, and an operand call. */
669
+ function fnContext(context, names) {
670
+ return { ...context, names, self: { position: "operand", extra: NO_EXTRA } };
671
+ }
672
+ function items(value) {
673
+ if (!Array.isArray(value))
674
+ throw new Error(`items must be an array, got ${value === null ? "null" : typeof value}`);
675
+ return value;
676
+ }
677
+ /**
678
+ * This function makes the machine of a run of programs: the world of a program, its evaluators
679
+ * and its runners. The kernel capabilities permit each call: the authority is `invokeProcedure()`,
680
+ * through the host of each effect.
681
+ */
682
+ export function programMachine(host) {
683
+ const parallel = host.parallel === true;
684
+ function* call(input) {
685
+ const { doc, context, parts } = input;
686
+ const origin = originOf(doc);
687
+ const source = origin?.source ?? { $proc: input.typePath[0] === "procs" ? input.typePath.slice(1) : input.typePath, input: doc.value ?? undefined };
688
+ const ref = origin?.call ?? normalizeRef(source);
689
+ const frame = origin?.synthesized ? undefined : frameOf(context.self);
690
+ const position = frame?.position ?? "nested";
691
+ const raw = host.rawInput(ref.path);
692
+ let value;
693
+ if (frame?.passed) {
694
+ // With passThrough, a step without input gets the result of the step before as a literal: it started already
695
+ value = raw ? { [LITERAL_KEY]: frame.passed.value } : frame.passed.value;
696
+ }
697
+ else if (raw) {
698
+ value = ref.input;
699
+ }
700
+ else if (ref.input !== undefined) {
701
+ const done = yield* evaluateParts(parts, partContext(context), input.all, parallel);
702
+ if ("failure" in done)
703
+ return done.failure;
704
+ value = input.fill(done.values);
705
+ }
706
+ const scope = raw && (origin?.scoped ?? true) ? scopeOfNames(context.names) : undefined;
707
+ if (frame?.position === "operand" && Object.keys(frame.extra).length > 0) {
708
+ value = { ...(typeof value === "object" && value !== null && !Array.isArray(value) ? value : {}), ...frame.extra };
709
+ }
710
+ if (raw)
711
+ value = withScope(value, scope);
712
+ const effect = { position, input: value, source, consumer: origin?.consumer };
713
+ return yield* input.effect(ref.path, effect);
714
+ }
715
+ function* data(input) {
716
+ const done = yield* evaluateParts(input.parts, partContext(input.context), input.all, parallel);
717
+ if ("failure" in done)
718
+ return done.failure;
719
+ return ok(input.fill(done.values));
720
+ }
721
+ function* deferred(input) {
722
+ const origin = originOf(input.doc);
723
+ const source = origin?.source;
724
+ const done = yield* evaluateParts(input.parts, partContext(input.context), input.all, parallel);
725
+ if ("failure" in done)
726
+ return done.failure;
727
+ const hydrated = input.fill(done.values).input;
728
+ return ok(hydrated === (origin?.call ?? normalizeRef(source)).input ? source : { ...source, input: hydrated });
729
+ }
730
+ function* chain(input) {
731
+ const source = sourceOf(input);
732
+ const operands = operandsOf(input);
733
+ const steps = operands.list("steps");
734
+ const sourceSteps = Array.isArray(source["steps"]) ? source["steps"] : [];
735
+ const extra = parentContext(source);
736
+ const passThrough = Boolean(source["passThrough"]);
737
+ let names = { ...input.context.names };
738
+ let passed;
739
+ if (passThrough) {
740
+ const result = yield* operands.run(operands.at("initialInput"), { ...input.context, names, self: null });
741
+ if (!result.ok)
742
+ return result;
743
+ passed = result.value;
744
+ }
745
+ const results = [];
746
+ for (const [index, step] of steps.entries()) {
747
+ const stepSource = sourceSteps[index];
748
+ const call = isAnyProcedureRef(stepSource) ? normalizeRef(stepSource) : undefined;
749
+ const result = yield* operands.run(step, operandContext(input.context, names, stepSource, extra, passThrough && call?.input === undefined ? { value: passed } : undefined));
750
+ if (!result.ok)
751
+ return result;
752
+ names = { ...names };
753
+ // A step with a $name gives its result to the later steps. Each step gives it as last.
754
+ const name = headerName(call?.$name);
755
+ if (name !== undefined)
756
+ setOwn(names, name, result.value);
757
+ setOwn(names, LAST, result.value);
758
+ passed = result.value;
759
+ results.push(result.value);
760
+ }
761
+ return ok({ results, final: results[results.length - 1] });
762
+ }
763
+ function* parallelRunner(input) {
764
+ const source = sourceOf(input);
765
+ const operands = operandsOf(input);
766
+ const tasks = operands.list("tasks");
767
+ const sourceTasks = Array.isArray(source["tasks"]) ? source["tasks"] : [];
768
+ const extra = parentContext(source);
769
+ const concurrency = source["concurrency"];
770
+ const failFast = Boolean(source["failFast"]);
771
+ const limit = typeof concurrency === "number" && concurrency > 0 ? Math.min(concurrency, tasks.length) : tasks.length;
772
+ let first;
773
+ const results = yield* input.all(tasks.map((task, index) => ({
774
+ key: String(index),
775
+ path: ["tasks", index],
776
+ *evaluate() {
777
+ const result = yield* operands.run(task, operandContext(input.context, input.context.names, sourceTasks[index], extra));
778
+ if (!result.ok)
779
+ first ??= result;
780
+ return result;
781
+ },
782
+ })), { concurrency: Math.max(1, limit), failFast });
783
+ if (failFast && first)
784
+ return first;
785
+ const errors = results.flatMap((result, index) => (result.ok ? [] : [{ index, error: messageOf(result.error) }]));
786
+ return ok({ results: results.map((result) => (result.ok ? result.value : undefined)), allSucceeded: errors.length === 0, errors });
787
+ }
788
+ function* conditional(input) {
789
+ const source = sourceOf(input);
790
+ const operands = operandsOf(input);
791
+ const extra = parentContext(source);
792
+ const { names } = input.context;
793
+ const condition = yield* operands.run(operands.at("condition"), operandContext(input.context, names, source["condition"], extra));
794
+ if (!condition.ok)
795
+ return condition;
796
+ const branch = conditionTruth(condition.value) ? "then" : "else";
797
+ // Only the selected branch runs
798
+ if (source[branch] === undefined)
799
+ return ok(undefined);
800
+ return yield* operands.run(operands.at(branch), operandContext(input.context, names, source[branch], extra));
801
+ }
802
+ function* tryCatch(input) {
803
+ const source = sourceOf(input);
804
+ const operands = operandsOf(input);
805
+ const { names } = input.context;
806
+ const tried = yield* operands.run(operands.at("try"), operandContext(input.context, names, source["try"], NO_EXTRA));
807
+ if (tried.ok)
808
+ return ok({ success: true, value: tried.value });
809
+ const message = messageOf(tried.error);
810
+ const caught = yield* operands.run(operands.at("catch"), operandContext(input.context, { ...names, error: message }, source["catch"], NO_EXTRA));
811
+ if (!caught.ok)
812
+ return caught;
813
+ return ok({ success: false, value: caught.value, error: message });
814
+ }
815
+ const shortCircuit = (stopOn) => function* (input) {
816
+ const source = sourceOf(input);
817
+ const operands = operandsOf(input);
818
+ const sourceValues = Array.isArray(source["values"]) ? source["values"] : [];
819
+ const extra = parentContext(source);
820
+ let last;
821
+ for (const [index, operand] of operands.list("values").entries()) {
822
+ const result = yield* operands.run(operand, operandContext(input.context, input.context.names, sourceValues[index], extra));
823
+ if (!result.ok)
824
+ return result;
825
+ last = result.value;
826
+ if (stopOn(last))
827
+ return ok(last);
828
+ }
829
+ return ok(last);
830
+ };
831
+ /** The items of `client.map` and `client.reduce`: an array gives a new array, as the interpreter did. */
832
+ function* itemsOf(input, operands) {
833
+ const result = yield* operands.run(operands.at("items"), partContext(input.context));
834
+ if (!result.ok)
835
+ return result;
836
+ return ok(Array.isArray(sourceOf(input)["items"]) && Array.isArray(result.value) ? [...result.value] : items(result.value));
837
+ }
838
+ function* map(input) {
839
+ const source = sourceOf(input);
840
+ const operands = operandsOf(input);
841
+ const listed = yield* itemsOf(input, operands);
842
+ if (!listed.ok)
843
+ return listed;
844
+ if (!isAnyProcedureRef(source["fn"]))
845
+ return ok({ results: listed.value });
846
+ const as = isPlainObject(input.data) && typeof input.data["as"] === "string" ? input.data["as"] : "item";
847
+ const results = [];
848
+ for (const [index, item] of listed.value.entries()) {
849
+ const names = { ...input.context.names };
850
+ setOwn(names, as, item);
851
+ setOwn(names, "index", index);
852
+ const result = yield* operands.run(operands.at("fn"), fnContext(input.context, names));
853
+ if (!result.ok)
854
+ return result;
855
+ results.push(result.value);
856
+ }
857
+ return ok({ results });
858
+ }
859
+ function* reduce(input) {
860
+ const source = sourceOf(input);
861
+ const operands = operandsOf(input);
862
+ if (!isAnyProcedureRef(source["fn"])) {
863
+ // Without fn, the result is the accumulated value, hydrated like any operand
864
+ const key = source["accumulated"] === undefined || source["accumulated"] === null ? "initial" : "accumulated";
865
+ return yield* operands.run(operands.at(key), partContext(input.context));
866
+ }
867
+ const listed = yield* itemsOf(input, operands);
868
+ if (!listed.ok)
869
+ return listed;
870
+ const initial = yield* operands.run(operands.at("initial"), partContext(input.context));
871
+ if (!initial.ok)
872
+ return initial;
873
+ let acc = initial.value;
874
+ for (const [index, item] of listed.value.entries()) {
875
+ const result = yield* operands.run(operands.at("fn"), fnContext(input.context, { ...input.context.names, acc, item, index }));
876
+ if (!result.ok)
877
+ return result;
878
+ acc = result.value;
879
+ }
880
+ return ok(acc);
881
+ }
882
+ return {
883
+ world: PROGRAM_WORLD,
884
+ plain: true,
885
+ roots: ["names"],
886
+ permits: () => true,
887
+ evaluators: {
888
+ "program.lit": ({ doc }) => ok(doc.value),
889
+ "program.fail": ({ doc }) => {
890
+ throw new Error(String(doc.value));
891
+ },
892
+ "program.ref": ({ doc, resolve }) => {
893
+ const found = resolve(doc.value, { hops: false });
894
+ if (found.ok)
895
+ return ok(found.thing);
896
+ // A missing field is undefined (PROGRAM-FORMAT section 4)
897
+ if (found.code === "#N/A")
898
+ return ok(undefined);
899
+ const origin = originOf(doc);
900
+ // In the input of a deferred call, the runner can bind the name later
901
+ if (origin?.deferred)
902
+ return ok(origin.source);
903
+ throw unknownName(origin?.source, doc.value);
904
+ },
905
+ },
906
+ runners: {
907
+ "program.call": call,
908
+ "program.data": data,
909
+ "program.deferred": deferred,
910
+ "program.chain": chain,
911
+ "program.parallel": parallelRunner,
912
+ "program.conditional": conditional,
913
+ "program.tryCatch": tryCatch,
914
+ "program.and": shortCircuit((value) => !value),
915
+ "program.or": shortCircuit((value) => Boolean(value)),
916
+ "program.map": map,
917
+ "program.reduce": reduce,
918
+ },
919
+ };
920
+ }
921
+ /** The error of a `$ref` name that no scope has. */
922
+ function unknownName(source, path) {
923
+ const text = isOutputRef(source)
924
+ ? source.$ref
925
+ : path
926
+ .slice(1)
927
+ .map((key, index) => (index === 0 ? (key === LAST ? "$last" : nameOfKey(String(key))) : String(key)))
928
+ .join(".");
929
+ const name = text.split(".")[0];
930
+ return new Error(`Unknown $ref name "${name}" in "${text}": no step, item or input has this name here. ` +
931
+ `For a literal $ref object, write { "$literal": { "$ref": ... } }.`);
932
+ }
933
+ // =============================================================================
934
+ // Runs
935
+ // =============================================================================
936
+ /** The observers of `observeHydration()`: they get the events of every hydration. */
937
+ const hydrationObservers = new Set();
938
+ /**
939
+ * This function adds an observer of every hydration in this process (or worker), and gives the
940
+ * function that removes it. A host that does not call `hydrateInput` itself (the Composer of
941
+ * the site runs `client.exec()`) uses it for its trace. The observer gets the events of all
942
+ * concurrent hydrations: use it where one program runs at a time. For the events of one program,
943
+ * give `onRef` to that program: `client.exec(ref, input, { onRef })` (docs review DOC-10).
944
+ */
945
+ export function observeHydration(observer) {
946
+ hydrationObservers.add(observer);
947
+ return () => {
948
+ hydrationObservers.delete(observer);
949
+ };
950
+ }
951
+ /** This function tells the observers that a call starts now. An observer that throws does not change the run. */
952
+ function notify(onRef, event) {
953
+ if (!onRef && hydrationObservers.size === 0)
954
+ return;
955
+ for (const observer of onRef ? [onRef, ...hydrationObservers] : hydrationObservers) {
956
+ try {
957
+ observer(event);
958
+ }
959
+ catch {
960
+ // A trace must not change the program
961
+ }
962
+ }
963
+ }
964
+ /** The machines of the runs, one for each raw-input rule and each value of `parallel`. */
965
+ const MACHINES = [new WeakMap(), new WeakMap()];
966
+ function machineFor(rawInput, parallel) {
967
+ const cache = MACHINES[parallel ? 1 : 0];
968
+ let machine = cache.get(rawInput);
969
+ if (!machine) {
970
+ machine = programMachine({ rawInput, parallel });
971
+ cache.set(rawInput, machine);
972
+ }
973
+ return machine;
974
+ }
975
+ /**
976
+ * This function runs a document on the kernel (`prepare(...).run(...)`). Each call is an effect:
977
+ * the host does it with the executor. A failed run throws the error of the host (the cause of the
978
+ * error value), so a caller gets the same error as from the call itself.
979
+ */
980
+ export async function runDocument(doc, options) {
981
+ const machine = machineFor(options.rawInput ?? isControlFlowPath, options.parallel === true);
982
+ const perform = (effect) => {
983
+ const payload = effect.payload;
984
+ const path = effect.path;
985
+ if (payload.position === "nested")
986
+ notify(options.onRef, { path, ref: payload.source, consumer: payload.consumer });
987
+ if (payload.position === "root" && options.rootExecutor)
988
+ return options.rootExecutor(path, payload.input);
989
+ return options.executor(path, payload.input);
990
+ };
991
+ const ctx = kernelContext({ names: namesOfScope(options.scope), self: options.self ?? null });
992
+ const result = await prepare(machine, doc).run(ctx, { perform });
993
+ if (!result.ok)
994
+ throw thrownValue(result.error);
995
+ return result.value;
996
+ }
997
+ /**
998
+ * Hydrate an input tree by executing any nested procedure references.
999
+ *
1000
+ * The input becomes a run document (`desugarInput()`), and the kernel runs it. Each call of the
1001
+ * input is an effect: the executor does it, and its result takes the place of the call.
1002
+ *
1003
+ * The rules:
1004
+ * - A ref with `$when: "$immediate"` (the default) runs. A ref whose procedure takes its input
1005
+ * raw (see `HydrateOptions.rawInput`) gets its input unchanged, with the scope attached.
1006
+ * - A ref with `$when: "$never"` stays as it is, with its whole subtree.
1007
+ * - A ref with `$when: "$parent"` or `$when: "<name>"` stays data for its runner. Its procedure
1008
+ * gets that ref later. The immediate refs of its input run now, unless the procedure takes its
1009
+ * input raw. A named ref runs now only when `contextStack` has the name.
1010
+ * - A `$ref` object gives a value from the scope. `{ $literal: value }` gives the value unchanged.
1011
+ * - An array whose items are all refs that run now, outside the input of a ref, runs as one
1012
+ * `client.chain`. An array with a deferred ref is a list.
1013
+ * - Values that are not plain data (`Date`, `Map`, typed arrays, class instances) stay unchanged.
1014
+ * A subtree without refs stays the same object.
1015
+ *
1016
+ * @param input - The input object potentially containing procedure references
1017
+ * @param executor - Function to execute procedure references
1018
+ * @param options - Hydration options
1019
+ * @returns The hydrated input with executed procedure refs replaced by their results
1020
+ */
1021
+ export async function hydrateInput(input, executor, options = {}) {
1022
+ const doc = desugarInput(input, {
1023
+ rawInput: options.rawInput,
1024
+ contextStack: options.contextStack,
1025
+ maxDepth: options.maxDepth,
1026
+ scoped: options.scope !== undefined,
1027
+ });
1028
+ if (!doc)
1029
+ return input;
1030
+ return (await runDocument(doc, {
1031
+ executor,
1032
+ rawInput: options.rawInput,
1033
+ onRef: options.onRef,
1034
+ scope: options.scope,
1035
+ parallel: options.parallel,
1036
+ }));
1037
+ }
1038
+ /**
1039
+ * Execute a deferred procedure reference.
1040
+ *
1041
+ * This is a helper for procedures that receive deferred refs (via $when) and need to execute
1042
+ * them with additional context. The fields of `additionalInput` go over the input of the ref. A
1043
+ * procedure that takes its input raw gets it with the scope. Another procedure gets it hydrated
1044
+ * (`hydrateInput()`), with the options (for example the runner's `contextStack`).
1045
+ *
1046
+ * @param ref - The procedure reference to execute
1047
+ * @param executor - Function to execute the procedure
1048
+ * @param additionalInput - Additional input to merge (e.g., cwd for dag.traverse)
1049
+ * @param options - Hydration options for the input of the ref
1050
+ * @returns The result of executing the procedure
1051
+ */
1052
+ export async function executeRef(ref, executor, additionalInput, options = {}) {
1053
+ const normalized = normalizeRef(ref);
1054
+ const baseInput = typeof normalized.input === "object" ? normalized.input : {};
1055
+ const mergedInput = { ...baseInput, ...additionalInput };
1056
+ if ((options.rawInput ?? isControlFlowPath)(normalized.path)) {
1057
+ return executor(normalized.path, withScope(mergedInput, options.scope));
1058
+ }
1059
+ return executor(normalized.path, await hydrateInput(mergedInput, executor, options));
1060
+ }
1061
+ /**
1062
+ * This function runs a program: its root call is the root document of one kernel run. A `$when`
1063
+ * on the root call does not apply. A procedure that takes its input raw gets its input with the
1064
+ * scope. Another procedure gets its input hydrated as a root: a list of calls is an implicit chain.
1065
+ * `client.exec()` and the body of a procedure that `procedure.define` made use it (docs review DOC-3).
1066
+ */
1067
+ export function runProgram(program, options) {
1068
+ const doc = desugarProgram(program, { rawInput: options.rawInput, scoped: options.scope !== undefined });
1069
+ return runDocument(doc, { ...options, self: ROOT_FRAME });
1070
+ }
1071
+ /**
1072
+ * The path and the input of the root call of a program: the call that `client.exec()` starts.
1073
+ * The kernel runs the program (`runProgram()`) up to its root call, and the function gives the
1074
+ * path and the input of that call. The caller then starts the call.
1075
+ */
1076
+ export async function prepareRootCall(ref, executor, options = {}) {
1077
+ let root;
1078
+ await runProgram(ref, {
1079
+ executor,
1080
+ rootExecutor: (path, input) => {
1081
+ root = { path, input };
1082
+ return undefined;
1083
+ },
1084
+ rawInput: options.rawInput,
1085
+ onRef: options.onRef,
1086
+ scope: options.scope,
1087
+ });
1088
+ return root;
1089
+ }
1090
+ /**
1091
+ * This function runs an operand of a control-flow procedure (`callOperand()`, `evaluateOperand()`).
1092
+ * A call is an operand call: only `$never` holds it back, its input is a new hydration, and it gets
1093
+ * the parent context. Another value is a new hydration.
1094
+ */
1095
+ export async function runOperand(value, options) {
1096
+ const out = desugarOperand(value, settingsOf({ rawInput: options.rawInput, scoped: options.scope !== undefined }), "");
1097
+ if ("data" in out)
1098
+ return out.data;
1099
+ const frame = { position: "operand", extra: options.extra ?? NO_EXTRA };
1100
+ const doc = "doc" in out ? out.doc : makeDoc(["program", "data"], out.template);
1101
+ return runDocument(doc, { ...options, self: isAnyProcedureRef(value) ? frame : null });
1102
+ }
1103
+ /**
1104
+ * This function runs a control-flow procedure on the kernel: its input becomes a document of its
1105
+ * kind, and the runner of the kind runs the operands. The input is raw: its scope (`scopeOf()`)
1106
+ * gives the names of the caller.
1107
+ */
1108
+ export async function runControl(name, input, options) {
1109
+ const value = desugarControlValue(name, input, { rawInput: options.rawInput, scoped: options.scope !== undefined });
1110
+ if (value === undefined)
1111
+ throw new TypeError(`client.${name}: the input does not have the shape of the procedure`);
1112
+ return runDocument(controlDocument(name, value, input, options.scope !== undefined), options);
1113
+ }
1114
+ /** The document of the run of a control-flow procedure: its own entry names the runner of the procedure. */
1115
+ function controlDocument(name, value, source, scoped) {
1116
+ const doc = { type: ["client", name], value, own: { eval: `program.${name}` } };
1117
+ ORIGINS.set(doc, { source, pointer: "", scoped });
1118
+ return doc;
1119
+ }
1120
+ //# sourceMappingURL=program.js.map