UNPKG

@cadenza.io/core

Version:

This is a framework for building asynchronous graphs and flows of tasks and signals.

1,675 lines (1,657 loc) 105 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { DebounceTask: () => DebounceTask, EphemeralTask: () => EphemeralTask, GraphContext: () => GraphContext, GraphRegistry: () => GraphRegistry, GraphRoutine: () => GraphRoutine, GraphRun: () => GraphRun, GraphRunner: () => GraphRunner, SignalBroker: () => SignalBroker, SignalEmitter: () => SignalEmitter, SignalTask: () => SignalTask, Task: () => Task, default: () => index_default }); module.exports = __toCommonJS(index_exports); // src/utils/tools.ts function deepCloneFilter(input, filterOut = () => false) { if (input === null || typeof input !== "object") { return input; } const visited = /* @__PURE__ */ new WeakMap(); const stack = []; const output = Array.isArray(input) ? [] : {}; stack.push({ source: input, target: output }); visited.set(input, output); while (stack.length) { const { source, target, key } = stack.pop(); const currentTarget = key !== void 0 ? target[key] : target; for (const [k, value] of Object.entries(source)) { if (filterOut(k)) continue; if (value && typeof value === "object") { if (visited.has(value)) { currentTarget[k] = visited.get(value); continue; } const clonedValue = Array.isArray(value) ? [] : {}; currentTarget[k] = clonedValue; visited.set(value, clonedValue); stack.push({ source: value, target: currentTarget, key: k }); } else { currentTarget[k] = value; } } } return output; } function formatTimestamp(timestamp) { return new Date(timestamp).toISOString(); } // src/engine/SignalBroker.ts var SignalBroker = class _SignalBroker { // execId -> emitted signals constructor() { this.debug = false; this.verbose = false; this.signalObservers = /* @__PURE__ */ new Map(); this.emitStacks = /* @__PURE__ */ new Map(); this.addSignal("meta.signal_broker.added"); } /** * Singleton instance for signal management. * @returns The broker instance. */ static get instance() { if (!this.instance_) { this.instance_ = new _SignalBroker(); } return this.instance_; } setDebug(value) { this.debug = value; } setVerbose(value) { this.verbose = value; } validateSignalName(signalName) { if (signalName.length > 100) { throw new Error( `Signal name must be less than 100 characters: ${signalName}` ); } if (signalName.includes(" ")) { throw new Error(`Signal name must not contain spaces: ${signalName}"`); } if (signalName.includes("\\")) { throw new Error( `Signal name must not contain backslashes: ${signalName}` ); } if (/[A-Z]/.test(signalName.split(":")[0].split(".").slice(1).join("."))) { throw new Error( `Signal name must not contain uppercase letters in the middle of the signal name. It is only allowed in the first part of the signal name: ${signalName}` ); } } /** * Initializes with runners. * @param runner Standard runner for user signals. * @param metaRunner Meta runner for 'meta.' signals (suppresses further meta-emits). */ bootstrap(runner, metaRunner) { this.runner = runner; this.metaRunner = metaRunner; } init() { Cadenza.createMetaTask( "Execute and clear queued signals", () => { for (const [id, signals] of this.emitStacks.entries()) { signals.forEach((context, signal) => { this.execute(signal, context); signals.delete(signal); }); this.emitStacks.delete(id); } return true; }, "Executes queued signals and clears the stack" ).doOn("meta.process_signal_queue_requested").emits("meta.signal_broker.queue_empty"); this.getSignalsTask = Cadenza.createMetaTask("Get signals", (ctx) => { return { __signals: Array.from(this.signalObservers.keys()), ...ctx }; }); } /** * Observes a signal with a routine/task. * @param signal The signal (e.g., 'domain.action', 'domain.*' for wildcards). * @param routineOrTask The observer. * @edge Duplicates ignored; supports wildcards for broad listening. */ observe(signal, routineOrTask) { this.addSignal(signal); this.signalObservers.get(signal).tasks.add(routineOrTask); } /** * Unsubscribes a routine/task from a signal. * @param signal The signal. * @param routineOrTask The observer. * @edge Removes all instances if duplicate; deletes if empty. */ unsubscribe(signal, routineOrTask) { const obs = this.signalObservers.get(signal); if (obs) { obs.tasks.delete(routineOrTask); if (obs.tasks.size === 0) { this.signalObservers.delete(signal); } } } /** * Emits a signal and bubbles to matching wildcards/parents (e.g., 'a.b.action' triggers 'a.b.action', 'a.b.*', 'a.*'). * @param signal The signal name. * @param context The payload. * @edge Fire-and-forget; guards against loops per execId (from context.__graphExecId). * @edge For distribution, SignalTask can prefix and proxy remote. */ emit(signal, context = {}) { const execId = context.__routineExecId || "global"; delete context.__routineExecId; if (!this.emitStacks.has(execId)) this.emitStacks.set(execId, /* @__PURE__ */ new Map()); const stack = this.emitStacks.get(execId); stack.set(signal, context); let executed = false; try { executed = this.execute(signal, context); } finally { if (executed) stack.delete(signal); if (stack.size === 0) this.emitStacks.delete(execId); } } execute(signal, context) { var _a, _b, _c; const isMeta = signal.startsWith("meta."); const isSubMeta = signal.startsWith("sub_meta.") || context.__isSubMeta; const isMetric = (_a = context.__signalEmission) == null ? void 0 : _a.isMetric; if (!isSubMeta && (!isMeta || this.debug)) { const emittedAt = Date.now(); context.__signalEmission = { ...context.__signalEmission, signalName: signal, emittedAt: formatTimestamp(emittedAt), consumed: false, consumedBy: null, isMeta }; } else if (isSubMeta) { context.__isSubMeta = true; delete context.__signalEmission; } else { delete context.__signalEmission; } if (this.debug && (!isMetric && !isSubMeta || this.verbose)) { console.log( `EMITTING ${signal} to listeners ${(_c = (_b = this.signalObservers.get(signal)) == null ? void 0 : _b.tasks.size) != null ? _c : 0} with context ${this.verbose ? JSON.stringify(context) : JSON.stringify(context).slice(0, 100)}` ); } let executed; executed = this.executeListener(signal, context); if (!isSubMeta) { const parts = signal.slice(0, Math.max(signal.lastIndexOf(":"), signal.lastIndexOf("."))).split("."); for (let i = parts.length; i > -1; i--) { const parent = parts.slice(0, i).join("."); executed = executed || this.executeListener(parent + ".*", context); } } return executed; } executeListener(signal, context) { const obs = this.signalObservers.get(signal); const isMeta = signal.startsWith("meta"); const runner = isMeta ? this.metaRunner : this.runner; if (obs && obs.tasks.size && runner) { obs.fn(runner, Array.from(obs.tasks), context); return true; } return false; } addSignal(signal) { let _signal = signal; if (!this.signalObservers.has(_signal)) { this.validateSignalName(_signal); this.signalObservers.set(_signal, { fn: (runner, tasks, context) => runner.run(tasks, context), tasks: /* @__PURE__ */ new Set() }); const sections = _signal.split(":"); if (sections.length === 2) { _signal = sections[0]; if (!this.signalObservers.has(sections[0])) { this.signalObservers.set(_signal, { fn: (runner, tasks, context) => runner.run(tasks, context), tasks: /* @__PURE__ */ new Set() }); } else { return; } } this.emit("meta.signal_broker.added", { __signalName: _signal }); } } // TODO schedule signals /** * Lists all observed signals. * @returns Array of signals. */ listObservedSignals() { return Array.from(this.signalObservers.keys()); } reset() { this.emitStacks.clear(); this.signalObservers.clear(); } }; // src/engine/GraphRunner.ts var import_uuid4 = require("uuid"); // src/engine/GraphRun.ts var import_uuid = require("uuid"); // src/utils/ColorRandomizer.ts var ColorRandomizer = class { constructor(numberOfSteps = 200, spread = 30) { this.stepCounter = 0; this.numberOfSteps = numberOfSteps; this.spread = spread; this.range = Math.floor(numberOfSteps / this.spread); } rainbow(numOfSteps, step) { let r, g, b; const h = step / numOfSteps; const i = ~~(h * 6); const f = h * 6 - i; const q = 1 - f; switch (i % 6) { case 0: r = 1; g = f; b = 0; break; case 1: r = q; g = 1; b = 0; break; case 2: r = 0; g = 1; b = f; break; case 3: r = 0; g = q; b = 1; break; case 4: r = f; g = 0; b = 1; break; case 5: r = 1; g = 0; b = q; break; default: r = 0; g = 0; b = 0; break; } const c = "#" + ("00" + (~~(r * 255)).toString(16)).slice(-2) + ("00" + (~~(g * 255)).toString(16)).slice(-2) + ("00" + (~~(b * 255)).toString(16)).slice(-2); return c; } getRandomColor() { this.stepCounter++; if (this.stepCounter > this.numberOfSteps) { this.stepCounter = 1; } const randomStep = this.stepCounter * this.range % this.numberOfSteps - this.range + Math.floor(this.stepCounter / this.spread); return this.rainbow(this.numberOfSteps, randomStep); } }; // src/engine/exporters/vue-flow/VueFlowExportVisitor.ts var VueFlowExportVisitor = class { constructor() { this.nodeCount = 0; this.elements = []; this.index = 0; this.numberOfLayerNodes = 0; this.contextToColor = {}; this.colorRandomizer = new ColorRandomizer(); } visitLayer(layer) { const snapshot = layer.export(); this.numberOfLayerNodes = snapshot.__numberOfNodes; this.index = 0; } visitNode(node) { const snapshot = node.export(); if (!this.contextToColor[snapshot.__context.id]) { this.contextToColor[snapshot.__context.id] = this.colorRandomizer.getRandomColor(); } const color = this.contextToColor[snapshot.__context.id]; this.elements.push({ id: snapshot.__id.slice(0, 8), label: snapshot.__task.__name, position: { x: snapshot.__task.__layerIndex * 500, y: -50 * this.numberOfLayerNodes * 0.5 + (this.index * 60 + 30) }, sourcePosition: "right", targetPosition: "left", style: { backgroundColor: `${color}`, width: "180px" }, data: { executionTime: snapshot.__executionTime, executionStart: snapshot.__executionStart, executionEnd: snapshot.__executionEnd, description: snapshot.__task.__description, functionString: snapshot.__task.__functionString, context: snapshot.__context.context, layerIndex: snapshot.__task.__layerIndex } }); for (const [index, nextNodeId] of snapshot.__nextNodes.entries()) { this.elements.push({ id: `${snapshot.__id.slice(0, 8)}-${index}`, source: snapshot.__id.slice(0, 8), target: nextNodeId.slice(0, 8) }); } this.index++; this.nodeCount++; } visitTask(task) { const snapshot = task.export(); this.elements.push({ id: snapshot.__id.slice(0, 8), label: snapshot.__name, position: { x: snapshot.__layerIndex * 300, y: this.index * 50 + 30 }, sourcePosition: "right", targetPosition: "left", data: { description: snapshot.__description, functionString: snapshot.__functionString, layerIndex: snapshot.__layerIndex } }); for (const [index, nextTaskId] of snapshot.__nextTasks.entries()) { this.elements.push({ id: `${snapshot.__id.slice(0, 8)}-${index}`, source: snapshot.__id.slice(0, 8), target: nextTaskId.slice(0, 8) }); } this.index++; this.nodeCount++; } getElements() { return this.elements; } getNodeCount() { return this.nodeCount; } }; // src/engine/exporters/vue-flow/VueFlowExporter.ts var VueFlowExporter = class { exportGraph(graph) { const exporterVisitor = new VueFlowExportVisitor(); const layers = graph.getIterator(); while (layers.hasNext()) { const layer = layers.next(); layer.accept(exporterVisitor); } return { elements: exporterVisitor.getElements(), numberOfNodes: exporterVisitor.getNodeCount() }; } exportStaticGraph(graph) { const exporterVisitor = new VueFlowExportVisitor(); let prevTask = null; for (const task of graph) { if (task === prevTask) { continue; } const tasks = task.getIterator(); const exportedTaskNames = []; while (tasks.hasNext()) { const task2 = tasks.next(); if (task2 && !exportedTaskNames.includes(task2.name)) { exportedTaskNames.push(task2.name); task2.accept(exporterVisitor); } } prevTask = task; } return { elements: exporterVisitor.getElements(), numberOfNodes: exporterVisitor.getNodeCount() }; } }; // src/engine/GraphRun.ts var GraphRun = class { constructor(strategy) { this.id = (0, import_uuid.v4)(); this.strategy = strategy; this.strategy.setRunInstance(this); this.exporter = new VueFlowExporter(); } setGraph(graph) { this.graph = graph; } addNode(node) { this.strategy.addNode(node); } // Composite function / Command execution run() { return this.strategy.run(); } // Composite function destroy() { var _a; (_a = this.graph) == null ? void 0 : _a.destroy(); this.graph = void 0; this.exporter = void 0; } // Composite function log() { var _a; console.log("vvvvvvvvvvvvvvvvv"); console.log("GraphRun"); console.log("vvvvvvvvvvvvvvvvv"); (_a = this.graph) == null ? void 0 : _a.log(); console.log("================="); } // Memento export() { var _a, _b; if (this.exporter && this.graph) { const data = this.strategy.export(); return { __id: this.id, __label: (_a = data.__startTime) != null ? _a : this.id, __graph: (_b = this.exporter) == null ? void 0 : _b.exportGraph(this.graph), __data: data }; } return { __id: this.id, __label: this.id, __graph: void 0, __data: {} }; } // Export Strategy setExporter(exporter) { this.exporter = exporter; } }; // src/graph/execution/GraphNode.ts var import_uuid3 = require("uuid"); // src/graph/context/GraphContext.ts var import_uuid2 = require("uuid"); var GraphContext = class _GraphContext { // __keys, frozen constructor(context) { if (Array.isArray(context)) { throw new Error("Array contexts not supported"); } this.fullContext = context; this.userData = Object.fromEntries( Object.entries(this.fullContext).filter(([key]) => !key.startsWith("__")) ); this.metadata = Object.fromEntries( Object.entries(this.fullContext).filter(([key]) => key.startsWith("__")) ); this.id = (0, import_uuid2.v4)(); } /** * Gets frozen user data (read-only, no clone). * @returns Frozen user context. */ getContext() { return this.userData; } getClonedContext() { return deepCloneFilter(this.userData); } /** * Gets full raw context (cloned for safety). * @returns Cloned full context. */ getFullContext() { return this.fullContext; } getClonedFullContext() { return deepCloneFilter(this.fullContext); } /** * Gets frozen metadata (read-only). * @returns Frozen metadata object. */ getMetadata() { return this.metadata; } /** * Clones this context (new instance). * @returns New GraphContext. */ clone() { return this.mutate(this.fullContext); } /** * Creates new context from data (via registry). * @param context New data. * @returns New GraphContext. */ mutate(context) { return new _GraphContext(context); } /** * Combines with another for uniques (joins userData). * @param otherContext The other. * @returns New combined GraphContext. * @edge Appends other.userData to joinedContexts in userData. */ combine(otherContext) { const newUser = { ...this.userData }; newUser.joinedContexts = this.userData.joinedContexts ? [...this.userData.joinedContexts] : [this.userData]; const otherUser = otherContext.userData; if (Array.isArray(otherUser.joinedContexts)) { newUser.joinedContexts.push(...otherUser.joinedContexts); } else { newUser.joinedContexts.push(otherUser); } const newFull = { ...this.fullContext, ...otherContext.fullContext, ...newUser }; return new _GraphContext(newFull); } /** * Exports the context. * @returns Exported object. */ export() { return { id: this.id, context: this.getFullContext() }; } }; // src/graph/iterators/GraphNodeIterator.ts var GraphNodeIterator = class { constructor(node) { this.currentLayer = []; this.nextLayer = []; this.index = 0; this.currentNode = node; this.currentLayer = [node]; } hasNext() { return !!this.currentNode; } next() { const nextNode = this.currentNode; if (!nextNode) { return void 0; } this.nextLayer.push(...nextNode.mapNext((n) => n)); this.index++; if (this.index === this.currentLayer.length) { this.currentLayer = this.nextLayer; this.nextLayer = []; this.index = 0; } this.currentNode = this.currentLayer.length ? this.currentLayer[this.index] : void 0; return nextNode; } }; // src/interfaces/SignalEmitter.ts var SignalEmitter = class { /** * Constructor for signal emitters. * @param silent If true, suppresses all emissions (e.g., for meta-runners to avoid loops; affects all emits). */ constructor(silent = false) { this.silent = silent; } /** * Emits a signal via the broker. * @param signal The signal name. * @param data Optional payload (defaults to empty object). */ emit(signal, data = {}) { Cadenza.broker.emit(signal, data); } /** * Emits a signal via the broker if not silent. * @param signal The signal name. * @param data Optional payload (defaults to empty object). */ emitMetrics(signal, data = {}) { if (this.silent) { return; } Cadenza.broker.emit(signal, data); } }; // src/utils/promise.ts function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } // src/graph/execution/GraphNode.ts var GraphNode = class _GraphNode extends SignalEmitter { constructor(task, context, routineExecId, prevNodes = [], debug = false, verbose = false) { var _a; super( task.isMeta && !debug || task.isSubMeta || ((_a = context == null ? void 0 : context.getMetadata()) == null ? void 0 : _a.__isSubMeta) ); this.divided = false; this.splitGroupId = ""; this.processing = false; this.subgraphComplete = false; this.graphComplete = false; this.result = false; this.retryCount = 0; this.retryDelay = 0; this.retries = 0; this.previousNodes = []; this.nextNodes = []; this.executionTime = 0; this.executionStart = 0; this.failed = false; this.errored = false; this.destroyed = false; this.debug = false; this.verbose = false; this.id = (0, import_uuid3.v4)(); this.task = task; this.context = context; this.retryCount = task.retryCount; this.retryDelay = task.retryDelay; this.previousNodes = prevNodes; this.routineExecId = routineExecId; this.splitGroupId = routineExecId; this.debug = debug; this.verbose = verbose; } setDebug(value) { this.debug = value; } isUnique() { return this.task.isUnique; } isMeta() { return this.task.isMeta; } isProcessed() { return this.divided; } isProcessing() { return this.processing; } subgraphDone() { return this.subgraphComplete; } graphDone() { return this.graphComplete; } isEqualTo(node) { return this.sharesTaskWith(node) && this.sharesContextWith(node) && this.isPartOfSameGraph(node); } isPartOfSameGraph(node) { return this.routineExecId === node.routineExecId; } sharesTaskWith(node) { return this.task.name === node.task.name; } sharesContextWith(node) { return this.context.id === node.context.id; } getLayerIndex() { return this.task.layerIndex; } getConcurrency() { return this.task.concurrency; } getTag() { return this.task.getTag(this.context); } scheduleOn(layer) { var _a, _b, _c; let shouldSchedule = true; const nodes = layer.getNodesByRoutineExecId(this.routineExecId); for (const node of nodes) { if (node.isEqualTo(this)) { shouldSchedule = false; break; } if (node.sharesTaskWith(this) && node.isUnique()) { node.consume(this); shouldSchedule = false; break; } } if (shouldSchedule) { this.layer = layer; layer.add(this); const context = this.context.getFullContext(); const scheduledAt = Date.now(); this.emitMetricsWithMetadata("meta.node.scheduled", { data: { uuid: this.id, routineExecutionId: this.routineExecId, executionTraceId: (_b = context.__executionTraceId) != null ? _b : (_a = context.__metadata) == null ? void 0 : _a.__executionTraceId, context: this.context.export(), taskName: this.task.name, taskVersion: this.task.version, isMeta: this.isMeta(), isScheduled: true, splitGroupId: this.splitGroupId, created: formatTimestamp(scheduledAt) } }); this.previousNodes.forEach((node) => { this.emitMetricsWithMetadata("meta.node.mapped", { data: { taskExecutionId: this.id, previousTaskExecutionId: node.id, executionCount: "increment" }, filter: { taskName: this.task.name, taskVersion: this.task.version, previousTaskName: node.task.name, previousTaskVersion: node.task.version } }); }); if (((_c = context.__signalEmission) == null ? void 0 : _c.consumed) === false && (!this.isMeta() || this.debug)) { this.emitMetricsWithMetadata("meta.node.consumed_signal", { data: { signalName: context.__signalEmission.signalName, taskName: this.task.name, taskVersion: this.task.version, taskExecutionId: this.id, consumedAt: formatTimestamp(scheduledAt) } }); context.__signalEmission.consumed = true; context.__signalEmission.consumedBy = this.id; } } } start() { if (this.executionStart === 0) { this.executionStart = Date.now(); } if (this.previousNodes.length === 0) { this.emitMetricsWithMetadata("meta.node.started_routine_execution", { data: { isRunning: true, started: formatTimestamp(this.executionStart) }, filter: { uuid: this.routineExecId } }); } if (this.debug && !this.task.isSubMeta && !this.context.getMetadata().__isSubMeta || this.verbose) { this.log(); } this.emitMetricsWithMetadata("meta.node.started", { data: { isRunning: true, started: formatTimestamp(this.executionStart) }, filter: { uuid: this.id } }); return this.executionStart; } end() { if (this.executionStart === 0) { return 0; } this.processing = false; const end = Date.now(); this.executionTime = end - this.executionStart; const context = this.context.getFullContext(); if (this.errored || this.failed) { this.emitMetricsWithMetadata("meta.node.errored", { data: { isRunning: false, errored: this.errored, failed: this.failed, errorMessage: context.__error }, filter: { uuid: this.id } }); } this.emitMetricsWithMetadata("meta.node.ended", { data: { isRunning: false, isComplete: true, resultContext: this.context.export(), errored: this.errored, failed: this.failed, errorMessage: context.__error, progress: 1, ended: formatTimestamp(end) }, filter: { uuid: this.id } }); if (this.graphDone()) { this.emitMetricsWithMetadata( `meta.node.ended_routine_execution:${this.routineExecId}`, { data: { isRunning: false, isComplete: true, resultContext: this.context.export(), progress: 1, ended: formatTimestamp(end) }, filter: { uuid: this.routineExecId } } ); } return end; } execute() { if (!this.divided && !this.processing) { this.processing = true; const inputValidation = this.task.validateInput( this.isMeta() ? this.context.getMetadata() : this.context.getContext() ); if (inputValidation !== true) { this.onError(inputValidation.__validationErrors); this.postProcess(); return this.nextNodes; } this.result = this.work(); if (this.result instanceof Promise) { return this.executeAsync(); } const nextNodes = this.postProcess(); if (nextNodes instanceof Promise) { return nextNodes; } this.nextNodes = nextNodes; } return this.nextNodes; } async workAsync() { try { this.result = await this.result; } catch (e) { const result = await this.retryAsync(e); if (result === e) { this.onError(e); } } } async executeAsync() { await this.workAsync(); const nextNodes = this.postProcess(); if (nextNodes instanceof Promise) { return nextNodes; } this.nextNodes = nextNodes; return this.nextNodes; } work() { try { const result = this.task.execute( this.context, this.emitWithMetadata.bind(this), this.onProgress.bind(this) ); if (result.errored || result.failed) { return this.retry(result); } return result; } catch (e) { const result = this.retry(e); return result.then((result2) => { if (result2 !== e) { return result2; } this.onError(e); return this.result; }); } } emitWithMetadata(signal, ctx) { const data = { ...ctx }; if (!this.task.isHidden) { data.__signalEmission = { taskName: this.task.name, taskVersion: this.task.version, taskExecutionId: this.id }; data.__metadata = { __routineExecId: this.routineExecId }; } this.emit(signal, data); } emitMetricsWithMetadata(signal, ctx) { const data = { ...ctx }; if (!this.task.isHidden) { data.__signalEmission = { taskName: this.task.name, taskVersion: this.task.version, taskExecutionId: this.id, isMetric: true }; data.__metadata = { __routineExecId: this.routineExecId }; } this.emitMetrics(signal, data); } onProgress(progress) { var _a, _b; progress = Math.min(Math.max(0, progress), 1); this.emitMetricsWithMetadata("meta.node.progress", { data: { progress }, filter: { uuid: this.id } }); this.emitMetricsWithMetadata( `meta.node.routine_execution_progress:${this.routineExecId}`, { data: { progress: progress * this.task.progressWeight / ((_b = (_a = this.layer) == null ? void 0 : _a.getIdenticalNodes(this).length) != null ? _b : 1) }, filter: { uuid: this.routineExecId } } ); } postProcess() { if (typeof this.result === "string") { this.onError( `Returning strings is not allowed. Returned: ${this.result}` ); } if (Array.isArray(this.result)) { this.onError(`Returning arrays is not allowed. Returned: ${this.result}`); } const nextNodes = this.divide(); if (nextNodes instanceof Promise) { return this.postProcessAsync(nextNodes); } this.nextNodes = nextNodes; this.finalize(); return this.nextNodes; } async postProcessAsync(nextNodes) { this.nextNodes = await nextNodes; this.finalize(); return this.nextNodes; } finalize() { if (this.nextNodes.length === 0) { this.completeSubgraph(); } if (this.errored || this.failed) { this.task.mapOnFailSignals( (signal) => this.emitWithMetadata(signal, this.context.getFullContext()) ); } else if (this.result !== void 0 && this.result !== false) { this.task.mapSignals( (signal) => this.emitWithMetadata(signal, this.context.getFullContext()) ); } this.end(); } onError(error, errorData = {}) { this.result = { ...this.context.getFullContext(), __error: `Node error: ${error}`, __retries: this.retries, error: `Node error: ${error}`, returnedValue: this.result, ...errorData }; this.migrate(this.result); this.errored = true; } async retry(prevResult) { if (this.retryCount === 0) { return prevResult; } await this.delayRetry(); return this.work(); } async retryAsync(prevResult) { if (this.retryCount === 0) { return prevResult; } await this.delayRetry(); this.result = this.work(); return this.workAsync(); } async delayRetry() { this.retryCount--; this.retries++; await sleep(this.retryDelay); this.retryDelay *= this.task.retryDelayFactor; if (this.retryDelay > this.task.retryDelayMax) { this.retryDelay = this.task.retryDelayMax; } } divide() { var _a; const newNodes = []; if (((_a = this.result) == null ? void 0 : _a.next) && typeof this.result.next === "function") { const generator = this.result; let current = generator.next(); if (current instanceof Promise) { return this.divideAsync(current); } while (!current.done && current.value !== void 0) { const outputValidation = this.task.validateOutput(current.value); if (outputValidation !== true) { this.onError(outputValidation.__validationErrors); break; } else { newNodes.push(...this.generateNewNodes(current.value)); current = generator.next(); } } } else if (this.result !== void 0 && !this.errored) { newNodes.push(...this.generateNewNodes(this.result)); if (typeof this.result !== "boolean") { const outputValidation = this.task.validateOutput(this.result); if (outputValidation !== true) { this.onError(outputValidation.__validationErrors); } this.divided = true; this.migrate({ ...this.result, ...this.context.getMetadata(), __nextNodes: newNodes.map((n) => n.id), __retries: this.retries }); return newNodes; } } if (this.errored) { newNodes.push( ...this.task.mapNext( (t) => this.clone().split((0, import_uuid3.v4)()).differentiate(t).migrate({ ...this.result }), true ) ); } this.divided = true; this.migrate({ ...this.context.getFullContext(), __nextNodes: newNodes.map((n) => n.id), __retries: this.retries }); return newNodes; } async divideAsync(current) { const nextNodes = []; const _current = await current; const outputValidation = this.task.validateOutput(_current.value); if (outputValidation !== true) { this.onError(outputValidation.__validationErrors); return nextNodes; } else { nextNodes.push(...this.generateNewNodes(_current.value)); } for await (const result of this.result) { const outputValidation2 = this.task.validateOutput(result); if (outputValidation2 !== true) { this.onError(outputValidation2.__validationErrors); return []; } else { nextNodes.push(...this.generateNewNodes(result)); } } this.divided = true; return nextNodes; } generateNewNodes(result) { const groupId = (0, import_uuid3.v4)(); const newNodes = []; if (typeof result !== "boolean") { const failed = result.failed !== void 0 && result.failed || result.error !== void 0; newNodes.push( ...this.task.mapNext((t) => { const context = t.isUnique ? { joinedContexts: [ { ...result, taskName: this.task.name, __nodeId: this.id } ], ...this.context.getMetadata() } : { ...result, ...this.context.getMetadata() }; return this.clone().split(groupId).differentiate(t).migrate(context); }, failed) ); this.failed = failed; } else { const shouldContinue = result; if (shouldContinue) { newNodes.push( ...this.task.mapNext((t) => { const newNode = this.clone().split(groupId).differentiate(t); if (t.isUnique) { newNode.migrate({ joinedContexts: [ { ...this.context.getContext(), taskName: this.task.name, __nodeId: this.id } ], ...this.context.getMetadata() }); } return newNode; }) ); } } return newNodes; } differentiate(task) { var _a, _b; this.task = task; this.retryCount = task.retryCount; this.retryDelay = task.retryDelay; this.silent = task.isMeta && !this.debug || task.isSubMeta || ((_b = (_a = this.context) == null ? void 0 : _a.getMetadata()) == null ? void 0 : _b.__isSubMeta); return this; } migrate(ctx) { this.context = new GraphContext(ctx); return this; } split(id) { this.splitGroupId = id; return this; } clone() { return new _GraphNode( this.task, this.context, this.routineExecId, [this], this.debug, this.verbose ); } consume(node) { this.context = this.context.combine(node.context); this.previousNodes = this.previousNodes.concat(node.previousNodes); node.completeSubgraph(); node.changeIdentity(this.id); node.destroy(); } changeIdentity(id) { this.id = id; } completeSubgraph() { for (const node of this.nextNodes) { if (!node.subgraphDone()) { return; } } this.subgraphComplete = true; if (this.previousNodes.length === 0) { this.completeGraph(); return; } this.previousNodes.forEach((n) => n.completeSubgraph()); } completeGraph() { this.graphComplete = true; this.nextNodes.forEach((n) => n.completeGraph()); } destroy() { this.context = null; this.task = null; this.nextNodes = []; this.previousNodes.forEach( (n) => n.nextNodes.splice(n.nextNodes.indexOf(this), 1) ); this.previousNodes = []; this.result = void 0; this.layer = void 0; this.destroyed = true; } getIterator() { return new GraphNodeIterator(this); } mapNext(callback) { return this.nextNodes.map(callback); } accept(visitor) { visitor.visitNode(this); } export() { return { __id: this.id, __task: this.task.export(), __context: this.context.export(), __result: this.result, __executionTime: this.executionTime, __executionStart: this.executionStart, __executionEnd: this.executionStart + this.executionTime, __nextNodes: this.nextNodes.map((node) => node.id), __previousNodes: this.previousNodes.map((node) => node.id), __routineExecId: this.routineExecId, __isProcessing: this.processing, __isMeta: this.isMeta(), __graphComplete: this.graphComplete, __failed: this.failed, __errored: this.errored, __isUnique: this.isUnique(), __splitGroupId: this.splitGroupId, __tag: this.getTag() }; } lightExport() { return { __id: this.id, __task: { __name: this.task.name, __version: this.task.version }, __context: this.context.export(), __executionTime: this.executionTime, __executionStart: this.executionStart, __nextNodes: this.nextNodes.map((node) => node.id), __previousNodes: this.previousNodes.map((node) => node.id), __routineExecId: this.routineExecId, __isProcessing: this.processing, __graphComplete: this.graphComplete, __isMeta: this.isMeta(), __failed: this.failed, __errored: this.errored, __isUnique: this.isUnique(), __splitGroupId: this.splitGroupId, __tag: this.getTag() }; } log() { console.log( "Node EXECUTION:", this.task.name, JSON.stringify(this.context.getFullContext()) ); } }; // src/graph/definition/GraphRoutine.ts var GraphRoutine = class extends SignalEmitter { constructor(name, tasks, description, isMeta = false) { super(); this.version = 1; this.isMeta = false; this.tasks = /* @__PURE__ */ new Set(); this.observedSignals = /* @__PURE__ */ new Set(); this.name = name; this.description = description; this.isMeta = isMeta; this.emit("meta.routine.created", { data: { name: this.name, version: this.version, description: this.description, isMeta: this.isMeta }, __routineInstance: this }); tasks.forEach((t) => { this.tasks.add(t); this.emit("meta.routine.task_added", { data: { taskName: t.name, taskVersion: t.version, routineName: this.name, routineVersion: this.version } }); }); } /** * Applies callback to starting tasks. * @param callBack The callback. * @returns Promise if async. */ async forEachTask(callBack) { const promises = []; for (const task of this.tasks) { const res = callBack(task); if (res instanceof Promise) promises.push(res); } await Promise.all(promises); } /** * Sets global Version. * @param version The Version. */ setVersion(version) { this.version = version; this.emit("meta.routine.global_version_set", { version: this.version }); } /** * Subscribes to signals (chainable). * @param signals The signal names. * @returns This for chaining. * @edge Duplicates ignored; assumes broker.observe binds this as handler. */ doOn(...signals) { signals.forEach((signal) => { if (this.observedSignals.has(signal)) return; Cadenza.broker.observe(signal, this); this.observedSignals.add(signal); }); return this; } /** * Unsubscribes from all observed signals. * @returns This for chaining. */ unsubscribeAll() { this.observedSignals.forEach( (signal) => Cadenza.broker.unsubscribe(signal, this) ); this.observedSignals.clear(); return this; } /** * Unsubscribes from specific signals. * @param signals The signals. * @returns This for chaining. * @edge No-op if not subscribed. */ unsubscribe(...signals) { signals.forEach((signal) => { if (this.observedSignals.has(signal)) { Cadenza.broker.unsubscribe(signal, this); this.observedSignals.delete(signal); } }); return this; } /** * Destroys the routine. */ destroy() { this.unsubscribeAll(); this.tasks.clear(); this.emit("meta.routine.destroyed", { data: { deleted: true }, filter: { name: this.name, version: this.version } }); } }; // src/graph/iterators/TaskIterator.ts var TaskIterator = class { constructor(task) { this.currentLayer = /* @__PURE__ */ new Set(); this.nextLayer = /* @__PURE__ */ new Set(); this.iterator = this.currentLayer[Symbol.iterator](); this.currentTask = task; this.currentLayer.add(task); } hasNext() { return !!this.currentTask; } next() { const nextTask = this.currentTask; if (!nextTask) { return void 0; } nextTask.mapNext((t) => this.nextLayer.add(t)); let next = this.iterator.next(); if (next.value === void 0) { this.currentLayer.clear(); this.currentLayer = this.nextLayer; this.nextLayer = /* @__PURE__ */ new Set(); this.iterator = this.currentLayer[Symbol.iterator](); next = this.iterator.next(); } this.currentTask = next.value; return nextTask; } }; // src/graph/definition/Task.ts var Task = class extends SignalEmitter { /** * Constructs a Task (static definition). * @param name Name. * @param task Function. * @param description Description. * @param concurrency Limit. * @param timeout ms. * @param register Register via signal (default true). * @param isUnique * @param isMeta * @param isSubMeta * @param isHidden * @param getTagCallback * @param inputSchema * @param validateInputContext * @param outputSchema * @param validateOutputContext * @param retryCount * @param retryDelay * @param retryDelayMax * @param retryDelayFactor * @edge Emits 'meta.task.created' with { __task: this } for seed. */ constructor(name, task, description = "", concurrency = 0, timeout = 0, register = true, isUnique = false, isMeta = false, isSubMeta = false, isHidden = false, getTagCallback = void 0, inputSchema = void 0, validateInputContext = false, outputSchema = void 0, validateOutputContext = false, retryCount = 0, retryDelay = 0, retryDelayMax = 0, retryDelayFactor = 1) { super(isSubMeta || isHidden); this.version = 1; this.isMeta = false; this.isSubMeta = false; this.isHidden = false; this.isUnique = false; this.throttled = false; this.isSignal = false; this.isDeputy = false; this.isEphemeral = false; this.isDebounce = false; this.inputContextSchema = void 0; this.validateInputContext = false; this.outputContextSchema = void 0; this.validateOutputContext = false; this.retryCount = 0; this.retryDelay = 0; this.retryDelayMax = 0; this.retryDelayFactor = 1; this.layerIndex = 0; this.progressWeight = 0; this.nextTasks = /* @__PURE__ */ new Set(); this.onFailTasks = /* @__PURE__ */ new Set(); this.predecessorTasks = /* @__PURE__ */ new Set(); this.destroyed = false; this.emitsSignals = /* @__PURE__ */ new Set(); this.signalsToEmitAfter = /* @__PURE__ */ new Set(); this.signalsToEmitOnFail = /* @__PURE__ */ new Set(); this.observedSignals = /* @__PURE__ */ new Set(); this.name = name; this.taskFunction = task.bind(this); this.description = description; this.concurrency = concurrency; this.timeout = timeout; this.isUnique = isUnique; this.isMeta = isMeta; this.isSubMeta = isSubMeta; this.isHidden = isHidden; this.inputContextSchema = inputSchema; this.validateInputContext = validateInputContext; this.outputContextSchema = outputSchema; this.validateOutputContext = validateOutputContext; this.retryCount = retryCount; this.retryDelay = retryDelay; this.retryDelayMax = retryDelayMax; this.retryDelayFactor = retryDelayFactor; if (getTagCallback) { this.getTag = (context) => getTagCallback(context, this); this.throttled = true; } if (register && !this.isHidden && !this.isSubMeta) { const { __functionString, __getTagCallback } = this.export(); this.emitWithMetadata("meta.task.created", { data: { name: this.name, version: this.version, description: this.description, functionString: __functionString, tagIdGetter: __getTagCallback, layerIndex: this.layerIndex, concurrency: this.concurrency, retryCount: this.retryCount, retryDelay: this.retryDelay, retryDelayMax: this.retryDelayMax, retryDelayFactor: this.retryDelayFactor, timeout: this.timeout, isUnique: this.isUnique, isSignal: this.isSignal, isThrottled: this.throttled, isDebounce: this.isDebounce, isEphemeral: this.isEphemeral, isMeta: this.isMeta, validateInputContext: this.validateInputContext, validateOutputContext: this.validateOutputContext, inputContextSchema: this.inputContextSchema, outputContextSchema: this.outputContextSchema }, __taskInstance: this }); } } getTag(context) { return this.name; } setVersion(version) { this.version = version; this.emitWithMetadata("meta.task.version_set", { __version: this.version }); } setTimeout(timeout) { this.timeout = timeout; } setConcurrency(concurrency) { this.concurrency = concurrency; } setProgressWeight(weight) { this.progressWeight = weight; } setInputContextSchema(schema) { this.inputContextSchema = schema; } setOutputContextSchema(schema) { this.outputContextSchema = schema; } setValidateInputContext(value) { this.validateInputContext = value; } setValidateOutputContext(value) { this.validateOutputContext = value; } emitWithMetadata(signal, ctx = {}) { const data = { ...ctx }; if (!this.isHidden && !this.isSubMeta) { data.__signalEmission = { taskName: this.name, taskVersion: this.version }; } this.emit(signal, data); } emitMetricsWithMetadata(signal, ctx = {}) { const data = { ...ctx }; if (!this.isHidden && !this.isSubMeta) { data.__signalEmission = { taskName: this.name, taskVersion: this.version, isMetric: true }; } this.emitMetrics(signal, data); } /** * Validates a context deeply against a schema. * @param data - The data to validate (input context or output result). * @param schema - The schema definition. * @param path - The current path for error reporting (default: 'root'). * @returns { { valid: boolean, errors: Record<string, string> } } - Validation result with detailed errors if invalid. * @description Recursively checks types, required fields, and constraints; allows extra properties not in schema. */ validateSchema(data, schema, path = "context") { const errors = {}; if (!schema || typeof schema !== "object") return { valid: true, errors }; const required = schema.required || []; for (const key of required) { if (!(key in data)) { errors[`${path}.${key}`] = `Required field '${key}' is missing`; } } const properties = schema.properties || {}; for (const [key, value] of Object.entries(data)) { if (key in properties) { const prop = properties[key]; const propType = prop.type; if (propType === "any") { continue; } if ((value === void 0 || value === null) && !prop.strict) { continue; } if (propType === "string" && typeof value !== "string") { errors[`${path}.${key}`] = `Expected 'string' for '${key}', got '${typeof value}'`; } else if (propType === "number" && typeof value !== "number") { errors[`${path}.${key}`] = `Expected 'number' for '${key}', got '${typeof value}'`; } else if (propType === "boolean" && typeof value !== "boolean") { errors[`${path}.${key}`] = `Expected 'boolean' for '${key}', got '${typeof value}'`; } else if (propType === "array" && !Array.isArray(value)) { errors[`${path}.${key}`] = `Expected 'array' for '${key}', got '${typeof value}'`; } else if (propType === "object" && (typeof value !== "object" || value === null || Array.isArray(value))) { errors[`${path}.${key}`] = `Expected 'object' for '${key}', got '${typeof value}'`; } else if (propType === "array" && prop.items) { if (Array.isArray(value)) { value.forEach((item,