@sapiom/orchestration-core 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/CHANGELOG.md +9 -0
- package/LICENSE +21 -0
- package/dist/cjs/check.d.ts +10 -0
- package/dist/cjs/check.js +154 -0
- package/dist/cjs/client.d.ts +17 -0
- package/dist/cjs/client.js +70 -0
- package/dist/cjs/config.d.ts +9 -0
- package/dist/cjs/config.js +38 -0
- package/dist/cjs/deploy.d.ts +12 -0
- package/dist/cjs/deploy.js +51 -0
- package/dist/cjs/errors.d.ts +15 -0
- package/dist/cjs/errors.js +23 -0
- package/dist/cjs/git.d.ts +2 -0
- package/dist/cjs/git.js +44 -0
- package/dist/cjs/index.d.ts +29 -0
- package/dist/cjs/index.js +50 -0
- package/dist/cjs/inspect.d.ts +41 -0
- package/dist/cjs/inspect.js +17 -0
- package/dist/cjs/link.d.ts +15 -0
- package/dist/cjs/link.js +19 -0
- package/dist/cjs/local/dispatcher.d.ts +35 -0
- package/dist/cjs/local/dispatcher.js +115 -0
- package/dist/cjs/local/load.d.ts +6 -0
- package/dist/cjs/local/load.js +102 -0
- package/dist/cjs/local/run-local.d.ts +26 -0
- package/dist/cjs/local/run-local.js +73 -0
- package/dist/cjs/local/stubs.d.ts +8 -0
- package/dist/cjs/local/stubs.js +35 -0
- package/dist/cjs/run.d.ts +11 -0
- package/dist/cjs/run.js +29 -0
- package/dist/cjs/scaffold.d.ts +22 -0
- package/dist/cjs/scaffold.js +145 -0
- package/dist/cjs/signal.d.ts +12 -0
- package/dist/cjs/signal.js +24 -0
- package/dist/esm/check.d.ts +10 -0
- package/dist/esm/check.js +115 -0
- package/dist/esm/client.d.ts +17 -0
- package/dist/esm/client.js +65 -0
- package/dist/esm/config.d.ts +9 -0
- package/dist/esm/config.js +29 -0
- package/dist/esm/deploy.d.ts +12 -0
- package/dist/esm/deploy.js +48 -0
- package/dist/esm/errors.d.ts +15 -0
- package/dist/esm/errors.js +19 -0
- package/dist/esm/git.d.ts +2 -0
- package/dist/esm/git.js +40 -0
- package/dist/esm/index.d.ts +29 -0
- package/dist/esm/index.js +15 -0
- package/dist/esm/inspect.d.ts +41 -0
- package/dist/esm/inspect.js +12 -0
- package/dist/esm/link.d.ts +15 -0
- package/dist/esm/link.js +16 -0
- package/dist/esm/local/dispatcher.d.ts +35 -0
- package/dist/esm/local/dispatcher.js +111 -0
- package/dist/esm/local/load.d.ts +6 -0
- package/dist/esm/local/load.js +63 -0
- package/dist/esm/local/run-local.d.ts +26 -0
- package/dist/esm/local/run-local.js +65 -0
- package/dist/esm/local/stubs.d.ts +8 -0
- package/dist/esm/local/stubs.js +31 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/run.d.ts +11 -0
- package/dist/esm/run.js +25 -0
- package/dist/esm/scaffold.d.ts +22 -0
- package/dist/esm/scaffold.js +135 -0
- package/dist/esm/signal.d.ts +12 -0
- package/dist/esm/signal.js +20 -0
- package/dist/tsconfig.cjs.tsbuildinfo +1 -0
- package/dist/tsconfig.esm.tsbuildinfo +1 -0
- package/package.json +73 -0
- package/templates/default/AGENTS.md +29 -0
- package/templates/default/CLAUDE.md +7 -0
- package/templates/default/README.md +30 -0
- package/templates/default/_gitignore +3 -0
- package/templates/default/index.ts +26 -0
- package/templates/default/package.json +21 -0
- package/templates/default/tsconfig.json +12 -0
package/dist/cjs/link.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.link = link;
|
|
4
|
+
const errors_js_1 = require("./errors.js");
|
|
5
|
+
async function link(opts, client) {
|
|
6
|
+
const list = await client.get('/definitions');
|
|
7
|
+
let def = list.find((d) => d.name === opts.name || d.slug === opts.name);
|
|
8
|
+
if (!def) {
|
|
9
|
+
if (!opts.create) {
|
|
10
|
+
throw new errors_js_1.OrchestrationError({
|
|
11
|
+
code: 'NOT_FOUND',
|
|
12
|
+
message: `No orchestration named '${opts.name}'.`,
|
|
13
|
+
hint: 'Create it with { create: true }, or pass the name of an existing one.',
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
def = await client.post('/definitions', { name: opts.name });
|
|
17
|
+
}
|
|
18
|
+
return { definitionId: def.id, name: def.name };
|
|
19
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type NextStepDirective, type OrchestrationDefinition } from '@sapiom/orchestration';
|
|
2
|
+
import { type StepDispatcher, type StepDispatchRequest, type WorkflowRunnerCore } from '@sapiom/orchestration-runtime';
|
|
3
|
+
import type { StubFile } from './stubs.js';
|
|
4
|
+
export interface LogEntry {
|
|
5
|
+
level: 'info' | 'warn' | 'error' | 'debug';
|
|
6
|
+
msg: string;
|
|
7
|
+
meta?: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
export interface LocalStepTrace {
|
|
10
|
+
step: string;
|
|
11
|
+
attempt: number;
|
|
12
|
+
input: unknown;
|
|
13
|
+
status: 'succeeded' | 'threw';
|
|
14
|
+
output?: unknown;
|
|
15
|
+
directive?: NextStepDirective;
|
|
16
|
+
error?: {
|
|
17
|
+
name: string;
|
|
18
|
+
message: string;
|
|
19
|
+
stack?: string;
|
|
20
|
+
};
|
|
21
|
+
logs: LogEntry[];
|
|
22
|
+
}
|
|
23
|
+
export declare class LocalStubDispatcher implements StepDispatcher {
|
|
24
|
+
private readonly definition;
|
|
25
|
+
private readonly stubs;
|
|
26
|
+
private core;
|
|
27
|
+
private maxAttemptsPerStep;
|
|
28
|
+
private signals;
|
|
29
|
+
readonly trace: LocalStepTrace[];
|
|
30
|
+
constructor(definition: OrchestrationDefinition, stubs: StubFile);
|
|
31
|
+
setCore(core: WorkflowRunnerCore): void;
|
|
32
|
+
setMaxAttempts(max: number): void;
|
|
33
|
+
setSignals(signals: Map<string, unknown>): void;
|
|
34
|
+
dispatch(request: StepDispatchRequest): Promise<void>;
|
|
35
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LocalStubDispatcher = void 0;
|
|
4
|
+
const orchestration_1 = require("@sapiom/orchestration");
|
|
5
|
+
const orchestration_runtime_1 = require("@sapiom/orchestration-runtime");
|
|
6
|
+
const stub_1 = require("@sapiom/tools/stub");
|
|
7
|
+
class LocalStubDispatcher {
|
|
8
|
+
constructor(definition, stubs) {
|
|
9
|
+
this.definition = definition;
|
|
10
|
+
this.stubs = stubs;
|
|
11
|
+
this.core = null;
|
|
12
|
+
this.signals = new Map();
|
|
13
|
+
this.trace = [];
|
|
14
|
+
}
|
|
15
|
+
setCore(core) {
|
|
16
|
+
this.core = core;
|
|
17
|
+
}
|
|
18
|
+
setMaxAttempts(max) {
|
|
19
|
+
this.maxAttemptsPerStep = max;
|
|
20
|
+
}
|
|
21
|
+
setSignals(signals) {
|
|
22
|
+
this.signals = signals;
|
|
23
|
+
}
|
|
24
|
+
async dispatch(request) {
|
|
25
|
+
if (!this.core)
|
|
26
|
+
throw new Error('LocalStubDispatcher: setCore() was not called');
|
|
27
|
+
const step = this.definition.steps[request.stepName];
|
|
28
|
+
if (!step)
|
|
29
|
+
throw new Error(`LocalStubDispatcher: no step '${request.stepName}' in the definition`);
|
|
30
|
+
const parsed = (0, orchestration_runtime_1.parseCorrelationId)(request.correlationId);
|
|
31
|
+
if (!parsed)
|
|
32
|
+
throw new Error(`LocalStubDispatcher: malformed correlationId '${request.correlationId}'`);
|
|
33
|
+
const logs = [];
|
|
34
|
+
const sharedStore = new orchestration_1.InMemoryContextStore(request.shared);
|
|
35
|
+
const overrides = (this.stubs.steps[request.stepName] ?? {});
|
|
36
|
+
const sapiom = (0, stub_1.createStubClient)({ overrides, signals: this.signals });
|
|
37
|
+
const ctx = {
|
|
38
|
+
executionId: request.executionId,
|
|
39
|
+
workflowName: request.workflowName,
|
|
40
|
+
organizationId: request.organizationId,
|
|
41
|
+
tenantId: request.tenantId,
|
|
42
|
+
input: request.workflowInput,
|
|
43
|
+
shared: sharedStore,
|
|
44
|
+
history: [],
|
|
45
|
+
attempts: request.attempt,
|
|
46
|
+
logger: makeLogger(logs),
|
|
47
|
+
sapiom,
|
|
48
|
+
};
|
|
49
|
+
let directive;
|
|
50
|
+
try {
|
|
51
|
+
directive = await step.run(request.input, ctx);
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
55
|
+
this.trace.push({
|
|
56
|
+
step: request.stepName,
|
|
57
|
+
attempt: request.attempt,
|
|
58
|
+
input: request.input,
|
|
59
|
+
status: 'threw',
|
|
60
|
+
error: { name: e.name, message: e.message, stack: e.stack },
|
|
61
|
+
logs,
|
|
62
|
+
});
|
|
63
|
+
await this.core.completeDispatchedStep({
|
|
64
|
+
protocol: 1,
|
|
65
|
+
correlationId: request.correlationId,
|
|
66
|
+
outcome: orchestration_runtime_1.STEP_COMPLETION_OUTCOME.THREW,
|
|
67
|
+
error: { name: e.name, message: e.message, stack: e.stack },
|
|
68
|
+
shared: sharedStore.snapshot(),
|
|
69
|
+
}, parsed, this.maxAttemptsPerStep);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const { output, wire } = splitDirective(directive);
|
|
73
|
+
this.trace.push({
|
|
74
|
+
step: request.stepName,
|
|
75
|
+
attempt: request.attempt,
|
|
76
|
+
input: request.input,
|
|
77
|
+
status: 'succeeded',
|
|
78
|
+
output,
|
|
79
|
+
directive,
|
|
80
|
+
logs,
|
|
81
|
+
});
|
|
82
|
+
const payload = {
|
|
83
|
+
protocol: 1,
|
|
84
|
+
correlationId: request.correlationId,
|
|
85
|
+
outcome: orchestration_runtime_1.STEP_COMPLETION_OUTCOME.RESULT,
|
|
86
|
+
result: { output, directive: wire },
|
|
87
|
+
shared: sharedStore.snapshot(),
|
|
88
|
+
};
|
|
89
|
+
await this.core.completeDispatchedStep(payload, parsed, this.maxAttemptsPerStep);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
exports.LocalStubDispatcher = LocalStubDispatcher;
|
|
93
|
+
function makeLogger(sink) {
|
|
94
|
+
const at = (level) => (msg, meta) => {
|
|
95
|
+
sink.push({ level, msg, ...(meta ? { meta } : {}) });
|
|
96
|
+
};
|
|
97
|
+
return { info: at('info'), warn: at('warn'), error: at('error'), debug: at('debug') };
|
|
98
|
+
}
|
|
99
|
+
function splitDirective(d) {
|
|
100
|
+
switch (d.kind) {
|
|
101
|
+
case 'continue':
|
|
102
|
+
return { output: d.input, wire: { kind: 'continue', stepName: d.stepName, input: d.input } };
|
|
103
|
+
case 'terminate':
|
|
104
|
+
return { output: d.output, wire: { kind: 'terminate', reason: d.reason } };
|
|
105
|
+
case 'fail':
|
|
106
|
+
return { output: d.output, wire: { kind: 'fail', reason: d.reason } };
|
|
107
|
+
case 'pause_until_signal':
|
|
108
|
+
return {
|
|
109
|
+
output: d.output,
|
|
110
|
+
wire: { kind: 'pause_until_signal', signal: d.signal, timeoutMs: d.timeoutMs, resumeStep: d.resumeStep },
|
|
111
|
+
};
|
|
112
|
+
case 'retry':
|
|
113
|
+
return { output: undefined, wire: { kind: 'retry', delayMs: d.delayMs, reason: d.reason } };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type OrchestrationDefinition, type WorkflowManifest } from '@sapiom/orchestration';
|
|
2
|
+
export interface LoadedDefinition {
|
|
3
|
+
definition: OrchestrationDefinition;
|
|
4
|
+
manifest: WorkflowManifest;
|
|
5
|
+
}
|
|
6
|
+
export declare function loadDefinition(sourceDir: string): Promise<LoadedDefinition>;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.loadDefinition = loadDefinition;
|
|
40
|
+
const node_crypto_1 = require("node:crypto");
|
|
41
|
+
const node_fs_1 = require("node:fs");
|
|
42
|
+
const node_os_1 = require("node:os");
|
|
43
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
44
|
+
const orchestration_1 = require("@sapiom/orchestration");
|
|
45
|
+
const esbuild = __importStar(require("esbuild"));
|
|
46
|
+
const errors_js_1 = require("../errors.js");
|
|
47
|
+
const LOCAL_SDK_VERSION = '0.0.0-local';
|
|
48
|
+
async function loadDefinition(sourceDir) {
|
|
49
|
+
const entryFile = node_path_1.default.join(sourceDir, 'index.ts');
|
|
50
|
+
if (!(0, node_fs_1.existsSync)(entryFile)) {
|
|
51
|
+
throw new errors_js_1.OrchestrationError({
|
|
52
|
+
code: 'NO_ENTRY',
|
|
53
|
+
message: `No index.ts found in ${sourceDir}.`,
|
|
54
|
+
hint: 'Run this from an orchestration project, or pass its directory.',
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
const tmp = (0, node_fs_1.mkdtempSync)(node_path_1.default.join((0, node_os_1.tmpdir)(), 'sapiom-run-'));
|
|
58
|
+
const bundlePath = node_path_1.default.join(tmp, 'definition.mjs');
|
|
59
|
+
try {
|
|
60
|
+
try {
|
|
61
|
+
await esbuild.build({
|
|
62
|
+
entryPoints: [entryFile],
|
|
63
|
+
outfile: bundlePath,
|
|
64
|
+
bundle: true,
|
|
65
|
+
platform: 'node',
|
|
66
|
+
target: 'node20',
|
|
67
|
+
format: 'esm',
|
|
68
|
+
logLevel: 'silent',
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
throw new errors_js_1.OrchestrationError({
|
|
73
|
+
code: 'BUNDLE_FAILED',
|
|
74
|
+
message: 'Failed to bundle the orchestration.',
|
|
75
|
+
hint: err instanceof Error ? err.message : String(err),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
const mod = await Promise.resolve(`${`file://${bundlePath}?t=${Date.now()}`}`).then(s => __importStar(require(s)));
|
|
79
|
+
const defs = Object.values(mod).filter(orchestration_1.isOrchestrationDefinition);
|
|
80
|
+
if (defs.length === 0) {
|
|
81
|
+
throw new errors_js_1.OrchestrationError({
|
|
82
|
+
code: 'NO_DEFINITION',
|
|
83
|
+
message: 'No orchestration was exported from index.ts.',
|
|
84
|
+
hint: 'Export the result of defineOrchestration({ … }).',
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
if (defs.length > 1) {
|
|
88
|
+
throw new errors_js_1.OrchestrationError({
|
|
89
|
+
code: 'MULTIPLE_DEFINITIONS',
|
|
90
|
+
message: 'index.ts exports more than one orchestration.',
|
|
91
|
+
hint: 'Export exactly one defineOrchestration({ … }) result.',
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
const definition = defs[0];
|
|
95
|
+
const sha256 = (0, node_crypto_1.createHash)('sha256').update((0, node_fs_1.readFileSync)(bundlePath)).digest('hex');
|
|
96
|
+
const manifest = orchestration_1.workflowManifestSchema.parse((0, orchestration_1.buildManifest)(definition, { sdkVersion: LOCAL_SDK_VERSION, artifact: { sha256, entryFile: 'definition.mjs' } }));
|
|
97
|
+
return { definition, manifest };
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
(0, node_fs_1.rmSync)(tmp, { recursive: true, force: true });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { OrchestrationDefinition, WorkflowManifest } from '@sapiom/orchestration';
|
|
2
|
+
import { type LocalStepTrace } from './dispatcher.js';
|
|
3
|
+
import { type StubFile } from './stubs.js';
|
|
4
|
+
export declare const STUBS_FILE: string;
|
|
5
|
+
export interface RunLocalOptions {
|
|
6
|
+
definition: OrchestrationDefinition;
|
|
7
|
+
manifest: WorkflowManifest;
|
|
8
|
+
input?: unknown;
|
|
9
|
+
stubs?: StubFile;
|
|
10
|
+
maxAttemptsPerStep?: number;
|
|
11
|
+
}
|
|
12
|
+
export type LocalRunOutcome = 'completed' | 'failed' | 'paused' | 'running';
|
|
13
|
+
export interface LocalRunResult {
|
|
14
|
+
outcome: LocalRunOutcome;
|
|
15
|
+
executionId: string;
|
|
16
|
+
output?: unknown;
|
|
17
|
+
error?: unknown;
|
|
18
|
+
steps: LocalStepTrace[];
|
|
19
|
+
}
|
|
20
|
+
export declare function runLocal(opts: RunLocalOptions): Promise<LocalRunResult>;
|
|
21
|
+
export declare function runLocalFromDir(opts: {
|
|
22
|
+
sourceDir: string;
|
|
23
|
+
input?: unknown;
|
|
24
|
+
stubs?: StubFile;
|
|
25
|
+
maxAttemptsPerStep?: number;
|
|
26
|
+
}): Promise<LocalRunResult>;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.STUBS_FILE = void 0;
|
|
7
|
+
exports.runLocal = runLocal;
|
|
8
|
+
exports.runLocalFromDir = runLocalFromDir;
|
|
9
|
+
const node_fs_1 = require("node:fs");
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const orchestration_runtime_1 = require("@sapiom/orchestration-runtime");
|
|
12
|
+
const errors_js_1 = require("../errors.js");
|
|
13
|
+
const dispatcher_js_1 = require("./dispatcher.js");
|
|
14
|
+
const load_js_1 = require("./load.js");
|
|
15
|
+
const stubs_js_1 = require("./stubs.js");
|
|
16
|
+
exports.STUBS_FILE = node_path_1.default.join('.sapiom-dev', 'stubs.json');
|
|
17
|
+
function loadStubsFile(sourceDir) {
|
|
18
|
+
const file = node_path_1.default.join(sourceDir, exports.STUBS_FILE);
|
|
19
|
+
if (!(0, node_fs_1.existsSync)(file))
|
|
20
|
+
return undefined;
|
|
21
|
+
let raw;
|
|
22
|
+
try {
|
|
23
|
+
raw = JSON.parse((0, node_fs_1.readFileSync)(file, 'utf8'));
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
throw new errors_js_1.OrchestrationError({
|
|
27
|
+
code: 'STUBS_INVALID',
|
|
28
|
+
message: `${exports.STUBS_FILE} is not valid JSON.`,
|
|
29
|
+
hint: err instanceof Error ? err.message : String(err),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return (0, stubs_js_1.parseStubFile)(raw);
|
|
33
|
+
}
|
|
34
|
+
const MAX_ADVANCES = 1000;
|
|
35
|
+
async function runLocal(opts) {
|
|
36
|
+
const stubs = opts.stubs ?? { version: 1, steps: {} };
|
|
37
|
+
const max = opts.maxAttemptsPerStep ?? orchestration_runtime_1.DEFAULT_MAX_ATTEMPTS_PER_STEP;
|
|
38
|
+
const store = new orchestration_runtime_1.InMemoryExecutionStore();
|
|
39
|
+
const dispatcher = new dispatcher_js_1.LocalStubDispatcher(opts.definition, stubs);
|
|
40
|
+
const signals = new Map();
|
|
41
|
+
dispatcher.setSignals(signals);
|
|
42
|
+
const core = new orchestration_runtime_1.WorkflowRunnerCore({ store, dispatcher, observer: orchestration_runtime_1.NOOP_OBSERVER });
|
|
43
|
+
dispatcher.setCore(core);
|
|
44
|
+
dispatcher.setMaxAttempts(max);
|
|
45
|
+
const executionId = await core.createExecution(opts.definition.name, opts.definition.entry, opts.input, {
|
|
46
|
+
manifest: opts.manifest,
|
|
47
|
+
});
|
|
48
|
+
let guard = 0;
|
|
49
|
+
while (guard++ < MAX_ADVANCES) {
|
|
50
|
+
await core.advance(executionId, max);
|
|
51
|
+
const row = await store.loadExecution(executionId);
|
|
52
|
+
if (!row || row.status === 'completed' || row.status === 'failed' || row.status === 'cancelled')
|
|
53
|
+
break;
|
|
54
|
+
if (row.status === 'paused') {
|
|
55
|
+
const payload = signals.get(row.pausedSignalCorrelationId ?? '') ?? {};
|
|
56
|
+
await core.resetForResume(executionId, { fromStepInput: payload });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const final = await store.loadExecution(executionId);
|
|
60
|
+
const outcome = final?.status === 'cancelled' ? 'failed' : (final?.status ?? 'running');
|
|
61
|
+
return {
|
|
62
|
+
outcome,
|
|
63
|
+
executionId,
|
|
64
|
+
output: final?.output,
|
|
65
|
+
error: final?.error,
|
|
66
|
+
steps: dispatcher.trace,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
async function runLocalFromDir(opts) {
|
|
70
|
+
const { definition, manifest } = await (0, load_js_1.loadDefinition)(opts.sourceDir);
|
|
71
|
+
const stubs = opts.stubs ?? loadStubsFile(opts.sourceDir);
|
|
72
|
+
return runLocal({ definition, manifest, input: opts.input, stubs, maxAttemptsPerStep: opts.maxAttemptsPerStep });
|
|
73
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const STUB_FILE_VERSION = 1;
|
|
2
|
+
export type StubResponse = unknown;
|
|
3
|
+
export type StepStubs = Record<string, StubResponse | StubResponse[]>;
|
|
4
|
+
export interface StubFile {
|
|
5
|
+
version: number;
|
|
6
|
+
steps: Record<string, StepStubs>;
|
|
7
|
+
}
|
|
8
|
+
export declare function parseStubFile(raw: unknown): StubFile;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.STUB_FILE_VERSION = void 0;
|
|
4
|
+
exports.parseStubFile = parseStubFile;
|
|
5
|
+
const errors_js_1 = require("../errors.js");
|
|
6
|
+
exports.STUB_FILE_VERSION = 1;
|
|
7
|
+
function parseStubFile(raw) {
|
|
8
|
+
if (raw == null || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
9
|
+
throw invalid('the stub file must be a JSON object.');
|
|
10
|
+
}
|
|
11
|
+
const obj = raw;
|
|
12
|
+
const version = obj.version ?? exports.STUB_FILE_VERSION;
|
|
13
|
+
if (typeof version !== 'number') {
|
|
14
|
+
throw invalid('`version` must be a number.');
|
|
15
|
+
}
|
|
16
|
+
const stepsRaw = obj.steps ?? {};
|
|
17
|
+
if (stepsRaw == null || typeof stepsRaw !== 'object' || Array.isArray(stepsRaw)) {
|
|
18
|
+
throw invalid('`steps` must be an object keyed by step name.');
|
|
19
|
+
}
|
|
20
|
+
const steps = {};
|
|
21
|
+
for (const [stepName, stepStubs] of Object.entries(stepsRaw)) {
|
|
22
|
+
if (stepStubs == null || typeof stepStubs !== 'object' || Array.isArray(stepStubs)) {
|
|
23
|
+
throw invalid(`steps.${stepName} must be an object keyed by capability path.`);
|
|
24
|
+
}
|
|
25
|
+
steps[stepName] = stepStubs;
|
|
26
|
+
}
|
|
27
|
+
return { version, steps };
|
|
28
|
+
}
|
|
29
|
+
function invalid(detail) {
|
|
30
|
+
return new errors_js_1.OrchestrationError({
|
|
31
|
+
code: 'STUBS_INVALID',
|
|
32
|
+
message: `Invalid stub file: ${detail}`,
|
|
33
|
+
hint: 'Expected { "version": 1, "steps": { "<step>": { "<capability.path>": <response> | [<response>] } } }.',
|
|
34
|
+
});
|
|
35
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { GatewayClient } from './client.js';
|
|
2
|
+
export interface RunOptions {
|
|
3
|
+
definitionId: string;
|
|
4
|
+
input?: unknown;
|
|
5
|
+
}
|
|
6
|
+
export interface RunResult {
|
|
7
|
+
executionId: string;
|
|
8
|
+
raw: Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
export declare function run(opts: RunOptions, client: GatewayClient): Promise<RunResult>;
|
|
11
|
+
export declare function parseJsonInput(raw: string): unknown;
|
package/dist/cjs/run.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.run = run;
|
|
4
|
+
exports.parseJsonInput = parseJsonInput;
|
|
5
|
+
const errors_js_1 = require("./errors.js");
|
|
6
|
+
async function run(opts, client) {
|
|
7
|
+
const { definitionId, input = {} } = opts;
|
|
8
|
+
const res = await client.post('/executions', { definitionId, input });
|
|
9
|
+
const executionId = res.executionId ?? res.id;
|
|
10
|
+
if (!executionId) {
|
|
11
|
+
throw new errors_js_1.OrchestrationError({
|
|
12
|
+
code: 'RUN_NO_ID',
|
|
13
|
+
message: 'The execution was started but no execution id was returned.',
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return { executionId, raw: res };
|
|
17
|
+
}
|
|
18
|
+
function parseJsonInput(raw) {
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(raw);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
throw new errors_js_1.OrchestrationError({
|
|
24
|
+
code: 'BAD_INPUT',
|
|
25
|
+
message: 'Input is not valid JSON.',
|
|
26
|
+
hint: 'Pass a valid JSON string, e.g. \'{"key":"value"}\'',
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare const DEFAULT_TEMPLATE = "default";
|
|
2
|
+
export interface ResolvedVersions {
|
|
3
|
+
orchestration: string;
|
|
4
|
+
tools: string;
|
|
5
|
+
zod: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function resolveVersions(): Promise<ResolvedVersions>;
|
|
8
|
+
export declare function listTemplates(templatesDir?: string): string[];
|
|
9
|
+
export declare function resolveTemplate(name: string, templatesDir?: string): string;
|
|
10
|
+
export interface ScaffoldOptions {
|
|
11
|
+
targetDir: string;
|
|
12
|
+
template?: string;
|
|
13
|
+
projectName?: string;
|
|
14
|
+
templatesDir?: string;
|
|
15
|
+
versions?: ResolvedVersions;
|
|
16
|
+
}
|
|
17
|
+
export interface ScaffoldResult {
|
|
18
|
+
targetDir: string;
|
|
19
|
+
template: string;
|
|
20
|
+
projectName: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function scaffold(opts: ScaffoldOptions): Promise<ScaffoldResult>;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.DEFAULT_TEMPLATE = void 0;
|
|
7
|
+
exports.resolveVersions = resolveVersions;
|
|
8
|
+
exports.listTemplates = listTemplates;
|
|
9
|
+
exports.resolveTemplate = resolveTemplate;
|
|
10
|
+
exports.scaffold = scaffold;
|
|
11
|
+
const node_fs_1 = require("node:fs");
|
|
12
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
13
|
+
const node_url_1 = require("node:url");
|
|
14
|
+
const errors_js_1 = require("./errors.js");
|
|
15
|
+
function resolveModuleDir() {
|
|
16
|
+
if (typeof __dirname !== 'undefined')
|
|
17
|
+
return __dirname;
|
|
18
|
+
try {
|
|
19
|
+
const metaUrl = eval('import.meta.url');
|
|
20
|
+
if (typeof metaUrl === 'string')
|
|
21
|
+
return node_path_1.default.dirname((0, node_url_1.fileURLToPath)(metaUrl));
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
}
|
|
25
|
+
return '';
|
|
26
|
+
}
|
|
27
|
+
const moduleDir = resolveModuleDir();
|
|
28
|
+
function getTemplatesDir(override) {
|
|
29
|
+
return override ?? process.env.SAPIOM_TEMPLATES_DIR ?? node_path_1.default.resolve(moduleDir, '..', '..', 'templates');
|
|
30
|
+
}
|
|
31
|
+
exports.DEFAULT_TEMPLATE = 'default';
|
|
32
|
+
const DOTFILE_NAMES = new Set(['_gitignore', '_npmrc']);
|
|
33
|
+
const REGISTRY = 'https://registry.npmjs.org';
|
|
34
|
+
const VERSION_FALLBACK = {
|
|
35
|
+
orchestration: '0.1.1',
|
|
36
|
+
tools: '0.1.1',
|
|
37
|
+
};
|
|
38
|
+
const ZOD_VERSION = '3.25.76';
|
|
39
|
+
async function latestNpmVersion(pkg) {
|
|
40
|
+
try {
|
|
41
|
+
const res = await fetch(`${REGISTRY}/${encodeURIComponent(pkg)}/latest`, {
|
|
42
|
+
signal: AbortSignal.timeout(5000),
|
|
43
|
+
});
|
|
44
|
+
if (!res.ok)
|
|
45
|
+
return null;
|
|
46
|
+
const json = (await res.json());
|
|
47
|
+
return typeof json.version === 'string' ? json.version : null;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async function resolveVersions() {
|
|
54
|
+
const [orchestration, tools] = await Promise.all([
|
|
55
|
+
latestNpmVersion('@sapiom/orchestration'),
|
|
56
|
+
latestNpmVersion('@sapiom/tools'),
|
|
57
|
+
]);
|
|
58
|
+
return {
|
|
59
|
+
orchestration: orchestration ?? VERSION_FALLBACK.orchestration,
|
|
60
|
+
tools: tools ?? VERSION_FALLBACK.tools,
|
|
61
|
+
zod: ZOD_VERSION,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function listTemplates(templatesDir) {
|
|
65
|
+
const root = getTemplatesDir(templatesDir);
|
|
66
|
+
if (!(0, node_fs_1.existsSync)(root))
|
|
67
|
+
return [];
|
|
68
|
+
return (0, node_fs_1.readdirSync)(root).filter((name) => (0, node_fs_1.statSync)(node_path_1.default.join(root, name)).isDirectory());
|
|
69
|
+
}
|
|
70
|
+
function resolveTemplate(name, templatesDir) {
|
|
71
|
+
const dir = node_path_1.default.join(getTemplatesDir(templatesDir), name);
|
|
72
|
+
if (!(0, node_fs_1.existsSync)(dir) || !(0, node_fs_1.statSync)(dir).isDirectory()) {
|
|
73
|
+
const available = listTemplates(templatesDir);
|
|
74
|
+
throw new errors_js_1.OrchestrationError({
|
|
75
|
+
code: 'UNKNOWN_TEMPLATE',
|
|
76
|
+
message: `Unknown template '${name}'.` +
|
|
77
|
+
(available.length ? ` Available: ${available.join(', ')}.` : ' No templates are bundled.'),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return dir;
|
|
81
|
+
}
|
|
82
|
+
function applyReplacements(file, replacements) {
|
|
83
|
+
let content;
|
|
84
|
+
try {
|
|
85
|
+
content = (0, node_fs_1.readFileSync)(file, 'utf8');
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
let changed = false;
|
|
91
|
+
for (const [token, value] of Object.entries(replacements)) {
|
|
92
|
+
if (content.includes(token)) {
|
|
93
|
+
content = content.split(token).join(value);
|
|
94
|
+
changed = true;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (changed)
|
|
98
|
+
(0, node_fs_1.writeFileSync)(file, content);
|
|
99
|
+
}
|
|
100
|
+
function walk(dir, onFile) {
|
|
101
|
+
for (const entry of (0, node_fs_1.readdirSync)(dir)) {
|
|
102
|
+
const full = node_path_1.default.join(dir, entry);
|
|
103
|
+
if ((0, node_fs_1.statSync)(full).isDirectory())
|
|
104
|
+
walk(full, onFile);
|
|
105
|
+
else
|
|
106
|
+
onFile(full);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function copyTemplate(templateDir, targetDir, replacements) {
|
|
110
|
+
(0, node_fs_1.cpSync)(templateDir, targetDir, { recursive: true });
|
|
111
|
+
walk(targetDir, (file) => {
|
|
112
|
+
const base = node_path_1.default.basename(file);
|
|
113
|
+
if (DOTFILE_NAMES.has(base)) {
|
|
114
|
+
const dotted = node_path_1.default.join(node_path_1.default.dirname(file), '.' + base.slice(1));
|
|
115
|
+
(0, node_fs_1.renameSync)(file, dotted);
|
|
116
|
+
applyReplacements(dotted, replacements);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
applyReplacements(file, replacements);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
async function scaffold(opts) {
|
|
123
|
+
const { targetDir } = opts;
|
|
124
|
+
const template = opts.template ?? exports.DEFAULT_TEMPLATE;
|
|
125
|
+
const projectName = opts.projectName ?? node_path_1.default.basename(targetDir);
|
|
126
|
+
if ((0, node_fs_1.existsSync)(targetDir) && (0, node_fs_1.readdirSync)(targetDir).length > 0) {
|
|
127
|
+
throw new errors_js_1.OrchestrationError({
|
|
128
|
+
code: 'DIR_NOT_EMPTY',
|
|
129
|
+
message: `Target directory '${targetDir}' already exists and is not empty.`,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
const templateDir = resolveTemplate(template, opts.templatesDir);
|
|
133
|
+
const versions = opts.versions ?? (await resolveVersions());
|
|
134
|
+
(0, node_fs_1.mkdirSync)(targetDir, { recursive: true });
|
|
135
|
+
copyTemplate(templateDir, targetDir, {
|
|
136
|
+
__PROJECT_NAME__: projectName,
|
|
137
|
+
__ORCHESTRATION_VERSION__: versions.orchestration,
|
|
138
|
+
__TOOLS_VERSION__: versions.tools,
|
|
139
|
+
__ZOD_VERSION__: versions.zod,
|
|
140
|
+
});
|
|
141
|
+
const devDir = node_path_1.default.join(targetDir, '.sapiom-dev');
|
|
142
|
+
(0, node_fs_1.mkdirSync)(devDir, { recursive: true });
|
|
143
|
+
(0, node_fs_1.writeFileSync)(node_path_1.default.join(devDir, 'stubs.json'), JSON.stringify({ version: 1, steps: {} }, null, 2) + '\n');
|
|
144
|
+
return { targetDir, template, projectName };
|
|
145
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { GatewayClient } from './client.js';
|
|
2
|
+
export interface SignalOptions {
|
|
3
|
+
executionId: string;
|
|
4
|
+
name: string;
|
|
5
|
+
correlationId: string;
|
|
6
|
+
payload?: unknown;
|
|
7
|
+
}
|
|
8
|
+
export interface SignalResult {
|
|
9
|
+
matched: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function signal(opts: SignalOptions, client: GatewayClient): Promise<SignalResult>;
|
|
12
|
+
export declare function parseSignalPayload(raw: string): unknown;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.signal = signal;
|
|
4
|
+
exports.parseSignalPayload = parseSignalPayload;
|
|
5
|
+
const errors_js_1 = require("./errors.js");
|
|
6
|
+
async function signal(opts, client) {
|
|
7
|
+
const res = await client.post(`/executions/${opts.executionId}/signals`, {
|
|
8
|
+
name: opts.name,
|
|
9
|
+
correlationId: opts.correlationId,
|
|
10
|
+
payload: opts.payload,
|
|
11
|
+
});
|
|
12
|
+
return { matched: res.matched ?? 0 };
|
|
13
|
+
}
|
|
14
|
+
function parseSignalPayload(raw) {
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(raw);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
throw new errors_js_1.OrchestrationError({
|
|
20
|
+
code: 'BAD_PAYLOAD',
|
|
21
|
+
message: 'Signal payload is not valid JSON.',
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|