@cadenza.io/core 3.8.0 → 3.9.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/README.md +74 -37
- package/dist/index.d.mts +11 -4
- package/dist/index.d.ts +11 -4
- package/dist/index.js +45 -11
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +45 -11
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,12 +7,12 @@ Cadenza is an innovative framework that extends traditional orchestration with e
|
|
|
7
7
|
The core package (@Cadenza.io/core) includes the foundational primitives for defining and executing graphs of tasks, managing contexts, handling signals, and bootstrapping executions. It's designed to be language-agnostic in model, with this TypeScript implementation serving as the reference.
|
|
8
8
|
|
|
9
9
|
Cadenza's design philosophy emphasizes:
|
|
10
|
-
- **
|
|
11
|
-
- **Meta-Layer Extension**: A self-reflective layer for monitoring, optimization, and auto-generation, enabling AI-driven self-development. Essentially extending the core
|
|
10
|
+
- **Decentralized Adaptive Orchestration (DAO)**: Explicit graphs for dependencies (orchestration) combined with signals for loose coupling (choreography). This combination allows for flexible and dynamic workflows, while still maintaining the benefits of traditional orchestration.
|
|
11
|
+
- **Meta-Layer Extension**: A self-reflective layer for monitoring, optimization, and auto-generation, enabling AI-driven self-development. Essentially used for extending the core features by using the core features.
|
|
12
12
|
- **Introspectability**: Exportable graphs, traceable executions, and metadata separation for transparency and debugging.
|
|
13
|
-
- **Modularity**: Lightweight core with extensions (e.g., distribution, UI integration) as separate packages.
|
|
13
|
+
- **Modularity**: Lightweight core with extensions (e.g., distribution, UI integration) as separate packages using the meta layer.
|
|
14
14
|
|
|
15
|
-
The core is suitable for local dynamic workflows using tasks and signals. But thanks to the meta layer, it also serves as the foundation for
|
|
15
|
+
The core is suitable for local dynamic workflows using tasks and signals. But thanks to the meta layer, it also serves as the foundation for distributed applications, and more, abstracting complexities like networking and security in extensions.
|
|
16
16
|
|
|
17
17
|
## Installation
|
|
18
18
|
|
|
@@ -25,88 +25,125 @@ npm install @cadenza.io/core
|
|
|
25
25
|
## Usage
|
|
26
26
|
|
|
27
27
|
### Creating Tasks
|
|
28
|
-
Tasks are the atomic units of work in Cadenza. They can be chained to form graphs.
|
|
28
|
+
Tasks are the atomic units of work in Cadenza. They can be chained to form complex graphs.
|
|
29
29
|
|
|
30
30
|
```typescript
|
|
31
31
|
import Cadenza from '@cadenza.io/core';
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
// Create functions for tasks
|
|
34
|
+
function validateContext(context) {
|
|
35
|
+
if (!context.foo) {
|
|
36
|
+
throw new Error('Missing foo');
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function processContext(context) {
|
|
42
|
+
return { bar: context.foo + '-bar' };
|
|
43
|
+
}
|
|
37
44
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
45
|
+
function logContext(context) {
|
|
46
|
+
console.log(context);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Wrap functions into tasks
|
|
50
|
+
const validateTask = Cadenza.createTask(
|
|
51
|
+
'Validate context',
|
|
52
|
+
validateContext
|
|
53
|
+
);
|
|
41
54
|
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
55
|
+
const processTask = Cadenza.createTask(
|
|
56
|
+
'Process context',
|
|
57
|
+
processContext,
|
|
58
|
+
);
|
|
45
59
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
),
|
|
60
|
+
const logTask = Cadenza.createTask(
|
|
61
|
+
'Log context',
|
|
62
|
+
logContext,
|
|
50
63
|
);
|
|
51
64
|
|
|
65
|
+
// Chain tasks together
|
|
66
|
+
validateTask.then(processTask).then(logTask);
|
|
67
|
+
|
|
68
|
+
// Equivalent to:
|
|
69
|
+
const validated = validateContext(context);
|
|
70
|
+
if (validated) {
|
|
71
|
+
const validatedContext = context;
|
|
72
|
+
const processedContext = processContext(validatedContext);
|
|
73
|
+
logContext(processedContext);
|
|
74
|
+
}
|
|
52
75
|
```
|
|
53
76
|
|
|
54
77
|
### Creating Routines
|
|
55
78
|
Routines are named entry points to graphs.
|
|
56
79
|
|
|
57
80
|
```typescript
|
|
58
|
-
const
|
|
81
|
+
const processContextRoutine = Cadenza.createRoutine(
|
|
82
|
+
'Process context',
|
|
83
|
+
[validateTask],
|
|
84
|
+
'Processes a context by first validating it, processing the string and logging it.',
|
|
85
|
+
);
|
|
59
86
|
```
|
|
60
87
|
|
|
61
88
|
### Running Graphs
|
|
62
89
|
Use a Runner to execute routines or tasks.
|
|
63
90
|
|
|
64
91
|
```typescript
|
|
65
|
-
|
|
66
|
-
await runner.run(routine, {foo: 'bar'});
|
|
92
|
+
Cadenza.run(processContextRoutine);
|
|
67
93
|
```
|
|
68
94
|
|
|
69
95
|
### Signals
|
|
70
|
-
Signals provide event-driven coordination.
|
|
96
|
+
Signals provide event-driven coordination. The signal syntax is `domain.event`.
|
|
71
97
|
|
|
72
98
|
```typescript
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
99
|
+
processContextRoutine.doOn('main.recived_context'); // Subscribe to a signal
|
|
100
|
+
processTask.doOn('main.context_updated'); // Works on tasks and routines
|
|
101
|
+
logTask.emits('process.done'); // Emits after successfull task execution
|
|
102
|
+
|
|
103
|
+
Cadenza.emit('main.recived_context', {foo: 'foo'}); // Emit from anywhere with a context
|
|
104
|
+
Cadenza.emit('main.context_updated', {foo: 'foo-bar'}); // This will trigger the processTask and subsequently logTask. Essentially, skipping the validationTask.
|
|
105
|
+
|
|
106
|
+
// Emit a signal from within a task
|
|
107
|
+
Candenza.createTask('Update context', (ctx, emit) => {
|
|
108
|
+
if (ctx.bar === 'foo-bar') {
|
|
109
|
+
ctx.foo = 'foo-baz';
|
|
110
|
+
emit('main.context_updated', ctx);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
77
113
|
```
|
|
78
114
|
|
|
79
115
|
### Using the Meta Layer
|
|
80
116
|
|
|
81
|
-
The meta layer serves as a tool for extending the core features. It follows the same rules and primitives as the user layer but runs on a separate meta runner. It consists of MetaTasks, MetaRoutines and meta signals. To trigger a meta flow you need to emit a meta signal (meta.
|
|
117
|
+
The meta layer serves as a tool for extending the core features. It follows the same rules and primitives as the user layer but runs on a separate meta runner. It consists of MetaTasks, MetaRoutines and meta signals. To trigger a meta flow you need to emit a meta signal (meta.domain.event).
|
|
82
118
|
|
|
83
119
|
```typescript
|
|
84
120
|
Cadenza.createTask('My task', (ctx) => {
|
|
85
121
|
console.log(ctx.foo);
|
|
86
122
|
return ctx;
|
|
87
|
-
}).emits('meta.some.
|
|
123
|
+
}).emits('meta.some.event');
|
|
88
124
|
|
|
89
125
|
Cadenza.createMetaTask('My meta task', (ctx) => {
|
|
90
126
|
console.log(ctx.__task.name);
|
|
91
127
|
return true;
|
|
92
|
-
})
|
|
128
|
+
})
|
|
129
|
+
.doOn('meta.some.event')
|
|
130
|
+
.emits('meta.some.other_event');
|
|
93
131
|
|
|
94
|
-
Cadenza.
|
|
132
|
+
Cadenza.emit('meta.some.event', {foo: 'bar'}); // Emit from anywhere
|
|
95
133
|
```
|
|
96
134
|
|
|
97
|
-
For full examples, see the
|
|
135
|
+
For full examples, see the cadenza-service package (https://github.com/cadenzaio/cadenza-service) or the test suite.
|
|
98
136
|
|
|
99
137
|
## Features
|
|
100
|
-
- **Graph-Based Orchestration**: Define tasks and routines with chaining for dependencies
|
|
101
|
-
- **Event-Driven Choreography**: Signals for loose coupling
|
|
138
|
+
- **Graph-Based Orchestration**: Define tasks and routines with chaining for dependencies and layering.
|
|
139
|
+
- **Event-Driven Choreography**: Signals for loose coupling with meta-signals for self-management.
|
|
102
140
|
- **Context Management**: Immutable contexts with metadata separation and schema validation.
|
|
103
|
-
- **Execution Engine**: Sync/async strategies, throttling, debouncing,
|
|
104
|
-
- **Bootstrapping**: Lazy initialization with seed tasks for registry and signal handling.
|
|
141
|
+
- **Execution Engine**: Sync/async strategies, throttling, debouncing, fan-in/fan-out merging, dynamic task creation/chaining/deletion.
|
|
105
142
|
|
|
106
143
|
## Architecture Overview
|
|
107
144
|
Cadenza's core is divided into:
|
|
108
145
|
- **Definition Layer**: Task, Routine for static graphs.
|
|
109
|
-
- **Execution Layer**: Node, Layer, Builder,
|
|
146
|
+
- **Execution Layer**: Node, Layer, Builder, Runner for runtime.
|
|
110
147
|
- **Signal Layer**: SignalBroker, SignalParticipant for coordination.
|
|
111
148
|
- **Context Layer**: GraphContext for data flow.
|
|
112
149
|
- **Registry Layer**: GraphRegistry for introspection.
|
package/dist/index.d.mts
CHANGED
|
@@ -441,11 +441,15 @@ declare class Task extends SignalEmitter implements Graph {
|
|
|
441
441
|
* @param context - The GraphContext to validate and execute.
|
|
442
442
|
* @param emit
|
|
443
443
|
* @param progressCallback - Callback for progress updates.
|
|
444
|
+
* @param nodeData
|
|
444
445
|
* @returns TaskResult from the taskFunction or error object on validation failure.
|
|
445
446
|
* @edge If validateInputContext is true, validates context; on failure, emits 'meta.task.validationFailed' with detailed errors.
|
|
446
447
|
* @edge If validateOutputContext is true, validates output; on failure, emits 'meta.task.outputValidationFailed' with detailed errors.
|
|
447
448
|
*/
|
|
448
|
-
execute(context: GraphContext, emit: (signal: string, context: AnyObject) => void, progressCallback: (progress: number) => void
|
|
449
|
+
execute(context: GraphContext, emit: (signal: string, context: AnyObject) => void, progressCallback: (progress: number) => void, nodeData: {
|
|
450
|
+
nodeId: string;
|
|
451
|
+
routineExecId: string;
|
|
452
|
+
}): TaskResult;
|
|
449
453
|
doAfter(...tasks: Task[]): this;
|
|
450
454
|
then(...tasks: Task[]): this;
|
|
451
455
|
decouple(task: Task): void;
|
|
@@ -779,7 +783,10 @@ declare class EphemeralTask extends Task {
|
|
|
779
783
|
readonly condition: (context: any) => boolean;
|
|
780
784
|
readonly isEphemeral: boolean;
|
|
781
785
|
constructor(name: string, task: TaskFunction, description?: string, once?: boolean, condition?: (context: any) => boolean, concurrency?: number, timeout?: number, register?: boolean, isUnique?: boolean, isMeta?: boolean, isSubMeta?: boolean, isHidden?: boolean, getTagCallback?: ThrottleTagGetter | undefined, inputSchema?: SchemaDefinition | undefined, validateInputContext?: boolean, outputSchema?: SchemaDefinition | undefined, validateOutputContext?: boolean, retryCount?: number, retryDelay?: number, retryDelayMax?: number, retryDelayFactor?: number);
|
|
782
|
-
execute(context: any, emit: (signal: string, context: AnyObject) => void, progressCallback: (progress: number) => void
|
|
786
|
+
execute(context: any, emit: (signal: string, context: AnyObject) => void, progressCallback: (progress: number) => void, nodeData: {
|
|
787
|
+
nodeId: string;
|
|
788
|
+
routineExecId: string;
|
|
789
|
+
}): TaskResult;
|
|
783
790
|
}
|
|
784
791
|
|
|
785
792
|
declare class GraphAsyncRun extends GraphRunStrategy {
|
|
@@ -832,8 +839,8 @@ declare class Cadenza {
|
|
|
832
839
|
* @throws Error if invalid.
|
|
833
840
|
*/
|
|
834
841
|
static validateName(name: string): void;
|
|
835
|
-
static
|
|
836
|
-
static
|
|
842
|
+
static run(task: Task | GraphRoutine, context: AnyObject): void;
|
|
843
|
+
static emit(event: string, data?: AnyObject): void;
|
|
837
844
|
/**
|
|
838
845
|
* Creates a standard Task and registers it in the GraphRegistry.
|
|
839
846
|
* @param name Unique identifier for the task.
|
package/dist/index.d.ts
CHANGED
|
@@ -441,11 +441,15 @@ declare class Task extends SignalEmitter implements Graph {
|
|
|
441
441
|
* @param context - The GraphContext to validate and execute.
|
|
442
442
|
* @param emit
|
|
443
443
|
* @param progressCallback - Callback for progress updates.
|
|
444
|
+
* @param nodeData
|
|
444
445
|
* @returns TaskResult from the taskFunction or error object on validation failure.
|
|
445
446
|
* @edge If validateInputContext is true, validates context; on failure, emits 'meta.task.validationFailed' with detailed errors.
|
|
446
447
|
* @edge If validateOutputContext is true, validates output; on failure, emits 'meta.task.outputValidationFailed' with detailed errors.
|
|
447
448
|
*/
|
|
448
|
-
execute(context: GraphContext, emit: (signal: string, context: AnyObject) => void, progressCallback: (progress: number) => void
|
|
449
|
+
execute(context: GraphContext, emit: (signal: string, context: AnyObject) => void, progressCallback: (progress: number) => void, nodeData: {
|
|
450
|
+
nodeId: string;
|
|
451
|
+
routineExecId: string;
|
|
452
|
+
}): TaskResult;
|
|
449
453
|
doAfter(...tasks: Task[]): this;
|
|
450
454
|
then(...tasks: Task[]): this;
|
|
451
455
|
decouple(task: Task): void;
|
|
@@ -779,7 +783,10 @@ declare class EphemeralTask extends Task {
|
|
|
779
783
|
readonly condition: (context: any) => boolean;
|
|
780
784
|
readonly isEphemeral: boolean;
|
|
781
785
|
constructor(name: string, task: TaskFunction, description?: string, once?: boolean, condition?: (context: any) => boolean, concurrency?: number, timeout?: number, register?: boolean, isUnique?: boolean, isMeta?: boolean, isSubMeta?: boolean, isHidden?: boolean, getTagCallback?: ThrottleTagGetter | undefined, inputSchema?: SchemaDefinition | undefined, validateInputContext?: boolean, outputSchema?: SchemaDefinition | undefined, validateOutputContext?: boolean, retryCount?: number, retryDelay?: number, retryDelayMax?: number, retryDelayFactor?: number);
|
|
782
|
-
execute(context: any, emit: (signal: string, context: AnyObject) => void, progressCallback: (progress: number) => void
|
|
786
|
+
execute(context: any, emit: (signal: string, context: AnyObject) => void, progressCallback: (progress: number) => void, nodeData: {
|
|
787
|
+
nodeId: string;
|
|
788
|
+
routineExecId: string;
|
|
789
|
+
}): TaskResult;
|
|
783
790
|
}
|
|
784
791
|
|
|
785
792
|
declare class GraphAsyncRun extends GraphRunStrategy {
|
|
@@ -832,8 +839,8 @@ declare class Cadenza {
|
|
|
832
839
|
* @throws Error if invalid.
|
|
833
840
|
*/
|
|
834
841
|
static validateName(name: string): void;
|
|
835
|
-
static
|
|
836
|
-
static
|
|
842
|
+
static run(task: Task | GraphRoutine, context: AnyObject): void;
|
|
843
|
+
static emit(event: string, data?: AnyObject): void;
|
|
837
844
|
/**
|
|
838
845
|
* Creates a standard Task and registers it in the GraphRegistry.
|
|
839
846
|
* @param name Unique identifier for the task.
|
package/dist/index.js
CHANGED
|
@@ -256,10 +256,25 @@ var SignalBroker = class _SignalBroker {
|
|
|
256
256
|
}
|
|
257
257
|
executeListener(signal, context) {
|
|
258
258
|
const obs = this.signalObservers.get(signal);
|
|
259
|
+
if (!obs || obs.tasks.size === 0) {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
259
262
|
const isMeta = signal.startsWith("meta");
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
+
if (!isMeta) {
|
|
264
|
+
const tasks = [];
|
|
265
|
+
const metaTasks = [];
|
|
266
|
+
obs.tasks.forEach(
|
|
267
|
+
(task) => task.isMeta ? metaTasks.push(task) : tasks.push(task)
|
|
268
|
+
);
|
|
269
|
+
if (tasks.length && this.runner) {
|
|
270
|
+
obs.fn(this.runner, tasks, context);
|
|
271
|
+
}
|
|
272
|
+
if (metaTasks.length && this.metaRunner) {
|
|
273
|
+
obs.fn(this.metaRunner, metaTasks, context);
|
|
274
|
+
}
|
|
275
|
+
return true;
|
|
276
|
+
} else if (this.metaRunner) {
|
|
277
|
+
obs.fn(this.metaRunner, Array.from(obs.tasks), context);
|
|
263
278
|
return true;
|
|
264
279
|
}
|
|
265
280
|
return false;
|
|
@@ -838,6 +853,23 @@ var GraphNode = class _GraphNode extends SignalEmitter {
|
|
|
838
853
|
}
|
|
839
854
|
});
|
|
840
855
|
});
|
|
856
|
+
if (context.__previousTaskExecutionId) {
|
|
857
|
+
this.emitMetricsWithMetadata(
|
|
858
|
+
"meta.node.detected_previous_task_execution",
|
|
859
|
+
{
|
|
860
|
+
data: {
|
|
861
|
+
taskExecutionId: this.id,
|
|
862
|
+
previousTaskExecutionId: context.__previousTaskExecutionId
|
|
863
|
+
},
|
|
864
|
+
filter: {
|
|
865
|
+
taskName: this.task.name,
|
|
866
|
+
taskVersion: this.task.version
|
|
867
|
+
},
|
|
868
|
+
...context
|
|
869
|
+
}
|
|
870
|
+
);
|
|
871
|
+
context.__previousTaskExecutionId = null;
|
|
872
|
+
}
|
|
841
873
|
if (context.__signalEmission?.consumed === false && (!this.isMeta() || this.debug)) {
|
|
842
874
|
this.emitMetricsWithMetadata("meta.node.consumed_signal", {
|
|
843
875
|
data: {
|
|
@@ -983,7 +1015,8 @@ var GraphNode = class _GraphNode extends SignalEmitter {
|
|
|
983
1015
|
const result = this.task.execute(
|
|
984
1016
|
this.context,
|
|
985
1017
|
this.emitWithMetadata.bind(this),
|
|
986
|
-
this.onProgress.bind(this)
|
|
1018
|
+
this.onProgress.bind(this),
|
|
1019
|
+
{ nodeId: this.id, routineExecId: this.routineExecId }
|
|
987
1020
|
);
|
|
988
1021
|
if (result?.errored || result?.failed) {
|
|
989
1022
|
return this.retry(result);
|
|
@@ -1839,11 +1872,12 @@ var Task = class extends SignalEmitter {
|
|
|
1839
1872
|
* @param context - The GraphContext to validate and execute.
|
|
1840
1873
|
* @param emit
|
|
1841
1874
|
* @param progressCallback - Callback for progress updates.
|
|
1875
|
+
* @param nodeData
|
|
1842
1876
|
* @returns TaskResult from the taskFunction or error object on validation failure.
|
|
1843
1877
|
* @edge If validateInputContext is true, validates context; on failure, emits 'meta.task.validationFailed' with detailed errors.
|
|
1844
1878
|
* @edge If validateOutputContext is true, validates output; on failure, emits 'meta.task.outputValidationFailed' with detailed errors.
|
|
1845
1879
|
*/
|
|
1846
|
-
execute(context, emit, progressCallback) {
|
|
1880
|
+
execute(context, emit, progressCallback, nodeData) {
|
|
1847
1881
|
return this.taskFunction(
|
|
1848
1882
|
this.isMeta ? context.getClonedFullContext() : context.getClonedContext(),
|
|
1849
1883
|
emit,
|
|
@@ -2661,8 +2695,8 @@ var EphemeralTask = class extends Task {
|
|
|
2661
2695
|
this.once = once;
|
|
2662
2696
|
this.condition = condition;
|
|
2663
2697
|
}
|
|
2664
|
-
execute(context, emit, progressCallback) {
|
|
2665
|
-
const result = super.execute(context, emit, progressCallback);
|
|
2698
|
+
execute(context, emit, progressCallback, nodeData) {
|
|
2699
|
+
const result = super.execute(context, emit, progressCallback, nodeData);
|
|
2666
2700
|
if (this.once || this.condition(result)) {
|
|
2667
2701
|
this.destroy();
|
|
2668
2702
|
}
|
|
@@ -3240,11 +3274,11 @@ var Cadenza = class {
|
|
|
3240
3274
|
throw new Error("Task or Routine name must be a non-empty string.");
|
|
3241
3275
|
}
|
|
3242
3276
|
}
|
|
3243
|
-
static
|
|
3244
|
-
this.runner
|
|
3277
|
+
static run(task, context) {
|
|
3278
|
+
this.runner?.run(task, context);
|
|
3245
3279
|
}
|
|
3246
|
-
static
|
|
3247
|
-
this.
|
|
3280
|
+
static emit(event, data = {}) {
|
|
3281
|
+
this.broker?.emit(event, data);
|
|
3248
3282
|
}
|
|
3249
3283
|
/**
|
|
3250
3284
|
* Creates a standard Task and registers it in the GraphRegistry.
|