@maxdrellin/xenocline 0.0.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.
Files changed (100) hide show
  1. package/.kodrdriv/config.yaml +10 -0
  2. package/.kodrdriv/context/content.md +26 -0
  3. package/LICENSE.md +66 -0
  4. package/README.md +739 -0
  5. package/__mocks__/src/execution/event.js +8 -0
  6. package/dist/aggregator.d.ts +28 -0
  7. package/dist/aggregator.js +31 -0
  8. package/dist/aggregator.js.map +1 -0
  9. package/dist/constants.d.ts +2 -0
  10. package/dist/context.d.ts +3 -0
  11. package/dist/event/aggregator.d.ts +16 -0
  12. package/dist/event/aggregator.js +29 -0
  13. package/dist/event/aggregator.js.map +1 -0
  14. package/dist/event/event.d.ts +14 -0
  15. package/dist/event/event.js +12 -0
  16. package/dist/event/event.js.map +1 -0
  17. package/dist/event/handler.d.ts +7 -0
  18. package/dist/event/handler.js +8 -0
  19. package/dist/event/handler.js.map +1 -0
  20. package/dist/event/node.d.ts +40 -0
  21. package/dist/event/node.js +24 -0
  22. package/dist/event/node.js.map +1 -0
  23. package/dist/event/phase.d.ts +15 -0
  24. package/dist/event/phase.js +12 -0
  25. package/dist/event/phase.js.map +1 -0
  26. package/dist/event/process.d.ts +14 -0
  27. package/dist/event/process.js +15 -0
  28. package/dist/event/process.js.map +1 -0
  29. package/dist/event/transition.d.ts +45 -0
  30. package/dist/event/transition.js +35 -0
  31. package/dist/event/transition.js.map +1 -0
  32. package/dist/event.d.ts +30 -0
  33. package/dist/execution/aggregator.d.ts +23 -0
  34. package/dist/execution/aggregator.js +81 -0
  35. package/dist/execution/aggregator.js.map +1 -0
  36. package/dist/execution/event.d.ts +26 -0
  37. package/dist/execution/event.js +29 -0
  38. package/dist/execution/event.js.map +1 -0
  39. package/dist/execution/next.d.ts +7 -0
  40. package/dist/execution/next.js +116 -0
  41. package/dist/execution/next.js.map +1 -0
  42. package/dist/execution/node.d.ts +4 -0
  43. package/dist/execution/node.js +170 -0
  44. package/dist/execution/node.js.map +1 -0
  45. package/dist/execution/process.d.ts +35 -0
  46. package/dist/execution/process.js +97 -0
  47. package/dist/execution/process.js.map +1 -0
  48. package/dist/input.d.ts +6 -0
  49. package/dist/input.js +4 -0
  50. package/dist/input.js.map +1 -0
  51. package/dist/logger.d.ts +12 -0
  52. package/dist/node/aggregatornode.d.ts +40 -0
  53. package/dist/node/aggregatornode.js +41 -0
  54. package/dist/node/aggregatornode.js.map +1 -0
  55. package/dist/node/node.d.ts +18 -0
  56. package/dist/node/node.js +80 -0
  57. package/dist/node/node.js.map +1 -0
  58. package/dist/node/phasenode.d.ts +21 -0
  59. package/dist/node/phasenode.js +35 -0
  60. package/dist/node/phasenode.js.map +1 -0
  61. package/dist/output.d.ts +6 -0
  62. package/dist/phase.d.ts +16 -0
  63. package/dist/phase.js +23 -0
  64. package/dist/phase.js.map +1 -0
  65. package/dist/process.d.ts +15 -0
  66. package/dist/process.js +106 -0
  67. package/dist/process.js.map +1 -0
  68. package/dist/transition/beginning.d.ts +19 -0
  69. package/dist/transition/beginning.js +31 -0
  70. package/dist/transition/beginning.js.map +1 -0
  71. package/dist/transition/connection.d.ts +26 -0
  72. package/dist/transition/connection.js +80 -0
  73. package/dist/transition/connection.js.map +1 -0
  74. package/dist/transition/decision.d.ts +20 -0
  75. package/dist/transition/decision.js +47 -0
  76. package/dist/transition/decision.js.map +1 -0
  77. package/dist/transition/next.d.ts +13 -0
  78. package/dist/transition/next.js +81 -0
  79. package/dist/transition/next.js.map +1 -0
  80. package/dist/transition/termination.d.ts +17 -0
  81. package/dist/transition/termination.js +50 -0
  82. package/dist/transition/termination.js.map +1 -0
  83. package/dist/transition/transition.d.ts +16 -0
  84. package/dist/transition/transition.js +72 -0
  85. package/dist/transition/transition.js.map +1 -0
  86. package/dist/util/general.d.ts +4 -0
  87. package/dist/util/general.js +6 -0
  88. package/dist/util/general.js.map +1 -0
  89. package/dist/utility/event/eventfilter.d.ts +7 -0
  90. package/dist/utility/event/eventfilter.js +15 -0
  91. package/dist/utility/event/eventfilter.js.map +1 -0
  92. package/dist/utility/event/filteredhandler.d.ts +13 -0
  93. package/dist/utility/event/filteredhandler.js +18 -0
  94. package/dist/utility/event/filteredhandler.js.map +1 -0
  95. package/dist/utility/event/logginghandler.d.ts +12 -0
  96. package/dist/xenocline.d.ts +72 -0
  97. package/dist/xenocline.js +21 -0
  98. package/dist/xenocline.js.map +1 -0
  99. package/eslint.config.mjs +82 -0
  100. package/package.json +71 -0
