@sanoma-ai/workflows 0.6.0 → 0.7.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/README.md +48 -14
- package/dist/apply.d.ts +2 -1
- package/dist/apply.js +29 -7
- package/dist/apply.js.map +1 -1
- package/dist/approvals.d.ts +27 -2
- package/dist/approvals.js +73 -9
- package/dist/approvals.js.map +1 -1
- package/dist/call.d.ts +3 -2
- package/dist/call.js +38 -30
- package/dist/call.js.map +1 -1
- package/dist/cedar.js +24 -9
- package/dist/cedar.js.map +1 -1
- package/dist/change.d.ts +219 -0
- package/dist/change.js +150 -0
- package/dist/change.js.map +1 -0
- package/dist/chats.d.ts +43 -0
- package/dist/chats.js +48 -0
- package/dist/chats.js.map +1 -0
- package/dist/client.d.ts +52 -6
- package/dist/client.js +88 -47
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +47 -5
- package/dist/config.js +71 -7
- package/dist/config.js.map +1 -1
- package/dist/database.d.ts +83 -0
- package/dist/database.js +92 -0
- package/dist/database.js.map +1 -0
- package/dist/db.d.ts +31 -1
- package/dist/db.js +89 -7
- package/dist/db.js.map +1 -1
- package/dist/deadlines.d.ts +88 -0
- package/dist/deadlines.js +171 -0
- package/dist/deadlines.js.map +1 -0
- package/dist/define.d.ts +25 -4
- package/dist/define.js +40 -0
- package/dist/define.js.map +1 -1
- package/dist/demo.d.ts +28 -0
- package/dist/demo.js +72 -0
- package/dist/demo.js.map +1 -0
- package/dist/describe.d.ts +5 -2
- package/dist/describe.js +10 -6
- package/dist/describe.js.map +1 -1
- package/dist/errors.d.ts +10 -1
- package/dist/errors.js +13 -0
- package/dist/errors.js.map +1 -1
- package/dist/fake.d.ts +8 -0
- package/dist/fake.js +11 -1
- package/dist/fake.js.map +1 -1
- package/dist/grid.d.ts +34 -0
- package/dist/grid.js +62 -0
- package/dist/grid.js.map +1 -0
- package/dist/host.d.ts +13 -2
- package/dist/host.js +2 -2
- package/dist/host.js.map +1 -1
- package/dist/index.d.ts +16 -8
- package/dist/index.js +12 -5
- package/dist/index.js.map +1 -1
- package/dist/ledger.d.ts +11 -2
- package/dist/ledger.js.map +1 -1
- package/dist/lint.js +1 -1
- package/dist/lint.js.map +1 -1
- package/dist/op.d.ts +14 -2
- package/dist/op.js.map +1 -1
- package/dist/policy.d.ts +7 -1
- package/dist/policy.js +2 -1
- package/dist/policy.js.map +1 -1
- package/dist/resource.d.ts +13 -1
- package/dist/resource.js +9 -1
- package/dist/resource.js.map +1 -1
- package/dist/resources.d.ts +2 -0
- package/dist/resources.js +10 -1
- package/dist/resources.js.map +1 -1
- package/dist/run.d.ts +5 -0
- package/dist/run.js.map +1 -1
- package/dist/sandbox.d.ts +38 -10
- package/dist/sandbox.js +66 -35
- package/dist/sandbox.js.map +1 -1
- package/dist/scenario.d.ts +18 -1
- package/dist/scenario.js +77 -27
- package/dist/scenario.js.map +1 -1
- package/dist/scenarios.d.ts +10 -1
- package/dist/scenarios.js +19 -4
- package/dist/scenarios.js.map +1 -1
- package/dist/schedules.d.ts +66 -0
- package/dist/schedules.js +158 -0
- package/dist/schedules.js.map +1 -0
- package/dist/schema.d.ts +6 -0
- package/dist/schema.js +13 -3
- package/dist/schema.js.map +1 -1
- package/dist/shared.d.ts +30 -2
- package/dist/shared.js +42 -4
- package/dist/shared.js.map +1 -1
- package/dist/source.d.ts +61 -2
- package/dist/source.js.map +1 -1
- package/dist/supervisor.d.ts +38 -0
- package/dist/supervisor.js +77 -0
- package/dist/supervisor.js.map +1 -0
- package/dist/version.d.ts +3 -1
- package/dist/version.js +3 -1
- package/dist/version.js.map +1 -1
- package/dist/wakeup-service.d.ts +83 -0
- package/dist/wakeup-service.js +421 -0
- package/dist/wakeup-service.js.map +1 -0
- package/dist/wakeup.d.ts +84 -0
- package/dist/wakeup.js +87 -0
- package/dist/wakeup.js.map +1 -0
- package/dist/worker.d.ts +39 -1
- package/dist/worker.js +130 -39
- package/dist/worker.js.map +1 -1
- package/oxlint.json +2 -1
- package/package.json +8 -2
- package/src/apply.ts +25 -6
- package/src/approvals.ts +89 -16
- package/src/call.ts +40 -29
- package/src/cedar.ts +24 -6
- package/src/change.ts +222 -0
- package/src/chats.ts +114 -0
- package/src/client.ts +145 -55
- package/src/config.ts +110 -13
- package/src/database.ts +152 -0
- package/src/db.ts +108 -9
- package/src/deadlines.ts +234 -0
- package/src/define.ts +71 -4
- package/src/demo.ts +87 -0
- package/src/describe.ts +18 -6
- package/src/errors.ts +14 -0
- package/src/fake.ts +12 -0
- package/src/grid.ts +100 -0
- package/src/host.ts +13 -4
- package/src/index.ts +61 -3
- package/src/ledger.ts +15 -3
- package/src/lint.ts +1 -1
- package/src/op.ts +15 -2
- package/src/policy.ts +7 -1
- package/src/resource.ts +22 -3
- package/src/resources.ts +12 -1
- package/src/run.ts +5 -0
- package/src/sandbox.ts +97 -37
- package/src/scenario.ts +97 -28
- package/src/scenarios.ts +21 -5
- package/src/schedules.ts +206 -0
- package/src/schema.ts +16 -3
- package/src/shared.ts +51 -4
- package/src/source.ts +58 -3
- package/src/supervisor.ts +107 -0
- package/src/version.ts +3 -1
- package/src/wakeup-service.ts +511 -0
- package/src/wakeup.ts +150 -0
- package/src/worker.ts +181 -44
package/src/source.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
2
|
import { promisify } from "node:util";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
+
import type { PermissionsGrid } from "./grid.ts";
|
|
4
5
|
import { defineConnector, defineDriver, DriverError } from "./op.ts";
|
|
5
6
|
|
|
6
7
|
// Where a company's files live, and how a change to them is recorded: a git repository on this
|
|
7
8
|
// machine, or one a host cloned. The runtime's writers (the app's Adopt, apply's lockfile) change
|
|
8
9
|
// files under `root`, then commit them through it. The launcher makes one; a config without one
|
|
9
|
-
// writes the files and commits nothing.
|
|
10
|
+
// writes the files and commits nothing. A writer that must leave the live checkout alone writes
|
|
11
|
+
// in a draft instead: a checkout of its own, on a branch of its own.
|
|
10
12
|
|
|
11
13
|
/** A company's repository, as the runtime writes to it. */
|
|
12
14
|
export interface Source {
|
|
@@ -21,10 +23,63 @@ export interface Source {
|
|
|
21
23
|
message: string;
|
|
22
24
|
author: string;
|
|
23
25
|
}): { revision: string } | undefined | Promise<{ revision: string } | undefined>;
|
|
24
|
-
/** The revision `ref` names, the checked-out one by default. */
|
|
26
|
+
/** The revision `ref` names, the checked-out one by default (a draft's by its `branch`). */
|
|
25
27
|
head(ref?: string): string | Promise<string>;
|
|
26
|
-
/** Pushes `ref` to the repository at `url`, signed in with `token
|
|
28
|
+
/** Pushes `ref` to the repository at `url`, signed in with `token` (a draft by its `branch`). */
|
|
27
29
|
push?(target: { url: string; token: string; ref: string }): void | Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* The draft `id` (letters, digits, `_` and `-`), opened: made from the checked-out revision the
|
|
32
|
+
* first time, then the same one, edits and all, until its branch is deleted.
|
|
33
|
+
*/
|
|
34
|
+
draft?(id: string): Promise<Draft>;
|
|
35
|
+
/** The open drafts' ids, sorted. */
|
|
36
|
+
drafts?(): Promise<string[]>;
|
|
37
|
+
/** What the draft `id` has committed since its branch left the checked-out one (read from the branch alone). */
|
|
38
|
+
diff?(id: string): Promise<DraftDiff>;
|
|
39
|
+
/** Closes the draft `id`: its checkout removed and its branch deleted, whatever it held; nothing when it is gone. */
|
|
40
|
+
closeDraft?(id: string): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* `sanoma check` on the draft `id`, as a child in its checkout, one at a time in the process. A
|
|
43
|
+
* check that gives no result (a config that does not load, no install, a timeout) is a result
|
|
44
|
+
* of one problem saying why.
|
|
45
|
+
*/
|
|
46
|
+
check?(id: string): Promise<CheckResult>;
|
|
47
|
+
/** The URL of the repository this one was cloned from (`origin`); undefined when it has none. */
|
|
48
|
+
remoteUrl?(): Promise<string | undefined>;
|
|
49
|
+
/**
|
|
50
|
+
* Fast-forwards the checked-out branch to `ref` in the repository at `url`, signed in with
|
|
51
|
+
* `token`, as `push` is: only when `ref` is checked out, nothing is uncommitted and the remote's
|
|
52
|
+
* `ref` contains it; otherwise `skipped`, saying why. The checked-out revision after.
|
|
53
|
+
*/
|
|
54
|
+
follow?(target: { url: string; token: string; ref: string }): Promise<{ revision: string } | { skipped: string }>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A draft: a checkout of its own, at `root`, on `branch`, which `commit` records in as
|
|
59
|
+
* `Source.commit` does in its root. Its `node_modules` may be the root's own install, linked in:
|
|
60
|
+
* a writer never writes under it.
|
|
61
|
+
*/
|
|
62
|
+
export interface Draft {
|
|
63
|
+
branch: string;
|
|
64
|
+
root: string;
|
|
65
|
+
commit: Source["commit"];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** A draft's change: the files it touches, relative to the root, and git's `--stat` of it. */
|
|
69
|
+
export interface DraftDiff {
|
|
70
|
+
files: string[];
|
|
71
|
+
summary: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** What `sanoma check` finds in a repo (a draft's too). */
|
|
75
|
+
export interface CheckResult {
|
|
76
|
+
ok: boolean;
|
|
77
|
+
/** One printable line each: `<file>:<line>:<col>: <message>`, or `<scenario> › <step>: <message>`. */
|
|
78
|
+
problems: string[];
|
|
79
|
+
/** What is worth fixing but not wrong, one line each, `<file>:<line>:<col>: warning: <message>`: the sites' `warnings`. */
|
|
80
|
+
warnings: string[];
|
|
81
|
+
/** The repo's Permissions grid (`permissionsGrid`), when its config loaded and its policies hold: what a change is reviewed by. */
|
|
82
|
+
grid?: PermissionsGrid;
|
|
28
83
|
}
|
|
29
84
|
|
|
30
85
|
/**
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { resolveConfig, type SanomaConfig } from "./config.ts";
|
|
2
|
+
import { shouldWake } from "./db.ts";
|
|
3
|
+
import type { SelfStop } from "./deadlines.ts";
|
|
4
|
+
import { warn } from "./log.ts";
|
|
5
|
+
import { errorMessage } from "./shared.ts";
|
|
6
|
+
import { startWorker, type Worker, type WorkerOptions } from "./worker.ts";
|
|
7
|
+
|
|
8
|
+
// One worker at a time in a process that outlives it. A worker that stops itself when idle (a
|
|
9
|
+
// wakeup declared) cannot launch again, so the next write that needs one, or the wakeup's wake,
|
|
10
|
+
// starts a fresh worker. `sanoma serve` runs its worker through it, and so does the testing
|
|
11
|
+
// kit's host in this process.
|
|
12
|
+
|
|
13
|
+
/** A worker under a supervisor (`superviseWorker`). */
|
|
14
|
+
export interface Supervisor {
|
|
15
|
+
/** True from the moment the worker stopped itself until a fresh one starts. */
|
|
16
|
+
readonly stopped: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Starts a fresh worker when the last one stopped itself, for a write that needs one (a run's
|
|
19
|
+
* start, an approval's decision); while one starts or runs, it is heard (below). Resolves once it runs, or
|
|
20
|
+
* once its failure to start is logged: the next call tries again. Never rejects.
|
|
21
|
+
*/
|
|
22
|
+
resume(): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* A wakeup's `GET /wake?generation=N`: `resume` when the worker stopped with `generation`
|
|
25
|
+
* registered (`shouldWake`); a no-op for any other, and while one starts or runs.
|
|
26
|
+
*/
|
|
27
|
+
wake(generation: number): Promise<void>;
|
|
28
|
+
/** Stops the worker running or starting, and starts none again. */
|
|
29
|
+
stop(): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface SupervisorOptions extends Omit<WorkerOptions, "onStopped"> {
|
|
33
|
+
/**
|
|
34
|
+
* False: the worker never stops itself, as on an app on this machine alone, whose wakeup
|
|
35
|
+
* would wake the instance the host runs. Default true.
|
|
36
|
+
*/
|
|
37
|
+
selfStop?: boolean;
|
|
38
|
+
/** Told each time the worker stops itself, once a fresh one is starting for a write it heard, or `stopped` is true. */
|
|
39
|
+
onStopped?: (stopped: SelfStop) => void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Starts a worker on `config` that stops itself when idle with a wakeup declared (`onStopped`
|
|
44
|
+
* in `WorkerOptions`), and a fresh one each time `resume` or `wake` asks after it did. Throws
|
|
45
|
+
* when the first does not start. A write heard while a worker starts or runs may land after its last check
|
|
46
|
+
* for being idle, so a worker that stops itself after one is started again at once: it stops
|
|
47
|
+
* again on its own when nothing came of it.
|
|
48
|
+
*/
|
|
49
|
+
export async function superviseWorker(config: SanomaConfig, options: SupervisorOptions = {}): Promise<Supervisor> {
|
|
50
|
+
const { onStopped, selfStop = true, ...workerOptions } = options;
|
|
51
|
+
let worker: Worker | undefined;
|
|
52
|
+
let starting: Promise<void> | undefined;
|
|
53
|
+
let closed = false;
|
|
54
|
+
// A write while the worker starts or runs, since it began starting.
|
|
55
|
+
let heard = false;
|
|
56
|
+
let databaseUrl: string | undefined;
|
|
57
|
+
|
|
58
|
+
const launch = async () => {
|
|
59
|
+
heard = false;
|
|
60
|
+
const started = await startWorker(config, { ...workerOptions, ...(selfStop && { onStopped: selfStopped }) });
|
|
61
|
+
if (closed) await started.stop();
|
|
62
|
+
else worker = started;
|
|
63
|
+
};
|
|
64
|
+
const selfStopped = (stopped: SelfStop) => {
|
|
65
|
+
worker = undefined;
|
|
66
|
+
if (closed) return;
|
|
67
|
+
// Resumed first, so `stopped` is false when `onStopped` (the exit rule) looks.
|
|
68
|
+
if (heard) void supervisor.resume();
|
|
69
|
+
onStopped?.(stopped);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const supervisor: Supervisor = {
|
|
73
|
+
get stopped() {
|
|
74
|
+
return !closed && !worker && !starting;
|
|
75
|
+
},
|
|
76
|
+
resume() {
|
|
77
|
+
if (closed) return Promise.resolve();
|
|
78
|
+
if (worker) {
|
|
79
|
+
heard = true;
|
|
80
|
+
return Promise.resolve();
|
|
81
|
+
}
|
|
82
|
+
if (starting) {
|
|
83
|
+
heard = true;
|
|
84
|
+
return starting;
|
|
85
|
+
}
|
|
86
|
+
return (starting = launch()
|
|
87
|
+
.catch((err: unknown) => warn(`the worker could not start again: ${errorMessage(err)}`))
|
|
88
|
+
.finally(() => {
|
|
89
|
+
starting = undefined;
|
|
90
|
+
}));
|
|
91
|
+
},
|
|
92
|
+
async wake(generation) {
|
|
93
|
+
if (!supervisor.stopped) return;
|
|
94
|
+
databaseUrl ??= resolveConfig(config).databaseUrl;
|
|
95
|
+
if (await shouldWake(databaseUrl, generation)) await supervisor.resume();
|
|
96
|
+
},
|
|
97
|
+
async stop() {
|
|
98
|
+
closed = true;
|
|
99
|
+
await starting;
|
|
100
|
+
const running = worker;
|
|
101
|
+
worker = undefined;
|
|
102
|
+
await running?.stop();
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
await launch();
|
|
106
|
+
return supervisor;
|
|
107
|
+
}
|
package/src/version.ts
CHANGED
|
@@ -16,8 +16,10 @@ export const RUNTIME_VERSION = (
|
|
|
16
16
|
* package release, so a run in flight on the old layout is not replayed against the new one.
|
|
17
17
|
* 2: calls run in program order; a decision event per approval (2026-10-07).
|
|
18
18
|
* 3: a start the policy holds waits on its approval before the body (2026-10-21).
|
|
19
|
+
* 4: a sleep for a duration reads DBOS's clock for its start (2026-10-25).
|
|
20
|
+
* 5: a scheduled run admits its starter and asks the policy in two steps before the body (2026-10-25).
|
|
19
21
|
*/
|
|
20
|
-
export const STEP_LAYOUT =
|
|
22
|
+
export const STEP_LAYOUT = 5;
|
|
21
23
|
|
|
22
24
|
let dbosVersion: string | undefined;
|
|
23
25
|
|