yaml-flow 2.0.0 → 2.1.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 +251 -2
- package/dist/batch/index.cjs +109 -0
- package/dist/batch/index.cjs.map +1 -0
- package/dist/batch/index.d.cts +126 -0
- package/dist/batch/index.d.ts +126 -0
- package/dist/batch/index.js +107 -0
- package/dist/batch/index.js.map +1 -0
- package/dist/config/index.cjs +80 -0
- package/dist/config/index.cjs.map +1 -0
- package/dist/config/index.d.cts +71 -0
- package/dist/config/index.d.ts +71 -0
- package/dist/config/index.js +77 -0
- package/dist/config/index.js.map +1 -0
- package/dist/index.cjs +181 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +179 -1
- package/dist/index.js.map +1 -1
- package/package.json +11 -1
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// src/batch/runner.ts
|
|
2
|
+
async function batch(items, options) {
|
|
3
|
+
const {
|
|
4
|
+
concurrency = 5,
|
|
5
|
+
processor,
|
|
6
|
+
onItemComplete,
|
|
7
|
+
onItemError,
|
|
8
|
+
onProgress,
|
|
9
|
+
signal
|
|
10
|
+
} = options;
|
|
11
|
+
const total = items.length;
|
|
12
|
+
const results = new Array(total);
|
|
13
|
+
const batchStart = Date.now();
|
|
14
|
+
let completed = 0;
|
|
15
|
+
let failed = 0;
|
|
16
|
+
let nextIndex = 0;
|
|
17
|
+
function makeProgress(active) {
|
|
18
|
+
const done = completed + failed;
|
|
19
|
+
return {
|
|
20
|
+
completed,
|
|
21
|
+
failed,
|
|
22
|
+
active,
|
|
23
|
+
pending: total - done - active,
|
|
24
|
+
total,
|
|
25
|
+
percent: total === 0 ? 100 : Math.round(done / total * 100),
|
|
26
|
+
elapsedMs: Date.now() - batchStart
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
if (total === 0) {
|
|
30
|
+
return { items: [], completed: 0, failed: 0, total: 0, durationMs: 0 };
|
|
31
|
+
}
|
|
32
|
+
return new Promise((resolve) => {
|
|
33
|
+
let active = 0;
|
|
34
|
+
function tryStartNext() {
|
|
35
|
+
while (active < concurrency && nextIndex < total) {
|
|
36
|
+
if (signal?.aborted) {
|
|
37
|
+
while (nextIndex < total) {
|
|
38
|
+
const idx2 = nextIndex++;
|
|
39
|
+
results[idx2] = {
|
|
40
|
+
item: items[idx2],
|
|
41
|
+
index: idx2,
|
|
42
|
+
status: "failed",
|
|
43
|
+
error: new Error("Batch aborted"),
|
|
44
|
+
durationMs: 0
|
|
45
|
+
};
|
|
46
|
+
failed++;
|
|
47
|
+
}
|
|
48
|
+
if (active === 0 && completed + failed === total) {
|
|
49
|
+
resolve({
|
|
50
|
+
items: results,
|
|
51
|
+
completed,
|
|
52
|
+
failed,
|
|
53
|
+
total,
|
|
54
|
+
durationMs: Date.now() - batchStart
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
const idx = nextIndex++;
|
|
60
|
+
const item = items[idx];
|
|
61
|
+
active++;
|
|
62
|
+
const itemStart = Date.now();
|
|
63
|
+
processor(item, idx).then((result) => {
|
|
64
|
+
results[idx] = {
|
|
65
|
+
item,
|
|
66
|
+
index: idx,
|
|
67
|
+
status: "completed",
|
|
68
|
+
result,
|
|
69
|
+
durationMs: Date.now() - itemStart
|
|
70
|
+
};
|
|
71
|
+
completed++;
|
|
72
|
+
onItemComplete?.(item, result, idx);
|
|
73
|
+
}).catch((err) => {
|
|
74
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
75
|
+
results[idx] = {
|
|
76
|
+
item,
|
|
77
|
+
index: idx,
|
|
78
|
+
status: "failed",
|
|
79
|
+
error,
|
|
80
|
+
durationMs: Date.now() - itemStart
|
|
81
|
+
};
|
|
82
|
+
failed++;
|
|
83
|
+
onItemError?.(item, error, idx);
|
|
84
|
+
}).finally(() => {
|
|
85
|
+
active--;
|
|
86
|
+
onProgress?.(makeProgress(active));
|
|
87
|
+
if (completed + failed === total) {
|
|
88
|
+
resolve({
|
|
89
|
+
items: results,
|
|
90
|
+
completed,
|
|
91
|
+
failed,
|
|
92
|
+
total,
|
|
93
|
+
durationMs: Date.now() - batchStart
|
|
94
|
+
});
|
|
95
|
+
} else {
|
|
96
|
+
tryStartNext();
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
tryStartNext();
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export { batch };
|
|
106
|
+
//# sourceMappingURL=index.js.map
|
|
107
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/batch/runner.ts"],"names":["idx"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Batch Runner — Core\n *\n * Slot-based concurrent processor. Pure control flow — no I/O opinions.\n *\n * @example Step Machine batch\n * ```ts\n * import { batch } from 'yaml-flow/batch';\n * import { createStepMachine, loadStepFlow } from 'yaml-flow/step-machine';\n *\n * const flow = await loadStepFlow('./support-ticket.yaml');\n * const results = await batch(tickets, {\n * concurrency: 5,\n * processor: async (ticket) => {\n * const machine = createStepMachine(flow, handlers);\n * return machine.run(ticket);\n * },\n * });\n * ```\n *\n * @example Event Graph batch\n * ```ts\n * import { batch } from 'yaml-flow/batch';\n * import { next, apply, createInitialExecutionState } from 'yaml-flow/event-graph';\n *\n * const results = await batch(items, {\n * concurrency: 3,\n * processor: async (item, index) => {\n * let state = createInitialExecutionState(graph, `exec-${index}`);\n * state = apply(state, { type: 'inject-tokens', tokens: [item.token], timestamp: new Date().toISOString() }, graph);\n * // ... drive the graph loop\n * return state;\n * },\n * });\n * ```\n */\n\nimport type {\n BatchOptions,\n BatchResult,\n BatchItemResult,\n BatchProgress,\n} from './types.js';\n\n/**\n * Run an array of items through an async processor with concurrency control.\n *\n * - Items are started in order, up to `concurrency` at a time.\n * - Results are returned in the original item order.\n * - If a processor throws, the item is marked as failed; other items continue.\n * - An AbortSignal prevents new items from starting (in-flight items are not cancelled).\n */\nexport async function batch<TItem, TResult>(\n items: TItem[],\n options: BatchOptions<TItem, TResult>\n): Promise<BatchResult<TItem, TResult>> {\n const {\n concurrency = 5,\n processor,\n onItemComplete,\n onItemError,\n onProgress,\n signal,\n } = options;\n\n const total = items.length;\n const results: BatchItemResult<TItem, TResult>[] = new Array(total);\n const batchStart = Date.now();\n\n let completed = 0;\n let failed = 0;\n let nextIndex = 0;\n\n function makeProgress(active: number): BatchProgress {\n const done = completed + failed;\n return {\n completed,\n failed,\n active,\n pending: total - done - active,\n total,\n percent: total === 0 ? 100 : Math.round((done / total) * 100),\n elapsedMs: Date.now() - batchStart,\n };\n }\n\n // Empty input — short-circuit\n if (total === 0) {\n return { items: [], completed: 0, failed: 0, total: 0, durationMs: 0 };\n }\n\n return new Promise<BatchResult<TItem, TResult>>((resolve) => {\n let active = 0;\n\n function tryStartNext() {\n while (active < concurrency && nextIndex < total) {\n // Respect abort signal — don't start new items\n if (signal?.aborted) {\n // Mark remaining as failed with abort error\n while (nextIndex < total) {\n const idx = nextIndex++;\n results[idx] = {\n item: items[idx],\n index: idx,\n status: 'failed',\n error: new Error('Batch aborted'),\n durationMs: 0,\n };\n failed++;\n }\n // If nothing is in-flight, resolve immediately\n if (active === 0 && completed + failed === total) {\n resolve({\n items: results,\n completed,\n failed,\n total,\n durationMs: Date.now() - batchStart,\n });\n }\n break;\n }\n\n const idx = nextIndex++;\n const item = items[idx];\n active++;\n const itemStart = Date.now();\n\n processor(item, idx)\n .then((result) => {\n results[idx] = {\n item,\n index: idx,\n status: 'completed',\n result,\n durationMs: Date.now() - itemStart,\n };\n completed++;\n onItemComplete?.(item, result, idx);\n })\n .catch((err) => {\n const error = err instanceof Error ? err : new Error(String(err));\n results[idx] = {\n item,\n index: idx,\n status: 'failed',\n error,\n durationMs: Date.now() - itemStart,\n };\n failed++;\n onItemError?.(item, error, idx);\n })\n .finally(() => {\n active--;\n onProgress?.(makeProgress(active));\n\n if (completed + failed === total) {\n resolve({\n items: results,\n completed,\n failed,\n total,\n durationMs: Date.now() - batchStart,\n });\n } else {\n tryStartNext();\n }\n });\n }\n }\n\n tryStartNext();\n });\n}\n"]}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/config/resolve-variables.ts
|
|
4
|
+
function interpolateString(template, vars) {
|
|
5
|
+
return template.replace(/\$\{([^}]+)\}/g, (match, key) => {
|
|
6
|
+
const value = vars[key.trim()];
|
|
7
|
+
return value !== void 0 ? String(value) : match;
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
function walkAndInterpolate(value, vars) {
|
|
11
|
+
if (typeof value === "string") {
|
|
12
|
+
return interpolateString(value, vars);
|
|
13
|
+
}
|
|
14
|
+
if (Array.isArray(value)) {
|
|
15
|
+
return value.map((item) => walkAndInterpolate(item, vars));
|
|
16
|
+
}
|
|
17
|
+
if (value !== null && typeof value === "object") {
|
|
18
|
+
const result = {};
|
|
19
|
+
for (const [k, v] of Object.entries(value)) {
|
|
20
|
+
result[k] = walkAndInterpolate(v, vars);
|
|
21
|
+
}
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
function resolveVariables(config, variables) {
|
|
27
|
+
return walkAndInterpolate(config, variables);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/config/resolve-config-templates.ts
|
|
31
|
+
function mergeConfigs(template, taskConfig) {
|
|
32
|
+
const merged = { ...template };
|
|
33
|
+
for (const [key, value] of Object.entries(taskConfig)) {
|
|
34
|
+
if (key === "config-template") continue;
|
|
35
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value) && merged[key] !== null && typeof merged[key] === "object" && !Array.isArray(merged[key])) {
|
|
36
|
+
merged[key] = {
|
|
37
|
+
...merged[key],
|
|
38
|
+
...value
|
|
39
|
+
};
|
|
40
|
+
} else {
|
|
41
|
+
merged[key] = value;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return merged;
|
|
45
|
+
}
|
|
46
|
+
function resolveConfigTemplates(config) {
|
|
47
|
+
const templates = config["configTemplates"] ?? config["config-templates"] ?? {};
|
|
48
|
+
const tasksKey = "tasks" in config ? "tasks" : "steps" in config ? "steps" : null;
|
|
49
|
+
if (!tasksKey) return config;
|
|
50
|
+
const tasks = config[tasksKey];
|
|
51
|
+
if (!tasks || typeof tasks !== "object") return config;
|
|
52
|
+
const resolvedTasks = {};
|
|
53
|
+
for (const [name, task] of Object.entries(tasks)) {
|
|
54
|
+
const taskConfig = task["config"];
|
|
55
|
+
const templateName = taskConfig?.["config-template"];
|
|
56
|
+
if (!templateName || !taskConfig) {
|
|
57
|
+
resolvedTasks[name] = task;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const template = templates[templateName];
|
|
61
|
+
if (!template) {
|
|
62
|
+
const { "config-template": _, ...rest } = taskConfig;
|
|
63
|
+
resolvedTasks[name] = { ...task, config: rest };
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
resolvedTasks[name] = {
|
|
67
|
+
...task,
|
|
68
|
+
config: mergeConfigs(template, taskConfig)
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const result = { ...config, [tasksKey]: resolvedTasks };
|
|
72
|
+
delete result["configTemplates"];
|
|
73
|
+
delete result["config-templates"];
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
exports.resolveConfigTemplates = resolveConfigTemplates;
|
|
78
|
+
exports.resolveVariables = resolveVariables;
|
|
79
|
+
//# sourceMappingURL=index.cjs.map
|
|
80
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/config/resolve-variables.ts","../../src/config/resolve-config-templates.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Variable interpolation for workflow configs.\n *\n * Walks any object/array and replaces `${KEY}` patterns with values from\n * a variables map. Pure function — returns a new object, never mutates.\n *\n * Works on both GraphConfig and StepFlowConfig (or any plain object).\n *\n * @example\n * ```ts\n * const resolved = resolveVariables(config, {\n * ENTITY_ID: 'ticket-42',\n * TOOLS_DIR: '/opt/tools',\n * });\n * ```\n */\n\nexport type Variables = Record<string, string | number | boolean>;\n\n/**\n * Replace `${KEY}` patterns in a string with values from the variables map.\n * Unmatched variables are left as-is.\n */\nfunction interpolateString(template: string, vars: Variables): string {\n return template.replace(/\\$\\{([^}]+)\\}/g, (match, key: string) => {\n const value = vars[key.trim()];\n return value !== undefined ? String(value) : match;\n });\n}\n\n/**\n * Recursively walk a value and interpolate any `${KEY}` patterns found in strings.\n * Returns a new object/array — never mutates the input.\n */\nfunction walkAndInterpolate<T>(value: T, vars: Variables): T {\n if (typeof value === 'string') {\n return interpolateString(value, vars) as unknown as T;\n }\n if (Array.isArray(value)) {\n return value.map((item) => walkAndInterpolate(item, vars)) as unknown as T;\n }\n if (value !== null && typeof value === 'object') {\n const result: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value)) {\n result[k] = walkAndInterpolate(v, vars);\n }\n return result as T;\n }\n // numbers, booleans, null, undefined — pass through\n return value;\n}\n\n/**\n * Resolve `${KEY}` variable references in a workflow config object.\n *\n * Pure function: config in → new config out. Works on any shape\n * (GraphConfig, StepFlowConfig, or arbitrary objects).\n *\n * @param config - The config object to interpolate\n * @param variables - Key-value pairs to substitute\n * @returns A new config with all `${KEY}` patterns replaced\n */\nexport function resolveVariables<T extends Record<string, unknown>>(\n config: T,\n variables: Variables,\n): T {\n return walkAndInterpolate(config, variables);\n}\n","/**\n * Config template resolution for workflow configs.\n *\n * In large graphs, many tasks share the same base config (cmd, timeout, cwd, headers, etc.).\n * Instead of duplicating, tasks reference a named template via `config-template`.\n * This function deep-merges the template into each task's config, then removes the reference.\n *\n * Pure function — returns a new config, never mutates.\n *\n * @example\n * ```ts\n * const config = {\n * configTemplates: {\n * PYTHON_TOOL: { cmd: 'python', timeout: 30000, cwd: '/workdata' }\n * },\n * tasks: {\n * analyze: {\n * provides: ['analysis'],\n * config: { 'config-template': 'PYTHON_TOOL', 'cmd-args': 'analyze.py' }\n * }\n * }\n * };\n * const resolved = resolveConfigTemplates(config);\n * // analyze.config → { cmd: 'python', timeout: 30000, cwd: '/workdata', 'cmd-args': 'analyze.py' }\n * ```\n */\n\n/** Shape of a config-templates block */\nexport type ConfigTemplates = Record<string, Record<string, unknown>>;\n\n/**\n * Deep-merge template into task config.\n * Task-level values override template values.\n * Nested objects are merged one level deep (like SwarmX's pattern).\n */\nfunction mergeConfigs(\n template: Record<string, unknown>,\n taskConfig: Record<string, unknown>,\n): Record<string, unknown> {\n const merged: Record<string, unknown> = { ...template };\n\n for (const [key, value] of Object.entries(taskConfig)) {\n if (key === 'config-template') continue; // strip the reference\n\n // One-level deep merge for nested objects (both sides must be plain objects)\n if (\n value !== null &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n merged[key] !== null &&\n typeof merged[key] === 'object' &&\n !Array.isArray(merged[key])\n ) {\n merged[key] = {\n ...(merged[key] as Record<string, unknown>),\n ...(value as Record<string, unknown>),\n };\n } else {\n merged[key] = value;\n }\n }\n\n return merged;\n}\n\n/**\n * Resolve `config-template` references in task configs against a `configTemplates` map.\n *\n * Accepts any config object that may contain:\n * - `configTemplates` (camelCase) or `config-templates` (kebab-case) at the top level\n * - `tasks` (event-graph) or `steps` (step-machine) containing task/step objects\n * - Each task/step may have a `config` sub-object with a `config-template` key\n *\n * Returns a new config with templates merged and references removed.\n * The `configTemplates` / `config-templates` key is also removed from the output.\n */\nexport function resolveConfigTemplates<T extends Record<string, unknown>>(config: T): T {\n // Find templates — support both naming conventions\n const templates: ConfigTemplates =\n (config['configTemplates'] as ConfigTemplates) ??\n (config['config-templates'] as ConfigTemplates) ??\n {};\n\n // Find the tasks/steps container\n const tasksKey = 'tasks' in config ? 'tasks' : 'steps' in config ? 'steps' : null;\n if (!tasksKey) return config; // nothing to resolve\n\n const tasks = config[tasksKey] as Record<string, Record<string, unknown>> | undefined;\n if (!tasks || typeof tasks !== 'object') return config;\n\n const resolvedTasks: Record<string, Record<string, unknown>> = {};\n\n for (const [name, task] of Object.entries(tasks)) {\n const taskConfig = task['config'] as Record<string, unknown> | undefined;\n const templateName = taskConfig?.['config-template'] as string | undefined;\n\n if (!templateName || !taskConfig) {\n resolvedTasks[name] = task;\n continue;\n }\n\n const template = templates[templateName];\n if (!template) {\n // Template not found — leave as-is but strip the reference\n const { 'config-template': _, ...rest } = taskConfig;\n resolvedTasks[name] = { ...task, config: rest };\n continue;\n }\n\n resolvedTasks[name] = {\n ...task,\n config: mergeConfigs(template, taskConfig),\n };\n }\n\n // Build result — remove the templates key from output\n const result = { ...config, [tasksKey]: resolvedTasks };\n delete result['configTemplates'];\n delete result['config-templates'];\n return result as T;\n}\n"]}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Variable interpolation for workflow configs.
|
|
3
|
+
*
|
|
4
|
+
* Walks any object/array and replaces `${KEY}` patterns with values from
|
|
5
|
+
* a variables map. Pure function — returns a new object, never mutates.
|
|
6
|
+
*
|
|
7
|
+
* Works on both GraphConfig and StepFlowConfig (or any plain object).
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* const resolved = resolveVariables(config, {
|
|
12
|
+
* ENTITY_ID: 'ticket-42',
|
|
13
|
+
* TOOLS_DIR: '/opt/tools',
|
|
14
|
+
* });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
type Variables = Record<string, string | number | boolean>;
|
|
18
|
+
/**
|
|
19
|
+
* Resolve `${KEY}` variable references in a workflow config object.
|
|
20
|
+
*
|
|
21
|
+
* Pure function: config in → new config out. Works on any shape
|
|
22
|
+
* (GraphConfig, StepFlowConfig, or arbitrary objects).
|
|
23
|
+
*
|
|
24
|
+
* @param config - The config object to interpolate
|
|
25
|
+
* @param variables - Key-value pairs to substitute
|
|
26
|
+
* @returns A new config with all `${KEY}` patterns replaced
|
|
27
|
+
*/
|
|
28
|
+
declare function resolveVariables<T extends Record<string, unknown>>(config: T, variables: Variables): T;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Config template resolution for workflow configs.
|
|
32
|
+
*
|
|
33
|
+
* In large graphs, many tasks share the same base config (cmd, timeout, cwd, headers, etc.).
|
|
34
|
+
* Instead of duplicating, tasks reference a named template via `config-template`.
|
|
35
|
+
* This function deep-merges the template into each task's config, then removes the reference.
|
|
36
|
+
*
|
|
37
|
+
* Pure function — returns a new config, never mutates.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* const config = {
|
|
42
|
+
* configTemplates: {
|
|
43
|
+
* PYTHON_TOOL: { cmd: 'python', timeout: 30000, cwd: '/workdata' }
|
|
44
|
+
* },
|
|
45
|
+
* tasks: {
|
|
46
|
+
* analyze: {
|
|
47
|
+
* provides: ['analysis'],
|
|
48
|
+
* config: { 'config-template': 'PYTHON_TOOL', 'cmd-args': 'analyze.py' }
|
|
49
|
+
* }
|
|
50
|
+
* }
|
|
51
|
+
* };
|
|
52
|
+
* const resolved = resolveConfigTemplates(config);
|
|
53
|
+
* // analyze.config → { cmd: 'python', timeout: 30000, cwd: '/workdata', 'cmd-args': 'analyze.py' }
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
/** Shape of a config-templates block */
|
|
57
|
+
type ConfigTemplates = Record<string, Record<string, unknown>>;
|
|
58
|
+
/**
|
|
59
|
+
* Resolve `config-template` references in task configs against a `configTemplates` map.
|
|
60
|
+
*
|
|
61
|
+
* Accepts any config object that may contain:
|
|
62
|
+
* - `configTemplates` (camelCase) or `config-templates` (kebab-case) at the top level
|
|
63
|
+
* - `tasks` (event-graph) or `steps` (step-machine) containing task/step objects
|
|
64
|
+
* - Each task/step may have a `config` sub-object with a `config-template` key
|
|
65
|
+
*
|
|
66
|
+
* Returns a new config with templates merged and references removed.
|
|
67
|
+
* The `configTemplates` / `config-templates` key is also removed from the output.
|
|
68
|
+
*/
|
|
69
|
+
declare function resolveConfigTemplates<T extends Record<string, unknown>>(config: T): T;
|
|
70
|
+
|
|
71
|
+
export { type ConfigTemplates, type Variables, resolveConfigTemplates, resolveVariables };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Variable interpolation for workflow configs.
|
|
3
|
+
*
|
|
4
|
+
* Walks any object/array and replaces `${KEY}` patterns with values from
|
|
5
|
+
* a variables map. Pure function — returns a new object, never mutates.
|
|
6
|
+
*
|
|
7
|
+
* Works on both GraphConfig and StepFlowConfig (or any plain object).
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* const resolved = resolveVariables(config, {
|
|
12
|
+
* ENTITY_ID: 'ticket-42',
|
|
13
|
+
* TOOLS_DIR: '/opt/tools',
|
|
14
|
+
* });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
type Variables = Record<string, string | number | boolean>;
|
|
18
|
+
/**
|
|
19
|
+
* Resolve `${KEY}` variable references in a workflow config object.
|
|
20
|
+
*
|
|
21
|
+
* Pure function: config in → new config out. Works on any shape
|
|
22
|
+
* (GraphConfig, StepFlowConfig, or arbitrary objects).
|
|
23
|
+
*
|
|
24
|
+
* @param config - The config object to interpolate
|
|
25
|
+
* @param variables - Key-value pairs to substitute
|
|
26
|
+
* @returns A new config with all `${KEY}` patterns replaced
|
|
27
|
+
*/
|
|
28
|
+
declare function resolveVariables<T extends Record<string, unknown>>(config: T, variables: Variables): T;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Config template resolution for workflow configs.
|
|
32
|
+
*
|
|
33
|
+
* In large graphs, many tasks share the same base config (cmd, timeout, cwd, headers, etc.).
|
|
34
|
+
* Instead of duplicating, tasks reference a named template via `config-template`.
|
|
35
|
+
* This function deep-merges the template into each task's config, then removes the reference.
|
|
36
|
+
*
|
|
37
|
+
* Pure function — returns a new config, never mutates.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* const config = {
|
|
42
|
+
* configTemplates: {
|
|
43
|
+
* PYTHON_TOOL: { cmd: 'python', timeout: 30000, cwd: '/workdata' }
|
|
44
|
+
* },
|
|
45
|
+
* tasks: {
|
|
46
|
+
* analyze: {
|
|
47
|
+
* provides: ['analysis'],
|
|
48
|
+
* config: { 'config-template': 'PYTHON_TOOL', 'cmd-args': 'analyze.py' }
|
|
49
|
+
* }
|
|
50
|
+
* }
|
|
51
|
+
* };
|
|
52
|
+
* const resolved = resolveConfigTemplates(config);
|
|
53
|
+
* // analyze.config → { cmd: 'python', timeout: 30000, cwd: '/workdata', 'cmd-args': 'analyze.py' }
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
/** Shape of a config-templates block */
|
|
57
|
+
type ConfigTemplates = Record<string, Record<string, unknown>>;
|
|
58
|
+
/**
|
|
59
|
+
* Resolve `config-template` references in task configs against a `configTemplates` map.
|
|
60
|
+
*
|
|
61
|
+
* Accepts any config object that may contain:
|
|
62
|
+
* - `configTemplates` (camelCase) or `config-templates` (kebab-case) at the top level
|
|
63
|
+
* - `tasks` (event-graph) or `steps` (step-machine) containing task/step objects
|
|
64
|
+
* - Each task/step may have a `config` sub-object with a `config-template` key
|
|
65
|
+
*
|
|
66
|
+
* Returns a new config with templates merged and references removed.
|
|
67
|
+
* The `configTemplates` / `config-templates` key is also removed from the output.
|
|
68
|
+
*/
|
|
69
|
+
declare function resolveConfigTemplates<T extends Record<string, unknown>>(config: T): T;
|
|
70
|
+
|
|
71
|
+
export { type ConfigTemplates, type Variables, resolveConfigTemplates, resolveVariables };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// src/config/resolve-variables.ts
|
|
2
|
+
function interpolateString(template, vars) {
|
|
3
|
+
return template.replace(/\$\{([^}]+)\}/g, (match, key) => {
|
|
4
|
+
const value = vars[key.trim()];
|
|
5
|
+
return value !== void 0 ? String(value) : match;
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
function walkAndInterpolate(value, vars) {
|
|
9
|
+
if (typeof value === "string") {
|
|
10
|
+
return interpolateString(value, vars);
|
|
11
|
+
}
|
|
12
|
+
if (Array.isArray(value)) {
|
|
13
|
+
return value.map((item) => walkAndInterpolate(item, vars));
|
|
14
|
+
}
|
|
15
|
+
if (value !== null && typeof value === "object") {
|
|
16
|
+
const result = {};
|
|
17
|
+
for (const [k, v] of Object.entries(value)) {
|
|
18
|
+
result[k] = walkAndInterpolate(v, vars);
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
function resolveVariables(config, variables) {
|
|
25
|
+
return walkAndInterpolate(config, variables);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// src/config/resolve-config-templates.ts
|
|
29
|
+
function mergeConfigs(template, taskConfig) {
|
|
30
|
+
const merged = { ...template };
|
|
31
|
+
for (const [key, value] of Object.entries(taskConfig)) {
|
|
32
|
+
if (key === "config-template") continue;
|
|
33
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value) && merged[key] !== null && typeof merged[key] === "object" && !Array.isArray(merged[key])) {
|
|
34
|
+
merged[key] = {
|
|
35
|
+
...merged[key],
|
|
36
|
+
...value
|
|
37
|
+
};
|
|
38
|
+
} else {
|
|
39
|
+
merged[key] = value;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return merged;
|
|
43
|
+
}
|
|
44
|
+
function resolveConfigTemplates(config) {
|
|
45
|
+
const templates = config["configTemplates"] ?? config["config-templates"] ?? {};
|
|
46
|
+
const tasksKey = "tasks" in config ? "tasks" : "steps" in config ? "steps" : null;
|
|
47
|
+
if (!tasksKey) return config;
|
|
48
|
+
const tasks = config[tasksKey];
|
|
49
|
+
if (!tasks || typeof tasks !== "object") return config;
|
|
50
|
+
const resolvedTasks = {};
|
|
51
|
+
for (const [name, task] of Object.entries(tasks)) {
|
|
52
|
+
const taskConfig = task["config"];
|
|
53
|
+
const templateName = taskConfig?.["config-template"];
|
|
54
|
+
if (!templateName || !taskConfig) {
|
|
55
|
+
resolvedTasks[name] = task;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const template = templates[templateName];
|
|
59
|
+
if (!template) {
|
|
60
|
+
const { "config-template": _, ...rest } = taskConfig;
|
|
61
|
+
resolvedTasks[name] = { ...task, config: rest };
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
resolvedTasks[name] = {
|
|
65
|
+
...task,
|
|
66
|
+
config: mergeConfigs(template, taskConfig)
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const result = { ...config, [tasksKey]: resolvedTasks };
|
|
70
|
+
delete result["configTemplates"];
|
|
71
|
+
delete result["config-templates"];
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export { resolveConfigTemplates, resolveVariables };
|
|
76
|
+
//# sourceMappingURL=index.js.map
|
|
77
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/config/resolve-variables.ts","../../src/config/resolve-config-templates.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Variable interpolation for workflow configs.\n *\n * Walks any object/array and replaces `${KEY}` patterns with values from\n * a variables map. Pure function — returns a new object, never mutates.\n *\n * Works on both GraphConfig and StepFlowConfig (or any plain object).\n *\n * @example\n * ```ts\n * const resolved = resolveVariables(config, {\n * ENTITY_ID: 'ticket-42',\n * TOOLS_DIR: '/opt/tools',\n * });\n * ```\n */\n\nexport type Variables = Record<string, string | number | boolean>;\n\n/**\n * Replace `${KEY}` patterns in a string with values from the variables map.\n * Unmatched variables are left as-is.\n */\nfunction interpolateString(template: string, vars: Variables): string {\n return template.replace(/\\$\\{([^}]+)\\}/g, (match, key: string) => {\n const value = vars[key.trim()];\n return value !== undefined ? String(value) : match;\n });\n}\n\n/**\n * Recursively walk a value and interpolate any `${KEY}` patterns found in strings.\n * Returns a new object/array — never mutates the input.\n */\nfunction walkAndInterpolate<T>(value: T, vars: Variables): T {\n if (typeof value === 'string') {\n return interpolateString(value, vars) as unknown as T;\n }\n if (Array.isArray(value)) {\n return value.map((item) => walkAndInterpolate(item, vars)) as unknown as T;\n }\n if (value !== null && typeof value === 'object') {\n const result: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value)) {\n result[k] = walkAndInterpolate(v, vars);\n }\n return result as T;\n }\n // numbers, booleans, null, undefined — pass through\n return value;\n}\n\n/**\n * Resolve `${KEY}` variable references in a workflow config object.\n *\n * Pure function: config in → new config out. Works on any shape\n * (GraphConfig, StepFlowConfig, or arbitrary objects).\n *\n * @param config - The config object to interpolate\n * @param variables - Key-value pairs to substitute\n * @returns A new config with all `${KEY}` patterns replaced\n */\nexport function resolveVariables<T extends Record<string, unknown>>(\n config: T,\n variables: Variables,\n): T {\n return walkAndInterpolate(config, variables);\n}\n","/**\n * Config template resolution for workflow configs.\n *\n * In large graphs, many tasks share the same base config (cmd, timeout, cwd, headers, etc.).\n * Instead of duplicating, tasks reference a named template via `config-template`.\n * This function deep-merges the template into each task's config, then removes the reference.\n *\n * Pure function — returns a new config, never mutates.\n *\n * @example\n * ```ts\n * const config = {\n * configTemplates: {\n * PYTHON_TOOL: { cmd: 'python', timeout: 30000, cwd: '/workdata' }\n * },\n * tasks: {\n * analyze: {\n * provides: ['analysis'],\n * config: { 'config-template': 'PYTHON_TOOL', 'cmd-args': 'analyze.py' }\n * }\n * }\n * };\n * const resolved = resolveConfigTemplates(config);\n * // analyze.config → { cmd: 'python', timeout: 30000, cwd: '/workdata', 'cmd-args': 'analyze.py' }\n * ```\n */\n\n/** Shape of a config-templates block */\nexport type ConfigTemplates = Record<string, Record<string, unknown>>;\n\n/**\n * Deep-merge template into task config.\n * Task-level values override template values.\n * Nested objects are merged one level deep (like SwarmX's pattern).\n */\nfunction mergeConfigs(\n template: Record<string, unknown>,\n taskConfig: Record<string, unknown>,\n): Record<string, unknown> {\n const merged: Record<string, unknown> = { ...template };\n\n for (const [key, value] of Object.entries(taskConfig)) {\n if (key === 'config-template') continue; // strip the reference\n\n // One-level deep merge for nested objects (both sides must be plain objects)\n if (\n value !== null &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n merged[key] !== null &&\n typeof merged[key] === 'object' &&\n !Array.isArray(merged[key])\n ) {\n merged[key] = {\n ...(merged[key] as Record<string, unknown>),\n ...(value as Record<string, unknown>),\n };\n } else {\n merged[key] = value;\n }\n }\n\n return merged;\n}\n\n/**\n * Resolve `config-template` references in task configs against a `configTemplates` map.\n *\n * Accepts any config object that may contain:\n * - `configTemplates` (camelCase) or `config-templates` (kebab-case) at the top level\n * - `tasks` (event-graph) or `steps` (step-machine) containing task/step objects\n * - Each task/step may have a `config` sub-object with a `config-template` key\n *\n * Returns a new config with templates merged and references removed.\n * The `configTemplates` / `config-templates` key is also removed from the output.\n */\nexport function resolveConfigTemplates<T extends Record<string, unknown>>(config: T): T {\n // Find templates — support both naming conventions\n const templates: ConfigTemplates =\n (config['configTemplates'] as ConfigTemplates) ??\n (config['config-templates'] as ConfigTemplates) ??\n {};\n\n // Find the tasks/steps container\n const tasksKey = 'tasks' in config ? 'tasks' : 'steps' in config ? 'steps' : null;\n if (!tasksKey) return config; // nothing to resolve\n\n const tasks = config[tasksKey] as Record<string, Record<string, unknown>> | undefined;\n if (!tasks || typeof tasks !== 'object') return config;\n\n const resolvedTasks: Record<string, Record<string, unknown>> = {};\n\n for (const [name, task] of Object.entries(tasks)) {\n const taskConfig = task['config'] as Record<string, unknown> | undefined;\n const templateName = taskConfig?.['config-template'] as string | undefined;\n\n if (!templateName || !taskConfig) {\n resolvedTasks[name] = task;\n continue;\n }\n\n const template = templates[templateName];\n if (!template) {\n // Template not found — leave as-is but strip the reference\n const { 'config-template': _, ...rest } = taskConfig;\n resolvedTasks[name] = { ...task, config: rest };\n continue;\n }\n\n resolvedTasks[name] = {\n ...task,\n config: mergeConfigs(template, taskConfig),\n };\n }\n\n // Build result — remove the templates key from output\n const result = { ...config, [tasksKey]: resolvedTasks };\n delete result['configTemplates'];\n delete result['config-templates'];\n return result as T;\n}\n"]}
|
package/dist/index.cjs
CHANGED
|
@@ -1515,6 +1515,184 @@ var FileStore = class {
|
|
|
1515
1515
|
}
|
|
1516
1516
|
};
|
|
1517
1517
|
|
|
1518
|
+
// src/batch/runner.ts
|
|
1519
|
+
async function batch(items, options) {
|
|
1520
|
+
const {
|
|
1521
|
+
concurrency = 5,
|
|
1522
|
+
processor,
|
|
1523
|
+
onItemComplete,
|
|
1524
|
+
onItemError,
|
|
1525
|
+
onProgress,
|
|
1526
|
+
signal
|
|
1527
|
+
} = options;
|
|
1528
|
+
const total = items.length;
|
|
1529
|
+
const results = new Array(total);
|
|
1530
|
+
const batchStart = Date.now();
|
|
1531
|
+
let completed = 0;
|
|
1532
|
+
let failed = 0;
|
|
1533
|
+
let nextIndex = 0;
|
|
1534
|
+
function makeProgress(active) {
|
|
1535
|
+
const done = completed + failed;
|
|
1536
|
+
return {
|
|
1537
|
+
completed,
|
|
1538
|
+
failed,
|
|
1539
|
+
active,
|
|
1540
|
+
pending: total - done - active,
|
|
1541
|
+
total,
|
|
1542
|
+
percent: total === 0 ? 100 : Math.round(done / total * 100),
|
|
1543
|
+
elapsedMs: Date.now() - batchStart
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
if (total === 0) {
|
|
1547
|
+
return { items: [], completed: 0, failed: 0, total: 0, durationMs: 0 };
|
|
1548
|
+
}
|
|
1549
|
+
return new Promise((resolve) => {
|
|
1550
|
+
let active = 0;
|
|
1551
|
+
function tryStartNext() {
|
|
1552
|
+
while (active < concurrency && nextIndex < total) {
|
|
1553
|
+
if (signal?.aborted) {
|
|
1554
|
+
while (nextIndex < total) {
|
|
1555
|
+
const idx2 = nextIndex++;
|
|
1556
|
+
results[idx2] = {
|
|
1557
|
+
item: items[idx2],
|
|
1558
|
+
index: idx2,
|
|
1559
|
+
status: "failed",
|
|
1560
|
+
error: new Error("Batch aborted"),
|
|
1561
|
+
durationMs: 0
|
|
1562
|
+
};
|
|
1563
|
+
failed++;
|
|
1564
|
+
}
|
|
1565
|
+
if (active === 0 && completed + failed === total) {
|
|
1566
|
+
resolve({
|
|
1567
|
+
items: results,
|
|
1568
|
+
completed,
|
|
1569
|
+
failed,
|
|
1570
|
+
total,
|
|
1571
|
+
durationMs: Date.now() - batchStart
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1574
|
+
break;
|
|
1575
|
+
}
|
|
1576
|
+
const idx = nextIndex++;
|
|
1577
|
+
const item = items[idx];
|
|
1578
|
+
active++;
|
|
1579
|
+
const itemStart = Date.now();
|
|
1580
|
+
processor(item, idx).then((result) => {
|
|
1581
|
+
results[idx] = {
|
|
1582
|
+
item,
|
|
1583
|
+
index: idx,
|
|
1584
|
+
status: "completed",
|
|
1585
|
+
result,
|
|
1586
|
+
durationMs: Date.now() - itemStart
|
|
1587
|
+
};
|
|
1588
|
+
completed++;
|
|
1589
|
+
onItemComplete?.(item, result, idx);
|
|
1590
|
+
}).catch((err) => {
|
|
1591
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
1592
|
+
results[idx] = {
|
|
1593
|
+
item,
|
|
1594
|
+
index: idx,
|
|
1595
|
+
status: "failed",
|
|
1596
|
+
error,
|
|
1597
|
+
durationMs: Date.now() - itemStart
|
|
1598
|
+
};
|
|
1599
|
+
failed++;
|
|
1600
|
+
onItemError?.(item, error, idx);
|
|
1601
|
+
}).finally(() => {
|
|
1602
|
+
active--;
|
|
1603
|
+
onProgress?.(makeProgress(active));
|
|
1604
|
+
if (completed + failed === total) {
|
|
1605
|
+
resolve({
|
|
1606
|
+
items: results,
|
|
1607
|
+
completed,
|
|
1608
|
+
failed,
|
|
1609
|
+
total,
|
|
1610
|
+
durationMs: Date.now() - batchStart
|
|
1611
|
+
});
|
|
1612
|
+
} else {
|
|
1613
|
+
tryStartNext();
|
|
1614
|
+
}
|
|
1615
|
+
});
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
tryStartNext();
|
|
1619
|
+
});
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
// src/config/resolve-variables.ts
|
|
1623
|
+
function interpolateString(template, vars) {
|
|
1624
|
+
return template.replace(/\$\{([^}]+)\}/g, (match, key) => {
|
|
1625
|
+
const value = vars[key.trim()];
|
|
1626
|
+
return value !== void 0 ? String(value) : match;
|
|
1627
|
+
});
|
|
1628
|
+
}
|
|
1629
|
+
function walkAndInterpolate(value, vars) {
|
|
1630
|
+
if (typeof value === "string") {
|
|
1631
|
+
return interpolateString(value, vars);
|
|
1632
|
+
}
|
|
1633
|
+
if (Array.isArray(value)) {
|
|
1634
|
+
return value.map((item) => walkAndInterpolate(item, vars));
|
|
1635
|
+
}
|
|
1636
|
+
if (value !== null && typeof value === "object") {
|
|
1637
|
+
const result = {};
|
|
1638
|
+
for (const [k, v] of Object.entries(value)) {
|
|
1639
|
+
result[k] = walkAndInterpolate(v, vars);
|
|
1640
|
+
}
|
|
1641
|
+
return result;
|
|
1642
|
+
}
|
|
1643
|
+
return value;
|
|
1644
|
+
}
|
|
1645
|
+
function resolveVariables(config, variables) {
|
|
1646
|
+
return walkAndInterpolate(config, variables);
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
// src/config/resolve-config-templates.ts
|
|
1650
|
+
function mergeConfigs(template, taskConfig) {
|
|
1651
|
+
const merged = { ...template };
|
|
1652
|
+
for (const [key, value] of Object.entries(taskConfig)) {
|
|
1653
|
+
if (key === "config-template") continue;
|
|
1654
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value) && merged[key] !== null && typeof merged[key] === "object" && !Array.isArray(merged[key])) {
|
|
1655
|
+
merged[key] = {
|
|
1656
|
+
...merged[key],
|
|
1657
|
+
...value
|
|
1658
|
+
};
|
|
1659
|
+
} else {
|
|
1660
|
+
merged[key] = value;
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
return merged;
|
|
1664
|
+
}
|
|
1665
|
+
function resolveConfigTemplates(config) {
|
|
1666
|
+
const templates = config["configTemplates"] ?? config["config-templates"] ?? {};
|
|
1667
|
+
const tasksKey = "tasks" in config ? "tasks" : "steps" in config ? "steps" : null;
|
|
1668
|
+
if (!tasksKey) return config;
|
|
1669
|
+
const tasks = config[tasksKey];
|
|
1670
|
+
if (!tasks || typeof tasks !== "object") return config;
|
|
1671
|
+
const resolvedTasks = {};
|
|
1672
|
+
for (const [name, task] of Object.entries(tasks)) {
|
|
1673
|
+
const taskConfig = task["config"];
|
|
1674
|
+
const templateName = taskConfig?.["config-template"];
|
|
1675
|
+
if (!templateName || !taskConfig) {
|
|
1676
|
+
resolvedTasks[name] = task;
|
|
1677
|
+
continue;
|
|
1678
|
+
}
|
|
1679
|
+
const template = templates[templateName];
|
|
1680
|
+
if (!template) {
|
|
1681
|
+
const { "config-template": _, ...rest } = taskConfig;
|
|
1682
|
+
resolvedTasks[name] = { ...task, config: rest };
|
|
1683
|
+
continue;
|
|
1684
|
+
}
|
|
1685
|
+
resolvedTasks[name] = {
|
|
1686
|
+
...task,
|
|
1687
|
+
config: mergeConfigs(template, taskConfig)
|
|
1688
|
+
};
|
|
1689
|
+
}
|
|
1690
|
+
const result = { ...config, [tasksKey]: resolvedTasks };
|
|
1691
|
+
delete result["configTemplates"];
|
|
1692
|
+
delete result["config-templates"];
|
|
1693
|
+
return result;
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1518
1696
|
exports.COMPLETION_STRATEGIES = COMPLETION_STRATEGIES;
|
|
1519
1697
|
exports.CONFLICT_STRATEGIES = CONFLICT_STRATEGIES;
|
|
1520
1698
|
exports.DEFAULTS = DEFAULTS;
|
|
@@ -1530,6 +1708,7 @@ exports.addDynamicTask = addDynamicTask;
|
|
|
1530
1708
|
exports.apply = apply;
|
|
1531
1709
|
exports.applyAll = applyAll;
|
|
1532
1710
|
exports.applyStepResult = applyStepResult;
|
|
1711
|
+
exports.batch = batch;
|
|
1533
1712
|
exports.checkCircuitBreaker = checkCircuitBreaker;
|
|
1534
1713
|
exports.computeAvailableOutputs = computeAvailableOutputs;
|
|
1535
1714
|
exports.computeStepInput = computeStepInput;
|
|
@@ -1553,6 +1732,8 @@ exports.isTaskCompleted = isTaskCompleted;
|
|
|
1553
1732
|
exports.isTaskRunning = isTaskRunning;
|
|
1554
1733
|
exports.loadStepFlow = loadStepFlow;
|
|
1555
1734
|
exports.next = next;
|
|
1735
|
+
exports.resolveConfigTemplates = resolveConfigTemplates;
|
|
1736
|
+
exports.resolveVariables = resolveVariables;
|
|
1556
1737
|
exports.validateStepFlowConfig = validateStepFlowConfig;
|
|
1557
1738
|
//# sourceMappingURL=index.cjs.map
|
|
1558
1739
|
//# sourceMappingURL=index.cjs.map
|