@@ -0,0 +1,116 @@
1
+ import { executeNode } from './node.js';
2
+ import { isConnection } from '../transition/connection.js';
3
+ import { isDecision } from '../transition/decision.js';
4
+ import { isTermination } from '../transition/termination.js';
5
+ import { dispatchEvent } from './event.js';
6
+ import { createDecisionEvent, createConnectionEvent, createTerminationEvent } from '../event/transition.js';
7
+
8
+ // Helper function to handle the next step in the process
9
+ async function handleNextStep(nodeOutput, nodeId, next, state) {
10
+ //console.log('[_HANDLE_NEXT_STEP_START]', { nodeId, nodeOutput, nextType: next && next.constructor ? next.constructor.name : typeof next, next });
11
+ if (Array.isArray(next) && next.length > 0 && next.every(isDecision)) {
12
+ const decisions = next;
13
+ // console.log('[_HANDLE_NEXT_STEP_DECISIONS]', { nodeId, count: decisions.length, decisions });
14
+ for (const decision of decisions){
15
+ // console.log('[_HANDLE_NEXT_STEP_DECISION_PROCESSING]', { nodeId, decisionId: decision.id, decision });
16
+ await dispatchEvent(state.eventState, createDecisionEvent(nodeId, 'start', decision, {
17
+ output: nodeOutput
18
+ }), state.context);
19
+ try {
20
+ const decisionOutcome = await decision.decide(nodeOutput, state.context);
21
+ await dispatchEvent(state.eventState, createDecisionEvent(nodeId, 'decide', decision, {
22
+ output: nodeOutput,
23
+ result: decisionOutcome
24
+ }), state.context);
25
+ // console.log('[_HANDLE_NEXT_STEP_DECISION_OUTCOME]', { nodeId, decisionId: decision.id, decisionOutcome });
26
+ await handleNextStep(nodeOutput, decision.id, decisionOutcome, state); // outcome processed, decision.id is context for next step if it's an error source. The original nodeId is implicitly the source of this decision.
27
+ await dispatchEvent(state.eventState, createDecisionEvent(nodeId, 'end', decision), state.context);
28
+ } catch (decisionError) {
29
+ // eslint-disable-next-line no-console
30
+ console.error(`[_HANDLE_NEXT_STEP_DECISION_ERROR] Error in decision ${decision.id} from node ${nodeId}:`, {
31
+ decisionError,
32
+ nodeId,
33
+ decisionId: decision.id
34
+ });
35
+ state.errors.push({
36
+ nodeId: decision.id,
37
+ message: decisionError.message
38
+ });
39
+ // Note: 'end' event for this decision path is skipped due to error.
40
+ }
41
+ }
42
+ } else if (Array.isArray(next) && next.length > 0 && next.every(isConnection)) {
43
+ //console.log('[_HANDLE_NEXT_STEP_CONNECTIONS]', { nodeId, count: next.length, connections: next });
44
+ const connections = next;
45
+ const nextPhasePromises = [];
46
+ for (const connection of connections){
47
+ let nextInput = nodeOutput;
48
+ let nextContext = state.context;
49
+ await dispatchEvent(state.eventState, createConnectionEvent(nodeId, 'start', connection, {
50
+ input: nextInput
51
+ }), state.context);
52
+ if (connection.transform) {
53
+ //console.log('[_HANDLE_NEXT_STEP_CONNECTION_TRANSFORM_START]', { nodeId, targetNodeId: connection.targetNodeId });
54
+ try {
55
+ const context = state.context;
56
+ [nextInput, nextContext] = await connection.transform(nodeOutput, context);
57
+ await dispatchEvent(state.eventState, createConnectionEvent(nodeId, 'transform', connection, {
58
+ input: nextInput,
59
+ output: nodeOutput,
60
+ context: nextContext
61
+ }), state.context);
62
+ state.context = nextContext;
63
+ //console.log('[_HANDLE_NEXT_STEP_CONNECTION_TRANSFORM_SUCCESS]', { nodeId, targetNodeId: connection.targetNodeId, nextInput, nextContext });
64
+ } catch (transformError) {
65
+ // eslint-disable-next-line no-console
66
+ console.error(`[_HANDLE_NEXT_STEP_CONNECTION_TRANSFORM_ERROR] Error in transform for connection from ${nodeId} to ${connection.targetNodeId}:`, {
67
+ transformError,
68
+ nodeId,
69
+ targetNodeId: connection.targetNodeId
70
+ });
71
+ state.errors.push({
72
+ nodeId: connection.targetNodeId,
73
+ message: transformError.message
74
+ });
75
+ continue;
76
+ }
77
+ }
78
+ //console.log('[_HANDLE_NEXT_STEP_CONNECTION_EXECUTE_TARGET]', { nodeId, targetNodeId: connection.targetNodeId, nextInput });
79
+ nextPhasePromises.push(executeNode(connection.targetNodeId, nextInput, state));
80
+ await dispatchEvent(state.eventState, createConnectionEvent(nodeId, 'end', connection), state.context);
81
+ }
82
+ // Optional: await Promise.all(nextPhasePromises); // Current design relies on executeProcess waiting on activeExecutions
83
+ //console.log('[_HANDLE_NEXT_STEP_CONNECTIONS_PROMISES_PUSHED]', { nodeId, count: nextPhasePromises.length });
84
+ } else if (isTermination(next)) {
85
+ //console.log('[_HANDLE_NEXT_STEP_TERMINATION]', { nodeId, termination: next });
86
+ const termination = next;
87
+ await dispatchEvent(state.eventState, createTerminationEvent(nodeId, 'start', termination, {
88
+ output: nodeOutput
89
+ }), state.context);
90
+ const result = nodeOutput;
91
+ if (termination.terminate) {
92
+ //console.log('[_HANDLE_NEXT_STEP_TERMINATION_CALLING_TERMINATE_FN]', { nodeId, terminationId: termination.id });
93
+ termination.terminate(nodeOutput, state.context);
94
+ await dispatchEvent(state.eventState, createTerminationEvent(nodeId, 'terminate', termination, {
95
+ output: nodeOutput
96
+ }), state.context);
97
+ }
98
+ state.results[termination.id] = result;
99
+ } else if (Array.isArray(next) && next.length === 0) {
100
+ // Empty array, potentially from a Decision that leads to no connections or an empty Connection array.
101
+ // This could mean an implicit termination for this path or simply no further action from this branch.
102
+ // If it's considered an end state for the path, store the result.
103
+ const result = nodeOutput;
104
+ state.results[nodeId] = result; // Using nodeId as the key for this implicit termination
105
+ } else {
106
+ // If there is no next (e.g. next is undefined or null after a decision), or it's an unhandled type.
107
+ // Consider this an end state and store the result with the nodeId
108
+ //console.log('[_HANDLE_NEXT_STEP_NO_NEXT_OR_UNHANDLED]', { nodeId, next, nodeOutput });
109
+ const result = nodeOutput;
110
+ state.results[nodeId] = result;
111
+ }
112
+ //console.log('[_HANDLE_NEXT_STEP_END]', { nodeId });
113
+ }
114
+
115
+ export { handleNextStep };
116
+ //# sourceMappingURL=next.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"next.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,4 @@
1
+ import { Input } from '../input';
2
+ import { Output } from '../output';
3
+ import { ExecutionState } from './process';
4
+ export declare function executeNode(nodeId: string, input: Input, state: ExecutionState): Promise<Output>;
@@ -0,0 +1,170 @@
1
+ import { executeAggregatorNode } from './aggregator.js';
2
+ import { handleNextStep } from './next.js';
3
+ import { isAggregatorNode } from '../node/aggregatornode.js';
4
+ import { isPhaseNode } from '../node/phasenode.js';
5
+ import { isDecision } from '../transition/decision.js';
6
+ import { createDecisionEvent } from '../event/transition.js';
7
+ import { dispatchEvent } from './event.js';
8
+ import { createAggregatorNodeEvent, createPhaseNodeEvent } from '../event/node.js';
9
+ import { createPhaseEvent } from '../event/phase.js';
10
+
11
+ async function executeNode(nodeId, input, state) {
12
+ //console.log('[EXECUTE_NODE_RECURSIVE_START]', { nodeId, input, phaseResultsKeys: Object.keys(state.phaseResults), activeExecutionsKeys: Array.from(state.activeExecutions.keys()), aggregatorDeferredsKeys: Array.from(state.aggregatorDeferreds.keys()) });
13
+ // 1. Check if result is already cached in phaseResults (final output, node fully completed)
14
+ if (state.phaseResults[nodeId]) {
15
+ //console.log('[EXECUTE_NODE_RECURSIVE_CACHE_HIT_PHASERESULTS]', { nodeId, result: state.phaseResults[nodeId] });
16
+ return state.phaseResults[nodeId];
17
+ }
18
+ const node = state.process.phases[nodeId];
19
+ if (!node) {
20
+ const error = new Error(`Node with ID "${nodeId}" not found.`);
21
+ //console.error('[EXECUTE_NODE_RECURSIVE_NODE_NOT_FOUND]', { nodeId, error });
22
+ state.errors.push({
23
+ nodeId,
24
+ message: error.message
25
+ });
26
+ throw error;
27
+ }
28
+ //console.log('[EXECUTE_NODE_RECURSIVE_NODE_FOUND]', { nodeId, nodeType: node.constructor.name, node });
29
+ // 2. Handle active/pending executions
30
+ // If it's an aggregator that has a deferred promise, it means it's pending.
31
+ // We need to re-evaluate it with the current input. The IIFE below will handle this.
32
+ if (state.activeExecutions.has(nodeId) && !isAggregatorNode(node)) {
33
+ // For non-aggregators, if already active, return the promise.
34
+ // Aggregators will fall through to the IIFE to allow input processing.
35
+ // The IIFE itself handles returning a shared deferred promise if needed.
36
+ //console.log('[EXECUTE_NODE_RECURSIVE_ACTIVE_EXECUTION_HIT_NON_AGGREGATOR]', { nodeId });
37
+ return state.activeExecutions.get(nodeId);
38
+ }
39
+ // If it IS an aggregator and state.activeExecutions.has(nodeId),
40
+ // it means its deferred.promise might be in activeExecutions from a previous input that made it pending.
41
+ // The IIFE logic below will correctly retrieve this deferred (if it exists and is still relevant)
42
+ // from state.aggregatorDeferreds.get(nodeId) and use its promise, or process the input.
43
+ //console.log('[EXECUTE_NODE_RECURSIVE_CONTINUING_TO_EXECUTION_LOGIC]', { nodeId, isActive: state.activeExecutions.has(nodeId), isAggregator: isAggregatorNode(node), hasDeferred: state.aggregatorDeferreds.has(nodeId) });
44
+ // If it's an aggregator and it's pending (has a deferred), we fall through to re-execute its logic within the IIFE.
45
+ // If it's the first call to any node, we fall through.
46
+ // 3. Mark as active and execute (or re-evaluate pending aggregator)
47
+ const executionPromise = (async ()=>{
48
+ //console.log('[EXECUTE_NODE_RECURSIVE_IIFE_START]', { nodeId, input });
49
+ try {
50
+ let output;
51
+ if (isAggregatorNode(node)) {
52
+ dispatchEvent(state.eventState, createAggregatorNodeEvent(nodeId, 'start', node, {
53
+ input
54
+ }), state.context);
55
+ output = await executeAggregatorNode(nodeId, node, input, state);
56
+ } else if (isPhaseNode(node)) {
57
+ dispatchEvent(state.eventState, createPhaseNodeEvent(nodeId, 'start', node, {
58
+ input
59
+ }), state.context);
60
+ dispatchEvent(state.eventState, createPhaseEvent(nodeId, 'start', node.phase, {
61
+ input
62
+ }), state.context);
63
+ output = await node.phase.execute(input);
64
+ dispatchEvent(state.eventState, createPhaseEvent(nodeId, 'execute', node.phase, {
65
+ input,
66
+ output
67
+ }), state.context);
68
+ //console.log('[EXECUTE_NODE_RECURSIVE_PHASE_NODE_EXECUTE_END]', { nodeId, output });
69
+ } else {
70
+ const error = new Error(`Unknown or invalid node type for ID "${nodeId}". Expected PhaseNode or AggregatorNode.`);
71
+ //console.error('[EXECUTE_NODE_RECURSIVE_UNKNOWN_NODE_TYPE]', { nodeId, node, error });
72
+ throw error;
73
+ }
74
+ state.phaseResults[nodeId] = output; // Set final output once ready/executed
75
+ //console.log('[EXECUTE_NODE_RECURSIVE_PHASE_RESULT_CACHED]', { nodeId, output });
76
+ // 4. Handle next step
77
+ if (node.next) {
78
+ //console.log('[EXECUTE_NODE_RECURSIVE_HANDLING_NEXT_STEP]', { nodeId, nextType: node.next.constructor.name, next: node.next });
79
+ if (Array.isArray(node.next) && node.next.length > 0 && node.next.every(isDecision)) {
80
+ //console.log('[EXECUTE_NODE_RECURSIVE_DECISIONS_FOUND]', { nodeId, count: node.next.length, decisions: node.next });
81
+ const decisions = node.next;
82
+ const decisionExecutionPromises = [];
83
+ for (const decision of decisions){
84
+ dispatchEvent(state.eventState, createDecisionEvent(nodeId, 'start', decision, {
85
+ output
86
+ }), state.context);
87
+ const decisionPromise = (async ()=>{
88
+ //console.log('[EXECUTE_NODE_RECURSIVE_DECISION_EXECUTE_START]', { nodeId, decisionId: decision.id, output });
89
+ try {
90
+ const decisionOutcome = await decision.decide(output, state.context);
91
+ dispatchEvent(state.eventState, createDecisionEvent(nodeId, 'decide', decision, {
92
+ output,
93
+ result: decisionOutcome
94
+ }), state.context);
95
+ //console.log('[EXECUTE_NODE_RECURSIVE_DECISION_OUTCOME]', { nodeId, decisionId: decision.id, decisionOutcome });
96
+ await handleNextStep(output, decision.id, decisionOutcome, state);
97
+ dispatchEvent(state.eventState, createDecisionEvent(nodeId, 'end', decision), state.context);
98
+ } catch (decisionError) {
99
+ // eslint-disable-next-line no-console
100
+ console.error(`[_HANDLE_NEXT_STEP_DECISION_ERROR] Error in decision ${decision.id} for node ${nodeId}:`, {
101
+ decisionError,
102
+ nodeId,
103
+ decisionId: decision.id
104
+ });
105
+ state.errors.push({
106
+ nodeId: decision.id,
107
+ message: decisionError.message
108
+ });
109
+ }
110
+ })();
111
+ decisionExecutionPromises.push(decisionPromise);
112
+ }
113
+ //console.log('[EXECUTE_NODE_RECURSIVE_WAITING_FOR_DECISIONS]', { nodeId, count: decisionExecutionPromises.length });
114
+ await Promise.all(decisionExecutionPromises);
115
+ //console.log('[EXECUTE_NODE_RECURSIVE_DECISIONS_COMPLETE]', { nodeId });
116
+ } else {
117
+ //console.log('[EXECUTE_NODE_RECURSIVE_CALLING_HANDLE_NEXT_STEP_FOR_NON_DECISION]', { nodeId, next: node.next });
118
+ await handleNextStep(output, nodeId, node.next, state);
119
+ }
120
+ } else {
121
+ //console.log('[EXECUTE_NODE_RECURSIVE_NO_NEXT_NODE_IMPLICIT_TERMINATION]', { nodeId, output });
122
+ const result = output;
123
+ state.results[nodeId] = result;
124
+ }
125
+ if (isPhaseNode(node)) {
126
+ dispatchEvent(state.eventState, createPhaseNodeEvent(nodeId, 'end', node, {
127
+ input,
128
+ output
129
+ }), state.context);
130
+ } else {
131
+ dispatchEvent(state.eventState, createAggregatorNodeEvent(nodeId, 'end', node, {
132
+ input,
133
+ output
134
+ }), state.context);
135
+ }
136
+ //console.log('[EXECUTE_NODE_RECURSIVE_IIFE_RETURNING_OUTPUT]', { nodeId, output });
137
+ return output;
138
+ } catch (error) {
139
+ // eslint-disable-next-line no-console
140
+ console.error(`[EXECUTE_NODE_RECURSIVE_IIFE_ERROR] Error executing node ${nodeId}:`, {
141
+ error,
142
+ nodeId
143
+ });
144
+ state.errors.push({
145
+ nodeId,
146
+ message: error.message
147
+ });
148
+ throw error;
149
+ } finally{
150
+ //console.log('[EXECUTE_NODE_RECURSIVE_IIFE_FINALLY]', { nodeId, hasAggregatorDeferred: state.aggregatorDeferreds.has(nodeId) });
151
+ // If a node completed (not pending via deferred mechanism) or an error occurred.
152
+ // An aggregator that is still pending (has a deferred) should keep its promise in activeExecutions.
153
+ if (!state.aggregatorDeferreds.has(nodeId)) {
154
+ //console.log('[EXECUTE_NODE_RECURSIVE_IIFE_FINALLY_DELETING_ACTIVE_EXECUTION]', { nodeId });
155
+ state.activeExecutions.delete(nodeId);
156
+ }
157
+ }
158
+ })();
159
+ // Store the promise from the IIFE.
160
+ // If it's an aggregator that went pending, executionPromise IS deferred.promise.
161
+ // If it's an aggregator that became ready, executionPromise is a promise resolving to its output.
162
+ // If it's a phase node, executionPromise is a promise resolving to its output.
163
+ //console.log('[EXECUTE_NODE_RECURSIVE_SETTING_ACTIVE_EXECUTION]', { nodeId });
164
+ state.activeExecutions.set(nodeId, executionPromise);
165
+ //console.log('[EXECUTE_NODE_RECURSIVE_END_RETURNING_PROMISE]', { nodeId });
166
+ return executionPromise;
167
+ }
168
+
169
+ export { executeNode };
170
+ //# sourceMappingURL=node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,35 @@
1
+ import { Context } from '../context';
2
+ import { AggregatorState } from './aggregator';
3
+ import { Input } from '../input';
4
+ import { Output } from '../output';
5
+ import { Process } from '../process';
6
+ import { Beginning } from '../transition/beginning';
7
+ import { Event, EventHandler, EventState } from '../event';
8
+ export interface PhaseResults {
9
+ [key: string]: Output;
10
+ }
11
+ export interface ProcessResults {
12
+ [key: string]: Output;
13
+ }
14
+ interface ProcessExecutionError {
15
+ message: string;
16
+ details?: any;
17
+ nodeId?: string;
18
+ }
19
+ export interface ProcessExecutionOptions<I extends Input = Input, C extends Context = Context> {
20
+ input: I;
21
+ context: C;
22
+ eventHandlers?: ReadonlyArray<EventHandler<Event, C>>;
23
+ }
24
+ export declare const DEFAULT_PROCESS_EXECUTION_OPTIONS: ProcessExecutionOptions<Input, Context>;
25
+ export interface ExecutionState<C extends Context = Context> extends AggregatorState {
26
+ process: Readonly<Process>;
27
+ context: C;
28
+ phaseResults: Record<string, Output>;
29
+ results: Record<string, Output>;
30
+ activeExecutions: Map<string, Promise<Output>>;
31
+ errors: ProcessExecutionError[];
32
+ readonly eventState: Readonly<EventState<C>>;
33
+ }
34
+ export declare function executeProcess<I extends Input = Input, O extends Output = Output, C extends Context = Context>(processInstance: Readonly<Process>, beginning: Beginning<I, C>, options?: Partial<ProcessExecutionOptions<I, C>>): Promise<[Record<string, O>, Record<string, O>, C]>;
35
+ export {};
@@ -0,0 +1,97 @@
1
+ import { executeNode } from './node.js';
2
+ import { createAggregatorState } from './aggregator.js';
3
+ import { EMPTY_INPUT } from '../input.js';
4
+ import { validateProcess } from '../process.js';
5
+ import { clean } from '../util/general.js';
6
+ import { createProcessEvent } from '../event/process.js';
7
+ import { createBeginningEvent } from '../event/transition.js';
8
+ import { createEventState, dispatchEvent } from './event.js';
9
+
10
+ const DEFAULT_PROCESS_EXECUTION_OPTIONS = {
11
+ input: EMPTY_INPUT,
12
+ context: {},
13
+ eventHandlers: []
14
+ };
15
+ async function executeProcess(processInstance, beginning, options) {
16
+ const processExecutionOptions = {
17
+ ...DEFAULT_PROCESS_EXECUTION_OPTIONS,
18
+ ...clean(options || {})
19
+ };
20
+ if (options && options.input) {
21
+ processExecutionOptions.input = options.input;
22
+ }
23
+ if (options && options.eventHandlers) {
24
+ processExecutionOptions.eventHandlers = options.eventHandlers;
25
+ }
26
+ const validationErrors = validateProcess(processInstance);
27
+ if (validationErrors.length > 0) {
28
+ const errorMessages = validationErrors.map((err)=>err.error).join('\n');
29
+ throw new Error(`Invalid process definition:\n${errorMessages}`);
30
+ }
31
+ const eventState = createEventState(processExecutionOptions.eventHandlers);
32
+ const state = {
33
+ process: processInstance,
34
+ context: processExecutionOptions.context,
35
+ results: {},
36
+ phaseResults: {},
37
+ activeExecutions: new Map(),
38
+ errors: [],
39
+ ...createAggregatorState(),
40
+ eventState: eventState
41
+ };
42
+ dispatchEvent(state.eventState, createProcessEvent(processInstance.name, 'start', processInstance, {
43
+ input: processExecutionOptions.input,
44
+ context: state.context
45
+ }), state.context);
46
+ dispatchEvent(state.eventState, createBeginningEvent(beginning.id, 'start', beginning, {
47
+ input: processExecutionOptions.input
48
+ }), state.context);
49
+ const initialInput = await beginning.begin(processExecutionOptions.input, state.context);
50
+ dispatchEvent(state.eventState, createBeginningEvent(beginning.id, 'begin', beginning, {
51
+ input: initialInput
52
+ }), state.context);
53
+ const initialNodeId = beginning.targetNodeId;
54
+ if (!state.process.phases[initialNodeId]) {
55
+ throw new Error(`Start phase ID "${initialNodeId}" not found in process phases.`);
56
+ }
57
+ try {
58
+ await executeNode(initialNodeId, initialInput, state);
59
+ const allPromises = Array.from(state.activeExecutions.values());
60
+ if (allPromises.length > 0) {
61
+ await Promise.all(allPromises);
62
+ }
63
+ } catch (error) {
64
+ const errorMessage = error instanceof Error ? error.message : String(error);
65
+ state.errors.push({
66
+ message: "Critical error during process execution",
67
+ details: errorMessage,
68
+ nodeId: initialNodeId
69
+ });
70
+ // eslint-disable-next-line no-console
71
+ console.error("[EXECUTE_PROCESS_CRITICAL_ERROR]", {
72
+ processName: processInstance.name,
73
+ error: errorMessage,
74
+ collectedErrors: state.errors
75
+ });
76
+ }
77
+ if (state.aggregatorDeferreds && state.aggregatorDeferreds.size > 0) {
78
+ const pendingNodeIds = state.pendingAggregatorIds ? state.pendingAggregatorIds().join(', ') : 'unknown';
79
+ // eslint-disable-next-line no-console
80
+ console.warn(`[EXECUTE_PROCESS_PENDING_AGGREGATORS] Process execution may have pending aggregators: ${pendingNodeIds}.`, {
81
+ processName: processInstance.name,
82
+ pendingNodeIds
83
+ });
84
+ }
85
+ dispatchEvent(state.eventState, createProcessEvent(processInstance.name, 'end', processInstance, {
86
+ input: processExecutionOptions.input,
87
+ context: state.context
88
+ }), state.context);
89
+ return [
90
+ state.results,
91
+ state.phaseResults,
92
+ state.context
93
+ ];
94
+ }
95
+
96
+ export { DEFAULT_PROCESS_EXECUTION_OPTIONS, executeProcess };
97
+ //# sourceMappingURL=process.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,6 @@
1
+ export interface Input {
2
+ [key: string]: unknown;
3
+ }
4
+ export declare const EMPTY_INPUT: Input;
5
+ export declare const isInput: (input: unknown) => input is Input;
6
+ export declare const validateInput: (input: unknown) => input is Input;
package/dist/input.js ADDED
@@ -0,0 +1,4 @@
1
+ const EMPTY_INPUT = {};
2
+
3
+ export { EMPTY_INPUT };
4
+ //# sourceMappingURL=input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;"}
@@ -0,0 +1,12 @@
1
+ export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'verbose' | 'silly';
2
+ export interface Logger {
3
+ name: string;
4
+ debug: (message: string, ...args: any[]) => void;
5
+ info: (message: string, ...args: any[]) => void;
6
+ warn: (message: string, ...args: any[]) => void;
7
+ error: (message: string, ...args: any[]) => void;
8
+ verbose: (message: string, ...args: any[]) => void;
9
+ silly: (message: string, ...args: any[]) => void;
10
+ }
11
+ export declare const DEFAULT_LOGGER: Logger;
12
+ export declare const wrapLogger: (toWrap: Logger, name?: string) => Logger;
@@ -0,0 +1,40 @@
1
+ import { Aggregator } from '../aggregator';
2
+ import { Next } from '../transition/next';
3
+ import { Context } from '../context';
4
+ import { Output } from '../output';
5
+ import { Node } from './node';
6
+ /**
7
+ * Represents a node in the process that aggregates multiple inputs of type IA
8
+ * into a single output of type O.
9
+ *
10
+ * @template O - The type of the output produced by this node's aggregate function. Must extend Output.
11
+ * @template C - The type of the context object passed to the aggregate function. Must extend Context.
12
+ */
13
+ export interface AggregatorNode<O extends Output = Output, // Output from the aggregate function
14
+ C extends Context = Context> extends Node<O, C> {
15
+ type: 'aggregator';
16
+ /**
17
+ * The core aggregation logic for this node.
18
+ * It takes an array of input items and a context, and returns a Promise resolving to the aggregated output.
19
+ * @param input - An input item of type Input.
20
+ * @param context - The context object.
21
+ * @returns A Promise that resolves to the aggregated output of type O.
22
+ */
23
+ aggregator: Aggregator<O, C>;
24
+ }
25
+ export interface AggregatorNodeOptions<O extends Output = Output, C extends Context = Context> {
26
+ next?: Next<O, C>;
27
+ }
28
+ export declare const DEFAULT_AGGREGATOR_NODE_OPTIONS: AggregatorNodeOptions<Output, Context>;
29
+ export declare const createAggregatorNode: <O extends Output = Output, C extends Context = Context>(id: string, aggregator: Aggregator<O, C>, options?: Partial<AggregatorNodeOptions<O, C>>) => Readonly<AggregatorNode<O, C>>;
30
+ /**
31
+ * Type guard to check if an object is an AggregatorNode.
32
+ *
33
+ * @param obj - The object to check.
34
+ * @returns True if the object is an AggregatorNode, false otherwise.
35
+ */
36
+ export declare const isAggregatorNode: (obj: any) => obj is AggregatorNode<any, any>;
37
+ export declare const validateAggregatorNode: (item: any, coordinates?: string[]) => Array<{
38
+ coordinates: string[];
39
+ error: string;
40
+ }>;
@@ -0,0 +1,41 @@
1
+ import { clean } from '../util/general.js';
2
+ import { createNode, isNode } from './node.js';
3
+
4
+ const DEFAULT_AGGREGATOR_NODE_OPTIONS = {};
5
+ const createAggregatorNode = (id, aggregator, options)=>{
6
+ let aggregatorNodeOptions = {
7
+ ...DEFAULT_AGGREGATOR_NODE_OPTIONS
8
+ };
9
+ if (options) {
10
+ aggregatorNodeOptions = {
11
+ ...aggregatorNodeOptions,
12
+ ...clean(options)
13
+ };
14
+ }
15
+ return {
16
+ ...createNode('aggregator', id, {
17
+ next: aggregatorNodeOptions.next
18
+ }),
19
+ aggregator
20
+ };
21
+ };
22
+ /**
23
+ * Type guard to check if an object is an AggregatorNode.
24
+ *
25
+ * @param obj - The object to check.
26
+ * @returns True if the object is an AggregatorNode, false otherwise.
27
+ */ const isAggregatorNode = (obj)=>{
28
+ if (!isNode(obj) || obj.type !== 'aggregator') {
29
+ return false;
30
+ }
31
+ // At this point, obj is a Node with type 'aggregator'.
32
+ // Now check for AggregatorNode specific properties.
33
+ const potentialAggNode = obj; // Cast to access specific props
34
+ if (!(typeof potentialAggNode.aggregator === 'object' && potentialAggNode.aggregator !== null && typeof potentialAggNode.aggregator.aggregate === 'function')) {
35
+ return false;
36
+ }
37
+ return true; // Not Termination and not a recognized array type for 'next'
38
+ };
39
+
40
+ export { DEFAULT_AGGREGATOR_NODE_OPTIONS, createAggregatorNode, isAggregatorNode };
41
+ //# sourceMappingURL=aggregatornode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aggregatornode.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,18 @@
1
+ import { Context } from '../context';
2
+ import { Output } from '../output';
3
+ import { Next } from '../transition/next';
4
+ export interface Node<O extends Output = Output, C extends Context = Context> {
5
+ id: string;
6
+ type: 'aggregator' | 'phase';
7
+ next?: Next<O, C>;
8
+ }
9
+ export interface NodeOptions<O extends Output = Output, C extends Context = Context> {
10
+ next?: Next<O, C>;
11
+ }
12
+ export declare const DEFAULT_NODE_OPTIONS: NodeOptions<Output, Context>;
13
+ export declare const createNode: <O extends Output = Output, C extends Context = Context>(type: "aggregator" | "phase", id: string, options?: Partial<NodeOptions<O, C>>) => Readonly<Node<O, C>>;
14
+ export declare const isNode: (item: any) => item is Node;
15
+ export declare const validateNode: (item: any, coordinates?: string[]) => Array<{
16
+ coordinates: string[];
17
+ error: string;
18
+ }>;
@@ -0,0 +1,80 @@
1
+ import { clean } from '../util/general.js';
2
+ import { validateNext, isNext } from '../transition/next.js';
3
+
4
+ const DEFAULT_NODE_OPTIONS = {};
5
+ const createNode = (type, id, options)=>{
6
+ let nodeOptions = {
7
+ ...DEFAULT_NODE_OPTIONS
8
+ };
9
+ if (options) {
10
+ nodeOptions = {
11
+ ...nodeOptions,
12
+ ...clean(options)
13
+ };
14
+ }
15
+ return {
16
+ id,
17
+ type,
18
+ next: nodeOptions.next
19
+ };
20
+ };
21
+ const isNode = (item)=>{
22
+ return item !== null && typeof item === 'object' && typeof item.id === 'string' && (item.type === 'aggregator' || item.type === 'phase') && (item.next === undefined || isNext(item.next));
23
+ };
24
+ const validateNode = (item, coordinates)=>{
25
+ const errors = [];
26
+ const currentCoordinates = [
27
+ ...coordinates || [],
28
+ 'Node'
29
+ ];
30
+ if (item === undefined || item === null) {
31
+ errors.push({
32
+ coordinates: [
33
+ ...currentCoordinates
34
+ ],
35
+ error: 'Node is undefined or null.'
36
+ });
37
+ return errors;
38
+ }
39
+ if (typeof item !== 'object') {
40
+ errors.push({
41
+ coordinates: [
42
+ ...currentCoordinates
43
+ ],
44
+ error: 'Node is not an object.'
45
+ });
46
+ return errors;
47
+ }
48
+ if (item.id === undefined || typeof item.id !== 'string') {
49
+ errors.push({
50
+ coordinates: [
51
+ ...currentCoordinates
52
+ ],
53
+ error: 'Node id is undefined or not a string.'
54
+ });
55
+ }
56
+ if (item.type === undefined || typeof item.type !== 'string') {
57
+ errors.push({
58
+ coordinates: [
59
+ ...currentCoordinates
60
+ ],
61
+ error: 'Node type is undefined or not a string.'
62
+ });
63
+ }
64
+ if (item.type !== 'aggregator' && item.type !== 'phase') {
65
+ errors.push({
66
+ coordinates: [
67
+ ...currentCoordinates
68
+ ],
69
+ error: 'Node type is not a valid type.'
70
+ });
71
+ }
72
+ currentCoordinates.push(`Node: ${item.id}`);
73
+ if (item.next !== undefined) {
74
+ errors.push(...validateNext(item.next, currentCoordinates));
75
+ }
76
+ return errors;
77
+ };
78
+
79
+ export { DEFAULT_NODE_OPTIONS, createNode, isNode, validateNode };
80
+ //# sourceMappingURL=node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}