omkit 0.0.15 → 0.1.1
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 +110 -97
- package/dist/actions/chrome-page.d.ts +12 -29
- package/dist/actions/chrome-page.js +19 -32
- package/dist/actions/command.d.ts +18 -39
- package/dist/actions/command.js +28 -80
- package/dist/actions/healthcheck.d.ts +3 -11
- package/dist/actions/healthcheck.js +8 -22
- package/dist/actions/index.d.ts +6 -8
- package/dist/actions/index.js +4 -6
- package/dist/actions/prompt.d.ts +5 -19
- package/dist/actions/prompt.js +10 -31
- package/dist/actions/tail-log.d.ts +7 -21
- package/dist/actions/tail-log.js +13 -34
- package/dist/actions/until-log.d.ts +4 -7
- package/dist/actions/until-log.js +3 -3
- package/dist/actions/watch-dir.d.ts +5 -19
- package/dist/actions/watch-dir.js +13 -28
- package/dist/actions/watch.d.ts +4 -12
- package/dist/actions/watch.js +9 -16
- package/dist/core/ActionRun.d.ts +97 -0
- package/dist/core/ActionRun.js +266 -0
- package/dist/core/ExecutionTree.d.ts +88 -0
- package/dist/core/ExecutionTree.js +320 -0
- package/dist/core/action.d.ts +6 -0
- package/dist/core/action.js +72 -0
- package/dist/core/context.d.ts +9 -0
- package/dist/core/context.js +8 -0
- package/dist/core/om.d.ts +9 -0
- package/dist/core/om.js +25 -0
- package/dist/core/step.d.ts +9 -0
- package/dist/core/step.js +18 -0
- package/dist/core/types.d.ts +128 -0
- package/dist/foundation/ActionRef.d.ts +18 -0
- package/dist/foundation/ActionRef.js +5 -0
- package/dist/{utils → foundation}/AsyncQueue.d.ts +2 -4
- package/dist/{utils → foundation}/AsyncQueue.js +6 -19
- package/dist/foundation/CancelledError.d.ts +10 -0
- package/dist/foundation/CancelledError.js +13 -0
- package/dist/foundation/Deferred.d.ts +8 -0
- package/dist/foundation/Deferred.js +10 -0
- package/dist/foundation/LogEntry.d.ts +29 -0
- package/dist/{orchestration/events → foundation}/events.d.ts +2 -11
- package/dist/{orchestration/events → foundation}/events.js +1 -7
- package/dist/{utils → foundation}/format.d.ts +3 -4
- package/dist/{utils → foundation}/format.js +12 -4
- package/dist/foundation/fsSafe.d.ts +7 -0
- package/dist/foundation/fsSafe.js +7 -0
- package/dist/foundation/ids.d.ts +9 -0
- package/dist/foundation/ids.js +10 -0
- package/dist/index.d.ts +11 -22
- package/dist/index.js +10 -30
- package/dist/output/LiveRenderer.d.ts +13 -0
- package/dist/output/LiveRenderer.js +56 -0
- package/dist/output/console/ConsoleCapture.d.ts +20 -0
- package/dist/output/console/ConsoleCapture.js +47 -0
- package/dist/output/folder/RunFolder.d.ts +11 -22
- package/dist/output/folder/RunFolder.js +18 -36
- package/dist/output/log/LogStore.d.ts +22 -0
- package/dist/output/log/LogStore.js +56 -0
- package/dist/output/log/RawStream.d.ts +11 -0
- package/dist/output/log/RawStream.js +18 -0
- package/dist/output/snapshot/SnapshotStore.d.ts +6 -43
- package/dist/output/snapshot/SnapshotStore.js +12 -52
- package/dist/output/writers/NodeLogWriter.d.ts +8 -0
- package/dist/output/writers/NodeLogWriter.js +36 -0
- package/dist/output/writers/ResultWriter.d.ts +3 -0
- package/dist/output/writers/ResultWriter.js +5 -0
- package/dist/output/writers/RollupWriter.d.ts +9 -0
- package/dist/output/writers/RollupWriter.js +29 -0
- package/dist/output/writers/entryLine.d.ts +10 -0
- package/dist/output/writers/entryLine.js +39 -0
- package/dist/output/writers/views.d.ts +38 -0
- package/dist/output/writers/views.js +6 -0
- package/dist/system/fs/FolderCache.d.ts +6 -34
- package/dist/system/fs/FolderCache.js +7 -38
- package/dist/system/proc.d.ts +51 -0
- package/dist/system/proc.js +148 -0
- package/dist/system/procRegistry.d.ts +52 -0
- package/dist/system/procRegistry.js +117 -0
- package/package.json +3 -1
- package/dist/orchestration/action/action.d.ts +0 -23
- package/dist/orchestration/action/action.js +0 -256
- package/dist/orchestration/action/types.d.ts +0 -182
- package/dist/orchestration/assert.d.ts +0 -24
- package/dist/orchestration/assert.js +0 -26
- package/dist/orchestration/spin/SpinHost.d.ts +0 -111
- package/dist/orchestration/spin/SpinHost.js +0 -345
- package/dist/orchestration/spin/spin.d.ts +0 -24
- package/dist/orchestration/spin/spin.js +0 -57
- package/dist/orchestration/spin/types.d.ts +0 -63
- package/dist/output/Output.d.ts +0 -24
- package/dist/output/Output.js +0 -27
- package/dist/output/index.d.ts +0 -11
- package/dist/output/index.js +0 -11
- package/dist/output/log/LogRenderer.d.ts +0 -73
- package/dist/output/log/LogRenderer.js +0 -133
- package/dist/output/log/LogsCollector.d.ts +0 -79
- package/dist/output/log/LogsCollector.js +0 -141
- package/dist/output/log/RunLog.d.ts +0 -54
- package/dist/output/log/RunLog.js +0 -152
- package/dist/output/log/console-capture.d.ts +0 -24
- package/dist/output/log/console-capture.js +0 -53
- package/dist/system/fs/streams.d.ts +0 -11
- package/dist/system/fs/streams.js +0 -14
- package/dist/system/process/process.d.ts +0 -35
- package/dist/system/process/process.js +0 -104
- package/dist/utils/serialize.d.ts +0 -2
- package/dist/utils/serialize.js +0 -12
- /package/dist/{orchestration/action → core}/types.js +0 -0
- /package/dist/{orchestration/spin/types.js → foundation/LogEntry.js} +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** A single-consumer async queue: `push` feeds it, `for await` drains it, `close` ends it. */
|
|
1
2
|
export class AsyncQueue {
|
|
2
3
|
items = [];
|
|
3
4
|
resolvers = [];
|
|
@@ -7,35 +8,23 @@ export class AsyncQueue {
|
|
|
7
8
|
return;
|
|
8
9
|
const resolver = this.resolvers.shift();
|
|
9
10
|
if (resolver) {
|
|
10
|
-
resolver({
|
|
11
|
-
done: false,
|
|
12
|
-
value: item,
|
|
13
|
-
});
|
|
11
|
+
resolver({ done: false, value: item });
|
|
14
12
|
return;
|
|
15
13
|
}
|
|
16
14
|
this.items.push(item);
|
|
17
15
|
}
|
|
18
|
-
/**
|
|
19
|
-
* Ends the queue: any parked reader resolves `{ done: true }` immediately, and
|
|
20
|
-
* once already-buffered items are drained, future reads do too. Idempotent.
|
|
21
|
-
*/
|
|
16
|
+
/** Ends the queue: parked readers resolve `{ done: true }`, drained reads too. Idempotent. */
|
|
22
17
|
close() {
|
|
23
18
|
if (this.closed)
|
|
24
19
|
return;
|
|
25
20
|
this.closed = true;
|
|
26
|
-
// Unblock everyone parked — nothing more is coming.
|
|
27
21
|
for (const resolve of this.resolvers.splice(0))
|
|
28
22
|
resolve({ done: true, value: undefined });
|
|
29
23
|
}
|
|
30
24
|
next() {
|
|
31
25
|
const item = this.items.shift();
|
|
32
|
-
if (item !== undefined)
|
|
33
|
-
return Promise.resolve({
|
|
34
|
-
done: false,
|
|
35
|
-
value: item,
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
// Drained and closed → the iteration is over.
|
|
26
|
+
if (item !== undefined)
|
|
27
|
+
return Promise.resolve({ done: false, value: item });
|
|
39
28
|
if (this.closed)
|
|
40
29
|
return Promise.resolve({ done: true, value: undefined });
|
|
41
30
|
return new Promise((resolve) => {
|
|
@@ -43,8 +32,6 @@ export class AsyncQueue {
|
|
|
43
32
|
});
|
|
44
33
|
}
|
|
45
34
|
[Symbol.asyncIterator]() {
|
|
46
|
-
return {
|
|
47
|
-
next: () => this.next(),
|
|
48
|
-
};
|
|
35
|
+
return { next: () => this.next() };
|
|
49
36
|
}
|
|
50
37
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The error a cancelled node reports — its `.ref` rejects with it and its `.result`
|
|
3
|
+
* resolves `{ ok: false, error: CancelledError }` — so an awaiter can tell cancellation
|
|
4
|
+
* from a genuine failure. A cancelled node is never a verdict failure.
|
|
5
|
+
*/
|
|
6
|
+
export declare class CancelledError extends Error {
|
|
7
|
+
constructor(message?: string);
|
|
8
|
+
}
|
|
9
|
+
/** True if `error` is a {@link CancelledError}. */
|
|
10
|
+
export declare const isCancelled: (error: unknown) => error is CancelledError;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The error a cancelled node reports — its `.ref` rejects with it and its `.result`
|
|
3
|
+
* resolves `{ ok: false, error: CancelledError }` — so an awaiter can tell cancellation
|
|
4
|
+
* from a genuine failure. A cancelled node is never a verdict failure.
|
|
5
|
+
*/
|
|
6
|
+
export class CancelledError extends Error {
|
|
7
|
+
constructor(message = "cancelled") {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "CancelledError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/** True if `error` is a {@link CancelledError}. */
|
|
13
|
+
export const isCancelled = (error) => error instanceof CancelledError;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** A promise paired with its settlers, for a set-once value (a handle, an Outcome, …). */
|
|
2
|
+
export interface Deferred<T> {
|
|
3
|
+
promise: Promise<T>;
|
|
4
|
+
resolve: (value: T) => void;
|
|
5
|
+
reject: (reason: unknown) => void;
|
|
6
|
+
}
|
|
7
|
+
/** Create a {@link Deferred}: a fresh promise with its `resolve`/`reject` captured. */
|
|
8
|
+
export declare function defer<T>(): Deferred<T>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Create a {@link Deferred}: a fresh promise with its `resolve`/`reject` captured. */
|
|
2
|
+
export function defer() {
|
|
3
|
+
let resolve;
|
|
4
|
+
let reject;
|
|
5
|
+
const promise = new Promise((res, rej) => {
|
|
6
|
+
resolve = res;
|
|
7
|
+
reject = rej;
|
|
8
|
+
});
|
|
9
|
+
return { promise, resolve, reject };
|
|
10
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One row on the run's timeline, and the read-only log view. Plain data shapes in
|
|
3
|
+
* foundation so every layer (output writers, core, actions) can name them without
|
|
4
|
+
* depending on the output layer. Attribution is lightweight — `nodeId`/`path`, not
|
|
5
|
+
* the full ActionRef — so name/tags aren't repeated per row.
|
|
6
|
+
*/
|
|
7
|
+
export interface LogEntry {
|
|
8
|
+
/** The run-global monotonic sequence — the cross-file merge/ordering key. */
|
|
9
|
+
readonly sequence: number;
|
|
10
|
+
/** Epoch millis when appended. */
|
|
11
|
+
readonly ts: number;
|
|
12
|
+
/** The owning node's id (attribution key). */
|
|
13
|
+
readonly nodeId: string;
|
|
14
|
+
/** The owning node's execution path (`main/chromePage_9f3c`). */
|
|
15
|
+
readonly path: string;
|
|
16
|
+
/** `info` | `error` | `event` | `assert` | `snapshot` | `tag` | `child` | `run`. */
|
|
17
|
+
readonly level: string;
|
|
18
|
+
/** Finer origin within the node (proc name, `event`, `run`, …). */
|
|
19
|
+
readonly source: string;
|
|
20
|
+
readonly message: string;
|
|
21
|
+
}
|
|
22
|
+
/** What an appender supplies; the store stamps `sequence` and `ts`. */
|
|
23
|
+
export type LogInput = Omit<LogEntry, "sequence" | "ts">;
|
|
24
|
+
/** The read-only view handed to actions — they can observe the log, never write it. */
|
|
25
|
+
export interface ReadableLog {
|
|
26
|
+
subscribe(options?: {
|
|
27
|
+
replay?: boolean;
|
|
28
|
+
}): AsyncIterable<LogEntry>;
|
|
29
|
+
}
|
|
@@ -12,20 +12,11 @@ type EmitArgs<P> = [P] extends [void] ? [] : [payload: P];
|
|
|
12
12
|
* immediately (with `isSnapshot: true`) before receiving live emits.
|
|
13
13
|
*/
|
|
14
14
|
export interface Emitter<Events extends object> {
|
|
15
|
-
/** Emit `key`'s payload to current listeners and retain it as the snapshot. */
|
|
16
15
|
emit<K extends keyof Events>(key: K, ...args: EmitArgs<Events[K]>): void;
|
|
17
|
-
/** Subscribe to `key`. Fires immediately with the snapshot if one exists. Returns an unsubscribe. */
|
|
18
16
|
listen<K extends keyof Events>(key: K, handler: EventHandler<Events[K]>): () => void;
|
|
19
|
-
/** Like {@link Emitter.listen} but auto-unsubscribes after the first delivery (snapshot counts). */
|
|
20
17
|
listenOnce<K extends keyof Events>(key: K, handler: EventHandler<Events[K]>): () => void;
|
|
21
|
-
/**
|
|
22
|
-
* Observe **every** live emit on this bus, whatever the key — the cross-cutting
|
|
23
|
-
* hook the bus itself stays out of. Used by the action engine to log each emit
|
|
24
|
-
* onto the run timeline (through the action's scoped logger), so the bus is pure
|
|
25
|
-
* pub/sub and logging is layered on top. Fires only for live emits, not snapshot
|
|
26
|
-
* replays. Returns an unsubscribe.
|
|
27
|
-
*/
|
|
18
|
+
/** Observe every live emit, whatever the key — the cross-cutting hook (e.g. logging). */
|
|
28
19
|
onAny(handler: (key: keyof Events, payload: unknown) => void): () => void;
|
|
29
20
|
}
|
|
30
|
-
export declare const events: <Events extends object>(
|
|
21
|
+
export declare const events: <Events extends object>() => Emitter<Events>;
|
|
31
22
|
export {};
|
|
@@ -1,19 +1,15 @@
|
|
|
1
|
-
export const events = (
|
|
1
|
+
export const events = () => {
|
|
2
2
|
const handlers = new Map();
|
|
3
3
|
const snapshots = new Map();
|
|
4
4
|
const anyHandlers = new Set();
|
|
5
5
|
const emit = (key, ...args) => {
|
|
6
6
|
const payload = args[0];
|
|
7
7
|
snapshots.set(key, payload);
|
|
8
|
-
// Notify the cross-cutting observers first (they log the emit onto the
|
|
9
|
-
// timeline), then the keyed handlers — the bus does no logging of its own.
|
|
10
|
-
// Copy so an observer unsubscribing mid-emit doesn't disturb iteration.
|
|
11
8
|
for (const handler of [...anyHandlers])
|
|
12
9
|
handler(key, payload);
|
|
13
10
|
const set = handlers.get(key);
|
|
14
11
|
if (!set)
|
|
15
12
|
return;
|
|
16
|
-
// Copy so a handler unsubscribing (or listenOnce) mid-emit doesn't disturb iteration.
|
|
17
13
|
for (const handler of [...set])
|
|
18
14
|
handler(payload, { isSnapshot: false });
|
|
19
15
|
};
|
|
@@ -24,7 +20,6 @@ export const events = (_namespace) => {
|
|
|
24
20
|
handlers.set(key, set);
|
|
25
21
|
}
|
|
26
22
|
set.add(handler);
|
|
27
|
-
// Snapshot on subscribe, if one has ever been emitted for this key.
|
|
28
23
|
if (snapshots.has(key))
|
|
29
24
|
handler(snapshots.get(key), { isSnapshot: true });
|
|
30
25
|
return () => {
|
|
@@ -32,7 +27,6 @@ export const events = (_namespace) => {
|
|
|
32
27
|
};
|
|
33
28
|
};
|
|
34
29
|
const listenOnce = (key, handler) => {
|
|
35
|
-
// A retained snapshot satisfies "once" immediately — deliver and we're done.
|
|
36
30
|
if (snapshots.has(key)) {
|
|
37
31
|
handler(snapshots.get(key), { isSnapshot: true });
|
|
38
32
|
return () => { };
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
export declare const pad: (n: number, width?: number) => string;
|
|
3
3
|
/** A date as `YYYY-MM-DD`. */
|
|
4
4
|
export declare const ymd: (d: Date) => string;
|
|
5
|
-
/**
|
|
6
|
-
* A clock time as `HH<sep>MM<sep>SS`. Default `:` for display; pass `-` for a
|
|
7
|
-
* filesystem-safe form (folder names can't contain colons on every platform).
|
|
8
|
-
*/
|
|
5
|
+
/** A clock time as `HH<sep>MM<sep>SS` (`-` for filesystem-safe folder names). */
|
|
9
6
|
export declare const hms: (d: Date, sep?: string) => string;
|
|
7
|
+
/** Render a value for the timeline: strings inline, everything else as JSON. */
|
|
8
|
+
export declare const renderValue: (v: unknown) => string;
|
|
@@ -2,8 +2,16 @@
|
|
|
2
2
|
export const pad = (n, width = 2) => String(n).padStart(width, "0");
|
|
3
3
|
/** A date as `YYYY-MM-DD`. */
|
|
4
4
|
export const ymd = (d) => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
5
|
-
/**
|
|
6
|
-
* A clock time as `HH<sep>MM<sep>SS`. Default `:` for display; pass `-` for a
|
|
7
|
-
* filesystem-safe form (folder names can't contain colons on every platform).
|
|
8
|
-
*/
|
|
5
|
+
/** A clock time as `HH<sep>MM<sep>SS` (`-` for filesystem-safe folder names). */
|
|
9
6
|
export const hms = (d, sep = ":") => `${pad(d.getHours())}${sep}${pad(d.getMinutes())}${sep}${pad(d.getSeconds())}`;
|
|
7
|
+
/** Render a value for the timeline: strings inline, everything else as JSON. */
|
|
8
|
+
export const renderValue = (v) => {
|
|
9
|
+
if (typeof v === "string")
|
|
10
|
+
return v;
|
|
11
|
+
try {
|
|
12
|
+
return JSON.stringify(v);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return String(v);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Make one path segment safe to use as a filename on any OS. Windows is the
|
|
3
|
+
* strictest — it forbids `<>:"/\|?*` (a `:` in an action name like
|
|
4
|
+
* `TSKB:root:watch:docs` would otherwise crash the log writer). We keep it simple:
|
|
5
|
+
* collapse every run of non-`[A-Za-z0-9._-]` characters to a single `-`.
|
|
6
|
+
*/
|
|
7
|
+
export declare const fsSafe: (segment: string) => string;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Make one path segment safe to use as a filename on any OS. Windows is the
|
|
3
|
+
* strictest — it forbids `<>:"/\|?*` (a `:` in an action name like
|
|
4
|
+
* `TSKB:root:watch:docs` would otherwise crash the log writer). We keep it simple:
|
|
5
|
+
* collapse every run of non-`[A-Za-z0-9._-]` characters to a single `-`.
|
|
6
|
+
*/
|
|
7
|
+
export const fsSafe = (segment) => segment.replace(/[^a-zA-Z0-9._-]+/g, "-");
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** A fresh v4 uuid for a node. */
|
|
2
|
+
export declare const newUuid: () => string;
|
|
3
|
+
/** The short form used in ids/paths — first 8 hex of the uuid. */
|
|
4
|
+
export declare const shortId: (uuid: string) => string;
|
|
5
|
+
/**
|
|
6
|
+
* A node's id: `${name}_${shortId}` → `chromePage_9f3c`. The root passes its own
|
|
7
|
+
* plain name (`main`) and skips this.
|
|
8
|
+
*/
|
|
9
|
+
export declare const makeId: (name: string, uuid: string) => string;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
/** A fresh v4 uuid for a node. */
|
|
3
|
+
export const newUuid = () => randomUUID();
|
|
4
|
+
/** The short form used in ids/paths — first 8 hex of the uuid. */
|
|
5
|
+
export const shortId = (uuid) => uuid.slice(0, 8);
|
|
6
|
+
/**
|
|
7
|
+
* A node's id: `${name}_${shortId}` → `chromePage_9f3c`. The root passes its own
|
|
8
|
+
* plain name (`main`) and skips this.
|
|
9
|
+
*/
|
|
10
|
+
export const makeId = (name, uuid) => `${name}_${shortId(uuid)}`;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,25 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* omkit (refactored core) — the ExecutionTree model.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* renderer, marker prefixing) are intentionally not exported.
|
|
4
|
+
* A curated public surface. `om(body)` runs a linear orchestration as the root of
|
|
5
|
+
* one run; `action(...)` defines typed units you launch with `.exec()`;
|
|
6
|
+
* `step(name, fn)` is the inline anonymous action. The run's introspectable output
|
|
7
|
+
* is the on-disk `result.json` / `raw.jsonl` / `.log` files — the ExecutionTree
|
|
8
|
+
* engine itself is internal and intentionally not exported.
|
|
10
9
|
*/
|
|
11
|
-
export {
|
|
12
|
-
export
|
|
13
|
-
export {
|
|
14
|
-
export
|
|
15
|
-
export {
|
|
16
|
-
export type { Nod, SpinContext, SpinBody } from "./orchestration/spin/types.ts";
|
|
17
|
-
export { events } from "./orchestration/events/events.ts";
|
|
18
|
-
export type { Emitter, EventHandler, EventMeta } from "./orchestration/events/events.ts";
|
|
19
|
-
export { createProc } from "./system/process/process.ts";
|
|
20
|
-
export type { Proc } from "./system/process/process.ts";
|
|
21
|
-
export { LogsCollector } from "./output/log/LogsCollector.ts";
|
|
22
|
-
export type { Logger, LogEntry, LogInput, SubscribeOptions } from "./output/log/LogsCollector.ts";
|
|
23
|
-
export { Output } from "./output/index.ts";
|
|
24
|
-
export type { SnapshotRef } from "./output/index.ts";
|
|
25
|
-
export { FolderCache } from "./system/fs/FolderCache.ts";
|
|
10
|
+
export { om } from "./core/om.ts";
|
|
11
|
+
export { action } from "./core/action.ts";
|
|
12
|
+
export { step } from "./core/step.ts";
|
|
13
|
+
export { CancelledError, isCancelled } from "./foundation/CancelledError.ts";
|
|
14
|
+
export type { Action, ActionInstance, ActionContext, OmContext, RunHandle, Outcome, } from "./core/types.ts";
|
package/dist/index.js
CHANGED
|
@@ -1,33 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* omkit (refactored core) — the ExecutionTree model.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* renderer, marker prefixing) are intentionally not exported.
|
|
4
|
+
* A curated public surface. `om(body)` runs a linear orchestration as the root of
|
|
5
|
+
* one run; `action(...)` defines typed units you launch with `.exec()`;
|
|
6
|
+
* `step(name, fn)` is the inline anonymous action. The run's introspectable output
|
|
7
|
+
* is the on-disk `result.json` / `raw.jsonl` / `.log` files — the ExecutionTree
|
|
8
|
+
* engine itself is internal and intentionally not exported.
|
|
10
9
|
*/
|
|
11
|
-
|
|
12
|
-
export { action } from "./
|
|
13
|
-
export {
|
|
14
|
-
|
|
15
|
-
export { spin } from "./orchestration/spin/spin.js";
|
|
16
|
-
// ── Events ─────────────────────────────────────────────────────────────────
|
|
17
|
-
export { events } from "./orchestration/events/events.js";
|
|
18
|
-
// ── Child processes ────────────────────────────────────────────────────────
|
|
19
|
-
export { createProc } from "./system/process/process.js";
|
|
20
|
-
// ── The log ────────────────────────────────────────────────────────────────
|
|
21
|
-
export { LogsCollector } from "./output/log/LogsCollector.js";
|
|
22
|
-
// ── Snapshots & artifacts ────────────────────────────────────────────────────
|
|
23
|
-
// The output subsystem is owned per-run by the spin and reached through context —
|
|
24
|
-
// `ctx.output` (folder / snapshots / runLog) inside an action, `ctx.snapshot` and
|
|
25
|
-
// `ctx.artifactsFolder` inside a spin body. The classes are exported for typing and
|
|
26
|
-
// advanced use; there is no process-global singleton or free-function form.
|
|
27
|
-
export { Output } from "./output/index.js";
|
|
28
|
-
// ── Folder cache ─────────────────────────────────────────────────────────────
|
|
29
|
-
// Backs the `.withCache(...inputs)` method on instances (fingerprints an
|
|
30
|
-
// action's input files/folders, not its outputs); exposed for scripts that want
|
|
31
|
-
// to inspect or manually invalidate the cache — `FolderCache.fingerprint(...)`,
|
|
32
|
-
// `FolderCache.dir`.
|
|
33
|
-
export { FolderCache } from "./system/fs/FolderCache.js";
|
|
10
|
+
export { om } from "./core/om.js";
|
|
11
|
+
export { action } from "./core/action.js";
|
|
12
|
+
export { step } from "./core/step.js";
|
|
13
|
+
export { CancelledError, isCancelled } from "./foundation/CancelledError.js";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { LogStore } from "./log/LogStore.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Renders a **curated** stream of milestones to the terminal, live — action
|
|
4
|
+
* launches, completions, and errors, plus events, asserts, and snapshots. The bulk
|
|
5
|
+
* output (console lines, proc stdout/stderr) is intentionally *not* shown live; it
|
|
6
|
+
* lives in the per-action `.log` files. Writes through the pre-patch terminal
|
|
7
|
+
* writer (so it can't feed back into console capture).
|
|
8
|
+
*/
|
|
9
|
+
export declare class LiveRenderer {
|
|
10
|
+
private readonly write;
|
|
11
|
+
constructor(write: (line: string) => void);
|
|
12
|
+
run(store: LogStore): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders a **curated** stream of milestones to the terminal, live — action
|
|
3
|
+
* launches, completions, and errors, plus events, asserts, and snapshots. The bulk
|
|
4
|
+
* output (console lines, proc stdout/stderr) is intentionally *not* shown live; it
|
|
5
|
+
* lives in the per-action `.log` files. Writes through the pre-patch terminal
|
|
6
|
+
* writer (so it can't feed back into console capture).
|
|
7
|
+
*/
|
|
8
|
+
export class LiveRenderer {
|
|
9
|
+
write;
|
|
10
|
+
constructor(write) {
|
|
11
|
+
this.write = write;
|
|
12
|
+
}
|
|
13
|
+
async run(store) {
|
|
14
|
+
for await (const entry of store.subscribe({ replay: true })) {
|
|
15
|
+
const line = format(entry);
|
|
16
|
+
if (line !== null)
|
|
17
|
+
this.write(line);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const firstLine = (message) => message.split("\n", 1)[0] ?? message;
|
|
22
|
+
/** ✓ ok · ⊘ cancelled (a clean stop) · ✗ failed. */
|
|
23
|
+
const doneIcon = (status) => status === "ok" ? "✓" : status === "cancelled" ? "⊘" : "✗";
|
|
24
|
+
/** Map a curated entry to a terminal line, or `null` to drop it from the live view. */
|
|
25
|
+
const format = (e) => {
|
|
26
|
+
// Action launch — bubbled onto the parent as `→ <childId> · <log>`.
|
|
27
|
+
if (e.level === "child" && e.source === "launch") {
|
|
28
|
+
const child = e.message.replace(/^→\s*/, "").split(" · ")[0];
|
|
29
|
+
return `▶ ${e.path}/${child}`;
|
|
30
|
+
}
|
|
31
|
+
// Lifecycle events: `launched` is already shown via the launch pointer above, so
|
|
32
|
+
// drop the duplicate; `done · <status>` renders with the status icon.
|
|
33
|
+
if (e.level === "event" && e.source === "lifecycle") {
|
|
34
|
+
if (e.message === "launched")
|
|
35
|
+
return null;
|
|
36
|
+
const status = e.message.replace(/^done · /, "");
|
|
37
|
+
return `${doneIcon(status)} ${e.path} · ${status}`;
|
|
38
|
+
}
|
|
39
|
+
// Run milestones (started / tearing down / finished).
|
|
40
|
+
if (e.level === "run")
|
|
41
|
+
return `· ${e.message}`;
|
|
42
|
+
// An action's *own* failure — not proc stderr (source = proc name), console.error,
|
|
43
|
+
// or a teardown cancellation (which is a clean stop, not an error).
|
|
44
|
+
if (e.level === "error" && e.source === "error") {
|
|
45
|
+
return `✗ ${e.path} · ${firstLine(e.message)}`;
|
|
46
|
+
}
|
|
47
|
+
// User-emitted events.
|
|
48
|
+
if (e.level === "event")
|
|
49
|
+
return `⚡ ${e.path} · ${e.message}`;
|
|
50
|
+
if (e.level === "assert")
|
|
51
|
+
return ` ${e.path} · ${e.message}`;
|
|
52
|
+
if (e.level === "snapshot")
|
|
53
|
+
return `📸 ${e.path} · ${e.message}`;
|
|
54
|
+
// Everything else (info / console / proc output / tag / bubbled child events) → files only.
|
|
55
|
+
return null;
|
|
56
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ActionRef } from "../../foundation/ActionRef.ts";
|
|
2
|
+
import type { LogStore } from "../log/LogStore.ts";
|
|
3
|
+
/** Resolves the action a console call should be attributed to (the ALS current node). */
|
|
4
|
+
export type AttributionProvider = () => ActionRef | undefined;
|
|
5
|
+
/**
|
|
6
|
+
* Folds every `console.*` call made during the run into the log, attributed to the
|
|
7
|
+
* action it was called in. Uses `patch-console`, which swaps the whole console
|
|
8
|
+
* surface (`log`/`table`/`trace`/`group`/…) over a captured `Console`, so native
|
|
9
|
+
* formatting is preserved. Level is stream-granular: stdout → `info`, stderr →
|
|
10
|
+
* `error`. The `AttributionProvider` (injected by `core`, wired to the ALS) keeps
|
|
11
|
+
* this layer ignorant of the execution tree.
|
|
12
|
+
*/
|
|
13
|
+
export declare class ConsoleCapture {
|
|
14
|
+
private readonly store;
|
|
15
|
+
private readonly attribution;
|
|
16
|
+
private restore;
|
|
17
|
+
constructor(store: LogStore, attribution: AttributionProvider);
|
|
18
|
+
install(): void;
|
|
19
|
+
uninstall(): void;
|
|
20
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Console as NodeConsole } from "node:console";
|
|
2
|
+
import patchConsole from "patch-console";
|
|
3
|
+
/**
|
|
4
|
+
* Folds every `console.*` call made during the run into the log, attributed to the
|
|
5
|
+
* action it was called in. Uses `patch-console`, which swaps the whole console
|
|
6
|
+
* surface (`log`/`table`/`trace`/`group`/…) over a captured `Console`, so native
|
|
7
|
+
* formatting is preserved. Level is stream-granular: stdout → `info`, stderr →
|
|
8
|
+
* `error`. The `AttributionProvider` (injected by `core`, wired to the ALS) keeps
|
|
9
|
+
* this layer ignorant of the execution tree.
|
|
10
|
+
*/
|
|
11
|
+
export class ConsoleCapture {
|
|
12
|
+
store;
|
|
13
|
+
attribution;
|
|
14
|
+
restore;
|
|
15
|
+
constructor(store, attribution) {
|
|
16
|
+
this.store = store;
|
|
17
|
+
this.attribution = attribution;
|
|
18
|
+
}
|
|
19
|
+
install() {
|
|
20
|
+
// patch-console does `new console.Console(...)`; some runners (vitest) swap the
|
|
21
|
+
// global console for one without `.Console`, so fill it from node:console.
|
|
22
|
+
const globalConsole = console;
|
|
23
|
+
if (typeof globalConsole.Console !== "function")
|
|
24
|
+
globalConsole.Console = NodeConsole;
|
|
25
|
+
this.restore = patchConsole((stream, data) => {
|
|
26
|
+
const ref = this.attribution();
|
|
27
|
+
if (!ref)
|
|
28
|
+
return;
|
|
29
|
+
const level = stream === "stderr" ? "error" : "info";
|
|
30
|
+
for (const line of data.split("\n")) {
|
|
31
|
+
if (line.length === 0)
|
|
32
|
+
continue;
|
|
33
|
+
this.store.append({
|
|
34
|
+
nodeId: ref.id,
|
|
35
|
+
path: ref.path,
|
|
36
|
+
level,
|
|
37
|
+
source: "console",
|
|
38
|
+
message: line,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
uninstall() {
|
|
44
|
+
this.restore?.();
|
|
45
|
+
this.restore = undefined;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -1,29 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Owns this
|
|
3
|
-
* `
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* a snapshot taken mid-run lands in the same folder as the log that's flushed later.
|
|
7
|
-
* The {@link SnapshotStore} and {@link RunLog} take a shared instance rather than
|
|
8
|
-
* reaching for module globals, so "where does this run's output go" has one owner.
|
|
2
|
+
* Owns this run's single output directory — `logs/<name>/<date>/<time>/` — where
|
|
3
|
+
* `raw.jsonl`, the per-action `.log` files, rollups, and `result.json` all live.
|
|
4
|
+
* The path is computed once, lazily, and always returned absolute so links written
|
|
5
|
+
* into logs resolve regardless of cwd.
|
|
9
6
|
*/
|
|
10
7
|
export declare class RunFolder {
|
|
11
8
|
private dir;
|
|
12
|
-
private
|
|
13
|
-
|
|
14
|
-
/** The entry script's base name (the run's name), e.g. "tskb-build". */
|
|
9
|
+
private ensured;
|
|
10
|
+
/** The entry script's base name — the run's folder name. */
|
|
15
11
|
name(): string;
|
|
16
|
-
/** The run
|
|
12
|
+
/** The absolute run directory, computed once and created on first access. */
|
|
17
13
|
path(): string;
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
-
/** Create the folder
|
|
21
|
-
ensure():
|
|
22
|
-
/**
|
|
23
|
-
* Absolute path to the run folder, created **synchronously** so a caller can write
|
|
24
|
-
* into it in the same tick. Absolute so it's safe to hand to a child process with a
|
|
25
|
-
* different cwd. This is the artifact drop for actions that want to write beside
|
|
26
|
-
* `run.log` (one folder per run; one run per process).
|
|
27
|
-
*/
|
|
28
|
-
artifacts(): string;
|
|
14
|
+
/** Absolute path to `name` inside the run folder; ensures the folder exists. */
|
|
15
|
+
file(name: string): string;
|
|
16
|
+
/** Create the run folder synchronously (idempotent). */
|
|
17
|
+
ensure(): void;
|
|
29
18
|
}
|
|
@@ -1,56 +1,38 @@
|
|
|
1
1
|
import { mkdirSync } from "node:fs";
|
|
2
|
-
import { mkdir } from "node:fs/promises";
|
|
3
2
|
import path from "node:path";
|
|
4
|
-
import { ymd, hms } from "../../
|
|
3
|
+
import { ymd, hms } from "../../foundation/format.js";
|
|
5
4
|
/**
|
|
6
|
-
* Owns this
|
|
7
|
-
* `
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* a snapshot taken mid-run lands in the same folder as the log that's flushed later.
|
|
11
|
-
* The {@link SnapshotStore} and {@link RunLog} take a shared instance rather than
|
|
12
|
-
* reaching for module globals, so "where does this run's output go" has one owner.
|
|
5
|
+
* Owns this run's single output directory — `logs/<name>/<date>/<time>/` — where
|
|
6
|
+
* `raw.jsonl`, the per-action `.log` files, rollups, and `result.json` all live.
|
|
7
|
+
* The path is computed once, lazily, and always returned absolute so links written
|
|
8
|
+
* into logs resolve regardless of cwd.
|
|
13
9
|
*/
|
|
14
10
|
export class RunFolder {
|
|
15
|
-
// Computed once on first access and cached — every consumer sees the same folder.
|
|
16
11
|
dir;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
// Whether the folder has been created synchronously (for `artifacts()`).
|
|
20
|
-
ensuredSync = false;
|
|
21
|
-
/** The entry script's base name (the run's name), e.g. "tskb-build". */
|
|
12
|
+
ensured = false;
|
|
13
|
+
/** The entry script's base name — the run's folder name. */
|
|
22
14
|
name() {
|
|
23
15
|
const entry = process.argv[1];
|
|
24
|
-
return entry ? path.basename(entry, path.extname(entry)) : "
|
|
16
|
+
return entry ? path.basename(entry, path.extname(entry)) : "om";
|
|
25
17
|
}
|
|
26
|
-
/** The run
|
|
18
|
+
/** The absolute run directory, computed once and created on first access. */
|
|
27
19
|
path() {
|
|
28
20
|
if (this.dir)
|
|
29
21
|
return this.dir;
|
|
30
22
|
const now = new Date();
|
|
31
|
-
this.dir = path.
|
|
23
|
+
this.dir = path.resolve("logs", this.name(), ymd(now), hms(now, "-"));
|
|
32
24
|
return this.dir;
|
|
33
25
|
}
|
|
34
|
-
/**
|
|
35
|
-
|
|
26
|
+
/** Absolute path to `name` inside the run folder; ensures the folder exists. */
|
|
27
|
+
file(name) {
|
|
28
|
+
this.ensure();
|
|
36
29
|
return path.join(this.path(), name);
|
|
37
30
|
}
|
|
38
|
-
/** Create the folder
|
|
31
|
+
/** Create the run folder synchronously (idempotent). */
|
|
39
32
|
ensure() {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
* into it in the same tick. Absolute so it's safe to hand to a child process with a
|
|
45
|
-
* different cwd. This is the artifact drop for actions that want to write beside
|
|
46
|
-
* `run.log` (one folder per run; one run per process).
|
|
47
|
-
*/
|
|
48
|
-
artifacts() {
|
|
49
|
-
const abs = path.resolve(this.path());
|
|
50
|
-
if (!this.ensuredSync) {
|
|
51
|
-
mkdirSync(abs, { recursive: true });
|
|
52
|
-
this.ensuredSync = true;
|
|
53
|
-
}
|
|
54
|
-
return abs;
|
|
33
|
+
if (this.ensured)
|
|
34
|
+
return;
|
|
35
|
+
mkdirSync(this.path(), { recursive: true });
|
|
36
|
+
this.ensured = true;
|
|
55
37
|
}
|
|
56
38
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { LogEntry, LogInput, ReadableLog } from "../../foundation/LogEntry.ts";
|
|
2
|
+
/**
|
|
3
|
+
* The run's single append-only timeline. Owns the run-global `sequence`, keeps the
|
|
4
|
+
* full history (for replay + finalize projections), and fans live entries out to
|
|
5
|
+
* subscribers. It holds no presentation — headers/prefixes are applied by writers
|
|
6
|
+
* at read time. Actions receive it only as {@link ReadableLog}.
|
|
7
|
+
*/
|
|
8
|
+
export declare class LogStore implements ReadableLog {
|
|
9
|
+
private seq;
|
|
10
|
+
private readonly history;
|
|
11
|
+
private readonly subscribers;
|
|
12
|
+
private ended;
|
|
13
|
+
/** Append one entry; stamps `sequence`/`ts`, retains it, fans it out. */
|
|
14
|
+
append(input: LogInput): LogEntry;
|
|
15
|
+
subscribe({ replay }?: {
|
|
16
|
+
replay?: boolean | undefined;
|
|
17
|
+
}): AsyncIterable<LogEntry>;
|
|
18
|
+
/** EOF for readers: end every live subscription and refuse new ones. Called once at finalize. */
|
|
19
|
+
close(): void;
|
|
20
|
+
/** The complete history — the source for finalize-time projections (per-node logs, rollups). */
|
|
21
|
+
entries(): readonly LogEntry[];
|
|
22
|
+
}
|