clyops-jobs 0.3.0 → 0.4.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 +7 -1
- package/clyops-jobs.cjs +23 -4
- package/dist/artifacts.d.ts +4 -0
- package/dist/artifacts.d.ts.map +1 -0
- package/dist/artifacts.js +1 -0
- package/dist/functions.d.ts +19 -5
- package/dist/functions.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/queue.d.ts +10 -1
- package/dist/queue.d.ts.map +1 -0
- package/dist/queue.js +4 -0
- package/dist/records.d.ts +18 -5
- package/dist/records.d.ts.map +1 -0
- package/dist/template.d.ts +3 -0
- package/dist/template.d.ts.map +1 -0
- package/dist/template.js +1 -0
- package/dist/util.d.ts +7 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +6 -1
- package/package.json +3 -2
- package/src/artifacts.ts +3 -0
- package/src/functions.ts +15 -2
- package/src/queue.ts +9 -1
- package/src/records.ts +15 -3
- package/src/template.ts +2 -0
- package/src/util.ts +7 -1
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ and no `node_modules`. It includes clyops-tools. `require('clyops-jobs')` loads
|
|
|
30
30
|
- `functions.NAME.script` is the tool, relative to a script root.
|
|
31
31
|
- `key` names the options block (default: the function name). Options are
|
|
32
32
|
`deepMerge(defaults[key], config[key])`, keyed like the tool's options (`out_dir`, `out-dir`)
|
|
33
|
-
and mapped onto its command line through `--help-json-schema` ([spec §13](
|
|
33
|
+
and mapped onto its command line through `--help-json-schema` ([spec §13](https://github.com/wankdanker/clyops/blob/main/spec/SPEC.md#13-json-input-toargv)).
|
|
34
34
|
- `positionals_order` (optional) is `first` (default) or `last`, for wrappers needing a specific order. `--` is only emitted for positional values starting with `-`.
|
|
35
35
|
- `positionals` (optional) lists the tool's positional arguments explicitly; otherwise they're
|
|
36
36
|
options keyed by argument name.
|
|
@@ -93,3 +93,9 @@ ESM only, Node 20+.
|
|
|
93
93
|
A missing or unreadable configured stdin file rejects `runScriptFunction()`
|
|
94
94
|
with its input-file error. It fails that job and stops its tool; the host worker
|
|
95
95
|
stays alive.
|
|
96
|
+
|
|
97
|
+
## Documentation
|
|
98
|
+
|
|
99
|
+
Read the [Node guide](https://clyops.io/node-tools.html) and
|
|
100
|
+
[complete public API reference](https://clyops.io/reference/node/index.html).
|
|
101
|
+
Type declarations and documentation comments are included in the package.
|
package/clyops-jobs.cjs
CHANGED
|
@@ -102,7 +102,18 @@ function copyFileEnsuringDir(source, destination) {
|
|
|
102
102
|
function pathParts(file) {
|
|
103
103
|
const name = (0, import_node_path.basename)(file);
|
|
104
104
|
const ext = (0, import_node_path.extname)(name);
|
|
105
|
-
return {
|
|
105
|
+
return {
|
|
106
|
+
/** Resolved filesystem path or HTTP endpoint path, according to the containing type. */
|
|
107
|
+
path: file,
|
|
108
|
+
/** Directory represented by this group or specified in .clyops settings. */
|
|
109
|
+
dir: (0, import_node_path.dirname)(file),
|
|
110
|
+
/** Program, command, option, or argument name as declared in its containing type. */
|
|
111
|
+
name,
|
|
112
|
+
/** Filename without its extension. */
|
|
113
|
+
basename: ext ? name.slice(0, -ext.length) : name,
|
|
114
|
+
/** Filename extension including the leading dot. */
|
|
115
|
+
ext: ext.replace(/^\./, "")
|
|
116
|
+
};
|
|
106
117
|
}
|
|
107
118
|
function objectValue(value) {
|
|
108
119
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
@@ -195,9 +206,8 @@ function loadSchema(file, opts = {}) {
|
|
|
195
206
|
// packages/tools/src/argv.ts
|
|
196
207
|
var import_node_fs3 = require("node:fs");
|
|
197
208
|
var import_node_path2 = require("node:path");
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
};
|
|
209
|
+
|
|
210
|
+
// packages/schema/src/names.ts
|
|
201
211
|
function inputKey(name) {
|
|
202
212
|
return name.replace(/-/g, "_");
|
|
203
213
|
}
|
|
@@ -209,6 +219,12 @@ function isBareFlag(option) {
|
|
|
209
219
|
const choices = [...option.choices].sort().join(",");
|
|
210
220
|
return option.isFlag && choices !== "false,true";
|
|
211
221
|
}
|
|
222
|
+
|
|
223
|
+
// packages/tools/src/argv.ts
|
|
224
|
+
var InputError = class extends Error {
|
|
225
|
+
/** HTTP status used by adapters to report invalid caller input. */
|
|
226
|
+
status = 400;
|
|
227
|
+
};
|
|
212
228
|
function isPathValued(validation, type) {
|
|
213
229
|
return type === "path" || validation === "path" || validation.startsWith("file:") || validation.startsWith("dir:");
|
|
214
230
|
}
|
|
@@ -724,6 +740,7 @@ function transitionJobRecord(record, change) {
|
|
|
724
740
|
// packages/jobs/src/queue.ts
|
|
725
741
|
var import_node_os = require("node:os");
|
|
726
742
|
var JobQueue = class {
|
|
743
|
+
/** Create an instance with the supplied configuration; omitted fields use the documented defaults. */
|
|
727
744
|
constructor(opts = {}) {
|
|
728
745
|
this.opts = opts;
|
|
729
746
|
}
|
|
@@ -768,9 +785,11 @@ var JobQueue = class {
|
|
|
768
785
|
this.opts.onChange?.(job.record);
|
|
769
786
|
return job;
|
|
770
787
|
}
|
|
788
|
+
/** Look up a retained job by ID; returns undefined when unknown or dropped. */
|
|
771
789
|
get(id) {
|
|
772
790
|
return this.jobs.get(id);
|
|
773
791
|
}
|
|
792
|
+
/** Return the current records for all retained jobs, including pending and finished jobs. */
|
|
774
793
|
list() {
|
|
775
794
|
return [...this.jobs.values()].map((j) => j.record);
|
|
776
795
|
}
|
package/dist/artifacts.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ResolvedFunction } from './functions.js';
|
|
2
2
|
import { type TemplateContext } from './template.js';
|
|
3
|
+
/** Compile an artifact glob into an anchored regular expression. */
|
|
3
4
|
export declare function globToRegex(pattern: string): RegExp;
|
|
4
5
|
/** Every file below `dir`, following symlinks without looping. */
|
|
5
6
|
export declare function listFilesRecursive(dir: string, visited?: Set<string>): string[];
|
|
@@ -13,7 +14,9 @@ export declare function findFilesByArtifactPattern(pattern: string, context: Tem
|
|
|
13
14
|
export declare function artifactIncludePatterns(fn: ResolvedFunction): string[];
|
|
14
15
|
/** How many artifacts a function must produce: `min`, or 1 for `required: true`. */
|
|
15
16
|
export declare function artifactRequirement(fn: ResolvedFunction): number;
|
|
17
|
+
/** An artifact copied to its destination, recording the original source path. */
|
|
16
18
|
export interface CopiedArtifact {
|
|
19
|
+
/** Original source path of the copied artifact. */
|
|
17
20
|
source: string;
|
|
18
21
|
/** `<keyPrefix><artifacts dir name>/<file name>`. */
|
|
19
22
|
key: string;
|
|
@@ -31,3 +34,4 @@ export declare function copyConfiguredArtifacts(fn: ResolvedFunction, context: T
|
|
|
31
34
|
}, opts?: {
|
|
32
35
|
keyPrefix?: string;
|
|
33
36
|
}): CopiedArtifact[];
|
|
37
|
+
//# sourceMappingURL=artifacts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"artifacts.d.ts","sourceRoot":"","sources":["../src/artifacts.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAK3E,oEAAoE;AACpE,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAiBnD;AAWD,kEAAkE;AAClE,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,cAAoB,GAAG,MAAM,EAAE,CAarF;AAED,oFAAoF;AACpF,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG;IAAE,KAAK,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAAG,MAAM,EAAE,CAQhI;AAED,sFAAsF;AACtF,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,gBAAgB,GAAG,MAAM,EAAE,CAatE;AAED,oFAAoF;AACpF,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,gBAAgB,GAAG,MAAM,CAMhE;AAED,iFAAiF;AACjF,MAAM,WAAW,cAAc;IAC7B,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC;IACf,qDAAqD;IACrD,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,EAAE,EAAE,gBAAgB,EACpB,OAAO,EAAE,eAAe,GAAG;IAAE,KAAK,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,EACjF,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAChC,cAAc,EAAE,CAwBlB"}
|
package/dist/artifacts.js
CHANGED
|
@@ -5,6 +5,7 @@ import { renderTemplateString } from './template.js';
|
|
|
5
5
|
import { copyFileEnsuringDir, isDirectoryPath, isFilePath, statFollowingSymlinks, truthy } from './util.js';
|
|
6
6
|
// A small glob subset as a regex: `*`, `?`, `**` and `**/` (which also matches no
|
|
7
7
|
// directory), with `/` separators.
|
|
8
|
+
/** Compile an artifact glob into an anchored regular expression. */
|
|
8
9
|
export function globToRegex(pattern) {
|
|
9
10
|
const source = String(pattern).replace(/\\/g, '/');
|
|
10
11
|
let out = '^';
|
package/dist/functions.d.ts
CHANGED
|
@@ -7,13 +7,17 @@ import { type Json } from './util.js';
|
|
|
7
7
|
* controlled by media-dropoff".
|
|
8
8
|
*/
|
|
9
9
|
export declare function configureLogging(opts: {
|
|
10
|
-
module?: string;
|
|
10
|
+
module?: string; /** Write a timestamped warning to stderr; supports printf-style arguments. */
|
|
11
11
|
warn?: (...args: unknown[]) => void;
|
|
12
12
|
verbose?: (...args: unknown[]) => void;
|
|
13
13
|
}): void;
|
|
14
|
+
/** A configured binding to an executable, optional input/output files, artifacts, and result templates. */
|
|
14
15
|
export interface FunctionDefinition {
|
|
16
|
+
/** Config key holding function options; defaults to the function name. */
|
|
15
17
|
key?: string;
|
|
18
|
+
/** Executable path; configured paths are resolved against the script root. */
|
|
16
19
|
script?: string;
|
|
20
|
+
/** Explicit positional templates; an empty array explicitly supplies no positionals. */
|
|
17
21
|
positionals?: unknown[];
|
|
18
22
|
/** Positionals before or after options (default: first). */
|
|
19
23
|
positionals_order?: 'first' | 'last';
|
|
@@ -21,11 +25,15 @@ export interface FunctionDefinition {
|
|
|
21
25
|
stdin?: string;
|
|
22
26
|
/** A template for the file the tool's stdout is written to; it also counts as an artifact. */
|
|
23
27
|
stdout?: string;
|
|
28
|
+
/** Artifact configuration consumed by the job engine after execution. */
|
|
24
29
|
artifacts?: unknown;
|
|
30
|
+
/** Task result or configured result templates, according to the containing interface. */
|
|
25
31
|
result?: unknown;
|
|
26
32
|
[key: string]: unknown;
|
|
27
33
|
}
|
|
34
|
+
/** A configured function with resolved executable and merged defaults and overrides. */
|
|
28
35
|
export interface ResolvedFunction {
|
|
36
|
+
/** Name of the configured function to invoke. */
|
|
29
37
|
functionName: string;
|
|
30
38
|
/** The config key holding its options (default: the function name). */
|
|
31
39
|
key: string;
|
|
@@ -33,6 +41,7 @@ export interface ResolvedFunction {
|
|
|
33
41
|
script: string;
|
|
34
42
|
/** `deepMerge(defaults[key], config[key])`. */
|
|
35
43
|
config: Json;
|
|
44
|
+
/** Original function binding, including positional, stream, artifact, and result templates. */
|
|
36
45
|
definition: FunctionDefinition;
|
|
37
46
|
}
|
|
38
47
|
/**
|
|
@@ -40,9 +49,11 @@ export interface ResolvedFunction {
|
|
|
40
49
|
* has no such function. A function without a `script` is an error.
|
|
41
50
|
*/
|
|
42
51
|
export declare function resolveFunctionConfig(scriptRoot: string, routeConfig: Json, functionName: string): ResolvedFunction | null;
|
|
52
|
+
/** Path base, template context, and controlled arguments used when constructing a job command. */
|
|
43
53
|
export interface CommandOptions {
|
|
44
54
|
/** Directory relative path options resolve against. */
|
|
45
55
|
configRoot: string;
|
|
56
|
+
/** Values available to dotted-path templates; null disables context-based rendering. */
|
|
46
57
|
context?: TemplateContext | null;
|
|
47
58
|
/** Options the trigger sets itself; config keys for them are ignored with a warning. Default: ['help']. */
|
|
48
59
|
controlled?: string[];
|
|
@@ -54,6 +65,7 @@ export interface CommandOptions {
|
|
|
54
65
|
}
|
|
55
66
|
/** The full command line for a function: [script, ...argv]. */
|
|
56
67
|
export declare function buildFunctionCommand(fn: ResolvedFunction, opts: CommandOptions, schema?: Schema): Promise<string[]>;
|
|
68
|
+
/** The completed tool execution with a color-stripped stderr tail. */
|
|
57
69
|
export interface FunctionRun extends RunResult {
|
|
58
70
|
/** The last 40 lines of stderr without color codes. */
|
|
59
71
|
stderrTail: string[];
|
|
@@ -64,10 +76,10 @@ export interface FunctionRun extends RunResult {
|
|
|
64
76
|
* result), and stderr is collected. Secret options are `***` in `command`.
|
|
65
77
|
*/
|
|
66
78
|
export declare function runScriptFunction(fn: ResolvedFunction, opts: CommandOptions & {
|
|
67
|
-
timeoutMs?: number;
|
|
68
|
-
env?: NodeJS.ProcessEnv;
|
|
69
|
-
cwd?: string;
|
|
70
|
-
signal?: AbortSignal;
|
|
79
|
+
timeoutMs?: number; /** Environment supplied to the process or read for option defaults. */
|
|
80
|
+
env?: NodeJS.ProcessEnv; /** Working directory used to resolve command-line paths and execute tools. */
|
|
81
|
+
cwd?: string; /** AbortSignal used to cancel execution and terminate the tool process tree. */
|
|
82
|
+
signal?: AbortSignal; /** Called for each decoded stderr chunk. */
|
|
71
83
|
onStderr?: (chunk: string) => void;
|
|
72
84
|
}): Promise<FunctionRun>;
|
|
73
85
|
/** The result record every function shares, plus its rendered `result` map. */
|
|
@@ -76,6 +88,8 @@ export declare function buildResultRecord(p: {
|
|
|
76
88
|
fields?: Json;
|
|
77
89
|
fn: ResolvedFunction;
|
|
78
90
|
artifactKeys: string[];
|
|
91
|
+
/** Values available to dotted-path templates; null disables context-based rendering. */
|
|
79
92
|
context: TemplateContext;
|
|
80
93
|
now: string;
|
|
81
94
|
}): Json;
|
|
95
|
+
//# sourceMappingURL=functions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"functions.d.ts","sourceRoot":"","sources":["../src/functions.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/queue.d.ts
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { type JobRecord } from './records.js';
|
|
2
|
+
/** A queued task, its current record, completion promise, optional result, and cancellation method. */
|
|
2
3
|
export interface Job<R> {
|
|
4
|
+
/** Current serializable job state, updated as the task progresses. */
|
|
3
5
|
record: JobRecord;
|
|
6
|
+
/** Task result or configured result templates, according to the containing interface. */
|
|
4
7
|
result?: R;
|
|
5
8
|
/** Resolves with the result when the job is done (rejects when it fails). */
|
|
6
9
|
done: Promise<R>;
|
|
7
10
|
/** Abort a pending or running job. */
|
|
8
11
|
cancel(): void;
|
|
9
12
|
}
|
|
13
|
+
/** Concurrency, retention, ID prefix, and lifecycle callbacks for the in-memory queue. */
|
|
10
14
|
export interface QueueOptions {
|
|
11
15
|
/** Jobs run at the same time (default: the number of CPUs). */
|
|
12
16
|
concurrency?: number;
|
|
@@ -19,23 +23,28 @@ export interface QueueOptions {
|
|
|
19
23
|
/** Called when a finished job is dropped (beyond `keep`), e.g. to remove its files. */
|
|
20
24
|
onDrop?: (record: JobRecord) => void;
|
|
21
25
|
}
|
|
26
|
+
/** An in-memory queue with bounded concurrency and retention. Await each job.done to observe its result. */
|
|
22
27
|
export declare class JobQueue<R = unknown> {
|
|
23
28
|
private opts;
|
|
24
29
|
private jobs;
|
|
25
30
|
private waiting;
|
|
26
31
|
private running;
|
|
27
32
|
private finished;
|
|
33
|
+
/** Create an instance with the supplied configuration; omitted fields use the documented defaults. */
|
|
28
34
|
constructor(opts?: QueueOptions);
|
|
29
35
|
/**
|
|
30
36
|
* Queue `task` as a job of `functionName`. The task gets an AbortSignal and
|
|
31
37
|
* a `stage` setter for progress; its return value is the job's result.
|
|
32
38
|
*/
|
|
33
39
|
add(functionName: string, task: (ctx: {
|
|
34
|
-
signal: AbortSignal;
|
|
40
|
+
signal: AbortSignal; /** Current progress stage, or null when unspecified. */
|
|
35
41
|
stage: (stage: string) => void;
|
|
36
42
|
}) => Promise<R>, fields?: Record<string, unknown>): Job<R>;
|
|
43
|
+
/** Look up a retained job by ID; returns undefined when unknown or dropped. */
|
|
37
44
|
get(id: string): Job<R> | undefined;
|
|
45
|
+
/** Return the current records for all retained jobs, including pending and finished jobs. */
|
|
38
46
|
list(): JobRecord[];
|
|
39
47
|
private slot;
|
|
40
48
|
private release;
|
|
41
49
|
}
|
|
50
|
+
//# sourceMappingURL=queue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../src/queue.ts"],"names":[],"mappings":"AAGA,OAAO,EAA+C,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAG3F,uGAAuG;AACvG,MAAM,WAAW,GAAG,CAAC,CAAC;IACpB,sEAAsE;IACtE,MAAM,EAAE,SAAS,CAAC;IAClB,yFAAyF;IACzF,MAAM,CAAC,EAAE,CAAC,CAAC;IACX,6EAA6E;IAC7E,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IACjB,sCAAsC;IACtC,MAAM,IAAI,IAAI,CAAC;CAChB;AAED,0FAA0F;AAC1F,MAAM,WAAW,YAAY;IAC3B,+DAA+D;IAC/D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0CAA0C;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,CAAC;IACvC,uFAAuF;IACvF,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,CAAC;CACtC;AAED,4GAA4G;AAC5G,qBAAa,QAAQ,CAAC,CAAC,GAAG,OAAO;IAOnB,OAAO,CAAC,IAAI;IANxB,OAAO,CAAC,IAAI,CAA6B;IACzC,OAAO,CAAC,OAAO,CAAsB;IACrC,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,QAAQ,CAAgB;IAEhC,sGAAsG;gBAClF,IAAI,GAAE,YAAiB;IAE3C;;;OAGG;IACH,GAAG,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,EAAE;QAA4G,MAAM,EAAE,WAAW,CAAC,CAAC,wDAAwD;QAAC,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,GAAG,CAAC,CAAC,CAAC;IAiC9T,+EAA+E;IAC/E,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,SAAS;IAInC,6FAA6F;IAC7F,IAAI,IAAI,SAAS,EAAE;IAInB,OAAO,CAAC,IAAI;IAQZ,OAAO,CAAC,OAAO;CAYhB"}
|
package/dist/queue.js
CHANGED
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
import { availableParallelism } from 'node:os';
|
|
4
4
|
import { newJobId, newJobRecord, transitionJobRecord } from './records.js';
|
|
5
5
|
import { timestampIso } from './util.js';
|
|
6
|
+
/** An in-memory queue with bounded concurrency and retention. Await each job.done to observe its result. */
|
|
6
7
|
export class JobQueue {
|
|
7
8
|
opts;
|
|
8
9
|
jobs = new Map();
|
|
9
10
|
waiting = [];
|
|
10
11
|
running = 0;
|
|
11
12
|
finished = [];
|
|
13
|
+
/** Create an instance with the supplied configuration; omitted fields use the documented defaults. */
|
|
12
14
|
constructor(opts = {}) {
|
|
13
15
|
this.opts = opts;
|
|
14
16
|
}
|
|
@@ -50,9 +52,11 @@ export class JobQueue {
|
|
|
50
52
|
this.opts.onChange?.(job.record);
|
|
51
53
|
return job;
|
|
52
54
|
}
|
|
55
|
+
/** Look up a retained job by ID; returns undefined when unknown or dropped. */
|
|
53
56
|
get(id) {
|
|
54
57
|
return this.jobs.get(id);
|
|
55
58
|
}
|
|
59
|
+
/** Return the current records for all retained jobs, including pending and finished jobs. */
|
|
56
60
|
list() {
|
|
57
61
|
return [...this.jobs.values()].map((j) => j.record);
|
|
58
62
|
}
|
package/dist/records.d.ts
CHANGED
|
@@ -1,17 +1,29 @@
|
|
|
1
|
+
/** Job lifecycle: pending, processing, done, or error. */
|
|
1
2
|
export type JobStatus = 'pending' | 'processing' | 'done' | 'error';
|
|
3
|
+
/** A serializable job state with lifecycle timestamps, error information, and history. */
|
|
2
4
|
export interface JobRecord {
|
|
5
|
+
/** Unique identifier for the job. */
|
|
3
6
|
job_id: string;
|
|
7
|
+
/** Configured function associated with the job. */
|
|
4
8
|
function: string;
|
|
9
|
+
/** Lifecycle state or HTTP input-error status, according to the containing type. */
|
|
5
10
|
status: JobStatus;
|
|
11
|
+
/** Current progress stage, or null when unspecified. */
|
|
6
12
|
stage: string | null;
|
|
13
|
+
/** ISO timestamp when the record was created. */
|
|
7
14
|
started_at: string;
|
|
15
|
+
/** ISO timestamp of the last transition. */
|
|
8
16
|
updated_at: string;
|
|
17
|
+
/** ISO timestamp when execution finished, or null while unfinished. */
|
|
9
18
|
completed_at: string | null;
|
|
19
|
+
/** Failure message, or null while no error has been recorded. */
|
|
10
20
|
error: string | null;
|
|
21
|
+
/** Storage key of the result, or null when absent. */
|
|
11
22
|
result_key: string | null;
|
|
23
|
+
/** Earlier job states, newest first. */
|
|
12
24
|
history: {
|
|
13
|
-
status: JobStatus;
|
|
14
|
-
stage: string | null;
|
|
25
|
+
status: JobStatus; /** Current progress stage, or null when unspecified. */
|
|
26
|
+
stage: string | null; /** ISO timestamp of the last transition. */
|
|
15
27
|
updated_at: string;
|
|
16
28
|
}[];
|
|
17
29
|
[field: string]: unknown;
|
|
@@ -20,18 +32,19 @@ export interface JobRecord {
|
|
|
20
32
|
export declare function newJobId(seed: string, prefix?: string): string;
|
|
21
33
|
/** A new record in the `pending` state; `fields` are flattened onto it. */
|
|
22
34
|
export declare function newJobRecord(p: {
|
|
23
|
-
jobId: string;
|
|
35
|
+
jobId: string; /** Name of the configured function to invoke. */
|
|
24
36
|
functionName: string;
|
|
25
37
|
fields?: Record<string, unknown>;
|
|
26
38
|
now?: string;
|
|
27
39
|
}): JobRecord;
|
|
28
40
|
/** Move a record on, pushing its current state onto the front of `history`. Returns a new record. */
|
|
29
41
|
export declare function transitionJobRecord(record: JobRecord, change: {
|
|
30
|
-
status: JobStatus;
|
|
31
|
-
stage?: string | null;
|
|
42
|
+
status: JobStatus; /** Current progress stage, or null when unspecified. */
|
|
43
|
+
stage?: string | null; /** Write a timestamped error to stderr; supports printf-style arguments. */
|
|
32
44
|
error?: string | null;
|
|
33
45
|
resultKey?: string | null;
|
|
34
46
|
completedAt?: string | null;
|
|
35
47
|
now?: string;
|
|
36
48
|
extra?: Record<string, unknown>;
|
|
37
49
|
}): JobRecord;
|
|
50
|
+
//# sourceMappingURL=records.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"records.d.ts","sourceRoot":"","sources":["../src/records.ts"],"names":[],"mappings":"AAOA,0DAA0D;AAC1D,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,YAAY,GAAG,MAAM,GAAG,OAAO,CAAC;AAEpE,0FAA0F;AAC1F,MAAM,WAAW,SAAS;IACxB,qCAAqC;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAC;IACjB,oFAAoF;IACpF,MAAM,EAAE,SAAS,CAAC;IAClB,wDAAwD;IACxD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,iDAAiD;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,4CAA4C;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,iEAAiE;IACjE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,sDAAsD;IACtD,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,wCAAwC;IACxC,OAAO,EAAE;QAAuF,MAAM,EAAE,SAAS,CAAC,CAAC,wDAAwD;QAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,4CAA4C;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACtQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CAC1B;AAED,gGAAgG;AAChG,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,SAAQ,GAAG,MAAM,CAG7D;AAED,2EAA2E;AAC3E,wBAAgB,YAAY,CAAC,CAAC,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC,CAAC,iDAAiD;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAepL;AAED,qGAAqG;AACrG,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE;IAAuF,MAAM,EAAE,SAAS,CAAC,CAAC,wDAAwD;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,4EAA4E;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAC5Y,SAAS,CAYX"}
|
package/dist/template.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
/** Values available to dotted-path template expressions. */
|
|
1
2
|
export type TemplateContext = {
|
|
2
3
|
[key: string]: unknown;
|
|
3
4
|
};
|
|
4
5
|
/** A dot-path value from the context; unknown paths are errors, objects become JSON. */
|
|
5
6
|
export declare function lookupTemplateValue(context: TemplateContext, expression: string): string;
|
|
7
|
+
/** Replace dotted-path ${expression} and {{expression}} templates using the context. */
|
|
6
8
|
export declare function renderTemplateString(value: string, context: TemplateContext): string;
|
|
7
9
|
/** Render strings and the strings in arrays; other scalars keep their type. */
|
|
8
10
|
export declare function renderConfigValue(value: unknown, context?: TemplateContext | null): unknown;
|
|
@@ -12,3 +14,4 @@ export declare function renderResultValue(value: unknown, context: TemplateConte
|
|
|
12
14
|
export declare function renderResultMap(resultMap: unknown, context: TemplateContext): {
|
|
13
15
|
[key: string]: unknown;
|
|
14
16
|
};
|
|
17
|
+
//# sourceMappingURL=template.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../src/template.ts"],"names":[],"mappings":"AACA,4DAA4D;AAC5D,MAAM,MAAM,eAAe,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC;AAEzD,wFAAwF;AACxF,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAUxF;AAED,wFAAwF;AACxF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,MAAM,CAIpF;AAED,+EAA+E;AAC/E,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,IAAI,GAAG,OAAO,CAK3F;AAED,4EAA4E;AAC5E,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAOnF;AAED,0EAA0E;AAC1E,wBAAgB,eAAe,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,eAAe,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAGxG"}
|
package/dist/template.js
CHANGED
|
@@ -11,6 +11,7 @@ export function lookupTemplateValue(context, expression) {
|
|
|
11
11
|
return '';
|
|
12
12
|
return typeof current === 'object' ? JSON.stringify(current) : String(current);
|
|
13
13
|
}
|
|
14
|
+
/** Replace dotted-path ${expression} and {{expression}} templates using the context. */
|
|
14
15
|
export function renderTemplateString(value, context) {
|
|
15
16
|
return String(value).replace(/\$\{\s*([A-Za-z0-9_.-]+)\s*\}|\{\{\s*([A-Za-z0-9_.-]+)\s*\}\}/g, (_, dollar, brace) => lookupTemplateValue(context, dollar || brace));
|
|
16
17
|
}
|
package/dist/util.d.ts
CHANGED
|
@@ -5,14 +5,19 @@ export declare function timestampIso(): string;
|
|
|
5
5
|
export declare function isEnabled(value: unknown): boolean;
|
|
6
6
|
/** Human-friendly booleans used in config files. */
|
|
7
7
|
export declare function truthy(value: unknown): boolean;
|
|
8
|
+
/** Create the directory and missing ancestors; filesystem failures throw. */
|
|
8
9
|
export declare function ensureDir(dir: string): void;
|
|
10
|
+
/** Read and parse a UTF-8 JSON file; malformed JSON and filesystem failures throw. */
|
|
9
11
|
export declare function readJson<T = unknown>(file: string): T;
|
|
10
12
|
/** Stable pretty-printed JSON with a trailing newline. */
|
|
11
13
|
export declare function writeJson(file: string, value: unknown): void;
|
|
12
14
|
/** stat() following symlinks; null when missing or inaccessible. */
|
|
13
15
|
export declare function statFollowingSymlinks(file: string): Stats | null;
|
|
16
|
+
/** Whether the path exists and refers to a directory, following symlinks. */
|
|
14
17
|
export declare function isDirectoryPath(file: string): boolean;
|
|
18
|
+
/** Whether the path exists and refers to a regular file, following symlinks. */
|
|
15
19
|
export declare function isFilePath(file: string): boolean;
|
|
20
|
+
/** Create destination parents and copy the file; filesystem failures throw. */
|
|
16
21
|
export declare function copyFileEnsuringDir(source: string, destination: string): void;
|
|
17
22
|
/** Path components for templates: `{path, dir, name, basename, ext}`. */
|
|
18
23
|
export declare function pathParts(file: string): {
|
|
@@ -22,6 +27,7 @@ export declare function pathParts(file: string): {
|
|
|
22
27
|
basename: string;
|
|
23
28
|
ext: string;
|
|
24
29
|
};
|
|
30
|
+
/** A JSON object used for job configuration. */
|
|
25
31
|
export type Json = {
|
|
26
32
|
[key: string]: unknown;
|
|
27
33
|
};
|
|
@@ -36,3 +42,4 @@ export declare function deepMerge(base: unknown, override: unknown): unknown;
|
|
|
36
42
|
export declare function resolveConfigPath(root: string, value: string): string;
|
|
37
43
|
/** Same rules as resolveConfigPath, for script paths against the script root. */
|
|
38
44
|
export declare const resolveScriptPath: typeof resolveConfigPath;
|
|
45
|
+
//# sourceMappingURL=util.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AACA,OAAO,EAAmD,KAAK,KAAK,EAAiB,MAAM,SAAS,CAAC;AAGrG,8DAA8D;AAC9D,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED,qEAAqE;AACrE,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEjD;AAED,oDAAoD;AACpD,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE9C;AAED,6EAA6E;AAC7E,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAE3C;AAED,sFAAsF;AACtF,wBAAgB,QAAQ,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,CAErD;AAED,0DAA0D;AAC1D,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAE5D;AAED,oEAAoE;AACpE,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CAMhE;AAED,6EAA6E;AAC7E,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAErD;AAED,gFAAgF;AAChF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEhD;AAED,+EAA+E;AAC/E,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CAG7E;AAED,yEAAyE;AACzE,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM;;;;;;EAIrC;AAED,gDAAgD;AAChD,MAAM,MAAM,IAAI,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC;AAE9C,4DAA4D;AAC5D,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAEhD;AAED,2EAA2E;AAC3E,wBAAgB,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAMnE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAIrE;AAED,iFAAiF;AACjF,eAAO,MAAM,iBAAiB,0BAAoB,CAAC"}
|
package/dist/util.js
CHANGED
|
@@ -13,9 +13,11 @@ export function isEnabled(value) {
|
|
|
13
13
|
export function truthy(value) {
|
|
14
14
|
return /^(true|1|yes|on)$/i.test(String(value));
|
|
15
15
|
}
|
|
16
|
+
/** Create the directory and missing ancestors; filesystem failures throw. */
|
|
16
17
|
export function ensureDir(dir) {
|
|
17
18
|
mkdirSync(dir, { recursive: true });
|
|
18
19
|
}
|
|
20
|
+
/** Read and parse a UTF-8 JSON file; malformed JSON and filesystem failures throw. */
|
|
19
21
|
export function readJson(file) {
|
|
20
22
|
return JSON.parse(readFileSync(file, 'utf8'));
|
|
21
23
|
}
|
|
@@ -32,12 +34,15 @@ export function statFollowingSymlinks(file) {
|
|
|
32
34
|
return null;
|
|
33
35
|
}
|
|
34
36
|
}
|
|
37
|
+
/** Whether the path exists and refers to a directory, following symlinks. */
|
|
35
38
|
export function isDirectoryPath(file) {
|
|
36
39
|
return Boolean(statFollowingSymlinks(file)?.isDirectory());
|
|
37
40
|
}
|
|
41
|
+
/** Whether the path exists and refers to a regular file, following symlinks. */
|
|
38
42
|
export function isFilePath(file) {
|
|
39
43
|
return Boolean(statFollowingSymlinks(file)?.isFile());
|
|
40
44
|
}
|
|
45
|
+
/** Create destination parents and copy the file; filesystem failures throw. */
|
|
41
46
|
export function copyFileEnsuringDir(source, destination) {
|
|
42
47
|
ensureDir(dirname(destination));
|
|
43
48
|
copyFileSync(source, destination);
|
|
@@ -46,7 +51,7 @@ export function copyFileEnsuringDir(source, destination) {
|
|
|
46
51
|
export function pathParts(file) {
|
|
47
52
|
const name = basename(file);
|
|
48
53
|
const ext = extname(name);
|
|
49
|
-
return { path: file, dir: dirname(file), name, basename: ext ? name.slice(0, -ext.length) : name, ext: ext.replace(/^\./, '') };
|
|
54
|
+
return { /** Resolved filesystem path or HTTP endpoint path, according to the containing type. */ path: file, /** Directory represented by this group or specified in .clyops settings. */ dir: dirname(file), /** Program, command, option, or argument name as declared in its containing type. */ name, /** Filename without its extension. */ basename: ext ? name.slice(0, -ext.length) : name, /** Filename extension including the leading dot. */ ext: ext.replace(/^\./, '') };
|
|
50
55
|
}
|
|
51
56
|
/** A plain object, or `{}` for scalars, arrays and null. */
|
|
52
57
|
export function objectValue(value) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clyops-jobs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Run clyops tools as jobs: config-bound functions, templated invocation, artifacts, job records and a queue.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -38,8 +38,9 @@
|
|
|
38
38
|
"typescript": "^5.6.0"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"clyops-tools": "0.
|
|
41
|
+
"clyops-tools": "0.4.0"
|
|
42
42
|
},
|
|
43
|
+
"homepage": "https://clyops.io/node-tools.html",
|
|
43
44
|
"scripts": {
|
|
44
45
|
"build": "tsc -p tsconfig.json",
|
|
45
46
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
package/src/artifacts.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { copyFileEnsuringDir, isDirectoryPath, isFilePath, statFollowingSymlinks
|
|
|
7
7
|
|
|
8
8
|
// A small glob subset as a regex: `*`, `?`, `**` and `**/` (which also matches no
|
|
9
9
|
// directory), with `/` separators.
|
|
10
|
+
/** Compile an artifact glob into an anchored regular expression. */
|
|
10
11
|
export function globToRegex(pattern: string): RegExp {
|
|
11
12
|
const source = String(pattern).replace(/\\/g, '/');
|
|
12
13
|
let out = '^';
|
|
@@ -87,7 +88,9 @@ export function artifactRequirement(fn: ResolvedFunction): number {
|
|
|
87
88
|
return artifacts.required === true || truthy(artifacts.required) ? 1 : 0;
|
|
88
89
|
}
|
|
89
90
|
|
|
91
|
+
/** An artifact copied to its destination, recording the original source path. */
|
|
90
92
|
export interface CopiedArtifact {
|
|
93
|
+
/** Original source path of the copied artifact. */
|
|
91
94
|
source: string;
|
|
92
95
|
/** `<keyPrefix><artifacts dir name>/<file name>`. */
|
|
93
96
|
key: string;
|
package/src/functions.ts
CHANGED
|
@@ -26,13 +26,17 @@ const LOGGER = {
|
|
|
26
26
|
* arguments; `module` names the trigger in messages such as "--help is
|
|
27
27
|
* controlled by media-dropoff".
|
|
28
28
|
*/
|
|
29
|
-
export function configureLogging(opts: { module?: string; warn?: (...args: unknown[]) => void; verbose?: (...args: unknown[]) => void }): void {
|
|
29
|
+
export function configureLogging(opts: { module?: string; /** Write a timestamped warning to stderr; supports printf-style arguments. */ warn?: (...args: unknown[]) => void; verbose?: (...args: unknown[]) => void }): void {
|
|
30
30
|
Object.assign(LOGGER, Object.fromEntries(Object.entries(opts).filter(([, v]) => v)));
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/** A configured binding to an executable, optional input/output files, artifacts, and result templates. */
|
|
33
34
|
export interface FunctionDefinition {
|
|
35
|
+
/** Config key holding function options; defaults to the function name. */
|
|
34
36
|
key?: string;
|
|
37
|
+
/** Executable path; configured paths are resolved against the script root. */
|
|
35
38
|
script?: string;
|
|
39
|
+
/** Explicit positional templates; an empty array explicitly supplies no positionals. */
|
|
36
40
|
positionals?: unknown[];
|
|
37
41
|
/** Positionals before or after options (default: first). */
|
|
38
42
|
positionals_order?: 'first' | 'last';
|
|
@@ -40,12 +44,16 @@ export interface FunctionDefinition {
|
|
|
40
44
|
stdin?: string;
|
|
41
45
|
/** A template for the file the tool's stdout is written to; it also counts as an artifact. */
|
|
42
46
|
stdout?: string;
|
|
47
|
+
/** Artifact configuration consumed by the job engine after execution. */
|
|
43
48
|
artifacts?: unknown;
|
|
49
|
+
/** Task result or configured result templates, according to the containing interface. */
|
|
44
50
|
result?: unknown;
|
|
45
51
|
[key: string]: unknown;
|
|
46
52
|
}
|
|
47
53
|
|
|
54
|
+
/** A configured function with resolved executable and merged defaults and overrides. */
|
|
48
55
|
export interface ResolvedFunction {
|
|
56
|
+
/** Name of the configured function to invoke. */
|
|
49
57
|
functionName: string;
|
|
50
58
|
/** The config key holding its options (default: the function name). */
|
|
51
59
|
key: string;
|
|
@@ -53,6 +61,7 @@ export interface ResolvedFunction {
|
|
|
53
61
|
script: string;
|
|
54
62
|
/** `deepMerge(defaults[key], config[key])`. */
|
|
55
63
|
config: Json;
|
|
64
|
+
/** Original function binding, including positional, stream, artifact, and result templates. */
|
|
56
65
|
definition: FunctionDefinition;
|
|
57
66
|
}
|
|
58
67
|
|
|
@@ -70,9 +79,11 @@ export function resolveFunctionConfig(scriptRoot: string, routeConfig: Json, fun
|
|
|
70
79
|
return { functionName, key, script: resolveScriptPath(scriptRoot, script), config, definition };
|
|
71
80
|
}
|
|
72
81
|
|
|
82
|
+
/** Path base, template context, and controlled arguments used when constructing a job command. */
|
|
73
83
|
export interface CommandOptions {
|
|
74
84
|
/** Directory relative path options resolve against. */
|
|
75
85
|
configRoot: string;
|
|
86
|
+
/** Values available to dotted-path templates; null disables context-based rendering. */
|
|
76
87
|
context?: TemplateContext | null;
|
|
77
88
|
/** Options the trigger sets itself; config keys for them are ignored with a warning. Default: ['help']. */
|
|
78
89
|
controlled?: string[];
|
|
@@ -115,6 +126,7 @@ export async function buildFunctionCommand(fn: ResolvedFunction, opts: CommandOp
|
|
|
115
126
|
return [fn.script, ...argv];
|
|
116
127
|
}
|
|
117
128
|
|
|
129
|
+
/** The completed tool execution with a color-stripped stderr tail. */
|
|
118
130
|
export interface FunctionRun extends RunResult {
|
|
119
131
|
/** The last 40 lines of stderr without color codes. */
|
|
120
132
|
stderrTail: string[];
|
|
@@ -134,7 +146,7 @@ function streamPath(template: string | undefined, opts: CommandOptions, base: st
|
|
|
134
146
|
*/
|
|
135
147
|
export async function runScriptFunction(
|
|
136
148
|
fn: ResolvedFunction,
|
|
137
|
-
opts: CommandOptions & { timeoutMs?: number; env?: NodeJS.ProcessEnv; cwd?: string; signal?: AbortSignal; onStderr?: (chunk: string) => void },
|
|
149
|
+
opts: CommandOptions & { timeoutMs?: number; /** Environment supplied to the process or read for option defaults. */ env?: NodeJS.ProcessEnv; /** Working directory used to resolve command-line paths and execute tools. */ cwd?: string; /** AbortSignal used to cancel execution and terminate the tool process tree. */ signal?: AbortSignal; /** Called for each decoded stderr chunk. */ onStderr?: (chunk: string) => void },
|
|
138
150
|
): Promise<FunctionRun> {
|
|
139
151
|
const schema = await loadSchema(fn.script);
|
|
140
152
|
const [file, ...argv] = await buildFunctionCommand(fn, opts, schema);
|
|
@@ -164,6 +176,7 @@ export function buildResultRecord(p: {
|
|
|
164
176
|
fields?: Json;
|
|
165
177
|
fn: ResolvedFunction;
|
|
166
178
|
artifactKeys: string[];
|
|
179
|
+
/** Values available to dotted-path templates; null disables context-based rendering. */
|
|
167
180
|
context: TemplateContext;
|
|
168
181
|
now: string;
|
|
169
182
|
}): Json {
|
package/src/queue.ts
CHANGED
|
@@ -4,8 +4,11 @@ import { availableParallelism } from 'node:os';
|
|
|
4
4
|
import { newJobId, newJobRecord, transitionJobRecord, type JobRecord } from './records.js';
|
|
5
5
|
import { timestampIso } from './util.js';
|
|
6
6
|
|
|
7
|
+
/** A queued task, its current record, completion promise, optional result, and cancellation method. */
|
|
7
8
|
export interface Job<R> {
|
|
9
|
+
/** Current serializable job state, updated as the task progresses. */
|
|
8
10
|
record: JobRecord;
|
|
11
|
+
/** Task result or configured result templates, according to the containing interface. */
|
|
9
12
|
result?: R;
|
|
10
13
|
/** Resolves with the result when the job is done (rejects when it fails). */
|
|
11
14
|
done: Promise<R>;
|
|
@@ -13,6 +16,7 @@ export interface Job<R> {
|
|
|
13
16
|
cancel(): void;
|
|
14
17
|
}
|
|
15
18
|
|
|
19
|
+
/** Concurrency, retention, ID prefix, and lifecycle callbacks for the in-memory queue. */
|
|
16
20
|
export interface QueueOptions {
|
|
17
21
|
/** Jobs run at the same time (default: the number of CPUs). */
|
|
18
22
|
concurrency?: number;
|
|
@@ -26,19 +30,21 @@ export interface QueueOptions {
|
|
|
26
30
|
onDrop?: (record: JobRecord) => void;
|
|
27
31
|
}
|
|
28
32
|
|
|
33
|
+
/** An in-memory queue with bounded concurrency and retention. Await each job.done to observe its result. */
|
|
29
34
|
export class JobQueue<R = unknown> {
|
|
30
35
|
private jobs = new Map<string, Job<R>>();
|
|
31
36
|
private waiting: (() => void)[] = [];
|
|
32
37
|
private running = 0;
|
|
33
38
|
private finished: string[] = [];
|
|
34
39
|
|
|
40
|
+
/** Create an instance with the supplied configuration; omitted fields use the documented defaults. */
|
|
35
41
|
constructor(private opts: QueueOptions = {}) {}
|
|
36
42
|
|
|
37
43
|
/**
|
|
38
44
|
* Queue `task` as a job of `functionName`. The task gets an AbortSignal and
|
|
39
45
|
* a `stage` setter for progress; its return value is the job's result.
|
|
40
46
|
*/
|
|
41
|
-
add(functionName: string, task: (ctx: { signal: AbortSignal; stage: (stage: string) => void }) => Promise<R>, fields: Record<string, unknown> = {}): Job<R> {
|
|
47
|
+
add(functionName: string, task: (ctx: { /** Termination signal, or null for an ordinary exit. RunOptions uses an AbortSignal for cancellation. */ signal: AbortSignal; /** Current progress stage, or null when unspecified. */ stage: (stage: string) => void }) => Promise<R>, fields: Record<string, unknown> = {}): Job<R> {
|
|
42
48
|
const id = newJobId(functionName, this.opts.prefix ?? 'job');
|
|
43
49
|
const abort = new AbortController();
|
|
44
50
|
const job = { record: newJobRecord({ jobId: id, functionName, fields }) } as Job<R>;
|
|
@@ -71,10 +77,12 @@ export class JobQueue<R = unknown> {
|
|
|
71
77
|
return job;
|
|
72
78
|
}
|
|
73
79
|
|
|
80
|
+
/** Look up a retained job by ID; returns undefined when unknown or dropped. */
|
|
74
81
|
get(id: string): Job<R> | undefined {
|
|
75
82
|
return this.jobs.get(id);
|
|
76
83
|
}
|
|
77
84
|
|
|
85
|
+
/** Return the current records for all retained jobs, including pending and finished jobs. */
|
|
78
86
|
list(): JobRecord[] {
|
|
79
87
|
return [...this.jobs.values()].map((j) => j.record);
|
|
80
88
|
}
|
package/src/records.ts
CHANGED
|
@@ -5,19 +5,31 @@
|
|
|
5
5
|
import { createHash } from 'node:crypto';
|
|
6
6
|
import { timestampIso } from './util.js';
|
|
7
7
|
|
|
8
|
+
/** Job lifecycle: pending, processing, done, or error. */
|
|
8
9
|
export type JobStatus = 'pending' | 'processing' | 'done' | 'error';
|
|
9
10
|
|
|
11
|
+
/** A serializable job state with lifecycle timestamps, error information, and history. */
|
|
10
12
|
export interface JobRecord {
|
|
13
|
+
/** Unique identifier for the job. */
|
|
11
14
|
job_id: string;
|
|
15
|
+
/** Configured function associated with the job. */
|
|
12
16
|
function: string;
|
|
17
|
+
/** Lifecycle state or HTTP input-error status, according to the containing type. */
|
|
13
18
|
status: JobStatus;
|
|
19
|
+
/** Current progress stage, or null when unspecified. */
|
|
14
20
|
stage: string | null;
|
|
21
|
+
/** ISO timestamp when the record was created. */
|
|
15
22
|
started_at: string;
|
|
23
|
+
/** ISO timestamp of the last transition. */
|
|
16
24
|
updated_at: string;
|
|
25
|
+
/** ISO timestamp when execution finished, or null while unfinished. */
|
|
17
26
|
completed_at: string | null;
|
|
27
|
+
/** Failure message, or null while no error has been recorded. */
|
|
18
28
|
error: string | null;
|
|
29
|
+
/** Storage key of the result, or null when absent. */
|
|
19
30
|
result_key: string | null;
|
|
20
|
-
|
|
31
|
+
/** Earlier job states, newest first. */
|
|
32
|
+
history: { /** Lifecycle state or HTTP input-error status, according to the containing type. */ status: JobStatus; /** Current progress stage, or null when unspecified. */ stage: string | null; /** ISO timestamp of the last transition. */ updated_at: string }[];
|
|
21
33
|
[field: string]: unknown;
|
|
22
34
|
}
|
|
23
35
|
|
|
@@ -28,7 +40,7 @@ export function newJobId(seed: string, prefix = 'job'): string {
|
|
|
28
40
|
}
|
|
29
41
|
|
|
30
42
|
/** A new record in the `pending` state; `fields` are flattened onto it. */
|
|
31
|
-
export function newJobRecord(p: { jobId: string; functionName: string; fields?: Record<string, unknown>; now?: string }): JobRecord {
|
|
43
|
+
export function newJobRecord(p: { jobId: string; /** Name of the configured function to invoke. */ functionName: string; fields?: Record<string, unknown>; now?: string }): JobRecord {
|
|
32
44
|
const now = p.now ?? timestampIso();
|
|
33
45
|
return {
|
|
34
46
|
job_id: p.jobId,
|
|
@@ -48,7 +60,7 @@ export function newJobRecord(p: { jobId: string; functionName: string; fields?:
|
|
|
48
60
|
/** Move a record on, pushing its current state onto the front of `history`. Returns a new record. */
|
|
49
61
|
export function transitionJobRecord(
|
|
50
62
|
record: JobRecord,
|
|
51
|
-
change: { status: JobStatus; stage?: string | null; error?: string | null; resultKey?: string | null; completedAt?: string | null; now?: string; extra?: Record<string, unknown> },
|
|
63
|
+
change: { /** Lifecycle state or HTTP input-error status, according to the containing type. */ status: JobStatus; /** Current progress stage, or null when unspecified. */ stage?: string | null; /** Write a timestamped error to stderr; supports printf-style arguments. */ error?: string | null; resultKey?: string | null; completedAt?: string | null; now?: string; extra?: Record<string, unknown> },
|
|
52
64
|
): JobRecord {
|
|
53
65
|
return {
|
|
54
66
|
...record,
|
package/src/template.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// `${dot.path}` and `{{dot.path}}` templates in config values.
|
|
2
|
+
/** Values available to dotted-path template expressions. */
|
|
2
3
|
export type TemplateContext = { [key: string]: unknown };
|
|
3
4
|
|
|
4
5
|
/** A dot-path value from the context; unknown paths are errors, objects become JSON. */
|
|
@@ -14,6 +15,7 @@ export function lookupTemplateValue(context: TemplateContext, expression: string
|
|
|
14
15
|
return typeof current === 'object' ? JSON.stringify(current) : String(current);
|
|
15
16
|
}
|
|
16
17
|
|
|
18
|
+
/** Replace dotted-path ${expression} and {{expression}} templates using the context. */
|
|
17
19
|
export function renderTemplateString(value: string, context: TemplateContext): string {
|
|
18
20
|
return String(value).replace(/\$\{\s*([A-Za-z0-9_.-]+)\s*\}|\{\{\s*([A-Za-z0-9_.-]+)\s*\}\}/g, (_, dollar, brace) =>
|
|
19
21
|
lookupTemplateValue(context, dollar || brace),
|
package/src/util.ts
CHANGED
|
@@ -17,10 +17,12 @@ export function truthy(value: unknown): boolean {
|
|
|
17
17
|
return /^(true|1|yes|on)$/i.test(String(value));
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/** Create the directory and missing ancestors; filesystem failures throw. */
|
|
20
21
|
export function ensureDir(dir: string): void {
|
|
21
22
|
mkdirSync(dir, { recursive: true });
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
/** Read and parse a UTF-8 JSON file; malformed JSON and filesystem failures throw. */
|
|
24
26
|
export function readJson<T = unknown>(file: string): T {
|
|
25
27
|
return JSON.parse(readFileSync(file, 'utf8')) as T;
|
|
26
28
|
}
|
|
@@ -39,14 +41,17 @@ export function statFollowingSymlinks(file: string): Stats | null {
|
|
|
39
41
|
}
|
|
40
42
|
}
|
|
41
43
|
|
|
44
|
+
/** Whether the path exists and refers to a directory, following symlinks. */
|
|
42
45
|
export function isDirectoryPath(file: string): boolean {
|
|
43
46
|
return Boolean(statFollowingSymlinks(file)?.isDirectory());
|
|
44
47
|
}
|
|
45
48
|
|
|
49
|
+
/** Whether the path exists and refers to a regular file, following symlinks. */
|
|
46
50
|
export function isFilePath(file: string): boolean {
|
|
47
51
|
return Boolean(statFollowingSymlinks(file)?.isFile());
|
|
48
52
|
}
|
|
49
53
|
|
|
54
|
+
/** Create destination parents and copy the file; filesystem failures throw. */
|
|
50
55
|
export function copyFileEnsuringDir(source: string, destination: string): void {
|
|
51
56
|
ensureDir(dirname(destination));
|
|
52
57
|
copyFileSync(source, destination);
|
|
@@ -56,9 +61,10 @@ export function copyFileEnsuringDir(source: string, destination: string): void {
|
|
|
56
61
|
export function pathParts(file: string) {
|
|
57
62
|
const name = basename(file);
|
|
58
63
|
const ext = extname(name);
|
|
59
|
-
return { path: file, dir: dirname(file), name, basename: ext ? name.slice(0, -ext.length) : name, ext: ext.replace(/^\./, '') };
|
|
64
|
+
return { /** Resolved filesystem path or HTTP endpoint path, according to the containing type. */ path: file, /** Directory represented by this group or specified in .clyops settings. */ dir: dirname(file), /** Program, command, option, or argument name as declared in its containing type. */ name, /** Filename without its extension. */ basename: ext ? name.slice(0, -ext.length) : name, /** Filename extension including the leading dot. */ ext: ext.replace(/^\./, '') };
|
|
60
65
|
}
|
|
61
66
|
|
|
67
|
+
/** A JSON object used for job configuration. */
|
|
62
68
|
export type Json = { [key: string]: unknown };
|
|
63
69
|
|
|
64
70
|
/** A plain object, or `{}` for scalars, arrays and null. */
|