@evoke-build/evoke 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core.wasm +0 -0
- package/dist/project.d.ts +4 -2
- package/dist/project.js +38 -5
- package/dist/types.d.ts +10 -0
- package/package.json +1 -1
- package/runtime/loader.mjs +16 -12
package/core.wasm
CHANGED
|
Binary file
|
package/dist/project.d.ts
CHANGED
|
@@ -20,7 +20,8 @@ export interface DecideOptions {
|
|
|
20
20
|
tags?: string[] | undefined;
|
|
21
21
|
/** This reflex alone is offered: how a weave decides a fragment, or a step with a value written into its words. */
|
|
22
22
|
only?: string | undefined;
|
|
23
|
-
/** Aborts the adapter call; `decide` rejects with the signal's reason.
|
|
23
|
+
/** Aborts the adapter call; `decide` rejects with the signal's reason. A weave's run it cancels: every body
|
|
24
|
+
* ended, every step that did not finish `skipped · cancelled`, the rejection's reason carrying the record. */
|
|
24
25
|
signal?: AbortSignal | undefined;
|
|
25
26
|
}
|
|
26
27
|
export interface RunOptions {
|
|
@@ -73,7 +74,8 @@ export interface WovenStep<R = AnyReflexes> {
|
|
|
73
74
|
}
|
|
74
75
|
/** A request planned and run: the plan; the whole's status — the worst step's, as the exit codes rank them, or
|
|
75
76
|
* why nothing ran: refused by the verdict, declined or unanswered at what the plan asked first; per step what
|
|
76
|
-
* became of it, none when nothing ran.
|
|
77
|
+
* became of it, none when nothing ran. A weave the signal cancelled rejects with the signal's reason and carries
|
|
78
|
+
* this record on it as `woven`: the steps that finished as they ended, every other one `skipped · cancelled`. */
|
|
77
79
|
export interface Woven<R = AnyReflexes> {
|
|
78
80
|
plan: W.Weave;
|
|
79
81
|
status: W.Status;
|
package/dist/project.js
CHANGED
|
@@ -380,9 +380,13 @@ function make(ground, invoked) {
|
|
|
380
380
|
return "filled";
|
|
381
381
|
}
|
|
382
382
|
/** The plan run: stage by stage, a stage's rounds each taken to the point of running in the words' order, then
|
|
383
|
-
* their bodies together; a step decided again with its bound values in its words when the run asks for it.
|
|
383
|
+
* their bodies together; a step decided again with its bound values in its words when the run asks for it. The
|
|
384
|
+
* signal aborted mid-run is a cancel: every body's group ended, the rounds under way and every round after
|
|
385
|
+
* reported `skipped · cancelled`, and the rejection — the signal's reason — carrying the record once it is
|
|
386
|
+
* whole. */
|
|
384
387
|
async function executed(woven, options, traces) {
|
|
385
388
|
const invoked = `weave(${JSON.stringify(shown(woven.input))})`;
|
|
389
|
+
const { signal } = options;
|
|
386
390
|
const progress = { decided: [], handled: [] };
|
|
387
391
|
const rounds = new Map();
|
|
388
392
|
const record = (handling, decision, became) => {
|
|
@@ -397,7 +401,7 @@ function make(ground, invoked) {
|
|
|
397
401
|
const running = call("weave.execute", { plan, ...gate, weave: woven, progress }, invoked);
|
|
398
402
|
if (running.type === "done") {
|
|
399
403
|
const { executed } = running;
|
|
400
|
-
|
|
404
|
+
const whole = {
|
|
401
405
|
plan: woven,
|
|
402
406
|
status: executed.worst,
|
|
403
407
|
steps: executed.steps.map(step => ({
|
|
@@ -408,10 +412,21 @@ function make(ground, invoked) {
|
|
|
408
412
|
rounds: rounds.get(step.step) ?? [],
|
|
409
413
|
})),
|
|
410
414
|
};
|
|
415
|
+
if (signal?.aborted)
|
|
416
|
+
throw carrying(signal.reason, whole);
|
|
417
|
+
return whole;
|
|
411
418
|
}
|
|
412
419
|
const { todo } = running;
|
|
413
420
|
if (todo.type === "decide") {
|
|
414
|
-
|
|
421
|
+
try {
|
|
422
|
+
progress.decided.push([todo.asked, await decided(todo.asked, options, traces)]);
|
|
423
|
+
}
|
|
424
|
+
catch (error) {
|
|
425
|
+
// The signal aborted the decision's call: the round it was for never starts.
|
|
426
|
+
if (!signal?.aborted || error !== signal.reason)
|
|
427
|
+
throw error;
|
|
428
|
+
progress.handled.push({ step: todo.step, round: todo.round, ...CANCELLED });
|
|
429
|
+
}
|
|
415
430
|
continue;
|
|
416
431
|
}
|
|
417
432
|
const bodies = [];
|
|
@@ -421,12 +436,17 @@ function make(ground, invoked) {
|
|
|
421
436
|
const own = step?.reflex === undefined || !handling.bound?.length ? undefined : traces.get(key({ text: handling.input, only: step.reflex }));
|
|
422
437
|
const trace = own ?? (step === undefined ? undefined : traces.get(key(askedFor(step, options.tags ?? [])))) ?? [];
|
|
423
438
|
const decision = lined(handling.decision, { input: handling.input, plan: plan.digest, trace });
|
|
439
|
+
// Nothing starts after the signal: a round handed after it is cancelled without a question asked.
|
|
440
|
+
if (signal?.aborted) {
|
|
441
|
+
record(handling, decision, CANCELLED);
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
424
444
|
const readied = await ready(decision, told(options, { step: handling.step, round: handling.round }));
|
|
425
445
|
if (!readied.ready) {
|
|
426
446
|
record(handling, readied.decision, { status: readied.status, why: readied.why });
|
|
427
447
|
continue;
|
|
428
448
|
}
|
|
429
|
-
bodies.push(ran(readied.decision,
|
|
449
|
+
bodies.push(ran(readied.decision, signal).then(became => record(handling, readied.decision, became)));
|
|
430
450
|
}
|
|
431
451
|
await Promise.all(bodies);
|
|
432
452
|
}
|
|
@@ -491,12 +511,15 @@ function make(ground, invoked) {
|
|
|
491
511
|
const options = signal === undefined ? {} : { signal };
|
|
492
512
|
return decision.outcome === "confirm" ? project.run(decision, { ...options, confirmed: true }) : project.run(decision, options);
|
|
493
513
|
}
|
|
494
|
-
/** One body run for a weave: what it returned, or its failure as the step's own outcome, never the weave's
|
|
514
|
+
/** One body run for a weave: what it returned, or its failure as the step's own outcome, never the weave's; a
|
|
515
|
+
* body the signal ended is the round cancelled. */
|
|
495
516
|
async function ran(decision, signal) {
|
|
496
517
|
try {
|
|
497
518
|
return { status: "ran", result: await bodied(decision, signal) };
|
|
498
519
|
}
|
|
499
520
|
catch (error) {
|
|
521
|
+
if (signal?.aborted && error === signal.reason)
|
|
522
|
+
return CANCELLED;
|
|
500
523
|
if (error instanceof FailureError) {
|
|
501
524
|
return { status: "failed", why: { type: "said", message: error.why === undefined ? error.what : `${error.what}: ${error.why}` } };
|
|
502
525
|
}
|
|
@@ -571,6 +594,16 @@ function make(ground, invoked) {
|
|
|
571
594
|
}
|
|
572
595
|
return project;
|
|
573
596
|
}
|
|
597
|
+
/** A round the signal cancelled, as its step and the record read it. */
|
|
598
|
+
const CANCELLED = { status: "skipped", why: { type: "cancelled" } };
|
|
599
|
+
/** The signal's reason with the record on it as `woven`, when the reason is something that can carry one: what
|
|
600
|
+
* a cancelled `weave` rejects with, so the caller's own test for a cancel holds and the steps are there to read. */
|
|
601
|
+
function carrying(reason, woven) {
|
|
602
|
+
if (reason !== null && (typeof reason === "object" || typeof reason === "function") && Object.isExtensible(reason)) {
|
|
603
|
+
Object.assign(reason, { woven });
|
|
604
|
+
}
|
|
605
|
+
return reason;
|
|
606
|
+
}
|
|
574
607
|
/** Whether two asks want the same things for the same reasons. */
|
|
575
608
|
function same(after, before) {
|
|
576
609
|
return JSON.stringify(after) === JSON.stringify(before);
|
package/dist/types.d.ts
CHANGED
|
@@ -754,6 +754,10 @@ export type Cap = {
|
|
|
754
754
|
} | {
|
|
755
755
|
type: "two_things";
|
|
756
756
|
contender: Contender;
|
|
757
|
+
}
|
|
758
|
+
/** A weave merged a part that matched nothing on its own back into these words: never run unasked. */
|
|
759
|
+
| {
|
|
760
|
+
type: "merged";
|
|
757
761
|
};
|
|
758
762
|
/** The confirm prompt: `evoke`'s own line, then the manifest's template filled in. */
|
|
759
763
|
export interface Prompt {
|
|
@@ -971,6 +975,10 @@ export type WeaveWhy = {
|
|
|
971
975
|
} | {
|
|
972
976
|
type: "read_as";
|
|
973
977
|
reflex: LocalName;
|
|
978
|
+
}
|
|
979
|
+
/** The weave was cancelled: the round the host ended, and every step that had not finished by then. */
|
|
980
|
+
| {
|
|
981
|
+
type: "cancelled";
|
|
974
982
|
} | {
|
|
975
983
|
type: "said";
|
|
976
984
|
message: string;
|
|
@@ -991,6 +999,8 @@ export interface Progress {
|
|
|
991
999
|
/** What the run needs a host to do next. */
|
|
992
1000
|
export type Todo = {
|
|
993
1001
|
type: "decide";
|
|
1002
|
+
step: number;
|
|
1003
|
+
round: number;
|
|
994
1004
|
asked: Asked;
|
|
995
1005
|
} | {
|
|
996
1006
|
type: "handle";
|
package/package.json
CHANGED
package/runtime/loader.mjs
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
// and its default export called with (args, { input, config, signal }), then one result line on stdout:
|
|
3
3
|
// { text, data? } or { error, refused? } — `refused` when the error is a refusal by Node's permission model or the
|
|
4
4
|
// kernel: what was refused, a permission or a syscall, and the path, so the host names the declaration's key. It
|
|
5
|
-
// exits when stdin closes
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// abandoned. The SDK ships this same
|
|
5
|
+
// exits when stdin closes before a body, and during one aborts the body's `signal` first, so its life is bounded
|
|
6
|
+
// by its parent's and the body still hears the end. The body's console and stdout go to stderr, as do the frames
|
|
7
|
+
// of an error it throws — the frames alone, without the message the host reports, and without the frames inside
|
|
8
|
+
// Node itself; an error thrown from a callback ends the body as a rejection does. SIGTERM, the deadline and
|
|
9
|
+
// stdin's end abort `signal`; a body that has not settled a second later is abandoned. The SDK ships this same
|
|
10
|
+
// file.
|
|
10
11
|
import { writeSync } from "node:fs";
|
|
11
12
|
import module from "node:module";
|
|
12
13
|
import { pathToFileURL } from "node:url";
|
|
@@ -23,6 +24,12 @@ try {
|
|
|
23
24
|
// Nothing to warm.
|
|
24
25
|
}
|
|
25
26
|
const controller = new AbortController();
|
|
27
|
+
// The body's signal aborted with the reason, and the loader gone a grace later whatever the body does: with a
|
|
28
|
+
// line for the host when it is still there to read one, silently when it is not.
|
|
29
|
+
const stop = (why, report) => {
|
|
30
|
+
if (!controller.signal.aborted) controller.abort(new Error(why));
|
|
31
|
+
setTimeout(() => (report ? fail(why) : process.exit(1)), GRACE);
|
|
32
|
+
};
|
|
26
33
|
// The whole line, however long: `process.stdout` below puts the pipe in non-blocking mode, so one write may take
|
|
27
34
|
// part of it, and the next may be told to wait a moment.
|
|
28
35
|
const out = (value) => {
|
|
@@ -56,7 +63,8 @@ process.stdin.on("data", (chunk) => {
|
|
|
56
63
|
run(buffered.slice(0, end));
|
|
57
64
|
}
|
|
58
65
|
});
|
|
59
|
-
|
|
66
|
+
// The host is gone: before a body, nothing to do; during one, the body hears it and the loader follows.
|
|
67
|
+
process.stdin.on("end", () => (started ? stop("the host is gone", false) : process.exit(0)));
|
|
60
68
|
|
|
61
69
|
async function run(line) {
|
|
62
70
|
let envelope;
|
|
@@ -66,13 +74,9 @@ async function run(line) {
|
|
|
66
74
|
return fail(`the envelope is not JSON: ${error.message}`);
|
|
67
75
|
}
|
|
68
76
|
const { run, args, input, config, deadline } = envelope;
|
|
69
|
-
|
|
70
|
-
if (!controller.signal.aborted) controller.abort(new Error(why));
|
|
71
|
-
setTimeout(() => fail(why), GRACE);
|
|
72
|
-
};
|
|
73
|
-
process.on("SIGTERM", () => stop("terminated"));
|
|
77
|
+
process.on("SIGTERM", () => stop("terminated", true));
|
|
74
78
|
process.on("uncaughtException", thrown);
|
|
75
|
-
const timer = setTimeout(() => stop(`timed out after ${deadline} ms
|
|
79
|
+
const timer = setTimeout(() => stop(`timed out after ${deadline} ms`, true), deadline);
|
|
76
80
|
try {
|
|
77
81
|
const module = await import(pathToFileURL(run).href);
|
|
78
82
|
if (module.default === undefined) throw new Error(`${run} has no default export`);
|