@multiplier-labs/stepflow 0.3.3 → 0.3.6
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-GYPNJBDP.js → chunk-D5HW7VEG.js} +269 -48
- package/dist/chunk-D5HW7VEG.js.map +1 -0
- package/dist/{chunk-KHTUWLVZ.js → chunk-FMPJBK2N.js} +55 -10
- package/dist/chunk-FMPJBK2N.js.map +1 -0
- package/dist/{chunk-ML35PIHX.js → chunk-JPXPB7U5.js} +52 -2
- package/dist/chunk-JPXPB7U5.js.map +1 -0
- package/dist/{chunk-4VOKMGP6.js → chunk-JUPZEBR3.js} +519 -522
- package/dist/chunk-JUPZEBR3.js.map +1 -0
- package/dist/events/index.d.ts +58 -3
- package/dist/events/index.js +1 -1
- package/dist/{index-Dzr437iW.d.ts → index-Cd0IZ40L.d.ts} +50 -11
- package/dist/index.d.ts +2 -12
- package/dist/index.js +764 -485
- package/dist/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +1 -1
- package/dist/scheduler/index.js +2 -2
- package/dist/storage/index.d.ts +313 -47
- package/dist/storage/index.js +4 -2
- package/package.json +27 -13
- package/.codekin/reports/comments/2026-04-04_comment-assessment.md +0 -179
- package/.codekin/reports/complexity/2026-04-03_complexity-report.md +0 -152
- package/.codekin/reports/dependencies/2026-04-04_dependency-health.md +0 -94
- package/.codekin/reports/docs-audit/2026-04-04_docs-audit.md +0 -10
- package/.codekin/reports/security/2026-04-03_security-audit.md +0 -208
- package/.github/CODEOWNERS +0 -15
- package/.github/dependabot.yml +0 -23
- package/.github/workflows/ci.yml +0 -38
- package/.github/workflows/publish.yml +0 -36
- package/.github/workflows/security.yml +0 -29
- package/CHANGELOG.md +0 -34
- package/SECURITY.md +0 -40
- package/dist/chunk-4VOKMGP6.js.map +0 -1
- package/dist/chunk-GYPNJBDP.js.map +0 -1
- package/dist/chunk-KHTUWLVZ.js.map +0 -1
- package/dist/chunk-ML35PIHX.js.map +0 -1
- package/docs/github-packages-setup.md +0 -97
- package/docs/stepflow-api-reference.md +0 -1661
- package/src/core/engine.test.ts +0 -1365
- package/src/core/engine.ts +0 -692
- package/src/core/index.ts +0 -7
- package/src/core/orchestrator.ts +0 -571
- 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 -657
- package/src/events/webhook.ts +0 -564
- package/src/index.ts +0 -270
- package/src/planning/index.ts +0 -55
- package/src/planning/planner.ts +0 -576
- package/src/planning/planning.test.ts +0 -727
- package/src/planning/registry.test.ts +0 -398
- package/src/planning/registry.ts +0 -365
- package/src/planning/types.ts +0 -566
- package/src/scheduler/cron.test.ts +0 -471
- package/src/scheduler/cron.ts +0 -414
- package/src/scheduler/index.ts +0 -8
- package/src/scheduler/postgres-persistence.ts +0 -518
- package/src/scheduler/sqlite-persistence.test.ts +0 -235
- package/src/scheduler/sqlite-persistence.ts +0 -261
- 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.ts +0 -1354
- package/src/storage/sqlite.test.ts +0 -466
- package/src/storage/sqlite.ts +0 -627
- 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 -8
- package/src/utils/logger.test.ts +0 -121
- package/src/utils/logger.ts +0 -96
- package/src/utils/postgres-deps.ts +0 -63
- package/src/utils/retry.test.ts +0 -177
- package/src/utils/retry.ts +0 -121
- package/tsconfig.json +0 -21
- package/tsup.config.ts +0 -14
- package/vitest.config.ts +0 -9
|
@@ -1,518 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PostgreSQL persistence adapter for schedules.
|
|
3
|
-
*
|
|
4
|
-
* Stores workflow schedules in a PostgreSQL database table using Kysely.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { Kysely as KyselyType } from 'kysely';
|
|
8
|
-
import type { Pool, PoolConfig } from 'pg';
|
|
9
|
-
import { loadPostgresDeps } from '../utils/postgres-deps.js';
|
|
10
|
-
|
|
11
|
-
// Lazy-loaded dependencies - populated by loadPostgresDeps() during initialize()
|
|
12
|
-
let Kysely: any;
|
|
13
|
-
let PostgresDialect: any;
|
|
14
|
-
let sql: any;
|
|
15
|
-
let pgModule: any;
|
|
16
|
-
import type { WorkflowSchedule } from './types.js';
|
|
17
|
-
import type { SchedulePersistence } from './cron.js';
|
|
18
|
-
|
|
19
|
-
// ============================================================================
|
|
20
|
-
// Database Types (Kysely schema)
|
|
21
|
-
// ============================================================================
|
|
22
|
-
|
|
23
|
-
/** Kysely row type for the workflow_schedules table. */
|
|
24
|
-
interface WorkflowSchedulesTable {
|
|
25
|
-
id: string;
|
|
26
|
-
/** Workflow kind this schedule targets. */
|
|
27
|
-
workflow_kind: string;
|
|
28
|
-
/** Trigger type: 'cron', 'workflow_completed', or 'manual'. */
|
|
29
|
-
trigger_type: string;
|
|
30
|
-
/** Cron expression for time-based triggers, null for non-cron schedules. */
|
|
31
|
-
cron_expression: string | null;
|
|
32
|
-
/** IANA timezone for cron evaluation, null defaults to UTC. */
|
|
33
|
-
timezone: string | null;
|
|
34
|
-
/** Workflow kind that triggers on completion, null for non-completion triggers. */
|
|
35
|
-
trigger_on_workflow_kind: string | null;
|
|
36
|
-
/** JSON-serialized array of RunStatus values that activate the trigger. */
|
|
37
|
-
trigger_on_status: string | null;
|
|
38
|
-
/** JSON-serialized input payload for spawned runs. */
|
|
39
|
-
input_json: string | null;
|
|
40
|
-
/** JSON-serialized arbitrary metadata for spawned runs. */
|
|
41
|
-
metadata_json: string | null;
|
|
42
|
-
/** Whether this schedule is active. */
|
|
43
|
-
enabled: boolean;
|
|
44
|
-
/** Timestamp of the most recent run spawned by this schedule. */
|
|
45
|
-
last_run_at: Date | null;
|
|
46
|
-
/** Run ID of the most recent run spawned by this schedule. */
|
|
47
|
-
last_run_id: string | null;
|
|
48
|
-
/** Next scheduled execution time. */
|
|
49
|
-
next_run_at: Date | null;
|
|
50
|
-
created_at: Date;
|
|
51
|
-
updated_at: Date;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
interface SchedulesDatabase {
|
|
55
|
-
workflow_schedules: WorkflowSchedulesTable;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// ============================================================================
|
|
59
|
-
// Configuration Types
|
|
60
|
-
// ============================================================================
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Configuration for PostgresSchedulePersistence.
|
|
64
|
-
*/
|
|
65
|
-
export interface PostgresSchedulePersistenceConfig {
|
|
66
|
-
/**
|
|
67
|
-
* PostgreSQL connection string.
|
|
68
|
-
* Example: "postgresql://user:pass@localhost:5432/dbname"
|
|
69
|
-
*/
|
|
70
|
-
connectionString?: string;
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Existing pg.Pool instance for connection sharing with application.
|
|
74
|
-
* If provided, the adapter will not close this pool on close().
|
|
75
|
-
*/
|
|
76
|
-
pool?: Pool;
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Pool configuration options (if not providing pool or connectionString).
|
|
80
|
-
*/
|
|
81
|
-
poolConfig?: PoolConfig;
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Schema name for the schedules table.
|
|
85
|
-
* @default 'public'
|
|
86
|
-
*/
|
|
87
|
-
schema?: string;
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Table name for schedules.
|
|
91
|
-
* @default 'workflow_schedules'
|
|
92
|
-
*/
|
|
93
|
-
tableName?: string;
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Automatically create tables on initialization.
|
|
97
|
-
* @default true
|
|
98
|
-
*/
|
|
99
|
-
autoMigrate?: boolean;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// ============================================================================
|
|
103
|
-
// PostgresSchedulePersistence Class
|
|
104
|
-
// ============================================================================
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* PostgreSQL-based persistence for workflow schedules.
|
|
108
|
-
*
|
|
109
|
-
* @example
|
|
110
|
-
* ```typescript
|
|
111
|
-
* const persistence = new PostgresSchedulePersistence({
|
|
112
|
-
* connectionString: process.env.DATABASE_URL,
|
|
113
|
-
* });
|
|
114
|
-
*
|
|
115
|
-
* await persistence.initialize();
|
|
116
|
-
*
|
|
117
|
-
* const scheduler = new CronScheduler({
|
|
118
|
-
* engine,
|
|
119
|
-
* persistence,
|
|
120
|
-
* });
|
|
121
|
-
* ```
|
|
122
|
-
*
|
|
123
|
-
* @example Sharing connection pool
|
|
124
|
-
* ```typescript
|
|
125
|
-
* import pg from 'pg';
|
|
126
|
-
*
|
|
127
|
-
* const pool = new pg.Pool({
|
|
128
|
-
* connectionString: process.env.DATABASE_URL,
|
|
129
|
-
* });
|
|
130
|
-
*
|
|
131
|
-
* const persistence = new PostgresSchedulePersistence({ pool });
|
|
132
|
-
* await persistence.initialize();
|
|
133
|
-
* ```
|
|
134
|
-
*/
|
|
135
|
-
export class PostgresSchedulePersistence implements SchedulePersistence {
|
|
136
|
-
private db!: KyselyType<SchedulesDatabase>;
|
|
137
|
-
private pool!: Pool;
|
|
138
|
-
private ownsPool = false;
|
|
139
|
-
private schema: string;
|
|
140
|
-
private tableName: string;
|
|
141
|
-
private autoMigrate: boolean;
|
|
142
|
-
private initialized = false;
|
|
143
|
-
private config: PostgresSchedulePersistenceConfig;
|
|
144
|
-
|
|
145
|
-
constructor(config: PostgresSchedulePersistenceConfig) {
|
|
146
|
-
this.schema = config.schema ?? 'public';
|
|
147
|
-
if (!/^[a-zA-Z_][a-zA-Z0-9_]{0,62}$/.test(this.schema)) {
|
|
148
|
-
throw new Error(
|
|
149
|
-
`Invalid schema name "${this.schema}". Schema must start with a letter or underscore, ` +
|
|
150
|
-
`contain only alphanumeric characters and underscores, and be at most 63 characters.`
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
this.tableName = config.tableName ?? 'workflow_schedules';
|
|
154
|
-
if (!/^[a-zA-Z_][a-zA-Z0-9_]{0,62}$/.test(this.tableName)) {
|
|
155
|
-
throw new Error(
|
|
156
|
-
`Invalid table name "${this.tableName}". Table name must start with a letter or underscore, ` +
|
|
157
|
-
`contain only alphanumeric characters and underscores, and be at most 63 characters.`
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
this.autoMigrate = config.autoMigrate !== false;
|
|
161
|
-
this.config = config;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Get a schema-scoped query builder.
|
|
166
|
-
* All queries MUST use this instead of this.db directly to respect config.schema.
|
|
167
|
-
*/
|
|
168
|
-
private get qb() {
|
|
169
|
-
return this.db.withSchema(this.schema);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
private ensureInitialized(): void {
|
|
173
|
-
if (!this.initialized) {
|
|
174
|
-
throw new Error(
|
|
175
|
-
'PostgresSchedulePersistence is not initialized. Call initialize() before using the adapter.'
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Initialize the persistence layer.
|
|
182
|
-
* Creates the schedules table if autoMigrate is enabled.
|
|
183
|
-
*/
|
|
184
|
-
async initialize(): Promise<void> {
|
|
185
|
-
if (this.initialized) {
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const deps = await loadPostgresDeps();
|
|
190
|
-
Kysely = deps.Kysely;
|
|
191
|
-
PostgresDialect = deps.PostgresDialect;
|
|
192
|
-
sql = deps.sql;
|
|
193
|
-
pgModule = deps.pgModule;
|
|
194
|
-
|
|
195
|
-
if (this.config.pool) {
|
|
196
|
-
this.pool = this.config.pool;
|
|
197
|
-
this.ownsPool = false;
|
|
198
|
-
} else if (this.config.connectionString) {
|
|
199
|
-
this.pool = new pgModule.Pool({ connectionString: this.config.connectionString });
|
|
200
|
-
this.ownsPool = true;
|
|
201
|
-
} else if (this.config.poolConfig) {
|
|
202
|
-
this.pool = new pgModule.Pool(this.config.poolConfig);
|
|
203
|
-
this.ownsPool = true;
|
|
204
|
-
} else {
|
|
205
|
-
throw new Error(
|
|
206
|
-
'PostgresSchedulePersistenceConfig must include either pool, connectionString, or poolConfig'
|
|
207
|
-
);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
this.db = new Kysely({
|
|
211
|
-
dialect: new PostgresDialect({
|
|
212
|
-
pool: this.pool,
|
|
213
|
-
}),
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
if (this.autoMigrate) {
|
|
217
|
-
await this.createTables();
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
this.initialized = true;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Close the database connection.
|
|
225
|
-
* Only closes the pool if it was created by this adapter.
|
|
226
|
-
*/
|
|
227
|
-
async close(): Promise<void> {
|
|
228
|
-
await this.db.destroy();
|
|
229
|
-
if (this.ownsPool) {
|
|
230
|
-
await this.pool.end();
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// ============================================================================
|
|
235
|
-
// Database Initialization
|
|
236
|
-
// ============================================================================
|
|
237
|
-
|
|
238
|
-
private async createTables(): Promise<void> {
|
|
239
|
-
const fullTableName = this.schema === 'public'
|
|
240
|
-
? this.tableName
|
|
241
|
-
: `${this.schema}.${this.tableName}`;
|
|
242
|
-
|
|
243
|
-
// Create schema if not public
|
|
244
|
-
if (this.schema !== 'public') {
|
|
245
|
-
await sql`CREATE SCHEMA IF NOT EXISTS ${sql.ref(this.schema)}`.execute(this.db);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Create schedules table.
|
|
249
|
-
// Schema layout:
|
|
250
|
-
// id — unique schedule identifier (TEXT PK)
|
|
251
|
-
// workflow_kind — target workflow type to spawn
|
|
252
|
-
// trigger_type — constrained to 'cron' | 'workflow_completed' | 'manual'
|
|
253
|
-
// cron_expression / timezone — cron trigger configuration
|
|
254
|
-
// trigger_on_workflow_kind — completion trigger source kind
|
|
255
|
-
// trigger_on_status — JSONB array of RunStatus values for completion triggers
|
|
256
|
-
// input_json / metadata_json — JSONB payloads for spawned runs
|
|
257
|
-
// enabled — whether this schedule is active
|
|
258
|
-
// last_run_at / last_run_id — most recent execution tracking
|
|
259
|
-
// next_run_at — next scheduled execution time
|
|
260
|
-
// created_at / updated_at — audit timestamps (TIMESTAMPTZ)
|
|
261
|
-
// No migration strategy is currently implemented; table is created idempotently.
|
|
262
|
-
await sql`
|
|
263
|
-
CREATE TABLE IF NOT EXISTS ${sql.table(fullTableName)} (
|
|
264
|
-
id TEXT PRIMARY KEY,
|
|
265
|
-
workflow_kind TEXT NOT NULL,
|
|
266
|
-
trigger_type TEXT NOT NULL,
|
|
267
|
-
cron_expression TEXT,
|
|
268
|
-
timezone TEXT DEFAULT 'UTC',
|
|
269
|
-
trigger_on_workflow_kind TEXT,
|
|
270
|
-
trigger_on_status JSONB,
|
|
271
|
-
input_json JSONB NOT NULL DEFAULT '{}',
|
|
272
|
-
metadata_json JSONB NOT NULL DEFAULT '{}',
|
|
273
|
-
enabled BOOLEAN NOT NULL DEFAULT true,
|
|
274
|
-
last_run_at TIMESTAMPTZ,
|
|
275
|
-
last_run_id TEXT,
|
|
276
|
-
next_run_at TIMESTAMPTZ,
|
|
277
|
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
278
|
-
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
279
|
-
CONSTRAINT ${sql.ref(`${this.tableName}_trigger_type_check`)} CHECK (
|
|
280
|
-
trigger_type IN ('cron', 'workflow_completed', 'manual')
|
|
281
|
-
)
|
|
282
|
-
)
|
|
283
|
-
`.execute(this.db);
|
|
284
|
-
|
|
285
|
-
// Create indexes
|
|
286
|
-
await sql`
|
|
287
|
-
CREATE INDEX IF NOT EXISTS ${sql.ref(`idx_${this.tableName}_workflow_kind`)}
|
|
288
|
-
ON ${sql.table(fullTableName)} (workflow_kind)
|
|
289
|
-
`.execute(this.db);
|
|
290
|
-
|
|
291
|
-
await sql`
|
|
292
|
-
CREATE INDEX IF NOT EXISTS ${sql.ref(`idx_${this.tableName}_enabled`)}
|
|
293
|
-
ON ${sql.table(fullTableName)} (enabled)
|
|
294
|
-
`.execute(this.db);
|
|
295
|
-
|
|
296
|
-
await sql`
|
|
297
|
-
CREATE INDEX IF NOT EXISTS ${sql.ref(`idx_${this.tableName}_trigger_type`)}
|
|
298
|
-
ON ${sql.table(fullTableName)} (trigger_type)
|
|
299
|
-
`.execute(this.db);
|
|
300
|
-
|
|
301
|
-
await sql`
|
|
302
|
-
CREATE INDEX IF NOT EXISTS ${sql.ref(`idx_${this.tableName}_next_run`)}
|
|
303
|
-
ON ${sql.table(fullTableName)} (next_run_at)
|
|
304
|
-
WHERE enabled = true
|
|
305
|
-
`.execute(this.db);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// ============================================================================
|
|
309
|
-
// SchedulePersistence Interface Implementation
|
|
310
|
-
// ============================================================================
|
|
311
|
-
|
|
312
|
-
async loadSchedules(): Promise<WorkflowSchedule[]> {
|
|
313
|
-
this.ensureInitialized();
|
|
314
|
-
const rows = await this.qb
|
|
315
|
-
.selectFrom(this.tableName as any)
|
|
316
|
-
.selectAll()
|
|
317
|
-
.execute() as WorkflowSchedulesTable[];
|
|
318
|
-
|
|
319
|
-
return rows.map(row => this.rowToSchedule(row));
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
async saveSchedule(schedule: WorkflowSchedule): Promise<void> {
|
|
323
|
-
this.ensureInitialized();
|
|
324
|
-
await this.qb
|
|
325
|
-
.insertInto(this.tableName as any)
|
|
326
|
-
.values({
|
|
327
|
-
id: schedule.id,
|
|
328
|
-
workflow_kind: schedule.workflowKind,
|
|
329
|
-
trigger_type: schedule.triggerType,
|
|
330
|
-
cron_expression: schedule.cronExpression ?? null,
|
|
331
|
-
timezone: schedule.timezone ?? null,
|
|
332
|
-
trigger_on_workflow_kind: schedule.triggerOnWorkflowKind ?? null,
|
|
333
|
-
trigger_on_status: schedule.triggerOnStatus ? JSON.stringify(schedule.triggerOnStatus) : null,
|
|
334
|
-
input_json: schedule.input ? JSON.stringify(schedule.input) : null,
|
|
335
|
-
metadata_json: schedule.metadata ? JSON.stringify(schedule.metadata) : null,
|
|
336
|
-
enabled: schedule.enabled,
|
|
337
|
-
last_run_at: schedule.lastRunAt ?? null,
|
|
338
|
-
last_run_id: schedule.lastRunId ?? null,
|
|
339
|
-
next_run_at: schedule.nextRunAt ?? null,
|
|
340
|
-
created_at: new Date(),
|
|
341
|
-
updated_at: new Date(),
|
|
342
|
-
})
|
|
343
|
-
.execute();
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
async updateSchedule(scheduleId: string, updates: Partial<WorkflowSchedule>): Promise<void> {
|
|
347
|
-
this.ensureInitialized();
|
|
348
|
-
// Partial update pattern: fetch the existing row, merge with incoming updates,
|
|
349
|
-
// then build a column-level update object. Each field is only included if it was
|
|
350
|
-
// explicitly provided in `updates` (or affected by the merge), so unchanged
|
|
351
|
-
// columns are left untouched in the database.
|
|
352
|
-
const existing = await this.qb
|
|
353
|
-
.selectFrom(this.tableName as any)
|
|
354
|
-
.selectAll()
|
|
355
|
-
.where('id', '=', scheduleId)
|
|
356
|
-
.executeTakeFirst() as WorkflowSchedulesTable | undefined;
|
|
357
|
-
|
|
358
|
-
if (!existing) {
|
|
359
|
-
throw new Error(`Schedule not found: ${scheduleId}`);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
const merged = {
|
|
363
|
-
...this.rowToSchedule(existing),
|
|
364
|
-
...updates,
|
|
365
|
-
};
|
|
366
|
-
|
|
367
|
-
// Field mapping: { domainKey, dbKey, serialize? }
|
|
368
|
-
// Each entry maps a WorkflowSchedule field to its DB column, with optional serialization.
|
|
369
|
-
const fieldMappings: Array<{
|
|
370
|
-
domainKey: keyof WorkflowSchedule;
|
|
371
|
-
dbKey: keyof WorkflowSchedulesTable;
|
|
372
|
-
serialize?: (value: any) => any;
|
|
373
|
-
}> = [
|
|
374
|
-
{ domainKey: 'workflowKind', dbKey: 'workflow_kind' },
|
|
375
|
-
{ domainKey: 'triggerType', dbKey: 'trigger_type' },
|
|
376
|
-
{ domainKey: 'cronExpression', dbKey: 'cron_expression' },
|
|
377
|
-
{ domainKey: 'timezone', dbKey: 'timezone' },
|
|
378
|
-
{ domainKey: 'triggerOnWorkflowKind', dbKey: 'trigger_on_workflow_kind' },
|
|
379
|
-
{ domainKey: 'triggerOnStatus', dbKey: 'trigger_on_status', serialize: v => v ? JSON.stringify(v) : null },
|
|
380
|
-
{ domainKey: 'input', dbKey: 'input_json', serialize: v => v ? JSON.stringify(v) : null },
|
|
381
|
-
{ domainKey: 'metadata', dbKey: 'metadata_json', serialize: v => v ? JSON.stringify(v) : null },
|
|
382
|
-
{ domainKey: 'enabled', dbKey: 'enabled' },
|
|
383
|
-
{ domainKey: 'lastRunAt', dbKey: 'last_run_at' },
|
|
384
|
-
{ domainKey: 'lastRunId', dbKey: 'last_run_id' },
|
|
385
|
-
{ domainKey: 'nextRunAt', dbKey: 'next_run_at' },
|
|
386
|
-
];
|
|
387
|
-
|
|
388
|
-
const updateData: Partial<WorkflowSchedulesTable> = {
|
|
389
|
-
updated_at: new Date(),
|
|
390
|
-
};
|
|
391
|
-
|
|
392
|
-
for (const { domainKey, dbKey, serialize } of fieldMappings) {
|
|
393
|
-
if (updates[domainKey] !== undefined || merged[domainKey] !== undefined) {
|
|
394
|
-
const value = serialize
|
|
395
|
-
? serialize(merged[domainKey])
|
|
396
|
-
: (merged[domainKey] ?? null);
|
|
397
|
-
(updateData as any)[dbKey] = value;
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
await this.qb
|
|
402
|
-
.updateTable(this.tableName as any)
|
|
403
|
-
.set(updateData)
|
|
404
|
-
.where('id', '=', scheduleId)
|
|
405
|
-
.execute();
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
async deleteSchedule(scheduleId: string): Promise<void> {
|
|
409
|
-
this.ensureInitialized();
|
|
410
|
-
await this.qb
|
|
411
|
-
.deleteFrom(this.tableName as any)
|
|
412
|
-
.where('id', '=', scheduleId)
|
|
413
|
-
.execute();
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// ============================================================================
|
|
417
|
-
// Additional Methods
|
|
418
|
-
// ============================================================================
|
|
419
|
-
|
|
420
|
-
/**
|
|
421
|
-
* Get a schedule by ID.
|
|
422
|
-
*/
|
|
423
|
-
async getSchedule(scheduleId: string): Promise<WorkflowSchedule | null> {
|
|
424
|
-
this.ensureInitialized();
|
|
425
|
-
const row = await this.qb
|
|
426
|
-
.selectFrom(this.tableName as any)
|
|
427
|
-
.selectAll()
|
|
428
|
-
.where('id', '=', scheduleId)
|
|
429
|
-
.executeTakeFirst() as WorkflowSchedulesTable | undefined;
|
|
430
|
-
|
|
431
|
-
return row ? this.rowToSchedule(row) : null;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
/**
|
|
435
|
-
* Get all enabled schedules that are due to run.
|
|
436
|
-
*/
|
|
437
|
-
async getDueSchedules(): Promise<WorkflowSchedule[]> {
|
|
438
|
-
this.ensureInitialized();
|
|
439
|
-
const now = new Date();
|
|
440
|
-
|
|
441
|
-
const rows = await this.qb
|
|
442
|
-
.selectFrom(this.tableName as any)
|
|
443
|
-
.selectAll()
|
|
444
|
-
.where('enabled', '=', true)
|
|
445
|
-
.where('trigger_type', '=', 'cron')
|
|
446
|
-
.where('next_run_at', '<=', now)
|
|
447
|
-
.execute() as WorkflowSchedulesTable[];
|
|
448
|
-
|
|
449
|
-
return rows.map(row => this.rowToSchedule(row));
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
/**
|
|
453
|
-
* Get schedules by workflow kind.
|
|
454
|
-
*/
|
|
455
|
-
async getSchedulesByWorkflowKind(workflowKind: string): Promise<WorkflowSchedule[]> {
|
|
456
|
-
this.ensureInitialized();
|
|
457
|
-
const rows = await this.qb
|
|
458
|
-
.selectFrom(this.tableName as any)
|
|
459
|
-
.selectAll()
|
|
460
|
-
.where('workflow_kind', '=', workflowKind)
|
|
461
|
-
.execute() as WorkflowSchedulesTable[];
|
|
462
|
-
|
|
463
|
-
return rows.map(row => this.rowToSchedule(row));
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
/**
|
|
467
|
-
* Get workflow completion triggers for a specific workflow kind.
|
|
468
|
-
*/
|
|
469
|
-
async getCompletionTriggers(triggerOnWorkflowKind: string): Promise<WorkflowSchedule[]> {
|
|
470
|
-
this.ensureInitialized();
|
|
471
|
-
const rows = await this.qb
|
|
472
|
-
.selectFrom(this.tableName as any)
|
|
473
|
-
.selectAll()
|
|
474
|
-
.where('enabled', '=', true)
|
|
475
|
-
.where('trigger_type', '=', 'workflow_completed')
|
|
476
|
-
.where('trigger_on_workflow_kind', '=', triggerOnWorkflowKind)
|
|
477
|
-
.execute() as WorkflowSchedulesTable[];
|
|
478
|
-
|
|
479
|
-
return rows.map(row => this.rowToSchedule(row));
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
// ============================================================================
|
|
483
|
-
// Helper Methods
|
|
484
|
-
// ============================================================================
|
|
485
|
-
|
|
486
|
-
private rowToSchedule(row: WorkflowSchedulesTable): WorkflowSchedule {
|
|
487
|
-
return {
|
|
488
|
-
id: row.id,
|
|
489
|
-
workflowKind: row.workflow_kind,
|
|
490
|
-
triggerType: row.trigger_type as WorkflowSchedule['triggerType'],
|
|
491
|
-
cronExpression: row.cron_expression ?? undefined,
|
|
492
|
-
timezone: row.timezone ?? undefined,
|
|
493
|
-
triggerOnWorkflowKind: row.trigger_on_workflow_kind ?? undefined,
|
|
494
|
-
triggerOnStatus: row.trigger_on_status
|
|
495
|
-
? (typeof row.trigger_on_status === 'string'
|
|
496
|
-
? JSON.parse(row.trigger_on_status)
|
|
497
|
-
: row.trigger_on_status)
|
|
498
|
-
: undefined,
|
|
499
|
-
input: row.input_json
|
|
500
|
-
? (typeof row.input_json === 'string'
|
|
501
|
-
? JSON.parse(row.input_json)
|
|
502
|
-
: row.input_json)
|
|
503
|
-
: undefined,
|
|
504
|
-
metadata: row.metadata_json
|
|
505
|
-
? (typeof row.metadata_json === 'string'
|
|
506
|
-
? JSON.parse(row.metadata_json)
|
|
507
|
-
: row.metadata_json)
|
|
508
|
-
: undefined,
|
|
509
|
-
enabled: row.enabled,
|
|
510
|
-
lastRunAt: row.last_run_at ? new Date(row.last_run_at) : undefined,
|
|
511
|
-
lastRunId: row.last_run_id ?? undefined,
|
|
512
|
-
nextRunAt: row.next_run_at ? new Date(row.next_run_at) : undefined,
|
|
513
|
-
};
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
// Convenience alias for shorter naming
|
|
518
|
-
export type { PostgresSchedulePersistenceConfig as PostgresSchedulePersistenceOptions };
|