@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
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import Database from 'better-sqlite3';
|
|
3
|
-
import { SQLiteSchedulePersistence } from './sqlite-persistence';
|
|
4
|
-
import type { WorkflowSchedule } from './types';
|
|
5
|
-
|
|
6
|
-
describe('SQLiteSchedulePersistence', () => {
|
|
7
|
-
let db: Database.Database;
|
|
8
|
-
let persistence: SQLiteSchedulePersistence;
|
|
9
|
-
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
// Use in-memory database for testing
|
|
12
|
-
db = new Database(':memory:');
|
|
13
|
-
persistence = new SQLiteSchedulePersistence({ db });
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
afterEach(() => {
|
|
17
|
-
db.close();
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
describe('saveSchedule', () => {
|
|
21
|
-
it('should save a cron schedule', async () => {
|
|
22
|
-
const schedule: WorkflowSchedule = {
|
|
23
|
-
id: 'test-1',
|
|
24
|
-
workflowKind: 'test.workflow',
|
|
25
|
-
triggerType: 'cron',
|
|
26
|
-
cronExpression: '0 * * * *',
|
|
27
|
-
timezone: 'UTC',
|
|
28
|
-
enabled: true,
|
|
29
|
-
input: { foo: 'bar' },
|
|
30
|
-
metadata: { user: 'test' },
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
await persistence.saveSchedule(schedule);
|
|
34
|
-
|
|
35
|
-
const loaded = await persistence.loadSchedules();
|
|
36
|
-
expect(loaded).toHaveLength(1);
|
|
37
|
-
expect(loaded[0].id).toBe('test-1');
|
|
38
|
-
expect(loaded[0].cronExpression).toBe('0 * * * *');
|
|
39
|
-
expect(loaded[0].input).toEqual({ foo: 'bar' });
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('should save a workflow completion trigger', async () => {
|
|
43
|
-
const schedule: WorkflowSchedule = {
|
|
44
|
-
id: 'test-2',
|
|
45
|
-
workflowKind: 'notification.send',
|
|
46
|
-
triggerType: 'workflow_completed',
|
|
47
|
-
triggerOnWorkflowKind: 'order.process',
|
|
48
|
-
triggerOnStatus: ['succeeded', 'failed'],
|
|
49
|
-
enabled: true,
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
await persistence.saveSchedule(schedule);
|
|
53
|
-
|
|
54
|
-
const loaded = await persistence.loadSchedules();
|
|
55
|
-
expect(loaded).toHaveLength(1);
|
|
56
|
-
expect(loaded[0].triggerType).toBe('workflow_completed');
|
|
57
|
-
expect(loaded[0].triggerOnWorkflowKind).toBe('order.process');
|
|
58
|
-
expect(loaded[0].triggerOnStatus).toEqual(['succeeded', 'failed']);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('should save schedule with dates', async () => {
|
|
62
|
-
const lastRunAt = new Date('2024-01-01T12:00:00Z');
|
|
63
|
-
const nextRunAt = new Date('2024-01-01T13:00:00Z');
|
|
64
|
-
|
|
65
|
-
const schedule: WorkflowSchedule = {
|
|
66
|
-
id: 'test-3',
|
|
67
|
-
workflowKind: 'test.workflow',
|
|
68
|
-
triggerType: 'cron',
|
|
69
|
-
cronExpression: '0 * * * *',
|
|
70
|
-
enabled: true,
|
|
71
|
-
lastRunAt,
|
|
72
|
-
lastRunId: 'run-123',
|
|
73
|
-
nextRunAt,
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
await persistence.saveSchedule(schedule);
|
|
77
|
-
|
|
78
|
-
const loaded = await persistence.loadSchedules();
|
|
79
|
-
expect(loaded[0].lastRunAt).toEqual(lastRunAt);
|
|
80
|
-
expect(loaded[0].lastRunId).toBe('run-123');
|
|
81
|
-
expect(loaded[0].nextRunAt).toEqual(nextRunAt);
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
describe('loadSchedules', () => {
|
|
86
|
-
it('should return empty array when no schedules', async () => {
|
|
87
|
-
const schedules = await persistence.loadSchedules();
|
|
88
|
-
expect(schedules).toHaveLength(0);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('should load multiple schedules', async () => {
|
|
92
|
-
await persistence.saveSchedule({
|
|
93
|
-
id: 'schedule-1',
|
|
94
|
-
workflowKind: 'workflow.a',
|
|
95
|
-
triggerType: 'cron',
|
|
96
|
-
cronExpression: '0 * * * *',
|
|
97
|
-
enabled: true,
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
await persistence.saveSchedule({
|
|
101
|
-
id: 'schedule-2',
|
|
102
|
-
workflowKind: 'workflow.b',
|
|
103
|
-
triggerType: 'manual',
|
|
104
|
-
enabled: false,
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
const schedules = await persistence.loadSchedules();
|
|
108
|
-
expect(schedules).toHaveLength(2);
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
describe('updateSchedule', () => {
|
|
113
|
-
it('should update enabled status', async () => {
|
|
114
|
-
await persistence.saveSchedule({
|
|
115
|
-
id: 'update-1',
|
|
116
|
-
workflowKind: 'test.workflow',
|
|
117
|
-
triggerType: 'cron',
|
|
118
|
-
cronExpression: '0 * * * *',
|
|
119
|
-
enabled: true,
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
await persistence.updateSchedule('update-1', { enabled: false });
|
|
123
|
-
|
|
124
|
-
const loaded = await persistence.loadSchedules();
|
|
125
|
-
expect(loaded[0].enabled).toBe(false);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it('should update cron expression', async () => {
|
|
129
|
-
await persistence.saveSchedule({
|
|
130
|
-
id: 'update-2',
|
|
131
|
-
workflowKind: 'test.workflow',
|
|
132
|
-
triggerType: 'cron',
|
|
133
|
-
cronExpression: '0 * * * *',
|
|
134
|
-
enabled: true,
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
await persistence.updateSchedule('update-2', {
|
|
138
|
-
cronExpression: '30 * * * *',
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
const loaded = await persistence.loadSchedules();
|
|
142
|
-
expect(loaded[0].cronExpression).toBe('30 * * * *');
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
it('should update lastRunAt and lastRunId', async () => {
|
|
146
|
-
await persistence.saveSchedule({
|
|
147
|
-
id: 'update-3',
|
|
148
|
-
workflowKind: 'test.workflow',
|
|
149
|
-
triggerType: 'cron',
|
|
150
|
-
cronExpression: '0 * * * *',
|
|
151
|
-
enabled: true,
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
const now = new Date();
|
|
155
|
-
await persistence.updateSchedule('update-3', {
|
|
156
|
-
lastRunAt: now,
|
|
157
|
-
lastRunId: 'run-456',
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
const loaded = await persistence.loadSchedules();
|
|
161
|
-
expect(loaded[0].lastRunAt).toEqual(now);
|
|
162
|
-
expect(loaded[0].lastRunId).toBe('run-456');
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it('should throw for non-existent schedule', async () => {
|
|
166
|
-
await expect(
|
|
167
|
-
persistence.updateSchedule('nonexistent', { enabled: false })
|
|
168
|
-
).rejects.toThrow('Schedule not found');
|
|
169
|
-
});
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
describe('deleteSchedule', () => {
|
|
173
|
-
it('should delete a schedule', async () => {
|
|
174
|
-
await persistence.saveSchedule({
|
|
175
|
-
id: 'delete-1',
|
|
176
|
-
workflowKind: 'test.workflow',
|
|
177
|
-
triggerType: 'cron',
|
|
178
|
-
cronExpression: '0 * * * *',
|
|
179
|
-
enabled: true,
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
await persistence.deleteSchedule('delete-1');
|
|
183
|
-
|
|
184
|
-
const loaded = await persistence.loadSchedules();
|
|
185
|
-
expect(loaded).toHaveLength(0);
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
it('should handle deleting non-existent schedule gracefully', async () => {
|
|
189
|
-
// This should not throw
|
|
190
|
-
await persistence.deleteSchedule('nonexistent');
|
|
191
|
-
});
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
describe('data integrity', () => {
|
|
195
|
-
it('should preserve all fields through save/load cycle', async () => {
|
|
196
|
-
const schedule: WorkflowSchedule = {
|
|
197
|
-
id: 'integrity-1',
|
|
198
|
-
workflowKind: 'complex.workflow',
|
|
199
|
-
triggerType: 'workflow_completed',
|
|
200
|
-
triggerOnWorkflowKind: 'parent.workflow',
|
|
201
|
-
triggerOnStatus: ['succeeded'],
|
|
202
|
-
input: { nested: { value: 123 } },
|
|
203
|
-
metadata: { tags: ['a', 'b'] },
|
|
204
|
-
enabled: true,
|
|
205
|
-
lastRunAt: new Date('2024-06-15T10:30:00Z'),
|
|
206
|
-
lastRunId: 'run-789',
|
|
207
|
-
nextRunAt: new Date('2024-06-15T11:00:00Z'),
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
await persistence.saveSchedule(schedule);
|
|
211
|
-
|
|
212
|
-
const loaded = await persistence.loadSchedules();
|
|
213
|
-
expect(loaded[0]).toEqual(schedule);
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
it('should handle null/undefined values correctly', async () => {
|
|
217
|
-
const schedule: WorkflowSchedule = {
|
|
218
|
-
id: 'nullable-1',
|
|
219
|
-
workflowKind: 'test.workflow',
|
|
220
|
-
triggerType: 'manual',
|
|
221
|
-
enabled: false,
|
|
222
|
-
// All other optional fields are undefined
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
await persistence.saveSchedule(schedule);
|
|
226
|
-
|
|
227
|
-
const loaded = await persistence.loadSchedules();
|
|
228
|
-
expect(loaded[0].cronExpression).toBeUndefined();
|
|
229
|
-
expect(loaded[0].timezone).toBeUndefined();
|
|
230
|
-
expect(loaded[0].input).toBeUndefined();
|
|
231
|
-
expect(loaded[0].metadata).toBeUndefined();
|
|
232
|
-
expect(loaded[0].lastRunAt).toBeUndefined();
|
|
233
|
-
});
|
|
234
|
-
});
|
|
235
|
-
});
|
|
@@ -1,286 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SQLite persistence adapter for schedules.
|
|
3
|
-
*
|
|
4
|
-
* Stores workflow schedules in a SQLite database table.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { Database, Statement } from 'better-sqlite3';
|
|
8
|
-
import type { WorkflowSchedule } from './types';
|
|
9
|
-
import type { SchedulePersistence } from './cron';
|
|
10
|
-
import type { Logger, RunStatus } from '../core/types';
|
|
11
|
-
import { generateId } from '../utils/id';
|
|
12
|
-
import { safeJsonParse as sharedSafeJsonParse } from '../utils/safe-json';
|
|
13
|
-
|
|
14
|
-
// ============================================================================
|
|
15
|
-
// Configuration Types
|
|
16
|
-
// ============================================================================
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Configuration for SQLiteSchedulePersistence.
|
|
20
|
-
*/
|
|
21
|
-
export interface SQLiteSchedulePersistenceConfig {
|
|
22
|
-
/** SQLite database instance */
|
|
23
|
-
db: Database;
|
|
24
|
-
|
|
25
|
-
/** Table name for schedules (default: workflow_schedules) */
|
|
26
|
-
tableName?: string;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Optional structured logger used to surface JSON corruption events.
|
|
30
|
-
* Without one, parse failures still increment the corruption counter
|
|
31
|
-
* exposed via `getJsonParseCorruptionCount()` but produce no log output.
|
|
32
|
-
*/
|
|
33
|
-
logger?: Logger;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// ============================================================================
|
|
37
|
-
// SQLiteSchedulePersistence Class
|
|
38
|
-
// ============================================================================
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* SQLite-based persistence for workflow schedules.
|
|
42
|
-
*
|
|
43
|
-
* @example
|
|
44
|
-
* ```typescript
|
|
45
|
-
* import Database from 'better-sqlite3';
|
|
46
|
-
*
|
|
47
|
-
* const db = new Database('workflow.db');
|
|
48
|
-
* const persistence = new SQLiteSchedulePersistence({ db });
|
|
49
|
-
*
|
|
50
|
-
* const scheduler = new CronScheduler({
|
|
51
|
-
* engine,
|
|
52
|
-
* persistence,
|
|
53
|
-
* });
|
|
54
|
-
* ```
|
|
55
|
-
*/
|
|
56
|
-
export class SQLiteSchedulePersistence implements SchedulePersistence {
|
|
57
|
-
private db: Database;
|
|
58
|
-
private tableName: string;
|
|
59
|
-
private logger: Logger | undefined;
|
|
60
|
-
private stmts: {
|
|
61
|
-
insert: Statement;
|
|
62
|
-
update: Statement;
|
|
63
|
-
delete: Statement;
|
|
64
|
-
getAll: Statement;
|
|
65
|
-
getById: Statement;
|
|
66
|
-
} | null = null;
|
|
67
|
-
|
|
68
|
-
constructor(config: SQLiteSchedulePersistenceConfig) {
|
|
69
|
-
this.db = config.db;
|
|
70
|
-
this.tableName = config.tableName ?? 'workflow_schedules';
|
|
71
|
-
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(this.tableName)) {
|
|
72
|
-
throw new Error(`Invalid table name: ${this.tableName}`);
|
|
73
|
-
}
|
|
74
|
-
this.logger = config.logger;
|
|
75
|
-
this.initializeDatabase();
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// ============================================================================
|
|
79
|
-
// Database Initialization
|
|
80
|
-
// ============================================================================
|
|
81
|
-
|
|
82
|
-
private initializeDatabase(): void {
|
|
83
|
-
// Create schedules table.
|
|
84
|
-
// Schema layout:
|
|
85
|
-
// id — unique schedule identifier (TEXT PK)
|
|
86
|
-
// workflow_kind — target workflow type to spawn
|
|
87
|
-
// trigger_type — 'cron', 'workflow_completed', or 'manual'
|
|
88
|
-
// cron_expression — cron expression for time-based triggers (nullable)
|
|
89
|
-
// timezone — IANA timezone for cron evaluation (nullable)
|
|
90
|
-
// trigger_on_* — fields for workflow-completion triggers
|
|
91
|
-
// input / metadata — JSON-serialized payloads for spawned runs
|
|
92
|
-
// enabled — whether this schedule is active (0/1)
|
|
93
|
-
// last_run_at/id — most recent execution tracking
|
|
94
|
-
// next_run_at — next scheduled execution time
|
|
95
|
-
// created_at/updated_at — audit timestamps
|
|
96
|
-
// No migration strategy is currently implemented; table is created idempotently.
|
|
97
|
-
this.db.exec(`
|
|
98
|
-
CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|
99
|
-
id TEXT PRIMARY KEY,
|
|
100
|
-
workflow_kind TEXT NOT NULL,
|
|
101
|
-
trigger_type TEXT NOT NULL,
|
|
102
|
-
cron_expression TEXT,
|
|
103
|
-
timezone TEXT,
|
|
104
|
-
trigger_on_workflow_kind TEXT,
|
|
105
|
-
trigger_on_status TEXT,
|
|
106
|
-
input TEXT,
|
|
107
|
-
metadata TEXT,
|
|
108
|
-
enabled INTEGER NOT NULL DEFAULT 1,
|
|
109
|
-
last_run_at TEXT,
|
|
110
|
-
last_run_id TEXT,
|
|
111
|
-
next_run_at TEXT,
|
|
112
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
113
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
114
|
-
)
|
|
115
|
-
`);
|
|
116
|
-
|
|
117
|
-
// Create indexes
|
|
118
|
-
this.db.exec(`
|
|
119
|
-
CREATE INDEX IF NOT EXISTS idx_${this.tableName}_workflow_kind
|
|
120
|
-
ON ${this.tableName}(workflow_kind);
|
|
121
|
-
|
|
122
|
-
CREATE INDEX IF NOT EXISTS idx_${this.tableName}_enabled
|
|
123
|
-
ON ${this.tableName}(enabled);
|
|
124
|
-
|
|
125
|
-
CREATE INDEX IF NOT EXISTS idx_${this.tableName}_trigger_type
|
|
126
|
-
ON ${this.tableName}(trigger_type);
|
|
127
|
-
`);
|
|
128
|
-
|
|
129
|
-
// Prepare statements
|
|
130
|
-
this.stmts = {
|
|
131
|
-
insert: this.db.prepare(`
|
|
132
|
-
INSERT INTO ${this.tableName} (
|
|
133
|
-
id, workflow_kind, trigger_type, cron_expression, timezone,
|
|
134
|
-
trigger_on_workflow_kind, trigger_on_status, input, metadata,
|
|
135
|
-
enabled, last_run_at, last_run_id, next_run_at
|
|
136
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
137
|
-
`),
|
|
138
|
-
update: this.db.prepare(`
|
|
139
|
-
UPDATE ${this.tableName}
|
|
140
|
-
SET workflow_kind = COALESCE(?, workflow_kind),
|
|
141
|
-
trigger_type = COALESCE(?, trigger_type),
|
|
142
|
-
cron_expression = ?,
|
|
143
|
-
timezone = ?,
|
|
144
|
-
trigger_on_workflow_kind = ?,
|
|
145
|
-
trigger_on_status = ?,
|
|
146
|
-
input = ?,
|
|
147
|
-
metadata = ?,
|
|
148
|
-
enabled = COALESCE(?, enabled),
|
|
149
|
-
last_run_at = ?,
|
|
150
|
-
last_run_id = ?,
|
|
151
|
-
next_run_at = ?,
|
|
152
|
-
updated_at = datetime('now')
|
|
153
|
-
WHERE id = ?
|
|
154
|
-
`),
|
|
155
|
-
delete: this.db.prepare(`
|
|
156
|
-
DELETE FROM ${this.tableName} WHERE id = ?
|
|
157
|
-
`),
|
|
158
|
-
getAll: this.db.prepare(`
|
|
159
|
-
SELECT * FROM ${this.tableName}
|
|
160
|
-
`),
|
|
161
|
-
getById: this.db.prepare(`
|
|
162
|
-
SELECT * FROM ${this.tableName} WHERE id = ?
|
|
163
|
-
`),
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// ============================================================================
|
|
168
|
-
// SchedulePersistence Interface Implementation
|
|
169
|
-
// ============================================================================
|
|
170
|
-
|
|
171
|
-
async loadSchedules(): Promise<WorkflowSchedule[]> {
|
|
172
|
-
const rows = this.stmts!.getAll.all() as ScheduleRow[];
|
|
173
|
-
return rows.map(row => this.rowToSchedule(row));
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
async saveSchedule(schedule: WorkflowSchedule): Promise<void> {
|
|
177
|
-
this.stmts!.insert.run(
|
|
178
|
-
schedule.id,
|
|
179
|
-
schedule.workflowKind,
|
|
180
|
-
schedule.triggerType,
|
|
181
|
-
schedule.cronExpression ?? null,
|
|
182
|
-
schedule.timezone ?? null,
|
|
183
|
-
schedule.triggerOnWorkflowKind ?? null,
|
|
184
|
-
schedule.triggerOnStatus ? JSON.stringify(schedule.triggerOnStatus) : null,
|
|
185
|
-
schedule.input ? JSON.stringify(schedule.input) : null,
|
|
186
|
-
schedule.metadata ? JSON.stringify(schedule.metadata) : null,
|
|
187
|
-
schedule.enabled ? 1 : 0,
|
|
188
|
-
schedule.lastRunAt?.toISOString() ?? null,
|
|
189
|
-
schedule.lastRunId ?? null,
|
|
190
|
-
schedule.nextRunAt?.toISOString() ?? null
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
async updateSchedule(scheduleId: string, updates: Partial<WorkflowSchedule>): Promise<void> {
|
|
195
|
-
// Get existing schedule to merge with updates
|
|
196
|
-
const existing = this.stmts!.getById.get(scheduleId) as ScheduleRow | undefined;
|
|
197
|
-
if (!existing) {
|
|
198
|
-
throw new Error(`Schedule not found: ${scheduleId}`);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const merged = {
|
|
202
|
-
...this.rowToSchedule(existing),
|
|
203
|
-
...updates,
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
this.stmts!.update.run(
|
|
207
|
-
updates.workflowKind ?? null,
|
|
208
|
-
updates.triggerType ?? null,
|
|
209
|
-
merged.cronExpression ?? null,
|
|
210
|
-
merged.timezone ?? null,
|
|
211
|
-
merged.triggerOnWorkflowKind ?? null,
|
|
212
|
-
merged.triggerOnStatus ? JSON.stringify(merged.triggerOnStatus) : null,
|
|
213
|
-
merged.input ? JSON.stringify(merged.input) : null,
|
|
214
|
-
merged.metadata ? JSON.stringify(merged.metadata) : null,
|
|
215
|
-
updates.enabled !== undefined ? (updates.enabled ? 1 : 0) : null,
|
|
216
|
-
merged.lastRunAt?.toISOString() ?? null,
|
|
217
|
-
merged.lastRunId ?? null,
|
|
218
|
-
merged.nextRunAt?.toISOString() ?? null,
|
|
219
|
-
scheduleId
|
|
220
|
-
);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
async deleteSchedule(scheduleId: string): Promise<void> {
|
|
224
|
-
this.stmts!.delete.run(scheduleId);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// ============================================================================
|
|
228
|
-
// Helper Methods
|
|
229
|
-
// ============================================================================
|
|
230
|
-
|
|
231
|
-
private safeJsonParse(
|
|
232
|
-
json: string,
|
|
233
|
-
fallback: unknown,
|
|
234
|
-
rowId: string,
|
|
235
|
-
column: string,
|
|
236
|
-
): unknown {
|
|
237
|
-
return sharedSafeJsonParse(json, fallback, {
|
|
238
|
-
component: 'SQLiteSchedulePersistence',
|
|
239
|
-
rowId,
|
|
240
|
-
column,
|
|
241
|
-
logger: this.logger,
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
private rowToSchedule(row: ScheduleRow): WorkflowSchedule {
|
|
246
|
-
return {
|
|
247
|
-
id: row.id,
|
|
248
|
-
workflowKind: row.workflow_kind,
|
|
249
|
-
triggerType: row.trigger_type as WorkflowSchedule['triggerType'],
|
|
250
|
-
cronExpression: row.cron_expression ?? undefined,
|
|
251
|
-
timezone: row.timezone ?? undefined,
|
|
252
|
-
triggerOnWorkflowKind: row.trigger_on_workflow_kind ?? undefined,
|
|
253
|
-
triggerOnStatus: row.trigger_on_status
|
|
254
|
-
? this.safeJsonParse(row.trigger_on_status, undefined, row.id, 'trigger_on_status') as RunStatus[] | undefined
|
|
255
|
-
: undefined,
|
|
256
|
-
input: row.input ? this.safeJsonParse(row.input, undefined, row.id, 'input') as Record<string, unknown> | undefined : undefined,
|
|
257
|
-
metadata: row.metadata ? this.safeJsonParse(row.metadata, undefined, row.id, 'metadata') as Record<string, unknown> | undefined : undefined,
|
|
258
|
-
enabled: row.enabled === 1,
|
|
259
|
-
lastRunAt: row.last_run_at ? new Date(row.last_run_at) : undefined,
|
|
260
|
-
lastRunId: row.last_run_id ?? undefined,
|
|
261
|
-
nextRunAt: row.next_run_at ? new Date(row.next_run_at) : undefined,
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// ============================================================================
|
|
267
|
-
// Types
|
|
268
|
-
// ============================================================================
|
|
269
|
-
|
|
270
|
-
interface ScheduleRow {
|
|
271
|
-
id: string;
|
|
272
|
-
workflow_kind: string;
|
|
273
|
-
trigger_type: string;
|
|
274
|
-
cron_expression: string | null;
|
|
275
|
-
timezone: string | null;
|
|
276
|
-
trigger_on_workflow_kind: string | null;
|
|
277
|
-
trigger_on_status: string | null;
|
|
278
|
-
input: string | null;
|
|
279
|
-
metadata: string | null;
|
|
280
|
-
enabled: number;
|
|
281
|
-
last_run_at: string | null;
|
|
282
|
-
last_run_id: string | null;
|
|
283
|
-
next_run_at: string | null;
|
|
284
|
-
created_at: string;
|
|
285
|
-
updated_at: string;
|
|
286
|
-
}
|
package/src/scheduler/types.ts
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Scheduler types for the workflow engine.
|
|
3
|
-
* Defines the type contracts for the cron and workflow-completion trigger
|
|
4
|
-
* scheduler, implemented by {@link CronScheduler}, {@link SQLiteSchedulePersistence},
|
|
5
|
-
* and {@link PostgresSchedulePersistence}.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { WorkflowKind, RunStatus } from '../core/types';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Schedule trigger types.
|
|
12
|
-
*/
|
|
13
|
-
export type TriggerType = 'cron' | 'workflow_completed' | 'manual';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Schedule definition.
|
|
17
|
-
*/
|
|
18
|
-
export interface WorkflowSchedule {
|
|
19
|
-
id: string;
|
|
20
|
-
workflowKind: WorkflowKind;
|
|
21
|
-
triggerType: TriggerType;
|
|
22
|
-
|
|
23
|
-
/** Cron expression for time-based triggers (e.g. `0 0 * * *` for daily). Only used when triggerType is 'cron'. */
|
|
24
|
-
cronExpression?: string;
|
|
25
|
-
/** IANA timezone for cron evaluation (e.g. 'America/New_York'). Defaults to 'UTC'. */
|
|
26
|
-
timezone?: string;
|
|
27
|
-
|
|
28
|
-
/** Workflow kind that triggers this schedule on completion. Only used when triggerType is 'workflow_completed'. */
|
|
29
|
-
triggerOnWorkflowKind?: WorkflowKind;
|
|
30
|
-
/** Run statuses that activate the completion trigger. If empty, any terminal status triggers. */
|
|
31
|
-
triggerOnStatus?: RunStatus[];
|
|
32
|
-
|
|
33
|
-
/** Input payload passed to the spawned workflow run. */
|
|
34
|
-
input?: Record<string, unknown>;
|
|
35
|
-
/** Arbitrary metadata attached to the spawned workflow run. */
|
|
36
|
-
metadata?: Record<string, unknown>;
|
|
37
|
-
|
|
38
|
-
/** Whether this schedule is active. Disabled schedules are not evaluated. */
|
|
39
|
-
enabled: boolean;
|
|
40
|
-
/** Timestamp of the most recent run spawned by this schedule. */
|
|
41
|
-
lastRunAt?: Date;
|
|
42
|
-
/** Run ID of the most recent run spawned by this schedule. */
|
|
43
|
-
lastRunId?: string;
|
|
44
|
-
/** Next scheduled execution time, computed from the cron expression and timezone. */
|
|
45
|
-
nextRunAt?: Date;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Scheduler interface.
|
|
50
|
-
* Implement this to create custom schedulers.
|
|
51
|
-
*/
|
|
52
|
-
export interface Scheduler {
|
|
53
|
-
/** Start the scheduler */
|
|
54
|
-
start(): Promise<void>;
|
|
55
|
-
|
|
56
|
-
/** Stop the scheduler */
|
|
57
|
-
stop(): Promise<void>;
|
|
58
|
-
|
|
59
|
-
/** Add a schedule */
|
|
60
|
-
addSchedule(schedule: Omit<WorkflowSchedule, 'id'>): Promise<WorkflowSchedule>;
|
|
61
|
-
|
|
62
|
-
/** Remove a schedule */
|
|
63
|
-
removeSchedule(scheduleId: string): Promise<void>;
|
|
64
|
-
|
|
65
|
-
/** Update a schedule */
|
|
66
|
-
updateSchedule(scheduleId: string, updates: Partial<WorkflowSchedule>): Promise<void>;
|
|
67
|
-
|
|
68
|
-
/** Get all schedules */
|
|
69
|
-
getSchedules(): Promise<WorkflowSchedule[]>;
|
|
70
|
-
|
|
71
|
-
/** Manually trigger a scheduled workflow */
|
|
72
|
-
triggerNow(scheduleId: string): Promise<string>;
|
|
73
|
-
}
|
package/src/storage/index.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Storage module exports.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export * from './types';
|
|
6
|
-
export * from './memory';
|
|
7
|
-
export * from './sqlite';
|
|
8
|
-
export * from './postgres';
|
|
9
|
-
|
|
10
|
-
// Re-export specific types for convenience
|
|
11
|
-
export type {
|
|
12
|
-
WorkflowStorage,
|
|
13
|
-
WorkflowRunRecord,
|
|
14
|
-
CreateRunInput,
|
|
15
|
-
UpdateRunInput,
|
|
16
|
-
ListRunsOptions,
|
|
17
|
-
PaginatedResult,
|
|
18
|
-
StepResult,
|
|
19
|
-
StepRecord,
|
|
20
|
-
ExtendedWorkflowRunRecord,
|
|
21
|
-
ExtendedRunStatus,
|
|
22
|
-
ExtendedStepStatus,
|
|
23
|
-
ExtendedListRunsOptions,
|
|
24
|
-
StepflowRunsTable,
|
|
25
|
-
StepflowStepResultsTable,
|
|
26
|
-
StepflowDatabase,
|
|
27
|
-
} from './types';
|