@multiplier-labs/stepflow 0.3.4 → 0.3.8
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 +9 -0
- package/dist/{chunk-5XJBAPXF.js → chunk-4IDPWWGS.js} +15 -4
- package/dist/chunk-4IDPWWGS.js.map +1 -0
- package/dist/{chunk-U3PCVK6O.js → chunk-T6GAZBTQ.js} +70 -38
- package/dist/chunk-T6GAZBTQ.js.map +1 -0
- package/dist/{chunk-FMPJBK2N.js → chunk-TVSADUYU.js} +3 -5
- package/dist/{chunk-FMPJBK2N.js.map → chunk-TVSADUYU.js.map} +1 -1
- package/dist/events/index.d.ts +43 -3
- package/dist/events/index.js +7 -3
- package/dist/{index-Cd0IZ40L.d.ts → index-CdXDv30T.d.ts} +10 -2
- package/dist/index.d.ts +5 -4
- package/dist/index.js +159 -46
- package/dist/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +2 -2
- package/dist/scheduler/index.js +1 -1
- package/dist/storage/index.d.ts +6 -4
- package/dist/storage/index.js +1 -1
- package/dist/{types-DSEjiLMj.d.ts → types-BfWe5yP6.d.ts} +2 -0
- package/package.json +26 -14
- package/.codekin/reports/code-review/2026-04-11_code-review-daily.md +0 -146
- package/.codekin/reports/code-review/2026-04-13_code-review-daily.md +0 -74
- package/.codekin/reports/comments/2026-04-04_comment-assessment.md +0 -179
- package/.codekin/reports/comments/2026-04-11_comment-assessment.md +0 -161
- package/.codekin/reports/comments/2026-04-14_comment-assessment.md +0 -34
- package/.codekin/reports/complexity/2026-04-03_complexity-report.md +0 -152
- package/.codekin/reports/complexity/2026-04-11_complexity-report.md +0 -152
- package/.codekin/reports/complexity/2026-04-14_complexity-report.md +0 -34
- package/.codekin/reports/dependencies/2026-04-04_dependency-health.md +0 -94
- package/.codekin/reports/dependencies/2026-04-11_dependency-health.md +0 -93
- package/.codekin/reports/docs-audit/2026-04-04_docs-audit.md +0 -10
- package/.codekin/reports/docs-audit/2026-04-11_docs-audit.md +0 -138
- package/.codekin/reports/docs-audit/2026-04-13_docs-audit.md +0 -121
- package/.codekin/reports/security/2026-04-03_security-audit.md +0 -208
- package/.codekin/reports/security/2026-04-11_security-audit.md +0 -297
- package/.github/CODEOWNERS +0 -15
- package/.github/dependabot.yml +0 -23
- package/.github/workflows/ci.yml +0 -38
- package/.github/workflows/publish.yml +0 -42
- package/.github/workflows/security.yml +0 -29
- package/CHANGELOG.md +0 -34
- package/SECURITY.md +0 -40
- package/dist/chunk-5XJBAPXF.js.map +0 -1
- package/dist/chunk-U3PCVK6O.js.map +0 -1
- package/docs/github-packages-setup.md +0 -97
- package/docs/stepflow-api-reference.md +0 -1663
- package/src/core/engine/concurrency.ts +0 -229
- package/src/core/engine/lifecycle.ts +0 -176
- package/src/core/engine.test.ts +0 -1435
- package/src/core/engine.ts +0 -598
- package/src/core/index.ts +0 -7
- package/src/core/orchestrator/persistence.ts +0 -165
- package/src/core/orchestrator/plan-resolution.ts +0 -140
- package/src/core/orchestrator/state-transitions.ts +0 -109
- package/src/core/orchestrator/step-runner.ts +0 -427
- package/src/core/orchestrator.ts +0 -252
- package/src/core/types.ts +0 -220
- package/src/events/index.ts +0 -8
- package/src/events/memory.test.ts +0 -107
- package/src/events/memory.ts +0 -92
- package/src/events/socketio.test.ts +0 -378
- package/src/events/socketio.ts +0 -262
- package/src/events/types.ts +0 -118
- package/src/events/webhook.test.ts +0 -949
- package/src/events/webhook.ts +0 -807
- package/src/index.ts +0 -270
- package/src/planning/evaluate-condition.test.ts +0 -153
- package/src/planning/evaluate-condition.ts +0 -105
- package/src/planning/index.ts +0 -55
- package/src/planning/planner.ts +0 -494
- package/src/planning/planning.test.ts +0 -727
- package/src/planning/registry.test.ts +0 -398
- package/src/planning/registry.ts +0 -283
- package/src/planning/types.ts +0 -566
- package/src/scheduler/cron.test.ts +0 -479
- package/src/scheduler/cron.ts +0 -438
- package/src/scheduler/index.ts +0 -8
- package/src/scheduler/postgres-persistence.ts +0 -530
- package/src/scheduler/sqlite-persistence.test.ts +0 -235
- package/src/scheduler/sqlite-persistence.ts +0 -286
- package/src/scheduler/types.ts +0 -73
- package/src/storage/index.ts +0 -27
- package/src/storage/memory.test.ts +0 -349
- package/src/storage/memory.ts +0 -227
- package/src/storage/postgres-core.ts +0 -550
- package/src/storage/postgres-migration.test.ts +0 -69
- package/src/storage/postgres-transaction-pagination.test.ts +0 -107
- package/src/storage/postgres.ts +0 -835
- package/src/storage/sqlite-core.ts +0 -151
- package/src/storage/sqlite.test.ts +0 -642
- package/src/storage/sqlite.ts +0 -717
- package/src/storage/types.ts +0 -343
- package/src/utils/errors.test.ts +0 -150
- package/src/utils/errors.ts +0 -161
- package/src/utils/id.ts +0 -21
- package/src/utils/index.ts +0 -10
- package/src/utils/logger.test.ts +0 -121
- package/src/utils/logger.ts +0 -96
- package/src/utils/pinned-fetch.test.ts +0 -84
- package/src/utils/pinned-fetch.ts +0 -118
- package/src/utils/postgres-deps.ts +0 -79
- package/src/utils/retry.test.ts +0 -177
- package/src/utils/retry.ts +0 -121
- package/src/utils/safe-json.test.ts +0 -166
- package/src/utils/safe-json.ts +0 -107
- package/src/utils/webhook-signature.test.ts +0 -111
- package/src/utils/webhook-signature.ts +0 -74
- package/tsconfig.json +0 -21
- package/tsup.config.ts +0 -14
- package/vitest.config.ts +0 -9
package/src/index.ts
DELETED
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Stepflow - Workflow Engine
|
|
3
|
-
*
|
|
4
|
-
* A framework-agnostic workflow engine that provides:
|
|
5
|
-
* - Workflow Definition & Registration
|
|
6
|
-
* - Async Execution Engine (non-blocking, fire-and-forget)
|
|
7
|
-
* - State Management (run/step status tracking with checkpointing)
|
|
8
|
-
* - Event System (real-time events for monitoring and UI updates)
|
|
9
|
-
* - Pluggable Storage (bring your own database/persistence layer)
|
|
10
|
-
* - Retry & Error Handling (configurable per-step error strategies)
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* ```typescript
|
|
14
|
-
* import { WorkflowEngine, MemoryStorageAdapter } from 'stepflow';
|
|
15
|
-
*
|
|
16
|
-
* const engine = new WorkflowEngine({
|
|
17
|
-
* storage: new MemoryStorageAdapter(),
|
|
18
|
-
* });
|
|
19
|
-
*
|
|
20
|
-
* engine.registerWorkflow({
|
|
21
|
-
* kind: 'email.send_campaign',
|
|
22
|
-
* name: 'Send Email Campaign',
|
|
23
|
-
* steps: [
|
|
24
|
-
* {
|
|
25
|
-
* key: 'load_recipients',
|
|
26
|
-
* name: 'Load Recipients',
|
|
27
|
-
* handler: async (ctx) => {
|
|
28
|
-
* const recipients = await loadRecipients(ctx.input.campaignId);
|
|
29
|
-
* return { recipients };
|
|
30
|
-
* },
|
|
31
|
-
* },
|
|
32
|
-
* {
|
|
33
|
-
* key: 'send_emails',
|
|
34
|
-
* name: 'Send Emails',
|
|
35
|
-
* handler: async (ctx) => {
|
|
36
|
-
* const { recipients } = ctx.results.load_recipients;
|
|
37
|
-
* // Send emails...
|
|
38
|
-
* return { sent: recipients.length };
|
|
39
|
-
* },
|
|
40
|
-
* onError: 'retry',
|
|
41
|
-
* maxRetries: 3,
|
|
42
|
-
* },
|
|
43
|
-
* ],
|
|
44
|
-
* });
|
|
45
|
-
*
|
|
46
|
-
* const runId = await engine.startRun({
|
|
47
|
-
* kind: 'email.send_campaign',
|
|
48
|
-
* input: { campaignId: '123' },
|
|
49
|
-
* });
|
|
50
|
-
*
|
|
51
|
-
* // Subscribe to events
|
|
52
|
-
* engine.subscribeToRun(runId, (event) => {
|
|
53
|
-
* console.log(`${event.eventType}: ${event.stepKey ?? 'run'}`);
|
|
54
|
-
* });
|
|
55
|
-
* ```
|
|
56
|
-
*/
|
|
57
|
-
|
|
58
|
-
// ============================================================================
|
|
59
|
-
// Core Types and Classes
|
|
60
|
-
// ============================================================================
|
|
61
|
-
|
|
62
|
-
export {
|
|
63
|
-
// Types
|
|
64
|
-
type WorkflowKind,
|
|
65
|
-
type RunStatus,
|
|
66
|
-
type StepStatus,
|
|
67
|
-
type StepErrorStrategy,
|
|
68
|
-
type WorkflowError,
|
|
69
|
-
type RunResult,
|
|
70
|
-
type Logger,
|
|
71
|
-
type SpawnChildOptions,
|
|
72
|
-
type WorkflowContext,
|
|
73
|
-
type WorkflowStep,
|
|
74
|
-
type WorkflowHooks,
|
|
75
|
-
type WorkflowDefinition,
|
|
76
|
-
} from './core/types';
|
|
77
|
-
|
|
78
|
-
export {
|
|
79
|
-
// Main Engine
|
|
80
|
-
WorkflowEngine,
|
|
81
|
-
type WorkflowEngineConfig,
|
|
82
|
-
type StartRunOptions,
|
|
83
|
-
} from './core/engine';
|
|
84
|
-
|
|
85
|
-
// ============================================================================
|
|
86
|
-
// Storage
|
|
87
|
-
// ============================================================================
|
|
88
|
-
|
|
89
|
-
export {
|
|
90
|
-
// Types
|
|
91
|
-
type StorageAdapter,
|
|
92
|
-
type WorkflowStorage,
|
|
93
|
-
type WorkflowRunRecord,
|
|
94
|
-
type WorkflowRunStepRecord,
|
|
95
|
-
type WorkflowEventRecord,
|
|
96
|
-
type ListRunsOptions,
|
|
97
|
-
type ListEventsOptions,
|
|
98
|
-
type PaginatedResult,
|
|
99
|
-
type CreateRunInput,
|
|
100
|
-
type UpdateRunInput,
|
|
101
|
-
type StepResult,
|
|
102
|
-
type StepRecord,
|
|
103
|
-
// Extended types for new WorkflowStorage implementations
|
|
104
|
-
type ExtendedWorkflowRunRecord,
|
|
105
|
-
type ExtendedRunStatus,
|
|
106
|
-
type ExtendedStepStatus,
|
|
107
|
-
type ExtendedListRunsOptions,
|
|
108
|
-
type StepflowRunsTable,
|
|
109
|
-
type StepflowStepResultsTable,
|
|
110
|
-
type StepflowDatabase,
|
|
111
|
-
} from './storage/types';
|
|
112
|
-
|
|
113
|
-
export {
|
|
114
|
-
// In-Memory Storage
|
|
115
|
-
MemoryStorageAdapter,
|
|
116
|
-
} from './storage/memory';
|
|
117
|
-
|
|
118
|
-
export {
|
|
119
|
-
// SQLite Storage
|
|
120
|
-
SQLiteStorageAdapter,
|
|
121
|
-
type SQLiteStorageConfig,
|
|
122
|
-
} from './storage/sqlite';
|
|
123
|
-
|
|
124
|
-
export {
|
|
125
|
-
// PostgreSQL Storage
|
|
126
|
-
PostgresStorageAdapter,
|
|
127
|
-
PostgresStorageAdapter as PostgresStorage,
|
|
128
|
-
type PostgresStorageConfig,
|
|
129
|
-
} from './storage/postgres';
|
|
130
|
-
|
|
131
|
-
// ============================================================================
|
|
132
|
-
// Events
|
|
133
|
-
// ============================================================================
|
|
134
|
-
|
|
135
|
-
export {
|
|
136
|
-
// Types
|
|
137
|
-
type EventTransport,
|
|
138
|
-
type WorkflowEvent,
|
|
139
|
-
type WorkflowEventType,
|
|
140
|
-
type BuiltInEventType,
|
|
141
|
-
type EventCallback,
|
|
142
|
-
type Unsubscribe,
|
|
143
|
-
} from './events/types';
|
|
144
|
-
|
|
145
|
-
export {
|
|
146
|
-
// In-Memory Event Transport
|
|
147
|
-
MemoryEventTransport,
|
|
148
|
-
} from './events/memory';
|
|
149
|
-
|
|
150
|
-
export {
|
|
151
|
-
// Socket.IO Event Transport
|
|
152
|
-
SocketIOEventTransport,
|
|
153
|
-
type SocketIOEventTransportConfig,
|
|
154
|
-
type SocketIOServer,
|
|
155
|
-
type SocketIOSocket,
|
|
156
|
-
type SocketIOAuthorizeFn,
|
|
157
|
-
} from './events/socketio';
|
|
158
|
-
|
|
159
|
-
export {
|
|
160
|
-
// Webhook Event Transport
|
|
161
|
-
WebhookEventTransport,
|
|
162
|
-
type WebhookEventTransportConfig,
|
|
163
|
-
type WebhookEndpoint,
|
|
164
|
-
type WebhookPayload,
|
|
165
|
-
} from './events/webhook';
|
|
166
|
-
|
|
167
|
-
// ============================================================================
|
|
168
|
-
// Scheduler
|
|
169
|
-
// ============================================================================
|
|
170
|
-
|
|
171
|
-
export {
|
|
172
|
-
type TriggerType,
|
|
173
|
-
type WorkflowSchedule,
|
|
174
|
-
type Scheduler,
|
|
175
|
-
} from './scheduler/types';
|
|
176
|
-
|
|
177
|
-
export {
|
|
178
|
-
CronScheduler,
|
|
179
|
-
type CronSchedulerConfig,
|
|
180
|
-
type SchedulePersistence,
|
|
181
|
-
} from './scheduler/cron';
|
|
182
|
-
|
|
183
|
-
export {
|
|
184
|
-
SQLiteSchedulePersistence,
|
|
185
|
-
type SQLiteSchedulePersistenceConfig,
|
|
186
|
-
} from './scheduler/sqlite-persistence';
|
|
187
|
-
|
|
188
|
-
export {
|
|
189
|
-
PostgresSchedulePersistence,
|
|
190
|
-
type PostgresSchedulePersistenceConfig,
|
|
191
|
-
} from './scheduler/postgres-persistence';
|
|
192
|
-
|
|
193
|
-
// ============================================================================
|
|
194
|
-
// Utilities
|
|
195
|
-
// ============================================================================
|
|
196
|
-
|
|
197
|
-
export {
|
|
198
|
-
// ID Generation
|
|
199
|
-
generateId,
|
|
200
|
-
} from './utils/id';
|
|
201
|
-
|
|
202
|
-
export {
|
|
203
|
-
// Errors
|
|
204
|
-
WorkflowEngineError,
|
|
205
|
-
WorkflowNotFoundError,
|
|
206
|
-
WorkflowAlreadyRegisteredError,
|
|
207
|
-
RunNotFoundError,
|
|
208
|
-
StepError,
|
|
209
|
-
StepTimeoutError,
|
|
210
|
-
WaitForRunTimeoutError,
|
|
211
|
-
WorkflowCanceledError,
|
|
212
|
-
WorkflowTimeoutError,
|
|
213
|
-
} from './utils/errors';
|
|
214
|
-
|
|
215
|
-
export {
|
|
216
|
-
// Logger
|
|
217
|
-
ConsoleLogger,
|
|
218
|
-
SilentLogger,
|
|
219
|
-
createScopedLogger,
|
|
220
|
-
sanitizeErrorForStorage,
|
|
221
|
-
type LogLevel,
|
|
222
|
-
} from './utils/logger';
|
|
223
|
-
|
|
224
|
-
export {
|
|
225
|
-
// Retry
|
|
226
|
-
type RetryOptions,
|
|
227
|
-
DEFAULT_RETRY_OPTIONS,
|
|
228
|
-
sleep,
|
|
229
|
-
withRetry,
|
|
230
|
-
calculateRetryDelay,
|
|
231
|
-
} from './utils/retry';
|
|
232
|
-
|
|
233
|
-
// ============================================================================
|
|
234
|
-
// Planning
|
|
235
|
-
// ============================================================================
|
|
236
|
-
|
|
237
|
-
export {
|
|
238
|
-
// Types
|
|
239
|
-
type ConditionOperator,
|
|
240
|
-
type RecipeCondition,
|
|
241
|
-
type RecipeDefaults,
|
|
242
|
-
type StepHandlerRef,
|
|
243
|
-
type RecipeStep,
|
|
244
|
-
type Recipe,
|
|
245
|
-
type PlanModificationType,
|
|
246
|
-
type PlanModification,
|
|
247
|
-
type PlannedStep,
|
|
248
|
-
type ChildWorkflowPlan,
|
|
249
|
-
type ResourceEstimate,
|
|
250
|
-
type Plan,
|
|
251
|
-
type PlanningPriority,
|
|
252
|
-
type PlanningConstraints,
|
|
253
|
-
type PlanningHints,
|
|
254
|
-
type PlanningContext,
|
|
255
|
-
type RecipeSelectionResult,
|
|
256
|
-
type PlanValidationResult,
|
|
257
|
-
type Planner,
|
|
258
|
-
type RegisteredStepHandler,
|
|
259
|
-
type StepHandlerRegistry,
|
|
260
|
-
type RecipeQueryOptions,
|
|
261
|
-
type RecipeRegistry,
|
|
262
|
-
|
|
263
|
-
// Implementations
|
|
264
|
-
MemoryStepHandlerRegistry,
|
|
265
|
-
MemoryRecipeRegistry,
|
|
266
|
-
createRegistry,
|
|
267
|
-
type CombinedRegistry,
|
|
268
|
-
RuleBasedPlanner,
|
|
269
|
-
type RuleBasedPlannerConfig,
|
|
270
|
-
} from './planning';
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
evaluateCondition,
|
|
4
|
-
getNestedValue,
|
|
5
|
-
MAX_REGEX_PATTERN_LENGTH,
|
|
6
|
-
} from './evaluate-condition';
|
|
7
|
-
|
|
8
|
-
describe('getNestedValue', () => {
|
|
9
|
-
it('returns top-level values', () => {
|
|
10
|
-
expect(getNestedValue({ a: 1 }, 'a')).toBe(1);
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it('returns nested values via dot notation', () => {
|
|
14
|
-
expect(getNestedValue({ a: { b: { c: 'deep' } } }, 'a.b.c')).toBe('deep');
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('returns undefined for missing paths', () => {
|
|
18
|
-
expect(getNestedValue({ a: 1 }, 'b')).toBeUndefined();
|
|
19
|
-
expect(getNestedValue({ a: { b: 1 } }, 'a.c')).toBeUndefined();
|
|
20
|
-
expect(getNestedValue({ a: { b: 1 } }, 'a.b.c')).toBeUndefined();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('returns undefined when traversing through a non-object', () => {
|
|
24
|
-
expect(getNestedValue({ a: 5 }, 'a.b')).toBeUndefined();
|
|
25
|
-
expect(getNestedValue({ a: null }, 'a.b')).toBeUndefined();
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('preserves falsy values (0, false, empty string)', () => {
|
|
29
|
-
expect(getNestedValue({ a: 0 }, 'a')).toBe(0);
|
|
30
|
-
expect(getNestedValue({ a: false }, 'a')).toBe(false);
|
|
31
|
-
expect(getNestedValue({ a: '' }, 'a')).toBe('');
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
describe('evaluateCondition', () => {
|
|
36
|
-
describe('eq / neq', () => {
|
|
37
|
-
it('uses strict equality', () => {
|
|
38
|
-
expect(evaluateCondition('eq', 1, 1)).toBe(true);
|
|
39
|
-
expect(evaluateCondition('eq', 1, '1')).toBe(false);
|
|
40
|
-
expect(evaluateCondition('eq', 'a', 'a')).toBe(true);
|
|
41
|
-
expect(evaluateCondition('neq', 1, 2)).toBe(true);
|
|
42
|
-
expect(evaluateCondition('neq', 'a', 'a')).toBe(false);
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
describe('numeric comparisons', () => {
|
|
47
|
-
it('gt / gte / lt / lte work for numbers', () => {
|
|
48
|
-
expect(evaluateCondition('gt', 5, 3)).toBe(true);
|
|
49
|
-
expect(evaluateCondition('gt', 3, 5)).toBe(false);
|
|
50
|
-
expect(evaluateCondition('gt', 5, 5)).toBe(false);
|
|
51
|
-
expect(evaluateCondition('gte', 5, 5)).toBe(true);
|
|
52
|
-
expect(evaluateCondition('lt', 3, 5)).toBe(true);
|
|
53
|
-
expect(evaluateCondition('lt', 5, 5)).toBe(false);
|
|
54
|
-
expect(evaluateCondition('lte', 5, 5)).toBe(true);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('returns false when either side is not a number', () => {
|
|
58
|
-
expect(evaluateCondition('gt', '5', 3)).toBe(false);
|
|
59
|
-
expect(evaluateCondition('gt', 5, '3')).toBe(false);
|
|
60
|
-
expect(evaluateCondition('gte', null, 0)).toBe(false);
|
|
61
|
-
expect(evaluateCondition('lt', undefined, 1)).toBe(false);
|
|
62
|
-
expect(evaluateCondition('lte', true, 0)).toBe(false);
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
describe('contains', () => {
|
|
67
|
-
it('matches substrings in strings', () => {
|
|
68
|
-
expect(evaluateCondition('contains', 'hello world', 'world')).toBe(true);
|
|
69
|
-
expect(evaluateCondition('contains', 'hello world', 'xyz')).toBe(false);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('matches elements in arrays', () => {
|
|
73
|
-
expect(evaluateCondition('contains', [1, 2, 3], 2)).toBe(true);
|
|
74
|
-
expect(evaluateCondition('contains', ['a', 'b'], 'c')).toBe(false);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('returns false for unsupported types', () => {
|
|
78
|
-
expect(evaluateCondition('contains', 42, 4)).toBe(false);
|
|
79
|
-
expect(evaluateCondition('contains', { a: 1 }, 'a')).toBe(false);
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
describe('matches', () => {
|
|
84
|
-
it('returns true for matching regex', () => {
|
|
85
|
-
expect(evaluateCondition('matches', 'hello world', '^hello')).toBe(true);
|
|
86
|
-
expect(evaluateCondition('matches', 'foo123', '\\d+')).toBe(true);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('returns false for non-matching regex', () => {
|
|
90
|
-
expect(evaluateCondition('matches', 'hello', '^world')).toBe(false);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it('returns false on invalid regex (does not throw)', () => {
|
|
94
|
-
expect(evaluateCondition('matches', 'hello', '[invalid')).toBe(false);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it('returns false when either side is not a string', () => {
|
|
98
|
-
expect(evaluateCondition('matches', 123, '\\d+')).toBe(false);
|
|
99
|
-
expect(evaluateCondition('matches', 'abc', /abc/)).toBe(false);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it('rejects unsafe RE2-incompatible patterns rather than throwing', () => {
|
|
103
|
-
// Backreferences are unsupported in RE2 — verify we degrade gracefully.
|
|
104
|
-
expect(evaluateCondition('matches', 'aa', '(a)\\1')).toBe(false);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it('rejects patterns longer than MAX_REGEX_PATTERN_LENGTH without compiling', () => {
|
|
108
|
-
// A pattern equal to the limit should still compile and match.
|
|
109
|
-
const atLimit = 'a'.repeat(MAX_REGEX_PATTERN_LENGTH);
|
|
110
|
-
expect(evaluateCondition('matches', atLimit, atLimit)).toBe(true);
|
|
111
|
-
|
|
112
|
-
// One character over the limit should be rejected (returns false, no throw).
|
|
113
|
-
const overLimit = 'a'.repeat(MAX_REGEX_PATTERN_LENGTH + 1);
|
|
114
|
-
expect(evaluateCondition('matches', overLimit, overLimit)).toBe(false);
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
describe('exists / notExists', () => {
|
|
119
|
-
it('exists is true for any non-empty value', () => {
|
|
120
|
-
expect(evaluateCondition('exists', 'value', undefined)).toBe(true);
|
|
121
|
-
expect(evaluateCondition('exists', 0, undefined)).toBe(true);
|
|
122
|
-
expect(evaluateCondition('exists', false, undefined)).toBe(true);
|
|
123
|
-
expect(evaluateCondition('exists', [], undefined)).toBe(true);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it('exists is false for undefined, null, or empty string', () => {
|
|
127
|
-
expect(evaluateCondition('exists', undefined, undefined)).toBe(false);
|
|
128
|
-
expect(evaluateCondition('exists', null, undefined)).toBe(false);
|
|
129
|
-
expect(evaluateCondition('exists', '', undefined)).toBe(false);
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it('notExists mirrors exists', () => {
|
|
133
|
-
expect(evaluateCondition('notExists', undefined, undefined)).toBe(true);
|
|
134
|
-
expect(evaluateCondition('notExists', null, undefined)).toBe(true);
|
|
135
|
-
expect(evaluateCondition('notExists', '', undefined)).toBe(true);
|
|
136
|
-
expect(evaluateCondition('notExists', 'value', undefined)).toBe(false);
|
|
137
|
-
expect(evaluateCondition('notExists', 0, undefined)).toBe(false);
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
describe('unknown operator', () => {
|
|
142
|
-
it('returns false for an unrecognized operator', () => {
|
|
143
|
-
// Cast through unknown to test the default branch.
|
|
144
|
-
expect(
|
|
145
|
-
evaluateCondition(
|
|
146
|
-
'bogus' as unknown as Parameters<typeof evaluateCondition>[0],
|
|
147
|
-
1,
|
|
148
|
-
1
|
|
149
|
-
)
|
|
150
|
-
).toBe(false);
|
|
151
|
-
});
|
|
152
|
-
});
|
|
153
|
-
});
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared condition evaluation helpers used by the planner and registry.
|
|
3
|
-
*
|
|
4
|
-
* Extracted to a single module so the planner's recipe scoring and the
|
|
5
|
-
* registry's `query({ matchConditions })` filter cannot drift apart.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import RE2 from 're2';
|
|
9
|
-
import type { ConditionOperator } from './types';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Maximum length (in characters) accepted for a `matches` regex pattern.
|
|
13
|
-
*
|
|
14
|
-
* Recipe conditions can come from user-supplied configuration; even with RE2's
|
|
15
|
-
* linear-time guarantee, very large patterns waste CPU/memory at compile time
|
|
16
|
-
* and inflate the per-evaluation cost. Cap the source length so a pathological
|
|
17
|
-
* recipe cannot hand the planner a megabyte of regex.
|
|
18
|
-
*/
|
|
19
|
-
export const MAX_REGEX_PATTERN_LENGTH = 512;
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Get a nested value from an object using dot notation.
|
|
23
|
-
*/
|
|
24
|
-
export function getNestedValue(
|
|
25
|
-
obj: Record<string, unknown>,
|
|
26
|
-
path: string
|
|
27
|
-
): unknown {
|
|
28
|
-
return path.split('.').reduce<unknown>((current, key) => {
|
|
29
|
-
if (current && typeof current === 'object' && key in current) {
|
|
30
|
-
return (current as Record<string, unknown>)[key];
|
|
31
|
-
}
|
|
32
|
-
return undefined;
|
|
33
|
-
}, obj);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Evaluate a single condition against a value.
|
|
38
|
-
*
|
|
39
|
-
* The `matches` operator uses RE2 (not the JS RegExp engine) to avoid
|
|
40
|
-
* catastrophic-backtracking DoS from untrusted recipe inputs.
|
|
41
|
-
*/
|
|
42
|
-
export function evaluateCondition(
|
|
43
|
-
operator: ConditionOperator,
|
|
44
|
-
fieldValue: unknown,
|
|
45
|
-
conditionValue: unknown
|
|
46
|
-
): boolean {
|
|
47
|
-
switch (operator) {
|
|
48
|
-
case 'eq':
|
|
49
|
-
return fieldValue === conditionValue;
|
|
50
|
-
|
|
51
|
-
case 'neq':
|
|
52
|
-
return fieldValue !== conditionValue;
|
|
53
|
-
|
|
54
|
-
case 'gt':
|
|
55
|
-
return typeof fieldValue === 'number' && typeof conditionValue === 'number'
|
|
56
|
-
? fieldValue > conditionValue
|
|
57
|
-
: false;
|
|
58
|
-
|
|
59
|
-
case 'gte':
|
|
60
|
-
return typeof fieldValue === 'number' && typeof conditionValue === 'number'
|
|
61
|
-
? fieldValue >= conditionValue
|
|
62
|
-
: false;
|
|
63
|
-
|
|
64
|
-
case 'lt':
|
|
65
|
-
return typeof fieldValue === 'number' && typeof conditionValue === 'number'
|
|
66
|
-
? fieldValue < conditionValue
|
|
67
|
-
: false;
|
|
68
|
-
|
|
69
|
-
case 'lte':
|
|
70
|
-
return typeof fieldValue === 'number' && typeof conditionValue === 'number'
|
|
71
|
-
? fieldValue <= conditionValue
|
|
72
|
-
: false;
|
|
73
|
-
|
|
74
|
-
case 'contains':
|
|
75
|
-
if (typeof fieldValue === 'string' && typeof conditionValue === 'string') {
|
|
76
|
-
return fieldValue.includes(conditionValue);
|
|
77
|
-
}
|
|
78
|
-
if (Array.isArray(fieldValue)) {
|
|
79
|
-
return fieldValue.includes(conditionValue);
|
|
80
|
-
}
|
|
81
|
-
return false;
|
|
82
|
-
|
|
83
|
-
case 'matches':
|
|
84
|
-
if (typeof fieldValue === 'string' && typeof conditionValue === 'string') {
|
|
85
|
-
if (conditionValue.length > MAX_REGEX_PATTERN_LENGTH) {
|
|
86
|
-
return false;
|
|
87
|
-
}
|
|
88
|
-
try {
|
|
89
|
-
return new RE2(conditionValue).test(fieldValue);
|
|
90
|
-
} catch {
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
return false;
|
|
95
|
-
|
|
96
|
-
case 'exists':
|
|
97
|
-
return fieldValue !== undefined && fieldValue !== null && fieldValue !== '';
|
|
98
|
-
|
|
99
|
-
case 'notExists':
|
|
100
|
-
return fieldValue === undefined || fieldValue === null || fieldValue === '';
|
|
101
|
-
|
|
102
|
-
default:
|
|
103
|
-
return false;
|
|
104
|
-
}
|
|
105
|
-
}
|
package/src/planning/index.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Planning module for the workflow engine.
|
|
3
|
-
* Provides recipes, plans, and planners for dynamic workflow orchestration.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// Types
|
|
7
|
-
export type {
|
|
8
|
-
// Condition types
|
|
9
|
-
ConditionOperator,
|
|
10
|
-
RecipeCondition,
|
|
11
|
-
|
|
12
|
-
// Recipe types
|
|
13
|
-
RecipeDefaults,
|
|
14
|
-
StepHandlerRef,
|
|
15
|
-
RecipeStep,
|
|
16
|
-
Recipe,
|
|
17
|
-
|
|
18
|
-
// Plan types
|
|
19
|
-
PlanModificationType,
|
|
20
|
-
PlanModification,
|
|
21
|
-
PlannedStep,
|
|
22
|
-
ChildWorkflowPlan,
|
|
23
|
-
ResourceEstimate,
|
|
24
|
-
Plan,
|
|
25
|
-
|
|
26
|
-
// Planning context types
|
|
27
|
-
PlanningPriority,
|
|
28
|
-
PlanningConstraints,
|
|
29
|
-
PlanningHints,
|
|
30
|
-
PlanningContext,
|
|
31
|
-
|
|
32
|
-
// Planner types
|
|
33
|
-
RecipeSelectionResult,
|
|
34
|
-
PlanValidationResult,
|
|
35
|
-
Planner,
|
|
36
|
-
|
|
37
|
-
// Registry types
|
|
38
|
-
RegisteredStepHandler,
|
|
39
|
-
StepHandlerRegistry,
|
|
40
|
-
RecipeQueryOptions,
|
|
41
|
-
RecipeRegistry,
|
|
42
|
-
} from './types';
|
|
43
|
-
|
|
44
|
-
// Implementations
|
|
45
|
-
export {
|
|
46
|
-
MemoryStepHandlerRegistry,
|
|
47
|
-
MemoryRecipeRegistry,
|
|
48
|
-
createRegistry,
|
|
49
|
-
type CombinedRegistry,
|
|
50
|
-
} from './registry';
|
|
51
|
-
|
|
52
|
-
export {
|
|
53
|
-
RuleBasedPlanner,
|
|
54
|
-
type RuleBasedPlannerConfig,
|
|
55
|
-
} from './planner';
|