workplane 0.4.1 → 0.4.3
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 +31 -1
- package/dist/cli.js +164 -8
- package/dist/cli.js.map +1 -1
- package/dist/migrate.js +243 -9
- package/dist/migrate.js.map +1 -1
- package/dist/migrations/pg/0001_loud_otto_octavius.sql +45 -0
- package/dist/migrations/pg/meta/0001_snapshot.json +900 -0
- package/dist/migrations/pg/meta/_journal.json +7 -0
- package/dist/migrations/sqlite/0001_curvy_anita_blake.sql +45 -0
- package/dist/migrations/sqlite/meta/0001_snapshot.json +841 -0
- package/dist/migrations/sqlite/meta/_journal.json +7 -0
- package/dist/server.js +1194 -62
- package/dist/server.js.map +1 -1
- package/dist/setup.js +252 -12
- package/dist/setup.js.map +1 -1
- package/package.json +2 -1
package/dist/migrate.js
CHANGED
|
@@ -17,11 +17,14 @@ __export(sqlite_exports, {
|
|
|
17
17
|
runInputEvents: () => runInputEvents2,
|
|
18
18
|
runLogs: () => runLogs2,
|
|
19
19
|
runs: () => runs2,
|
|
20
|
-
tasks: () => tasks2
|
|
20
|
+
tasks: () => tasks2,
|
|
21
|
+
workplanRuns: () => workplanRuns2,
|
|
22
|
+
workplanSchedules: () => workplanSchedules2,
|
|
23
|
+
workplanStepResults: () => workplanStepResults2
|
|
21
24
|
});
|
|
22
25
|
import { isNull as isNull2, sql as sql3 } from "drizzle-orm";
|
|
23
26
|
import { index as index2, integer as integer2, sqliteTable, text as text2, uniqueIndex as uniqueIndex2 } from "drizzle-orm/sqlite-core";
|
|
24
|
-
var nowDefault, tasks2, nodes2, runs2, runLogs2, artifacts2, runInputEvents2;
|
|
27
|
+
var nowDefault, tasks2, nodes2, runs2, runLogs2, artifacts2, workplanSchedules2, workplanRuns2, workplanStepResults2, runInputEvents2;
|
|
25
28
|
var init_sqlite = __esm({
|
|
26
29
|
"../db/src/schema/sqlite.ts"() {
|
|
27
30
|
"use strict";
|
|
@@ -83,6 +86,49 @@ var init_sqlite = __esm({
|
|
|
83
86
|
}, (table) => ({
|
|
84
87
|
runIdIdx: index2("idx_artifacts_run_id").on(table.runId)
|
|
85
88
|
}));
|
|
89
|
+
workplanSchedules2 = sqliteTable("workplan_schedules", {
|
|
90
|
+
id: text2("id").primaryKey(),
|
|
91
|
+
planId: text2("plan_id").notNull(),
|
|
92
|
+
name: text2("name").notNull(),
|
|
93
|
+
cronExpression: text2("cron_expression").notNull(),
|
|
94
|
+
timezone: text2("timezone").notNull(),
|
|
95
|
+
inputs: text2("inputs", { mode: "json" }).$type().notNull().default({}),
|
|
96
|
+
enabled: integer2("enabled", { mode: "boolean" }).notNull().default(true),
|
|
97
|
+
lastRunAt: text2("last_run_at"),
|
|
98
|
+
nextRunAt: text2("next_run_at"),
|
|
99
|
+
createdBy: text2("created_by"),
|
|
100
|
+
createdAt: text2("created_at").notNull().default(nowDefault),
|
|
101
|
+
updatedAt: text2("updated_at").notNull().default(nowDefault)
|
|
102
|
+
}, (table) => ({
|
|
103
|
+
dueIdx: index2("idx_workplan_schedules_due").on(table.nextRunAt)
|
|
104
|
+
}));
|
|
105
|
+
workplanRuns2 = sqliteTable("workplan_runs", {
|
|
106
|
+
id: text2("id").primaryKey(),
|
|
107
|
+
scheduleId: text2("schedule_id").references(() => workplanSchedules2.id),
|
|
108
|
+
planId: text2("plan_id").notNull(),
|
|
109
|
+
planName: text2("plan_name").notNull(),
|
|
110
|
+
status: text2("status").notNull().default("running"),
|
|
111
|
+
idempotencyKey: text2("idempotency_key"),
|
|
112
|
+
createdAt: text2("created_at").notNull().default(nowDefault),
|
|
113
|
+
endedAt: text2("ended_at"),
|
|
114
|
+
error: text2("error")
|
|
115
|
+
}, (table) => ({
|
|
116
|
+
scheduleIdx: index2("idx_workplan_runs_schedule_id").on(table.scheduleId),
|
|
117
|
+
idempotencyUnique: uniqueIndex2("idx_workplan_runs_idempotency").on(table.idempotencyKey)
|
|
118
|
+
}));
|
|
119
|
+
workplanStepResults2 = sqliteTable("workplan_step_results", {
|
|
120
|
+
id: text2("id").primaryKey(),
|
|
121
|
+
workplanRunId: text2("workplan_run_id").notNull().references(() => workplanRuns2.id),
|
|
122
|
+
stepId: text2("step_id").notNull(),
|
|
123
|
+
stepName: text2("step_name").notNull(),
|
|
124
|
+
output: text2("output"),
|
|
125
|
+
exitCode: integer2("exit_code"),
|
|
126
|
+
durationMs: integer2("duration_ms"),
|
|
127
|
+
metadata: text2("metadata", { mode: "json" }).$type(),
|
|
128
|
+
createdAt: text2("created_at").notNull().default(nowDefault)
|
|
129
|
+
}, (table) => ({
|
|
130
|
+
runIdx: index2("idx_workplan_step_results_run_id").on(table.workplanRunId)
|
|
131
|
+
}));
|
|
86
132
|
runInputEvents2 = sqliteTable("run_input_events", {
|
|
87
133
|
id: integer2("id").primaryKey({ autoIncrement: true }),
|
|
88
134
|
runId: text2("run_id").notNull().references(() => runs2.id),
|
|
@@ -151,7 +197,7 @@ function parseCsv(value) {
|
|
|
151
197
|
// ../db/src/migration.ts
|
|
152
198
|
import { mkdirSync as mkdirSync2 } from "fs";
|
|
153
199
|
import { homedir as homedir3 } from "os";
|
|
154
|
-
import { dirname as dirname2, join as join3, resolve } from "path";
|
|
200
|
+
import { dirname as dirname2, join as join3, resolve as resolve2 } from "path";
|
|
155
201
|
import { fileURLToPath } from "url";
|
|
156
202
|
|
|
157
203
|
// ../db/src/client.ts
|
|
@@ -164,7 +210,7 @@ import Database from "better-sqlite3";
|
|
|
164
210
|
import { Pool } from "pg";
|
|
165
211
|
|
|
166
212
|
// ../db/src/pg-store.ts
|
|
167
|
-
import { and, asc, desc, eq, gt, sql as sql2 } from "drizzle-orm";
|
|
213
|
+
import { and, arrayContains, asc, desc, eq, gt, isNotNull, lte, sql as sql2 } from "drizzle-orm";
|
|
168
214
|
|
|
169
215
|
// ../db/src/schema/pg.ts
|
|
170
216
|
var pg_exports = {};
|
|
@@ -174,10 +220,13 @@ __export(pg_exports, {
|
|
|
174
220
|
runInputEvents: () => runInputEvents,
|
|
175
221
|
runLogs: () => runLogs,
|
|
176
222
|
runs: () => runs,
|
|
177
|
-
tasks: () => tasks
|
|
223
|
+
tasks: () => tasks,
|
|
224
|
+
workplanRuns: () => workplanRuns,
|
|
225
|
+
workplanSchedules: () => workplanSchedules,
|
|
226
|
+
workplanStepResults: () => workplanStepResults
|
|
178
227
|
});
|
|
179
228
|
import { isNull, sql } from "drizzle-orm";
|
|
180
|
-
import { bigint, bigserial, index, integer, jsonb, pgTable, text, timestamp, uniqueIndex } from "drizzle-orm/pg-core";
|
|
229
|
+
import { bigint, bigserial, boolean, index, integer, jsonb, pgTable, text, timestamp, uniqueIndex } from "drizzle-orm/pg-core";
|
|
181
230
|
var tasks = pgTable("tasks", {
|
|
182
231
|
id: text("id").primaryKey(),
|
|
183
232
|
kind: text("kind").notNull(),
|
|
@@ -235,6 +284,49 @@ var artifacts = pgTable("artifacts", {
|
|
|
235
284
|
}, (table) => ({
|
|
236
285
|
runIdIdx: index("idx_artifacts_run_id").on(table.runId)
|
|
237
286
|
}));
|
|
287
|
+
var workplanSchedules = pgTable("workplan_schedules", {
|
|
288
|
+
id: text("id").primaryKey(),
|
|
289
|
+
planId: text("plan_id").notNull(),
|
|
290
|
+
name: text("name").notNull(),
|
|
291
|
+
cronExpression: text("cron_expression").notNull(),
|
|
292
|
+
timezone: text("timezone").notNull(),
|
|
293
|
+
inputs: jsonb("inputs").$type().notNull().default({}),
|
|
294
|
+
enabled: boolean("enabled").notNull().default(true),
|
|
295
|
+
lastRunAt: timestamp("last_run_at", { withTimezone: true }),
|
|
296
|
+
nextRunAt: timestamp("next_run_at", { withTimezone: true }),
|
|
297
|
+
createdBy: text("created_by"),
|
|
298
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
299
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
300
|
+
}, (table) => ({
|
|
301
|
+
dueIdx: index("idx_workplan_schedules_due").on(table.nextRunAt)
|
|
302
|
+
}));
|
|
303
|
+
var workplanRuns = pgTable("workplan_runs", {
|
|
304
|
+
id: text("id").primaryKey(),
|
|
305
|
+
scheduleId: text("schedule_id").references(() => workplanSchedules.id),
|
|
306
|
+
planId: text("plan_id").notNull(),
|
|
307
|
+
planName: text("plan_name").notNull(),
|
|
308
|
+
status: text("status").notNull().default("running"),
|
|
309
|
+
idempotencyKey: text("idempotency_key"),
|
|
310
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
311
|
+
endedAt: timestamp("ended_at", { withTimezone: true }),
|
|
312
|
+
error: text("error")
|
|
313
|
+
}, (table) => ({
|
|
314
|
+
scheduleIdx: index("idx_workplan_runs_schedule_id").on(table.scheduleId),
|
|
315
|
+
idempotencyUnique: uniqueIndex("idx_workplan_runs_idempotency").on(table.idempotencyKey)
|
|
316
|
+
}));
|
|
317
|
+
var workplanStepResults = pgTable("workplan_step_results", {
|
|
318
|
+
id: text("id").primaryKey(),
|
|
319
|
+
workplanRunId: text("workplan_run_id").notNull().references(() => workplanRuns.id),
|
|
320
|
+
stepId: text("step_id").notNull(),
|
|
321
|
+
stepName: text("step_name").notNull(),
|
|
322
|
+
output: text("output"),
|
|
323
|
+
exitCode: integer("exit_code"),
|
|
324
|
+
durationMs: integer("duration_ms"),
|
|
325
|
+
metadata: jsonb("metadata").$type(),
|
|
326
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
|
|
327
|
+
}, (table) => ({
|
|
328
|
+
runIdx: index("idx_workplan_step_results_run_id").on(table.workplanRunId)
|
|
329
|
+
}));
|
|
238
330
|
var runInputEvents = pgTable("run_input_events", {
|
|
239
331
|
id: bigserial("id", { mode: "number" }).primaryKey(),
|
|
240
332
|
runId: text("run_id").notNull().references(() => runs.id),
|
|
@@ -249,7 +341,7 @@ var runInputEvents = pgTable("run_input_events", {
|
|
|
249
341
|
}));
|
|
250
342
|
|
|
251
343
|
// ../db/src/sqlite-store.ts
|
|
252
|
-
import { and as and2, asc as asc2, desc as desc2, eq as eq2, gt as gt2 } from "drizzle-orm";
|
|
344
|
+
import { and as and2, asc as asc2, desc as desc2, eq as eq2, gt as gt2, lte as lte2, sql as sql4 } from "drizzle-orm";
|
|
253
345
|
init_sqlite();
|
|
254
346
|
|
|
255
347
|
// ../db/src/client.ts
|
|
@@ -266,6 +358,144 @@ function getDrizzle(pool) {
|
|
|
266
358
|
return drizzlePg(pool, { schema: pg_exports });
|
|
267
359
|
}
|
|
268
360
|
|
|
361
|
+
// ../db/src/baseline-migrations.ts
|
|
362
|
+
import { createHash } from "crypto";
|
|
363
|
+
import { readFileSync } from "fs";
|
|
364
|
+
import { resolve } from "path";
|
|
365
|
+
function readJournal(migrationsFolder) {
|
|
366
|
+
return JSON.parse(
|
|
367
|
+
readFileSync(resolve(migrationsFolder, "meta/_journal.json"), "utf8")
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
function migrationHash(migrationsFolder, tag) {
|
|
371
|
+
const sql5 = readFileSync(resolve(migrationsFolder, `${tag}.sql`), "utf8");
|
|
372
|
+
return createHash("sha256").update(sql5).digest("hex");
|
|
373
|
+
}
|
|
374
|
+
function baselineEntries(migrationsFolder, upToIdx) {
|
|
375
|
+
const journal = readJournal(migrationsFolder);
|
|
376
|
+
return journal.entries.filter((entry) => entry.idx <= upToIdx).map((entry) => ({
|
|
377
|
+
hash: migrationHash(migrationsFolder, entry.tag),
|
|
378
|
+
createdAt: entry.when,
|
|
379
|
+
tag: entry.tag
|
|
380
|
+
}));
|
|
381
|
+
}
|
|
382
|
+
async function tableExistsPg(pool, tableName) {
|
|
383
|
+
const result = await pool.query(
|
|
384
|
+
`SELECT EXISTS (
|
|
385
|
+
SELECT 1 FROM information_schema.tables
|
|
386
|
+
WHERE table_schema = 'public' AND table_name = $1
|
|
387
|
+
) AS exists`,
|
|
388
|
+
[tableName]
|
|
389
|
+
);
|
|
390
|
+
return Boolean(result.rows[0]?.exists);
|
|
391
|
+
}
|
|
392
|
+
async function getLastMigrationCreatedAtPg(pool) {
|
|
393
|
+
await pool.query("CREATE SCHEMA IF NOT EXISTS drizzle");
|
|
394
|
+
await pool.query(`
|
|
395
|
+
CREATE TABLE IF NOT EXISTS drizzle.__drizzle_migrations (
|
|
396
|
+
id SERIAL PRIMARY KEY,
|
|
397
|
+
hash text NOT NULL,
|
|
398
|
+
created_at bigint
|
|
399
|
+
)
|
|
400
|
+
`);
|
|
401
|
+
const result = await pool.query(
|
|
402
|
+
"SELECT created_at FROM drizzle.__drizzle_migrations ORDER BY created_at DESC LIMIT 1"
|
|
403
|
+
);
|
|
404
|
+
const value = result.rows[0]?.created_at;
|
|
405
|
+
return value === null || value === void 0 ? null : Number(value);
|
|
406
|
+
}
|
|
407
|
+
async function insertBaselinePg(pool, entries) {
|
|
408
|
+
const inserted = [];
|
|
409
|
+
const lastCreatedAt = await getLastMigrationCreatedAtPg(pool);
|
|
410
|
+
for (const entry of entries) {
|
|
411
|
+
if (lastCreatedAt !== null && lastCreatedAt >= entry.createdAt) {
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
const existing = await pool.query(
|
|
415
|
+
"SELECT id FROM drizzle.__drizzle_migrations WHERE created_at = $1 LIMIT 1",
|
|
416
|
+
[entry.createdAt]
|
|
417
|
+
);
|
|
418
|
+
if (existing.rowCount) {
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
await pool.query(
|
|
422
|
+
'INSERT INTO drizzle.__drizzle_migrations ("hash", "created_at") VALUES ($1, $2)',
|
|
423
|
+
[entry.hash, entry.createdAt]
|
|
424
|
+
);
|
|
425
|
+
inserted.push(entry.tag);
|
|
426
|
+
}
|
|
427
|
+
return inserted;
|
|
428
|
+
}
|
|
429
|
+
async function baselineLegacyPostgresMigrations(pool, migrationsFolder) {
|
|
430
|
+
const hasTasks = await tableExistsPg(pool, "tasks");
|
|
431
|
+
if (!hasTasks) {
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
const hasWorkplanSchedules = await tableExistsPg(pool, "workplan_schedules");
|
|
435
|
+
const upToIdx = hasWorkplanSchedules ? 1 : 0;
|
|
436
|
+
const inserted = await insertBaselinePg(pool, baselineEntries(migrationsFolder, upToIdx));
|
|
437
|
+
if (inserted.length > 0) {
|
|
438
|
+
process.stdout.write(
|
|
439
|
+
`Baselined existing Postgres schema for migration(s): ${inserted.join(", ")}
|
|
440
|
+
`
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
function tableExistsSqlite(db, tableName) {
|
|
445
|
+
const row = db.prepare(
|
|
446
|
+
"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ? LIMIT 1"
|
|
447
|
+
).get(tableName);
|
|
448
|
+
return Boolean(row);
|
|
449
|
+
}
|
|
450
|
+
function getLastMigrationCreatedAtSqlite(db) {
|
|
451
|
+
db.exec(`
|
|
452
|
+
CREATE TABLE IF NOT EXISTS __drizzle_migrations (
|
|
453
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
454
|
+
hash text NOT NULL,
|
|
455
|
+
created_at numeric
|
|
456
|
+
)
|
|
457
|
+
`);
|
|
458
|
+
const row = db.prepare(
|
|
459
|
+
"SELECT created_at FROM __drizzle_migrations ORDER BY created_at DESC LIMIT 1"
|
|
460
|
+
).get();
|
|
461
|
+
return row?.created_at ?? null;
|
|
462
|
+
}
|
|
463
|
+
function insertBaselineSqlite(db, entries) {
|
|
464
|
+
const inserted = [];
|
|
465
|
+
const lastCreatedAt = getLastMigrationCreatedAtSqlite(db);
|
|
466
|
+
const insert = db.prepare(
|
|
467
|
+
'INSERT INTO __drizzle_migrations ("hash", "created_at") VALUES (?, ?)'
|
|
468
|
+
);
|
|
469
|
+
const exists = db.prepare(
|
|
470
|
+
"SELECT 1 FROM __drizzle_migrations WHERE created_at = ? LIMIT 1"
|
|
471
|
+
);
|
|
472
|
+
for (const entry of entries) {
|
|
473
|
+
if (lastCreatedAt !== null && lastCreatedAt >= entry.createdAt) {
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
if (exists.get(entry.createdAt)) {
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
insert.run(entry.hash, entry.createdAt);
|
|
480
|
+
inserted.push(entry.tag);
|
|
481
|
+
}
|
|
482
|
+
return inserted;
|
|
483
|
+
}
|
|
484
|
+
function baselineLegacySqliteMigrations(db, migrationsFolder) {
|
|
485
|
+
if (!tableExistsSqlite(db, "tasks")) {
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
const hasWorkplanSchedules = tableExistsSqlite(db, "workplan_schedules");
|
|
489
|
+
const upToIdx = hasWorkplanSchedules ? 1 : 0;
|
|
490
|
+
const inserted = insertBaselineSqlite(db, baselineEntries(migrationsFolder, upToIdx));
|
|
491
|
+
if (inserted.length > 0) {
|
|
492
|
+
process.stdout.write(
|
|
493
|
+
`Baselined existing SQLite schema for migration(s): ${inserted.join(", ")}
|
|
494
|
+
`
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
269
499
|
// ../db/src/migration.ts
|
|
270
500
|
var distDir = dirname2(fileURLToPath(import.meta.url));
|
|
271
501
|
async function migratePostgres(databaseUrl) {
|
|
@@ -296,8 +526,10 @@ async function migratePostgres(databaseUrl) {
|
|
|
296
526
|
await adminPool.end();
|
|
297
527
|
}
|
|
298
528
|
const pool = getPool(databaseUrl);
|
|
529
|
+
const migrationsFolder = resolve2(distDir, "migrations/pg");
|
|
530
|
+
await baselineLegacyPostgresMigrations(pool, migrationsFolder);
|
|
299
531
|
const { migrate } = await import("drizzle-orm/node-postgres/migrator");
|
|
300
|
-
await migrate(getDrizzle(pool), { migrationsFolder
|
|
532
|
+
await migrate(getDrizzle(pool), { migrationsFolder });
|
|
301
533
|
await pool.end();
|
|
302
534
|
process.stdout.write("Postgres migrations applied.\n");
|
|
303
535
|
}
|
|
@@ -312,8 +544,10 @@ async function migrateSqlite(databaseUrl) {
|
|
|
312
544
|
const sqlite = new Database2(filePath);
|
|
313
545
|
sqlite.pragma("journal_mode = WAL");
|
|
314
546
|
sqlite.pragma("synchronous = NORMAL");
|
|
547
|
+
const migrationsFolder = resolve2(distDir, "migrations/sqlite");
|
|
548
|
+
baselineLegacySqliteMigrations(sqlite, migrationsFolder);
|
|
315
549
|
const db = drizzle(sqlite, { schema: sqliteSchema });
|
|
316
|
-
migrate(db, { migrationsFolder
|
|
550
|
+
migrate(db, { migrationsFolder });
|
|
317
551
|
sqlite.close();
|
|
318
552
|
process.stdout.write(`SQLite migrations applied: ${filePath}
|
|
319
553
|
`);
|