@codemation/core 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +390 -0
- package/dist/{EngineRuntimeRegistration.types-BP6tsaNP.d.ts → EngineRuntimeRegistration.types-D1fyApMI.d.ts} +2 -2
- package/dist/{EngineWorkflowRunnerService-DzOCa1BW.d.cts → EngineRuntimeRegistration.types-pB3FnzqR.d.cts} +17 -17
- package/dist/{InMemoryRunDataFactory-1iz7_SnO.d.cts → InMemoryRunDataFactory-Xw7v4-sj.d.cts} +31 -29
- package/dist/InMemoryRunEventBusRegistry-VM3OWnHo.cjs +47 -0
- package/dist/InMemoryRunEventBusRegistry-VM3OWnHo.cjs.map +1 -0
- package/dist/InMemoryRunEventBusRegistry-sM4z4n_i.js +41 -0
- package/dist/InMemoryRunEventBusRegistry-sM4z4n_i.js.map +1 -0
- package/dist/{RunIntentService-BqhmdoA1.d.ts → RunIntentService-BE9CAkbf.d.ts} +966 -471
- package/dist/{RunIntentService-S-1lW-gS.d.cts → RunIntentService-siBSjaaY.d.cts} +859 -493
- package/dist/bootstrap/index.cjs +5 -2
- package/dist/bootstrap/index.d.cts +212 -135
- package/dist/bootstrap/index.d.ts +4 -4
- package/dist/bootstrap/index.js +3 -3
- package/dist/{bootstrap-BaN6hZ5I.cjs → bootstrap-Cm5ruQxx.cjs} +263 -12
- package/dist/bootstrap-Cm5ruQxx.cjs.map +1 -0
- package/dist/bootstrap-D3r505ko.js +454 -0
- package/dist/bootstrap-D3r505ko.js.map +1 -0
- package/dist/{index-CVs9rVhl.d.ts → index-DeLl1Tne.d.ts} +632 -230
- package/dist/index.cjs +323 -176
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +544 -91
- package/dist/index.d.ts +3 -3
- package/dist/index.js +299 -166
- package/dist/index.js.map +1 -1
- package/dist/{runtime-DUW6tIJ1.js → runtime-BGNbRnqs.js} +934 -75
- package/dist/runtime-BGNbRnqs.js.map +1 -0
- package/dist/{runtime-Dvo2ru5A.cjs → runtime-DKXJwTNv.cjs} +1028 -73
- package/dist/runtime-DKXJwTNv.cjs.map +1 -0
- package/dist/testing.cjs +5 -5
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +2 -2
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +4 -4
- package/dist/testing.js.map +1 -1
- package/package.json +7 -2
- package/src/ai/AiHost.ts +42 -14
- package/src/authoring/DefinedCollectionRegistry.ts +17 -0
- package/src/authoring/defineCollection.types.ts +181 -0
- package/src/authoring/definePollingTrigger.types.ts +396 -0
- package/src/authoring/definePollingTriggerInternals.ts +74 -0
- package/src/authoring/index.ts +19 -0
- package/src/bootstrap/index.ts +9 -0
- package/src/bootstrap/runtime/EngineRuntimeRegistrar.ts +21 -14
- package/src/browser.ts +1 -0
- package/src/contracts/CodemationTelemetryAttributeNames.ts +6 -0
- package/src/contracts/NoOpNodeExecutionTelemetry.ts +2 -11
- package/src/contracts/NoOpTelemetrySpanScope.ts +46 -10
- package/src/contracts/assertionTypes.ts +63 -0
- package/src/contracts/baseTypes.ts +12 -0
- package/src/contracts/collectionTypes.ts +44 -0
- package/src/contracts/credentialTypes.ts +23 -1
- package/src/contracts/executionPersistenceContracts.ts +30 -0
- package/src/contracts/index.ts +4 -0
- package/src/contracts/runTypes.ts +37 -1
- package/src/contracts/runtimeTypes.ts +42 -0
- package/src/contracts/telemetryTypes.ts +8 -0
- package/src/contracts/testTriggerTypes.ts +66 -0
- package/src/contracts/workflowTypes.ts +36 -7
- package/src/contracts.ts +59 -0
- package/src/events/ConnectionInvocationEventPublisher.ts +46 -0
- package/src/events/index.ts +1 -0
- package/src/events/runEvents.ts +74 -0
- package/src/execution/ChildExecutionScopeFactory.ts +55 -0
- package/src/execution/DefaultExecutionContextFactory.ts +6 -0
- package/src/execution/ExecutionTelemetryCostTrackingDecoratorFactory.ts +18 -0
- package/src/execution/NodeExecutor.ts +10 -2
- package/src/execution/NodeInstanceFactory.ts +13 -1
- package/src/execution/NodeInstantiationError.ts +16 -0
- package/src/execution/NodeRunStateWriter.ts +7 -0
- package/src/execution/NodeRunStateWriterFactory.ts +7 -0
- package/src/execution/WorkflowRunExecutionContextFactory.ts +3 -0
- package/src/execution/index.ts +2 -0
- package/src/index.ts +8 -0
- package/src/orchestration/AbortControllerFactory.ts +9 -0
- package/src/orchestration/NodeExecutionRequestHandlerService.ts +1 -0
- package/src/orchestration/RunContinuationService.ts +3 -0
- package/src/orchestration/RunStartService.ts +122 -3
- package/src/orchestration/TestSuiteOrchestrator.ts +350 -0
- package/src/orchestration/TestSuiteRunIdFactory.ts +11 -0
- package/src/orchestration/TriggerRuntimeService.ts +34 -7
- package/src/orchestration/index.ts +9 -0
- package/src/runtime/EngineFactory.ts +12 -0
- package/src/testing/WorkflowTestKitNodeRegistrationContextFactory.ts +1 -3
- package/src/triggers/polling/PollingTriggerDedupWindow.ts +23 -0
- package/src/triggers/polling/PollingTriggerLogger.ts +18 -0
- package/src/triggers/polling/PollingTriggerRuntime.ts +122 -0
- package/src/triggers/polling/index.ts +5 -0
- package/src/types/index.ts +12 -9
- package/src/workflow/definition/NodeIterationIdFactory.ts +26 -0
- package/src/workflow/dsl/NodeIdSlugifier.ts +18 -0
- package/src/workflow/dsl/WorkflowBuilder.ts +71 -3
- package/src/workflow/dsl/WorkflowDefinitionError.ts +15 -0
- package/src/workflow/index.ts +3 -0
- package/dist/InMemoryRunEventBusRegistry-B0_C4OnP.cjs +0 -262
- package/dist/InMemoryRunEventBusRegistry-B0_C4OnP.cjs.map +0 -1
- package/dist/InMemoryRunEventBusRegistry-C2U83Hmv.js +0 -238
- package/dist/InMemoryRunEventBusRegistry-C2U83Hmv.js.map +0 -1
- package/dist/bootstrap-BaN6hZ5I.cjs.map +0 -1
- package/dist/bootstrap-d_BMaDT4.js +0 -221
- package/dist/bootstrap-d_BMaDT4.js.map +0 -1
- package/dist/runtime-DUW6tIJ1.js.map +0 -1
- package/dist/runtime-Dvo2ru5A.cjs.map +0 -1
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { DefinedCollectionRegistry } from "./DefinedCollectionRegistry";
|
|
2
|
+
|
|
3
|
+
export type CollectionFieldType = "text" | "int" | "bigint" | "double" | "bool" | "timestamptz" | "jsonb" | "uuid";
|
|
4
|
+
|
|
5
|
+
export interface CollectionColumnBuilder {
|
|
6
|
+
notNull(): CollectionColumnBuilder;
|
|
7
|
+
default(value: unknown): CollectionColumnBuilder;
|
|
8
|
+
readonly _type: CollectionFieldType;
|
|
9
|
+
readonly _nullable: boolean;
|
|
10
|
+
readonly _default?: unknown;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
class CollectionColumnBuilderImpl implements CollectionColumnBuilder {
|
|
14
|
+
_nullable: boolean;
|
|
15
|
+
_default?: unknown;
|
|
16
|
+
|
|
17
|
+
constructor(readonly _type: CollectionFieldType) {
|
|
18
|
+
this._nullable = true;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
notNull(): CollectionColumnBuilder {
|
|
22
|
+
this._nullable = false;
|
|
23
|
+
return this;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
default(value: unknown): CollectionColumnBuilder {
|
|
27
|
+
this._default = value;
|
|
28
|
+
this._nullable = false;
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const c = {
|
|
34
|
+
text(): CollectionColumnBuilder {
|
|
35
|
+
return new CollectionColumnBuilderImpl("text");
|
|
36
|
+
},
|
|
37
|
+
int(): CollectionColumnBuilder {
|
|
38
|
+
return new CollectionColumnBuilderImpl("int");
|
|
39
|
+
},
|
|
40
|
+
bigint(): CollectionColumnBuilder {
|
|
41
|
+
return new CollectionColumnBuilderImpl("bigint");
|
|
42
|
+
},
|
|
43
|
+
double(): CollectionColumnBuilder {
|
|
44
|
+
return new CollectionColumnBuilderImpl("double");
|
|
45
|
+
},
|
|
46
|
+
bool(): CollectionColumnBuilder {
|
|
47
|
+
return new CollectionColumnBuilderImpl("bool");
|
|
48
|
+
},
|
|
49
|
+
timestamptz(): CollectionColumnBuilder {
|
|
50
|
+
return new CollectionColumnBuilderImpl("timestamptz");
|
|
51
|
+
},
|
|
52
|
+
jsonb(): CollectionColumnBuilder {
|
|
53
|
+
return new CollectionColumnBuilderImpl("jsonb");
|
|
54
|
+
},
|
|
55
|
+
uuid(): CollectionColumnBuilder {
|
|
56
|
+
return new CollectionColumnBuilderImpl("uuid");
|
|
57
|
+
},
|
|
58
|
+
} as const;
|
|
59
|
+
|
|
60
|
+
export interface CollectionFieldDefinition {
|
|
61
|
+
readonly type: CollectionFieldType;
|
|
62
|
+
readonly nullable: boolean;
|
|
63
|
+
readonly default?: unknown;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface CollectionIndexDefinition {
|
|
67
|
+
readonly on: ReadonlyArray<string>;
|
|
68
|
+
readonly unique?: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface CollectionDefinition {
|
|
72
|
+
readonly name: string;
|
|
73
|
+
readonly fields: Readonly<Record<string, CollectionFieldDefinition>>;
|
|
74
|
+
readonly indexes: ReadonlyArray<CollectionIndexDefinition>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface DefinedCollection<TDefinition extends CollectionDefinition = CollectionDefinition> {
|
|
78
|
+
readonly kind: "defined-collection";
|
|
79
|
+
readonly definition: TDefinition;
|
|
80
|
+
register(context: { registerCollection(d: CollectionDefinition): void }): void;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Validates that a name follows the required pattern: lowercase + underscores, starts with letter.
|
|
85
|
+
*/
|
|
86
|
+
function validateCollectionName(name: string): void {
|
|
87
|
+
const pattern = /^[a-z][a-z0-9_]*$/;
|
|
88
|
+
if (!pattern.test(name)) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`Collection name "${name}" must start with a lowercase letter and contain only lowercase letters, digits, and underscores.`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Validates that all field names follow the required pattern.
|
|
97
|
+
*/
|
|
98
|
+
function validateFieldNames(fields: Record<string, CollectionFieldDefinition>): void {
|
|
99
|
+
const pattern = /^[a-z][a-z0-9_]*$/;
|
|
100
|
+
const reserved = ["id", "created_at", "updated_at"];
|
|
101
|
+
|
|
102
|
+
for (const fieldName of Object.keys(fields)) {
|
|
103
|
+
if (reserved.includes(fieldName)) {
|
|
104
|
+
throw new Error(`Field name "${fieldName}" is reserved for internal use.`);
|
|
105
|
+
}
|
|
106
|
+
if (!pattern.test(fieldName)) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
`Field name "${fieldName}" must start with a lowercase letter and contain only lowercase letters, digits, and underscores.`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Validates that all indexed fields exist in the declared fields.
|
|
116
|
+
*/
|
|
117
|
+
function validateIndexes(indexes: ReadonlyArray<CollectionIndexDefinition>, fieldNames: Set<string>): void {
|
|
118
|
+
for (const index of indexes) {
|
|
119
|
+
for (const fieldName of index.on) {
|
|
120
|
+
if (!fieldNames.has(fieldName)) {
|
|
121
|
+
throw new Error(`Index references non-existent field "${fieldName}".`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface DefineCollectionOptions {
|
|
128
|
+
readonly name: string;
|
|
129
|
+
readonly fields: Record<string, CollectionColumnBuilder>;
|
|
130
|
+
readonly indexes?: ReadonlyArray<CollectionIndexDefinition>;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function defineCollection<TName extends string>(
|
|
134
|
+
options: DefineCollectionOptions & { name: TName },
|
|
135
|
+
): DefinedCollection<
|
|
136
|
+
CollectionDefinition & {
|
|
137
|
+
name: TName;
|
|
138
|
+
}
|
|
139
|
+
> {
|
|
140
|
+
validateCollectionName(options.name);
|
|
141
|
+
|
|
142
|
+
// Convert the column builders to field definitions
|
|
143
|
+
const fields: Record<string, CollectionFieldDefinition> = {};
|
|
144
|
+
for (const [fieldName, builder] of Object.entries(options.fields)) {
|
|
145
|
+
const columnBuilder = builder as CollectionColumnBuilder;
|
|
146
|
+
fields[fieldName] = {
|
|
147
|
+
type: columnBuilder._type,
|
|
148
|
+
nullable: columnBuilder._nullable,
|
|
149
|
+
default: columnBuilder._default,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
validateFieldNames(fields);
|
|
154
|
+
|
|
155
|
+
const fieldNames = new Set(Object.keys(fields));
|
|
156
|
+
const indexes = options.indexes ?? [];
|
|
157
|
+
validateIndexes(indexes, fieldNames);
|
|
158
|
+
|
|
159
|
+
const definition: CollectionDefinition = {
|
|
160
|
+
name: options.name,
|
|
161
|
+
fields,
|
|
162
|
+
indexes,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// Register immediately (mirror defineNode behavior)
|
|
166
|
+
DefinedCollectionRegistry.register(definition);
|
|
167
|
+
|
|
168
|
+
const result: DefinedCollection = {
|
|
169
|
+
kind: "defined-collection",
|
|
170
|
+
definition,
|
|
171
|
+
register(context: { registerCollection(d: CollectionDefinition): void }) {
|
|
172
|
+
context.registerCollection(definition);
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
return result as DefinedCollection<
|
|
177
|
+
CollectionDefinition & {
|
|
178
|
+
name: TName;
|
|
179
|
+
}
|
|
180
|
+
>;
|
|
181
|
+
}
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* definePollingTrigger — declarative helper for authoring polling triggers.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the ergonomics of `defineNode` / `defineRestNode` / `defineCredential`.
|
|
5
|
+
* Plugin authors supply a `poll` function plus metadata; the helper synthesises the
|
|
6
|
+
* two internal classes (`DefinedPollingTriggerRuntime` + `DefinedPollingTriggerConfig`)
|
|
7
|
+
* that the engine's trigger machinery requires. Internal classes, DI annotations, and
|
|
8
|
+
* `PollingTriggerRuntime` wiring are hidden from the plugin-author surface entirely.
|
|
9
|
+
*/
|
|
10
|
+
import type {
|
|
11
|
+
Items,
|
|
12
|
+
JsonValue,
|
|
13
|
+
NodeExecutionContext,
|
|
14
|
+
NodeOutputs,
|
|
15
|
+
TestableTriggerNode,
|
|
16
|
+
TriggerNodeConfig,
|
|
17
|
+
TriggerSetupContext,
|
|
18
|
+
TriggerTestItemsContext,
|
|
19
|
+
TypeToken,
|
|
20
|
+
} from "..";
|
|
21
|
+
import type { CredentialJsonRecord, CredentialRequirement } from "../contracts/credentialTypes";
|
|
22
|
+
import type { DefinedNodeCredentialAccessors, DefinedNodeCredentialBindings } from "./defineNode.types";
|
|
23
|
+
import { node as persistedNode } from "../runtime-types/runtimeTypeDecorators.types";
|
|
24
|
+
import {
|
|
25
|
+
definedNodeCredentialRequirementFactory,
|
|
26
|
+
definedNodeCredentialAccessorFactory,
|
|
27
|
+
} from "./definePollingTriggerInternals";
|
|
28
|
+
import type { ZodType } from "zod";
|
|
29
|
+
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// Public types
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
type MaybePromise<TValue> = TValue | Promise<TValue>;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Context passed into the `poll` callback on each tick.
|
|
38
|
+
*/
|
|
39
|
+
export interface DefinePollingTriggerPollContext<
|
|
40
|
+
TConfig extends CredentialJsonRecord,
|
|
41
|
+
TState extends JsonValue | undefined,
|
|
42
|
+
TBindings extends DefinedNodeCredentialBindings | undefined,
|
|
43
|
+
> {
|
|
44
|
+
readonly config: TConfig;
|
|
45
|
+
readonly state: TState;
|
|
46
|
+
readonly credentials: DefinedNodeCredentialAccessors<TBindings>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* What `poll` must return each tick.
|
|
51
|
+
*/
|
|
52
|
+
export interface DefinePollingTriggerPollResult<TItemJson, TState extends JsonValue | undefined> {
|
|
53
|
+
/**
|
|
54
|
+
* New items to emit. Each item may carry an optional `dedupKey`; duplicate keys are
|
|
55
|
+
* filtered out against a rolling dedup window (managed internally by the runtime).
|
|
56
|
+
* Items without a `dedupKey` are always emitted.
|
|
57
|
+
*/
|
|
58
|
+
readonly items: ReadonlyArray<{ json: TItemJson; dedupKey?: string }>;
|
|
59
|
+
/** Persisted as the trigger's setup state for the next tick. */
|
|
60
|
+
readonly nextState: TState;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Context passed into the `execute` callback for post-emit enrichment (e.g. fetching
|
|
65
|
+
* attachment bytes). Mirrors `NodeExecutionContext` so plugin authors use familiar patterns.
|
|
66
|
+
*/
|
|
67
|
+
export type DefinePollingTriggerExecuteContext<TConfig extends TriggerNodeConfig<any, any>> =
|
|
68
|
+
NodeExecutionContext<TConfig>;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Context passed into the `testItems` callback.
|
|
72
|
+
*/
|
|
73
|
+
export type DefinePollingTriggerTestItemsContext<TConfig extends TriggerNodeConfig<any, any>> =
|
|
74
|
+
TriggerTestItemsContext<TConfig>;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Options accepted by `definePollingTrigger`.
|
|
78
|
+
*/
|
|
79
|
+
export interface DefinePollingTriggerOptions<
|
|
80
|
+
TKey extends string,
|
|
81
|
+
TConfig extends CredentialJsonRecord,
|
|
82
|
+
TItemJson,
|
|
83
|
+
TState extends JsonValue | undefined,
|
|
84
|
+
TBindings extends DefinedNodeCredentialBindings | undefined = undefined,
|
|
85
|
+
> {
|
|
86
|
+
/**
|
|
87
|
+
* Unique node-token-id-style key, e.g. `"msgraph-mail.on-new-mail"`.
|
|
88
|
+
* Used as the persisted runtime type name — must be stable across deployments.
|
|
89
|
+
*/
|
|
90
|
+
readonly key: TKey;
|
|
91
|
+
readonly title: string;
|
|
92
|
+
readonly description?: string;
|
|
93
|
+
/** Canvas icon (same contract as `NodeConfigBase.icon`). */
|
|
94
|
+
readonly icon?: string;
|
|
95
|
+
/**
|
|
96
|
+
* Zod schema for the trigger's user-facing configuration.
|
|
97
|
+
* When provided, the returned `create()` method is typed against the inferred config type.
|
|
98
|
+
*/
|
|
99
|
+
readonly configSchema?: ZodType<TConfig>;
|
|
100
|
+
/** Credential bindings keyed by slot (same format as `defineNode`). */
|
|
101
|
+
readonly credentials?: TBindings;
|
|
102
|
+
/**
|
|
103
|
+
* Called once when the trigger arms (or re-arms after a server restart) to provide the
|
|
104
|
+
* initial value for `state` when no persisted state exists.
|
|
105
|
+
*/
|
|
106
|
+
initialState?(): TState;
|
|
107
|
+
/**
|
|
108
|
+
* Polling interval in milliseconds. The runtime enforces a minimum of 25 ms.
|
|
109
|
+
* @default 60_000
|
|
110
|
+
*/
|
|
111
|
+
readonly pollIntervalMs?: number;
|
|
112
|
+
/**
|
|
113
|
+
* The per-tick poll logic. Called by the runtime each interval.
|
|
114
|
+
* Must return new items plus the next persisted state.
|
|
115
|
+
*/
|
|
116
|
+
poll(
|
|
117
|
+
pollCtx: DefinePollingTriggerPollContext<TConfig, TState, TBindings>,
|
|
118
|
+
): MaybePromise<DefinePollingTriggerPollResult<TItemJson, TState>>;
|
|
119
|
+
/**
|
|
120
|
+
* Optional post-emit enrichment step (runs in the normal node-execute phase after the
|
|
121
|
+
* trigger fires and the workflow run starts). Use for expensive per-item work such as
|
|
122
|
+
* fetching attachment bytes via `ctx.binary.attach`. When omitted, the trigger passes
|
|
123
|
+
* items through unchanged.
|
|
124
|
+
*/
|
|
125
|
+
execute?(
|
|
126
|
+
items: Items<TItemJson>,
|
|
127
|
+
ctx: NodeExecutionContext<DefinedPollingTriggerConfig<TConfig, TItemJson>>,
|
|
128
|
+
): MaybePromise<NodeOutputs>;
|
|
129
|
+
/**
|
|
130
|
+
* Optional implementation for the workflow UI's "Test" button. Should return a small
|
|
131
|
+
* sample of current items without consulting or mutating polling state.
|
|
132
|
+
*/
|
|
133
|
+
testItems?(
|
|
134
|
+
ctx: TriggerTestItemsContext<DefinedPollingTriggerConfig<TConfig, TItemJson>>,
|
|
135
|
+
): MaybePromise<Items<TItemJson>>;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
// DefinedPollingTrigger (returned object)
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* The object returned by `definePollingTrigger`. Register it via
|
|
144
|
+
* `definePlugin({ nodes: [myTrigger] })` or call `.register(ctx)` directly.
|
|
145
|
+
*
|
|
146
|
+
* `poll` is also directly callable for unit-testing — no runtime needed.
|
|
147
|
+
*/
|
|
148
|
+
export interface DefinedPollingTrigger<
|
|
149
|
+
TKey extends string,
|
|
150
|
+
TConfig extends CredentialJsonRecord,
|
|
151
|
+
TItemJson,
|
|
152
|
+
TState extends JsonValue | undefined,
|
|
153
|
+
TBindings extends DefinedNodeCredentialBindings | undefined = undefined,
|
|
154
|
+
> {
|
|
155
|
+
readonly kind: "defined-polling-trigger";
|
|
156
|
+
readonly key: TKey;
|
|
157
|
+
readonly title: string;
|
|
158
|
+
readonly description?: string;
|
|
159
|
+
/**
|
|
160
|
+
* Create the trigger config for use in workflow definitions.
|
|
161
|
+
* @param cfg - User-facing trigger configuration.
|
|
162
|
+
* @param name - Display name (defaults to `title`).
|
|
163
|
+
* @param id - Optional stable node id.
|
|
164
|
+
*/
|
|
165
|
+
create(cfg: TConfig, name?: string, id?: string): DefinedPollingTriggerConfig<TConfig, TItemJson>;
|
|
166
|
+
/**
|
|
167
|
+
* Test seam: call `poll` directly without starting the runtime.
|
|
168
|
+
* Returns `{ items, nextState }` just like the real runtime receives.
|
|
169
|
+
*/
|
|
170
|
+
poll(
|
|
171
|
+
pollCtx: Omit<DefinePollingTriggerPollContext<TConfig, TState, TBindings>, "credentials"> & {
|
|
172
|
+
credentials?: DefinedNodeCredentialAccessors<TBindings>;
|
|
173
|
+
},
|
|
174
|
+
): MaybePromise<DefinePollingTriggerPollResult<TItemJson, TState>>;
|
|
175
|
+
/** Registers the synthesised runtime class with the plugin container. */
|
|
176
|
+
register(context: { registerNode<TValue>(token: TypeToken<TValue>, implementation?: TypeToken<TValue>): void }): void;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
// DefinedPollingTriggerConfig (TriggerNodeConfig shape for the engine)
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* TriggerNodeConfig produced by `DefinedPollingTrigger.create(...)`.
|
|
185
|
+
* Holds user configuration and credential requirements for the engine.
|
|
186
|
+
* The setup state type is opaque `JsonValue | undefined` — the runtime
|
|
187
|
+
* uses an internal wrapped shape that plugin authors never see.
|
|
188
|
+
*/
|
|
189
|
+
export class DefinedPollingTriggerConfig<TConfig extends CredentialJsonRecord, TItemJson> implements TriggerNodeConfig<
|
|
190
|
+
TItemJson,
|
|
191
|
+
JsonValue | undefined
|
|
192
|
+
> {
|
|
193
|
+
readonly kind = "trigger" as const;
|
|
194
|
+
readonly type: TypeToken<unknown>;
|
|
195
|
+
readonly icon: string | undefined;
|
|
196
|
+
|
|
197
|
+
constructor(
|
|
198
|
+
public readonly name: string,
|
|
199
|
+
public readonly cfg: TConfig,
|
|
200
|
+
typeToken: TypeToken<unknown>,
|
|
201
|
+
icon: string | undefined,
|
|
202
|
+
private readonly credentialRequirements: ReadonlyArray<CredentialRequirement>,
|
|
203
|
+
public readonly id?: string,
|
|
204
|
+
) {
|
|
205
|
+
this.type = typeToken;
|
|
206
|
+
this.icon = icon;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
getCredentialRequirements(): ReadonlyArray<CredentialRequirement> {
|
|
210
|
+
return this.credentialRequirements;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
// Internal wrapped state helpers
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
|
|
218
|
+
/** Opaque shape stored in the trigger setup state repository. @internal */
|
|
219
|
+
interface InternalWrappedState {
|
|
220
|
+
readonly userState: JsonValue | undefined;
|
|
221
|
+
readonly seenKeys: ReadonlyArray<string>;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function isWrappedState(value: unknown): value is InternalWrappedState {
|
|
225
|
+
return (
|
|
226
|
+
value !== null &&
|
|
227
|
+
typeof value === "object" &&
|
|
228
|
+
"seenKeys" in (value as Record<string, unknown>) &&
|
|
229
|
+
Array.isArray((value as InternalWrappedState).seenKeys)
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ---------------------------------------------------------------------------
|
|
234
|
+
// Implementation factory
|
|
235
|
+
// ---------------------------------------------------------------------------
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Declarative helper for authoring polling triggers.
|
|
239
|
+
*
|
|
240
|
+
* ```ts
|
|
241
|
+
* export const onNewMail = definePollingTrigger({
|
|
242
|
+
* key: "my-plugin.on-new-mail",
|
|
243
|
+
* title: "On new mail",
|
|
244
|
+
* configSchema: z.object({ folder: z.string() }),
|
|
245
|
+
* credentials: { auth: myOAuthCredentialType },
|
|
246
|
+
* initialState: () => ({ lastSeenId: undefined }),
|
|
247
|
+
* pollIntervalMs: 60_000,
|
|
248
|
+
* async poll({ config, state, credentials }) {
|
|
249
|
+
* const session = await credentials.auth();
|
|
250
|
+
* const messages = await fetchMessages(session, config.folder, state.lastSeenId);
|
|
251
|
+
* return {
|
|
252
|
+
* items: messages.map(m => ({ json: m, dedupKey: m.id })),
|
|
253
|
+
* nextState: { lastSeenId: messages[0]?.id ?? state.lastSeenId },
|
|
254
|
+
* };
|
|
255
|
+
* },
|
|
256
|
+
* });
|
|
257
|
+
* ```
|
|
258
|
+
*/
|
|
259
|
+
export function definePollingTrigger<
|
|
260
|
+
TKey extends string,
|
|
261
|
+
TConfig extends CredentialJsonRecord,
|
|
262
|
+
TItemJson,
|
|
263
|
+
TState extends JsonValue | undefined,
|
|
264
|
+
TBindings extends DefinedNodeCredentialBindings | undefined = undefined,
|
|
265
|
+
>(
|
|
266
|
+
options: DefinePollingTriggerOptions<TKey, TConfig, TItemJson, TState, TBindings>,
|
|
267
|
+
): DefinedPollingTrigger<TKey, TConfig, TItemJson, TState, TBindings> {
|
|
268
|
+
const credentialRequirements = definedNodeCredentialRequirementFactory.create(options.credentials);
|
|
269
|
+
const DEFAULT_INTERVAL_MS = 60_000;
|
|
270
|
+
|
|
271
|
+
type TConfig_ = DefinedPollingTriggerConfig<TConfig, TItemJson>;
|
|
272
|
+
|
|
273
|
+
// ---------------------------------------------------------------------------
|
|
274
|
+
// Synthesised runtime class (implements TestableTriggerNode)
|
|
275
|
+
// ---------------------------------------------------------------------------
|
|
276
|
+
|
|
277
|
+
const DefinedPollingTriggerRuntime = class implements TestableTriggerNode<TConfig_> {
|
|
278
|
+
readonly kind = "trigger" as const;
|
|
279
|
+
readonly outputPorts = ["main"] as const;
|
|
280
|
+
|
|
281
|
+
async setup(ctx: TriggerSetupContext<TConfig_, JsonValue | undefined>): Promise<JsonValue | undefined> {
|
|
282
|
+
const cfg = ctx.config.cfg;
|
|
283
|
+
const intervalMs =
|
|
284
|
+
(cfg as Partial<{ pollIntervalMs: number }>).pollIntervalMs ?? options.pollIntervalMs ?? DEFAULT_INTERVAL_MS;
|
|
285
|
+
|
|
286
|
+
// Unwrap previously persisted state, or create the initial wrapped state.
|
|
287
|
+
const persisted = ctx.previousState;
|
|
288
|
+
const existingWrapped: InternalWrappedState | undefined = isWrappedState(persisted) ? persisted : undefined;
|
|
289
|
+
const seedWrapped: InternalWrappedState = existingWrapped ?? {
|
|
290
|
+
userState: options.initialState ? options.initialState() : undefined,
|
|
291
|
+
seenKeys: [],
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const result = await ctx.polling.start<InternalWrappedState, TItemJson>({
|
|
295
|
+
intervalMs,
|
|
296
|
+
seedState: seedWrapped,
|
|
297
|
+
runCycle: async ({ previousState }) => {
|
|
298
|
+
const wrapped: InternalWrappedState = previousState ?? seedWrapped;
|
|
299
|
+
const seenSet = new Set(wrapped.seenKeys);
|
|
300
|
+
|
|
301
|
+
const credentialAccessors = definedNodeCredentialAccessorFactory.create(
|
|
302
|
+
options.credentials,
|
|
303
|
+
ctx,
|
|
304
|
+
) as DefinedNodeCredentialAccessors<TBindings>;
|
|
305
|
+
|
|
306
|
+
const pollResult = await options.poll({
|
|
307
|
+
config: cfg,
|
|
308
|
+
state: wrapped.userState as TState,
|
|
309
|
+
credentials: credentialAccessors,
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
// Dedup: filter items whose dedupKey is already seen
|
|
313
|
+
const newItems: Array<{ json: TItemJson }> = [];
|
|
314
|
+
const newKeys: string[] = [];
|
|
315
|
+
for (const item of pollResult.items) {
|
|
316
|
+
if (item.dedupKey !== undefined) {
|
|
317
|
+
if (seenSet.has(item.dedupKey)) {
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
newKeys.push(item.dedupKey);
|
|
321
|
+
}
|
|
322
|
+
newItems.push({ json: item.json });
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Merge keys, cap the window at 2000 to bound state size
|
|
326
|
+
const allKeys = [...wrapped.seenKeys, ...newKeys];
|
|
327
|
+
const cappedKeys = allKeys.length > 2000 ? allKeys.slice(allKeys.length - 2000) : allKeys;
|
|
328
|
+
|
|
329
|
+
const nextWrapped: InternalWrappedState = {
|
|
330
|
+
userState: pollResult.nextState,
|
|
331
|
+
seenKeys: cappedKeys,
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
return {
|
|
335
|
+
items: newItems as Items<TItemJson>,
|
|
336
|
+
nextState: nextWrapped,
|
|
337
|
+
};
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
return result as JsonValue | undefined;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
async execute(items: Items<TItemJson>, ctx: NodeExecutionContext<TConfig_>): Promise<NodeOutputs> {
|
|
345
|
+
if (options.execute) {
|
|
346
|
+
return await options.execute(items, ctx);
|
|
347
|
+
}
|
|
348
|
+
return { main: items };
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
async getTestItems(ctx: TriggerTestItemsContext<TConfig_>): Promise<Items> {
|
|
352
|
+
if (options.testItems) {
|
|
353
|
+
return await options.testItems(ctx);
|
|
354
|
+
}
|
|
355
|
+
return [];
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
persistedNode({ name: options.key })(DefinedPollingTriggerRuntime);
|
|
360
|
+
|
|
361
|
+
// ---------------------------------------------------------------------------
|
|
362
|
+
// Returned definition object
|
|
363
|
+
// ---------------------------------------------------------------------------
|
|
364
|
+
|
|
365
|
+
const definition: DefinedPollingTrigger<TKey, TConfig, TItemJson, TState, TBindings> = {
|
|
366
|
+
kind: "defined-polling-trigger",
|
|
367
|
+
key: options.key,
|
|
368
|
+
title: options.title,
|
|
369
|
+
description: options.description,
|
|
370
|
+
|
|
371
|
+
create(cfg: TConfig, name = options.title, id?: string): DefinedPollingTriggerConfig<TConfig, TItemJson> {
|
|
372
|
+
return new DefinedPollingTriggerConfig<TConfig, TItemJson>(
|
|
373
|
+
name,
|
|
374
|
+
cfg,
|
|
375
|
+
DefinedPollingTriggerRuntime,
|
|
376
|
+
options.icon,
|
|
377
|
+
credentialRequirements,
|
|
378
|
+
id,
|
|
379
|
+
);
|
|
380
|
+
},
|
|
381
|
+
|
|
382
|
+
poll(pollCtx) {
|
|
383
|
+
return options.poll({
|
|
384
|
+
config: pollCtx.config,
|
|
385
|
+
state: pollCtx.state,
|
|
386
|
+
credentials: (pollCtx.credentials ?? {}) as DefinedNodeCredentialAccessors<TBindings>,
|
|
387
|
+
});
|
|
388
|
+
},
|
|
389
|
+
|
|
390
|
+
register(context) {
|
|
391
|
+
context.registerNode(DefinedPollingTriggerRuntime);
|
|
392
|
+
},
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
return definition;
|
|
396
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared internal helpers for defineNode and definePollingTrigger.
|
|
3
|
+
* Not part of the public API — import only from authoring helpers.
|
|
4
|
+
*/
|
|
5
|
+
import type { AnyCredentialType, CredentialRequirement, CredentialTypeId } from "../contracts/credentialTypes";
|
|
6
|
+
import type { NodeExecutionContext } from "../contracts/runtimeTypes";
|
|
7
|
+
import type { RunnableNodeConfig } from "../contracts/workflowTypes";
|
|
8
|
+
import type { DefinedNodeCredentialAccessors, DefinedNodeCredentialBindings } from "./defineNode.types";
|
|
9
|
+
|
|
10
|
+
type ResolvableCredentialType = AnyCredentialType | CredentialTypeId;
|
|
11
|
+
|
|
12
|
+
export const definedNodeCredentialRequirementFactory = {
|
|
13
|
+
create(bindings: DefinedNodeCredentialBindings | undefined): ReadonlyArray<CredentialRequirement> {
|
|
14
|
+
if (!bindings) {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
return Object.entries(bindings).map(([slotKey, binding]) => {
|
|
18
|
+
if (typeof binding === "string" || this.isCredentialType(binding)) {
|
|
19
|
+
return {
|
|
20
|
+
slotKey,
|
|
21
|
+
label: this.humanize(slotKey),
|
|
22
|
+
acceptedTypes: [this.resolveTypeId(binding)],
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const types = Array.isArray(binding.type) ? binding.type : [binding.type];
|
|
27
|
+
return {
|
|
28
|
+
slotKey,
|
|
29
|
+
label: binding.label ?? this.humanize(slotKey),
|
|
30
|
+
acceptedTypes: types.map((entry) => this.resolveTypeId(entry)),
|
|
31
|
+
optional: binding.optional,
|
|
32
|
+
helpText: binding.helpText,
|
|
33
|
+
helpUrl: binding.helpUrl,
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
isCredentialType(value: unknown): value is AnyCredentialType {
|
|
39
|
+
return (
|
|
40
|
+
Boolean(value) &&
|
|
41
|
+
typeof value === "object" &&
|
|
42
|
+
"definition" in (value as Record<string, unknown>) &&
|
|
43
|
+
typeof (value as AnyCredentialType).definition?.typeId === "string"
|
|
44
|
+
);
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
resolveTypeId(type: ResolvableCredentialType): string {
|
|
48
|
+
return typeof type === "string" ? type : type.definition.typeId;
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
humanize(key: string): string {
|
|
52
|
+
return key
|
|
53
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
54
|
+
.replace(/[-_.]+/g, " ")
|
|
55
|
+
.replace(/\s+/g, " ")
|
|
56
|
+
.trim()
|
|
57
|
+
.replace(/^./, (character) => character.toUpperCase());
|
|
58
|
+
},
|
|
59
|
+
} as const;
|
|
60
|
+
|
|
61
|
+
export const definedNodeCredentialAccessorFactory = {
|
|
62
|
+
create<TBindings extends DefinedNodeCredentialBindings | undefined>(
|
|
63
|
+
bindings: TBindings,
|
|
64
|
+
ctx:
|
|
65
|
+
| NodeExecutionContext<RunnableNodeConfig<any, any>>
|
|
66
|
+
| { getCredential<TSession = unknown>(slotKey: string): Promise<TSession> },
|
|
67
|
+
): DefinedNodeCredentialAccessors<TBindings> {
|
|
68
|
+
if (!bindings) {
|
|
69
|
+
return {} as DefinedNodeCredentialAccessors<TBindings>;
|
|
70
|
+
}
|
|
71
|
+
const entries = Object.keys(bindings).map((slotKey) => [slotKey, () => ctx.getCredential(slotKey)] as const);
|
|
72
|
+
return Object.fromEntries(entries) as DefinedNodeCredentialAccessors<TBindings>;
|
|
73
|
+
},
|
|
74
|
+
} as const;
|
package/src/authoring/index.ts
CHANGED
|
@@ -14,3 +14,22 @@ export { defineBatchNode, defineNode } from "./defineNode.types";
|
|
|
14
14
|
export type { DefineCredentialOptions } from "./defineCredential.types";
|
|
15
15
|
export { defineCredential } from "./defineCredential.types";
|
|
16
16
|
export { callableTool } from "./callableTool.types";
|
|
17
|
+
export { DefinedCollectionRegistry } from "./DefinedCollectionRegistry";
|
|
18
|
+
export type {
|
|
19
|
+
DefinedCollection,
|
|
20
|
+
CollectionDefinition,
|
|
21
|
+
CollectionFieldDefinition,
|
|
22
|
+
CollectionIndexDefinition,
|
|
23
|
+
CollectionColumnBuilder,
|
|
24
|
+
DefineCollectionOptions,
|
|
25
|
+
} from "./defineCollection.types";
|
|
26
|
+
export { defineCollection, c } from "./defineCollection.types";
|
|
27
|
+
export type {
|
|
28
|
+
DefinePollingTriggerOptions,
|
|
29
|
+
DefinePollingTriggerPollContext,
|
|
30
|
+
DefinePollingTriggerPollResult,
|
|
31
|
+
DefinePollingTriggerExecuteContext,
|
|
32
|
+
DefinePollingTriggerTestItemsContext,
|
|
33
|
+
DefinedPollingTrigger,
|
|
34
|
+
} from "./definePollingTrigger.types";
|
|
35
|
+
export { definePollingTrigger, DefinedPollingTriggerConfig } from "./definePollingTrigger.types";
|
package/src/bootstrap/index.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
/** Composition-root engine graph and advanced runtime wiring. Not part of the main `@codemation/core` barrel. */
|
|
2
2
|
export { Engine } from "../orchestration/Engine";
|
|
3
|
+
export {
|
|
4
|
+
AbortControllerFactory,
|
|
5
|
+
TestSuiteOrchestrator,
|
|
6
|
+
TestSuiteRunIdFactory,
|
|
7
|
+
type RunTestSuiteArgs,
|
|
8
|
+
type TestSuiteCaseOutcome,
|
|
9
|
+
type TestSuiteOrchestratorEngine,
|
|
10
|
+
type TestSuiteRunResult,
|
|
11
|
+
} from "../orchestration";
|
|
3
12
|
export { EngineFactory, type EngineCompositionDeps } from "../runtime/EngineFactory";
|
|
4
13
|
export {
|
|
5
14
|
EngineRuntimeRegistrar,
|