workplane 0.4.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli.js +188 -73
- package/dist/cli.js.map +1 -1
- package/dist/migrate.js +14 -14
- package/dist/migrate.js.map +1 -1
- package/dist/node.js +40 -40
- package/dist/node.js.map +1 -1
- package/dist/server.js +254 -140
- package/dist/server.js.map +1 -1
- package/dist/setup.js +14 -14
- package/dist/setup.js.map +1 -1
- package/package.json +1 -1
package/dist/setup.js
CHANGED
|
@@ -9,7 +9,7 @@ var __export = (target, all) => {
|
|
|
9
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
//
|
|
12
|
+
// ../../libs/db/src/schema/pg.ts
|
|
13
13
|
var pg_exports = {};
|
|
14
14
|
__export(pg_exports, {
|
|
15
15
|
artifacts: () => artifacts,
|
|
@@ -26,7 +26,7 @@ import { isNull, sql } from "drizzle-orm";
|
|
|
26
26
|
import { bigint, bigserial, boolean, index, integer, jsonb, pgTable, text, timestamp, uniqueIndex } from "drizzle-orm/pg-core";
|
|
27
27
|
var tasks, nodes, runs, runLogs, artifacts, workplanSchedules, workplanRuns, workplanStepResults, runInputEvents;
|
|
28
28
|
var init_pg = __esm({
|
|
29
|
-
"
|
|
29
|
+
"../../libs/db/src/schema/pg.ts"() {
|
|
30
30
|
"use strict";
|
|
31
31
|
tasks = pgTable("tasks", {
|
|
32
32
|
id: text("id").primaryKey(),
|
|
@@ -143,16 +143,16 @@ var init_pg = __esm({
|
|
|
143
143
|
}
|
|
144
144
|
});
|
|
145
145
|
|
|
146
|
-
//
|
|
146
|
+
// ../../libs/db/src/pg-store.ts
|
|
147
147
|
import { and, arrayContains, asc, desc, eq, gt, isNotNull, lte, sql as sql2 } from "drizzle-orm";
|
|
148
148
|
var init_pg_store = __esm({
|
|
149
|
-
"
|
|
149
|
+
"../../libs/db/src/pg-store.ts"() {
|
|
150
150
|
"use strict";
|
|
151
151
|
init_pg();
|
|
152
152
|
}
|
|
153
153
|
});
|
|
154
154
|
|
|
155
|
-
//
|
|
155
|
+
// ../../libs/db/src/schema/sqlite.ts
|
|
156
156
|
var sqlite_exports = {};
|
|
157
157
|
__export(sqlite_exports, {
|
|
158
158
|
artifacts: () => artifacts2,
|
|
@@ -169,7 +169,7 @@ import { isNull as isNull2, sql as sql3 } from "drizzle-orm";
|
|
|
169
169
|
import { index as index2, integer as integer2, sqliteTable, text as text2, uniqueIndex as uniqueIndex2 } from "drizzle-orm/sqlite-core";
|
|
170
170
|
var nowDefault, tasks2, nodes2, runs2, runLogs2, artifacts2, workplanSchedules2, workplanRuns2, workplanStepResults2, runInputEvents2;
|
|
171
171
|
var init_sqlite = __esm({
|
|
172
|
-
"
|
|
172
|
+
"../../libs/db/src/schema/sqlite.ts"() {
|
|
173
173
|
"use strict";
|
|
174
174
|
nowDefault = sql3`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`;
|
|
175
175
|
tasks2 = sqliteTable("tasks", {
|
|
@@ -287,16 +287,16 @@ var init_sqlite = __esm({
|
|
|
287
287
|
}
|
|
288
288
|
});
|
|
289
289
|
|
|
290
|
-
//
|
|
290
|
+
// ../../libs/db/src/sqlite-store.ts
|
|
291
291
|
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";
|
|
292
292
|
var init_sqlite_store = __esm({
|
|
293
|
-
"
|
|
293
|
+
"../../libs/db/src/sqlite-store.ts"() {
|
|
294
294
|
"use strict";
|
|
295
295
|
init_sqlite();
|
|
296
296
|
}
|
|
297
297
|
});
|
|
298
298
|
|
|
299
|
-
//
|
|
299
|
+
// ../../libs/db/src/client.ts
|
|
300
300
|
import { mkdirSync } from "fs";
|
|
301
301
|
import { homedir } from "os";
|
|
302
302
|
import { dirname, join } from "path";
|
|
@@ -316,7 +316,7 @@ function getDrizzle(pool) {
|
|
|
316
316
|
}
|
|
317
317
|
var pools;
|
|
318
318
|
var init_client = __esm({
|
|
319
|
-
"
|
|
319
|
+
"../../libs/db/src/client.ts"() {
|
|
320
320
|
"use strict";
|
|
321
321
|
init_pg_store();
|
|
322
322
|
init_sqlite_store();
|
|
@@ -326,7 +326,7 @@ var init_client = __esm({
|
|
|
326
326
|
}
|
|
327
327
|
});
|
|
328
328
|
|
|
329
|
-
//
|
|
329
|
+
// ../../libs/db/src/baseline-migrations.ts
|
|
330
330
|
import { createHash } from "crypto";
|
|
331
331
|
import { readFileSync } from "fs";
|
|
332
332
|
import { resolve } from "path";
|
|
@@ -464,12 +464,12 @@ function baselineLegacySqliteMigrations(db, migrationsFolder) {
|
|
|
464
464
|
}
|
|
465
465
|
}
|
|
466
466
|
var init_baseline_migrations = __esm({
|
|
467
|
-
"
|
|
467
|
+
"../../libs/db/src/baseline-migrations.ts"() {
|
|
468
468
|
"use strict";
|
|
469
469
|
}
|
|
470
470
|
});
|
|
471
471
|
|
|
472
|
-
//
|
|
472
|
+
// ../../libs/db/src/migration.ts
|
|
473
473
|
var migration_exports = {};
|
|
474
474
|
__export(migration_exports, {
|
|
475
475
|
runMigration: () => runMigration
|
|
@@ -541,7 +541,7 @@ async function runMigration(databaseUrl) {
|
|
|
541
541
|
}
|
|
542
542
|
var distDir;
|
|
543
543
|
var init_migration = __esm({
|
|
544
|
-
"
|
|
544
|
+
"../../libs/db/src/migration.ts"() {
|
|
545
545
|
"use strict";
|
|
546
546
|
init_client();
|
|
547
547
|
init_baseline_migrations();
|
package/dist/setup.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../db/src/schema/pg.ts","../../db/src/pg-store.ts","../../db/src/schema/sqlite.ts","../../db/src/sqlite-store.ts","../../db/src/client.ts","../../db/src/baseline-migrations.ts","../../db/src/migration.ts","../../cli/src/setup.ts"],"sourcesContent":["import { isNull, sql } from \"drizzle-orm\";\nimport { bigint, bigserial, boolean, index, integer, jsonb, pgTable, text, timestamp, uniqueIndex } from \"drizzle-orm/pg-core\";\n\nexport const tasks = pgTable(\"tasks\", {\n id: text(\"id\").primaryKey(),\n kind: text(\"kind\").notNull(),\n adapter: text(\"adapter\").notNull(),\n payload: jsonb(\"payload\").$type<Record<string, unknown>>().notNull(),\n requires: text(\"requires\").array().notNull().default(sql`'{}'::text[]`),\n status: text(\"status\").notNull(),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp(\"updated_at\", { withTimezone: true }).notNull().defaultNow(),\n}, (table) => ({\n statusCreatedAtIdx: index(\"idx_tasks_status_created_at\").on(table.status, table.createdAt),\n}));\n\nexport const nodes = pgTable(\"nodes\", {\n id: text(\"id\").primaryKey(),\n name: text(\"name\").notNull(),\n capabilities: text(\"capabilities\").array().notNull().default(sql`'{}'::text[]`),\n status: text(\"status\").notNull().default(\"online\"),\n lastHeartbeatAt: timestamp(\"last_heartbeat_at\", { withTimezone: true }),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp(\"updated_at\", { withTimezone: true }).notNull().defaultNow(),\n});\n\nexport const runs = pgTable(\"runs\", {\n id: text(\"id\").primaryKey(),\n taskId: text(\"task_id\").notNull().references(() => tasks.id),\n nodeId: text(\"node_id\").notNull().references(() => nodes.id),\n attempt: integer(\"attempt\").notNull(),\n status: text(\"status\").notNull(),\n startedAt: timestamp(\"started_at\", { withTimezone: true }),\n endedAt: timestamp(\"ended_at\", { withTimezone: true }),\n error: text(\"error\"),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp(\"updated_at\", { withTimezone: true }).notNull().defaultNow(),\n}, (table) => ({\n taskIdIdx: index(\"idx_runs_task_id\").on(table.taskId),\n nodeIdIdx: index(\"idx_runs_node_id\").on(table.nodeId),\n}));\n\nexport const runLogs = pgTable(\"run_logs\", {\n id: bigserial(\"id\", { mode: \"number\" }).primaryKey(),\n runId: text(\"run_id\").notNull().references(() => runs.id),\n stepName: text(\"step_name\"),\n stream: text(\"stream\").notNull(),\n message: text(\"message\").notNull(),\n timestamp: timestamp(\"timestamp\", { withTimezone: true }).notNull().defaultNow(),\n}, (table) => ({\n runIdTimestampIdx: index(\"idx_run_logs_run_id_timestamp\").on(table.runId, table.timestamp),\n}));\n\nexport const artifacts = pgTable(\"artifacts\", {\n id: text(\"id\").primaryKey(),\n runId: text(\"run_id\").notNull().references(() => runs.id),\n type: text(\"type\").notNull(),\n name: text(\"name\").notNull(),\n path: text(\"path\").notNull(),\n metadata: jsonb(\"metadata\").$type<Record<string, unknown>>(),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n}, (table) => ({\n runIdIdx: index(\"idx_artifacts_run_id\").on(table.runId),\n}));\n\nexport const workplanSchedules = pgTable(\"workplan_schedules\", {\n id: text(\"id\").primaryKey(),\n planId: text(\"plan_id\").notNull(),\n name: text(\"name\").notNull(),\n cronExpression: text(\"cron_expression\").notNull(),\n timezone: text(\"timezone\").notNull(),\n inputs: jsonb(\"inputs\").$type<Record<string, unknown>>().notNull().default({}),\n enabled: boolean(\"enabled\").notNull().default(true),\n lastRunAt: timestamp(\"last_run_at\", { withTimezone: true }),\n nextRunAt: timestamp(\"next_run_at\", { withTimezone: true }),\n createdBy: text(\"created_by\"),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp(\"updated_at\", { withTimezone: true }).notNull().defaultNow(),\n}, (table) => ({\n dueIdx: index(\"idx_workplan_schedules_due\").on(table.nextRunAt),\n}));\n\nexport const workplanRuns = pgTable(\"workplan_runs\", {\n id: text(\"id\").primaryKey(),\n scheduleId: text(\"schedule_id\").references(() => workplanSchedules.id),\n planId: text(\"plan_id\").notNull(),\n planName: text(\"plan_name\").notNull(),\n status: text(\"status\").notNull().default(\"running\"),\n idempotencyKey: text(\"idempotency_key\"),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n endedAt: timestamp(\"ended_at\", { withTimezone: true }),\n error: text(\"error\"),\n}, (table) => ({\n scheduleIdx: index(\"idx_workplan_runs_schedule_id\").on(table.scheduleId),\n idempotencyUnique: uniqueIndex(\"idx_workplan_runs_idempotency\").on(table.idempotencyKey),\n}));\n\nexport const workplanStepResults = pgTable(\"workplan_step_results\", {\n id: text(\"id\").primaryKey(),\n workplanRunId: text(\"workplan_run_id\").notNull().references(() => workplanRuns.id),\n stepId: text(\"step_id\").notNull(),\n stepName: text(\"step_name\").notNull(),\n output: text(\"output\"),\n exitCode: integer(\"exit_code\"),\n durationMs: integer(\"duration_ms\"),\n metadata: jsonb(\"metadata\").$type<Record<string, unknown>>(),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n}, (table) => ({\n runIdx: index(\"idx_workplan_step_results_run_id\").on(table.workplanRunId),\n}));\n\nexport const runInputEvents = pgTable(\"run_input_events\", {\n id: bigserial(\"id\", { mode: \"number\" }).primaryKey(),\n runId: text(\"run_id\").notNull().references(() => runs.id),\n sequence: bigint(\"sequence\", { mode: \"number\" }).notNull(),\n kind: text(\"kind\").notNull(),\n payload: jsonb(\"payload\").$type<Record<string, unknown>>().notNull(),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n deliveredAt: timestamp(\"delivered_at\", { withTimezone: true }),\n}, (table) => ({\n runSequenceUniqueIdx: uniqueIndex(\"idx_run_input_events_run_sequence\").on(table.runId, table.sequence),\n undeliveredIdx: index(\"idx_run_input_events_undelivered\").on(table.runId, table.sequence).where(isNull(table.deliveredAt)),\n}));\n","import { and, arrayContains, asc, desc, eq, gt, isNotNull, lte, sql } from \"drizzle-orm\";\nimport { makeId } from \"../../core/src/ids.js\";\nimport type { DrizzleDb } from \"./client.js\";\nimport { artifacts, nodes, runInputEvents, runLogs, runs, tasks, workplanRuns, workplanSchedules, workplanStepResults } from \"./schema/pg.js\";\nimport type { WorkplaneStore } from \"./store-interface.js\";\nimport type {\n AppendInputEventInput,\n ArtifactInput,\n ArtifactRecord,\n CreateTaskInput,\n CreateWorkplanRunInput,\n CreateWorkplanScheduleInput,\n NodePollResult,\n NodeRecord,\n RunInputEvent,\n RunLogInput,\n RunLogRecord,\n RunRecord,\n TaskRecord,\n UpdateWorkplanScheduleInput,\n WorkplanRunRecord,\n WorkplanScheduleRecord,\n WorkplanStepResultInput,\n WorkplanStepResultRecord,\n} from \"../../types/src/index.js\";\n\nfunction toTaskRecord(row: typeof tasks.$inferSelect): TaskRecord {\n return {\n id: row.id,\n kind: row.kind,\n adapter: row.adapter,\n payload: row.payload,\n requires: row.requires ?? [],\n status: row.status as TaskRecord[\"status\"],\n createdAt: row.createdAt.toISOString(),\n updatedAt: row.updatedAt.toISOString(),\n };\n}\n\nfunction toRunRecord(row: typeof runs.$inferSelect): RunRecord {\n return {\n id: row.id,\n taskId: row.taskId,\n nodeId: row.nodeId,\n attempt: row.attempt,\n status: row.status as RunRecord[\"status\"],\n startedAt: row.startedAt?.toISOString() ?? null,\n endedAt: row.endedAt?.toISOString() ?? null,\n error: row.error ?? null,\n };\n}\n\nfunction toNodeRecord(row: typeof nodes.$inferSelect): NodeRecord {\n return {\n id: row.id,\n name: row.name,\n capabilities: row.capabilities ?? [],\n status: row.status as NodeRecord[\"status\"],\n lastHeartbeatAt: row.lastHeartbeatAt?.toISOString() ?? null,\n };\n}\n\nfunction toLogRecord(row: typeof runLogs.$inferSelect): RunLogRecord {\n return {\n id: row.id,\n runId: row.runId,\n stepName: row.stepName ?? null,\n stream: row.stream as RunLogRecord[\"stream\"],\n message: row.message,\n timestamp: row.timestamp.toISOString(),\n };\n}\n\nfunction toArtifactRecord(row: typeof artifacts.$inferSelect): ArtifactRecord {\n return {\n id: row.id,\n runId: row.runId,\n type: row.type,\n name: row.name,\n path: row.path,\n metadata: row.metadata ?? null,\n createdAt: row.createdAt.toISOString(),\n };\n}\n\nfunction toInputEventRecord(row: typeof runInputEvents.$inferSelect): RunInputEvent {\n return {\n id: row.id,\n runId: row.runId,\n sequence: row.sequence,\n kind: row.kind as RunInputEvent[\"kind\"],\n payload: row.payload,\n createdAt: row.createdAt.toISOString(),\n deliveredAt: row.deliveredAt?.toISOString() ?? null,\n };\n}\n\nfunction toScheduleRecord(row: typeof workplanSchedules.$inferSelect): WorkplanScheduleRecord {\n return {\n id: row.id,\n planId: row.planId,\n name: row.name,\n cronExpression: row.cronExpression,\n timezone: row.timezone,\n inputs: row.inputs ?? {},\n enabled: row.enabled,\n lastRunAt: row.lastRunAt?.toISOString() ?? null,\n nextRunAt: row.nextRunAt?.toISOString() ?? null,\n createdBy: row.createdBy ?? null,\n createdAt: row.createdAt.toISOString(),\n updatedAt: row.updatedAt.toISOString(),\n };\n}\n\nfunction toWorkplanRunRecord(row: typeof workplanRuns.$inferSelect): WorkplanRunRecord {\n return {\n id: row.id,\n scheduleId: row.scheduleId ?? null,\n planId: row.planId,\n planName: row.planName,\n status: row.status as WorkplanRunRecord[\"status\"],\n idempotencyKey: row.idempotencyKey ?? null,\n createdAt: row.createdAt.toISOString(),\n endedAt: row.endedAt?.toISOString() ?? null,\n error: row.error ?? null,\n };\n}\n\nfunction toWorkplanStepResultRecord(row: typeof workplanStepResults.$inferSelect): WorkplanStepResultRecord {\n return {\n id: row.id,\n workplanRunId: row.workplanRunId,\n stepId: row.stepId,\n stepName: row.stepName,\n output: row.output ?? null,\n exitCode: row.exitCode ?? null,\n durationMs: row.durationMs ?? null,\n metadata: row.metadata ?? null,\n createdAt: row.createdAt.toISOString(),\n };\n}\n\nfunction mapRawTask(row: Record<string, unknown>): TaskRecord {\n return {\n id: String(row.id),\n kind: String(row.kind),\n adapter: String(row.adapter),\n payload: (row.payload as Record<string, unknown>) ?? {},\n requires: (row.requires as string[]) ?? [],\n status: row.status as TaskRecord[\"status\"],\n createdAt: new Date(String(row.created_at)).toISOString(),\n updatedAt: new Date(String(row.updated_at)).toISOString(),\n };\n}\n\nfunction mapRawRun(row: Record<string, unknown>): RunRecord {\n return {\n id: String(row.id),\n taskId: String(row.task_id),\n nodeId: String(row.node_id),\n attempt: Number(row.attempt),\n status: row.status as RunRecord[\"status\"],\n startedAt: row.started_at ? new Date(String(row.started_at)).toISOString() : null,\n endedAt: row.ended_at ? new Date(String(row.ended_at)).toISOString() : null,\n error: row.error ? String(row.error) : null,\n };\n}\n\nexport class PgStore implements WorkplaneStore {\n constructor(private readonly db: DrizzleDb) {}\n\n async createTask(input: CreateTaskInput): Promise<TaskRecord> {\n const taskId = makeId(\"task\");\n const [row] = await this.db.insert(tasks).values({\n id: taskId,\n kind: input.kind,\n adapter: input.adapter,\n payload: input.payload,\n requires: input.requires ?? [],\n status: \"queued\",\n }).returning();\n return toTaskRecord(row);\n }\n\n async listTasks(status?: TaskRecord[\"status\"]): Promise<TaskRecord[]> {\n const rows = await this.db\n .select()\n .from(tasks)\n .where(status ? eq(tasks.status, status) : undefined)\n .orderBy(desc(tasks.createdAt))\n .limit(100);\n return rows.map(toTaskRecord);\n }\n\n async getTask(taskId: string): Promise<TaskRecord | null> {\n const [row] = await this.db.select().from(tasks).where(eq(tasks.id, taskId));\n return row ? toTaskRecord(row) : null;\n }\n\n async listRuns(filters?: { taskId?: string; status?: RunRecord[\"status\"] }): Promise<RunRecord[]> {\n const conditions = [];\n if (filters?.taskId) conditions.push(eq(runs.taskId, filters.taskId));\n if (filters?.status) conditions.push(eq(runs.status, filters.status));\n\n const rows = await this.db\n .select()\n .from(runs)\n .where(conditions.length ? and(...conditions) : undefined)\n .orderBy(desc(runs.createdAt))\n .limit(100);\n return rows.map(toRunRecord);\n }\n\n async getRun(runId: string): Promise<RunRecord | null> {\n const [row] = await this.db.select().from(runs).where(eq(runs.id, runId));\n return row ? toRunRecord(row) : null;\n }\n\n async registerNode(name: string, capabilities: string[], preferredId?: string): Promise<NodeRecord> {\n const [existing] = await this.db.select().from(nodes).where(eq(nodes.name, name));\n\n if (existing) {\n const [updated] = await this.db.update(nodes)\n .set({ capabilities, status: \"online\", lastHeartbeatAt: sql`now()`, updatedAt: sql`now()` })\n .where(eq(nodes.id, existing.id))\n .returning();\n return toNodeRecord(updated);\n }\n\n const nodeId = preferredId ?? makeId(\"node\");\n const [inserted] = await this.db.insert(nodes).values({\n id: nodeId, name, capabilities, status: \"online\", lastHeartbeatAt: sql`now()`,\n }).returning();\n return toNodeRecord(inserted);\n }\n\n async getRunCancellationState(runId: string): Promise<{ runStatus: string; taskStatus: string } | null> {\n const [row] = await this.db\n .select({ runStatus: runs.status, taskStatus: tasks.status })\n .from(runs)\n .innerJoin(tasks, eq(tasks.id, runs.taskId))\n .where(eq(runs.id, runId));\n return row ?? null;\n }\n\n async pollNode(nodeId: string, capabilities: string[]): Promise<NodePollResult | null> {\n return await this.db.transaction(async (tx) => {\n await tx.update(nodes)\n .set({\n capabilities,\n status: \"online\",\n lastHeartbeatAt: sql`now()`,\n updatedAt: sql`now()`,\n })\n .where(eq(nodes.id, nodeId));\n\n const [taskRow] = await tx\n .select()\n .from(tasks)\n .where(and(eq(tasks.status, \"queued\"), arrayContains(tasks.requires, capabilities)))\n .orderBy(asc(tasks.createdAt))\n .limit(1)\n .for(\"update\", { skipLocked: true });\n\n if (!taskRow) return null;\n\n const taskId = taskRow.id;\n\n const attemptResult = await tx.execute(sql`\n SELECT COALESCE(MAX(attempt), 0) AS max_attempt FROM runs WHERE task_id = ${taskId}\n `);\n const attempt = Number((attemptResult.rows[0] as Record<string, unknown>).max_attempt) + 1;\n\n const runId = makeId(\"run\");\n\n const runResult = await tx.execute(sql`\n INSERT INTO runs (id, task_id, node_id, attempt, status, started_at)\n VALUES (${runId}, ${taskId}, ${nodeId}, ${attempt}, 'assigned', now())\n RETURNING *\n `);\n\n await tx.execute(sql`\n UPDATE tasks SET status = 'assigned', updated_at = now() WHERE id = ${taskId}\n `);\n\n return {\n task: toTaskRecord(taskRow),\n run: mapRawRun(runResult.rows[0] as Record<string, unknown>),\n };\n });\n }\n\n async updateRunStatus(runId: string, status: RunRecord[\"status\"], error?: string): Promise<RunRecord | null> {\n return await this.db.transaction(async (tx) => {\n const currentResult = await tx.execute(sql`\n SELECT r.*, t.status AS task_status\n FROM runs r JOIN tasks t ON t.id = r.task_id\n WHERE r.id = ${runId}\n FOR UPDATE\n `);\n\n if (currentResult.rows.length === 0) return null;\n\n const currentRow = currentResult.rows[0] as Record<string, unknown>;\n const currentTaskStatus = String(currentRow.task_status);\n\n if (currentTaskStatus === \"cancelled\" && status !== \"cancelled\") {\n return mapRawRun(currentRow);\n }\n\n const isTerminal = status === \"succeeded\" || status === \"failed\" || status === \"cancelled\";\n const [updatedRun] = await tx.update(runs)\n .set(isTerminal\n ? { status, error: error ?? null, endedAt: sql`now()`, updatedAt: sql`now()` }\n : { status, error: error ?? null, updatedAt: sql`now()` })\n .where(eq(runs.id, runId))\n .returning();\n\n if (!updatedRun) return null;\n\n const nextTaskStatus = isTerminal ? status : \"running\";\n if (currentTaskStatus !== \"cancelled\" || status === \"cancelled\") {\n await tx.update(tasks)\n .set({ status: nextTaskStatus, updatedAt: sql`now()` })\n .where(eq(tasks.id, updatedRun.taskId));\n }\n\n return toRunRecord(updatedRun);\n });\n }\n\n async appendRunLogs(runId: string, logs: RunLogInput[]): Promise<number> {\n if (logs.length === 0) return 0;\n await this.db.insert(runLogs).values(\n logs.map((log) => ({\n runId,\n stepName: log.stepName ?? null,\n stream: log.stream,\n message: log.message,\n })),\n );\n return logs.length;\n }\n\n async getRunLogs(runId: string): Promise<RunLogRecord[]> {\n const rows = await this.db\n .select()\n .from(runLogs)\n .where(eq(runLogs.runId, runId))\n .orderBy(asc(runLogs.timestamp));\n return rows.map(toLogRecord);\n }\n\n async createArtifact(runId: string, input: ArtifactInput): Promise<ArtifactRecord> {\n const artifactId = makeId(\"artifact\");\n const [row] = await this.db.insert(artifacts).values({\n id: artifactId,\n runId,\n type: input.type,\n name: input.name,\n path: input.path,\n metadata: input.metadata ?? null,\n }).returning();\n return toArtifactRecord(row);\n }\n\n async listRunArtifacts(runId: string): Promise<ArtifactRecord[]> {\n const rows = await this.db\n .select()\n .from(artifacts)\n .where(eq(artifacts.runId, runId))\n .orderBy(asc(artifacts.createdAt));\n return rows.map(toArtifactRecord);\n }\n\n async retryTask(taskId: string): Promise<TaskRecord | null> {\n const [row] = await this.db.update(tasks)\n .set({ status: \"queued\", updatedAt: sql`now()` })\n .where(and(eq(tasks.id, taskId), eq(tasks.status, \"failed\")))\n .returning();\n return row ? toTaskRecord(row) : null;\n }\n\n async cancelTask(taskId: string): Promise<TaskRecord | null> {\n return await this.db.transaction(async (tx) => {\n const taskResult = await tx.execute(sql`\n SELECT * FROM tasks WHERE id = ${taskId} FOR UPDATE\n `);\n\n if (taskResult.rows.length === 0) return null;\n\n const taskRow = taskResult.rows[0] as Record<string, unknown>;\n const taskStatus = String(taskRow.status);\n\n if (taskStatus === \"succeeded\" || taskStatus === \"failed\" || taskStatus === \"cancelled\") {\n return null;\n }\n\n if (taskStatus === \"assigned\" || taskStatus === \"running\") {\n const runResult = await tx.execute(sql`\n SELECT * FROM runs WHERE task_id = ${taskId} ORDER BY attempt DESC LIMIT 1 FOR UPDATE\n `);\n\n if (runResult.rows.length > 0) {\n const runRow = runResult.rows[0] as Record<string, unknown>;\n const runStatus = String(runRow.status);\n if (runStatus !== \"succeeded\" && runStatus !== \"failed\" && runStatus !== \"cancelled\") {\n await tx.execute(sql`\n UPDATE runs SET status = 'cancelled', ended_at = now(), updated_at = now(),\n error = COALESCE(error, 'task cancelled')\n WHERE id = ${runRow.id}\n `);\n }\n }\n }\n\n const [updatedTask] = await tx.update(tasks)\n .set({ status: \"cancelled\", updatedAt: sql`now()` })\n .where(eq(tasks.id, taskId))\n .returning();\n\n return toTaskRecord(updatedTask);\n });\n }\n\n async appendInputEvent(runId: string, input: AppendInputEventInput): Promise<RunInputEvent> {\n return await this.db.transaction(async (tx) => {\n const seqResult = await tx.execute(sql`\n SELECT COALESCE(MAX(sequence), 0) + 1 AS next_seq FROM run_input_events WHERE run_id = ${runId}\n `);\n const sequence = Number((seqResult.rows[0] as Record<string, unknown>).next_seq);\n\n const [inserted] = await tx.insert(runInputEvents)\n .values({ runId, sequence, kind: input.kind, payload: input.payload })\n .returning();\n\n return toInputEventRecord(inserted);\n });\n }\n\n async getInputEvents(runId: string, afterSequence: number): Promise<RunInputEvent[]> {\n const rows = await this.db\n .select()\n .from(runInputEvents)\n .where(and(eq(runInputEvents.runId, runId), gt(runInputEvents.sequence, afterSequence)))\n .orderBy(asc(runInputEvents.sequence));\n return rows.map(toInputEventRecord);\n }\n\n async markInputDelivered(runId: string, sequence: number): Promise<void> {\n await this.db.update(runInputEvents)\n .set({ deliveredAt: sql`now()` })\n .where(and(eq(runInputEvents.runId, runId), eq(runInputEvents.sequence, sequence)));\n }\n\n async createWorkplanSchedule(input: CreateWorkplanScheduleInput): Promise<WorkplanScheduleRecord> {\n const [row] = await this.db.insert(workplanSchedules).values({\n id: makeId(\"sched\"),\n planId: input.planId,\n name: input.name,\n cronExpression: input.cronExpression,\n timezone: input.timezone,\n inputs: input.inputs ?? {},\n enabled: input.enabled ?? true,\n nextRunAt: input.enabled === false ? null : input.nextRunAt ? new Date(input.nextRunAt) : null,\n createdBy: input.createdBy ?? null,\n }).returning();\n return toScheduleRecord(row);\n }\n\n async listWorkplanSchedules(enabled?: boolean): Promise<WorkplanScheduleRecord[]> {\n const rows = await this.db.select().from(workplanSchedules)\n .where(enabled === undefined ? undefined : eq(workplanSchedules.enabled, enabled))\n .orderBy(desc(workplanSchedules.createdAt));\n return rows.map(toScheduleRecord);\n }\n\n async getWorkplanSchedule(scheduleId: string): Promise<WorkplanScheduleRecord | null> {\n const [row] = await this.db.select().from(workplanSchedules).where(eq(workplanSchedules.id, scheduleId));\n return row ? toScheduleRecord(row) : null;\n }\n\n async updateWorkplanSchedule(\n scheduleId: string,\n input: UpdateWorkplanScheduleInput,\n ): Promise<WorkplanScheduleRecord | null> {\n const patch: Partial<typeof workplanSchedules.$inferInsert> = {};\n if (input.name !== undefined) patch.name = input.name;\n if (input.cronExpression !== undefined) patch.cronExpression = input.cronExpression;\n if (input.timezone !== undefined) patch.timezone = input.timezone;\n if (input.inputs !== undefined) patch.inputs = input.inputs;\n if (input.enabled !== undefined) patch.enabled = input.enabled;\n if (input.nextRunAt !== undefined) patch.nextRunAt = input.nextRunAt ? new Date(input.nextRunAt) : null;\n patch.updatedAt = new Date();\n\n const [row] = await this.db.update(workplanSchedules)\n .set(patch)\n .where(eq(workplanSchedules.id, scheduleId))\n .returning();\n return row ? toScheduleRecord(row) : null;\n }\n\n async deleteWorkplanSchedule(scheduleId: string): Promise<boolean> {\n return await this.db.transaction(async (tx) => {\n await tx.update(workplanRuns)\n .set({ scheduleId: null })\n .where(eq(workplanRuns.scheduleId, scheduleId));\n\n const deleted = await tx.delete(workplanSchedules)\n .where(eq(workplanSchedules.id, scheduleId))\n .returning({ id: workplanSchedules.id });\n return deleted.length > 0;\n });\n }\n\n async listDueWorkplanSchedules(asOf: string): Promise<WorkplanScheduleRecord[]> {\n const asOfDate = new Date(asOf);\n const rows = await this.db.select().from(workplanSchedules)\n .where(and(\n eq(workplanSchedules.enabled, true),\n isNotNull(workplanSchedules.nextRunAt),\n lte(workplanSchedules.nextRunAt, asOfDate),\n ))\n .orderBy(asc(workplanSchedules.nextRunAt));\n return rows.map(toScheduleRecord);\n }\n\n async markWorkplanScheduleRan(scheduleId: string, lastRunAt: string, nextRunAt: string): Promise<void> {\n await this.db.update(workplanSchedules)\n .set({\n lastRunAt: new Date(lastRunAt),\n nextRunAt: new Date(nextRunAt),\n updatedAt: sql`now()`,\n })\n .where(eq(workplanSchedules.id, scheduleId));\n }\n\n async tryCreateWorkplanRun(input: CreateWorkplanRunInput): Promise<WorkplanRunRecord | null> {\n try {\n const [row] = await this.db.insert(workplanRuns).values({\n id: makeId(\"wprun\"),\n scheduleId: input.scheduleId ?? null,\n planId: input.planId,\n planName: input.planName,\n status: \"running\",\n idempotencyKey: input.idempotencyKey ?? null,\n }).returning();\n return toWorkplanRunRecord(row);\n } catch (err) {\n const code = (err as { code?: string }).code;\n if (input.idempotencyKey && code === \"23505\") {\n return null;\n }\n throw err;\n }\n }\n\n async updateWorkplanRun(\n runId: string,\n status: WorkplanRunRecord[\"status\"],\n error?: string,\n endedAt?: string,\n ): Promise<WorkplanRunRecord | null> {\n const [row] = await this.db.update(workplanRuns)\n .set({\n status,\n error: error ?? null,\n endedAt: endedAt ? new Date(endedAt) : status !== \"running\" ? new Date() : null,\n })\n .where(eq(workplanRuns.id, runId))\n .returning();\n return row ? toWorkplanRunRecord(row) : null;\n }\n\n async getWorkplanRun(runId: string): Promise<WorkplanRunRecord | null> {\n const [row] = await this.db.select().from(workplanRuns).where(eq(workplanRuns.id, runId));\n return row ? toWorkplanRunRecord(row) : null;\n }\n\n async listWorkplanRuns(filters?: { scheduleId?: string }): Promise<WorkplanRunRecord[]> {\n const rows = await this.db.select().from(workplanRuns)\n .where(filters?.scheduleId ? eq(workplanRuns.scheduleId, filters.scheduleId) : undefined)\n .orderBy(desc(workplanRuns.createdAt))\n .limit(100);\n return rows.map(toWorkplanRunRecord);\n }\n\n async appendWorkplanStepResults(\n runId: string,\n steps: WorkplanStepResultInput[],\n ): Promise<WorkplanStepResultRecord[]> {\n if (steps.length === 0) return [];\n const rows = await this.db.insert(workplanStepResults).values(\n steps.map((step) => ({\n id: makeId(\"wpstep\"),\n workplanRunId: runId,\n stepId: step.stepId,\n stepName: step.stepName,\n output: step.output ?? null,\n exitCode: step.exitCode ?? null,\n durationMs: step.durationMs ?? null,\n metadata: step.metadata ?? null,\n })),\n ).returning();\n return rows.map(toWorkplanStepResultRecord);\n }\n\n async listWorkplanStepResults(runId: string): Promise<WorkplanStepResultRecord[]> {\n const rows = await this.db.select().from(workplanStepResults)\n .where(eq(workplanStepResults.workplanRunId, runId))\n .orderBy(asc(workplanStepResults.createdAt));\n return rows.map(toWorkplanStepResultRecord);\n }\n}\n","import { isNull, sql } from \"drizzle-orm\";\nimport { index, integer, sqliteTable, text, uniqueIndex } from \"drizzle-orm/sqlite-core\";\n\nconst nowDefault = sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`;\n\nexport const tasks = sqliteTable(\"tasks\", {\n id: text(\"id\").primaryKey(),\n kind: text(\"kind\").notNull(),\n adapter: text(\"adapter\").notNull(),\n payload: text(\"payload\", { mode: \"json\" }).$type<Record<string, unknown>>().notNull().default({}),\n requires: text(\"requires\", { mode: \"json\" }).$type<string[]>().notNull().default([]),\n status: text(\"status\").notNull(),\n createdAt: text(\"created_at\").notNull().default(nowDefault),\n updatedAt: text(\"updated_at\").notNull().default(nowDefault),\n}, (table) => ({\n statusCreatedAtIdx: index(\"idx_tasks_status_created_at\").on(table.status, table.createdAt),\n}));\n\nexport const nodes = sqliteTable(\"nodes\", {\n id: text(\"id\").primaryKey(),\n name: text(\"name\").notNull(),\n capabilities: text(\"capabilities\", { mode: \"json\" }).$type<string[]>().notNull().default([]),\n status: text(\"status\").notNull().default(\"online\"),\n lastHeartbeatAt: text(\"last_heartbeat_at\"),\n createdAt: text(\"created_at\").notNull().default(nowDefault),\n updatedAt: text(\"updated_at\").notNull().default(nowDefault),\n});\n\nexport const runs = sqliteTable(\"runs\", {\n id: text(\"id\").primaryKey(),\n taskId: text(\"task_id\").notNull().references(() => tasks.id),\n nodeId: text(\"node_id\").notNull().references(() => nodes.id),\n attempt: integer(\"attempt\").notNull(),\n status: text(\"status\").notNull(),\n startedAt: text(\"started_at\"),\n endedAt: text(\"ended_at\"),\n error: text(\"error\"),\n createdAt: text(\"created_at\").notNull().default(nowDefault),\n updatedAt: text(\"updated_at\").notNull().default(nowDefault),\n}, (table) => ({\n taskIdIdx: index(\"idx_runs_task_id\").on(table.taskId),\n nodeIdIdx: index(\"idx_runs_node_id\").on(table.nodeId),\n}));\n\nexport const runLogs = sqliteTable(\"run_logs\", {\n id: integer(\"id\").primaryKey({ autoIncrement: true }),\n runId: text(\"run_id\").notNull().references(() => runs.id),\n stepName: text(\"step_name\"),\n stream: text(\"stream\").notNull(),\n message: text(\"message\").notNull(),\n timestamp: text(\"timestamp\").notNull().default(nowDefault),\n}, (table) => ({\n runIdTimestampIdx: index(\"idx_run_logs_run_id_timestamp\").on(table.runId, table.timestamp),\n}));\n\nexport const artifacts = sqliteTable(\"artifacts\", {\n id: text(\"id\").primaryKey(),\n runId: text(\"run_id\").notNull().references(() => runs.id),\n type: text(\"type\").notNull(),\n name: text(\"name\").notNull(),\n path: text(\"path\").notNull(),\n metadata: text(\"metadata\", { mode: \"json\" }).$type<Record<string, unknown>>(),\n createdAt: text(\"created_at\").notNull().default(nowDefault),\n}, (table) => ({\n runIdIdx: index(\"idx_artifacts_run_id\").on(table.runId),\n}));\n\nexport const workplanSchedules = sqliteTable(\"workplan_schedules\", {\n id: text(\"id\").primaryKey(),\n planId: text(\"plan_id\").notNull(),\n name: text(\"name\").notNull(),\n cronExpression: text(\"cron_expression\").notNull(),\n timezone: text(\"timezone\").notNull(),\n inputs: text(\"inputs\", { mode: \"json\" }).$type<Record<string, unknown>>().notNull().default({}),\n enabled: integer(\"enabled\", { mode: \"boolean\" }).notNull().default(true),\n lastRunAt: text(\"last_run_at\"),\n nextRunAt: text(\"next_run_at\"),\n createdBy: text(\"created_by\"),\n createdAt: text(\"created_at\").notNull().default(nowDefault),\n updatedAt: text(\"updated_at\").notNull().default(nowDefault),\n}, (table) => ({\n dueIdx: index(\"idx_workplan_schedules_due\").on(table.nextRunAt),\n}));\n\nexport const workplanRuns = sqliteTable(\"workplan_runs\", {\n id: text(\"id\").primaryKey(),\n scheduleId: text(\"schedule_id\").references(() => workplanSchedules.id),\n planId: text(\"plan_id\").notNull(),\n planName: text(\"plan_name\").notNull(),\n status: text(\"status\").notNull().default(\"running\"),\n idempotencyKey: text(\"idempotency_key\"),\n createdAt: text(\"created_at\").notNull().default(nowDefault),\n endedAt: text(\"ended_at\"),\n error: text(\"error\"),\n}, (table) => ({\n scheduleIdx: index(\"idx_workplan_runs_schedule_id\").on(table.scheduleId),\n idempotencyUnique: uniqueIndex(\"idx_workplan_runs_idempotency\").on(table.idempotencyKey),\n}));\n\nexport const workplanStepResults = sqliteTable(\"workplan_step_results\", {\n id: text(\"id\").primaryKey(),\n workplanRunId: text(\"workplan_run_id\").notNull().references(() => workplanRuns.id),\n stepId: text(\"step_id\").notNull(),\n stepName: text(\"step_name\").notNull(),\n output: text(\"output\"),\n exitCode: integer(\"exit_code\"),\n durationMs: integer(\"duration_ms\"),\n metadata: text(\"metadata\", { mode: \"json\" }).$type<Record<string, unknown>>(),\n createdAt: text(\"created_at\").notNull().default(nowDefault),\n}, (table) => ({\n runIdx: index(\"idx_workplan_step_results_run_id\").on(table.workplanRunId),\n}));\n\nexport const runInputEvents = sqliteTable(\"run_input_events\", {\n id: integer(\"id\").primaryKey({ autoIncrement: true }),\n runId: text(\"run_id\").notNull().references(() => runs.id),\n sequence: integer(\"sequence\").notNull(),\n kind: text(\"kind\").notNull(),\n payload: text(\"payload\", { mode: \"json\" }).$type<Record<string, unknown>>().notNull().default({}),\n createdAt: text(\"created_at\").notNull().default(nowDefault),\n deliveredAt: text(\"delivered_at\"),\n}, (table) => ({\n runSequenceUniqueIdx: uniqueIndex(\"idx_run_input_events_run_sequence\").on(table.runId, table.sequence),\n undeliveredIdx: index(\"idx_run_input_events_undelivered\").on(table.runId, table.sequence).where(isNull(table.deliveredAt)),\n}));\n","import { and, asc, desc, eq, gt, lte, sql } from \"drizzle-orm\";\nimport type { BetterSQLite3Database } from \"drizzle-orm/better-sqlite3\";\nimport { makeId } from \"../../core/src/ids.js\";\nimport { artifacts, nodes, runInputEvents, runLogs, runs, tasks, workplanRuns, workplanSchedules, workplanStepResults } from \"./schema/sqlite.js\";\nimport type * as sqliteSchema from \"./schema/sqlite.js\";\nimport type { WorkplaneStore } from \"./store-interface.js\";\nimport type {\n AppendInputEventInput,\n ArtifactInput,\n ArtifactRecord,\n CreateTaskInput,\n CreateWorkplanRunInput,\n CreateWorkplanScheduleInput,\n NodePollResult,\n NodeRecord,\n RunInputEvent,\n RunLogInput,\n RunLogRecord,\n RunRecord,\n TaskRecord,\n UpdateWorkplanScheduleInput,\n WorkplanRunRecord,\n WorkplanScheduleRecord,\n WorkplanStepResultInput,\n WorkplanStepResultRecord,\n} from \"../../types/src/index.js\";\n\ntype SqliteDb = BetterSQLite3Database<typeof sqliteSchema>;\n\nfunction nowIso(): string {\n return new Date().toISOString();\n}\n\n// Drizzle-typed mappers for simple CRUD methods (camelCase, text timestamps)\n\nfunction toTaskRecord(row: typeof tasks.$inferSelect): TaskRecord {\n return {\n id: row.id,\n kind: row.kind,\n adapter: row.adapter,\n payload: row.payload,\n requires: row.requires ?? [],\n status: row.status as TaskRecord[\"status\"],\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n\nfunction toRunRecord(row: typeof runs.$inferSelect): RunRecord {\n return {\n id: row.id,\n taskId: row.taskId,\n nodeId: row.nodeId,\n attempt: row.attempt,\n status: row.status as RunRecord[\"status\"],\n startedAt: row.startedAt ?? null,\n endedAt: row.endedAt ?? null,\n error: row.error ?? null,\n };\n}\n\nfunction toNodeRecord(row: typeof nodes.$inferSelect): NodeRecord {\n return {\n id: row.id,\n name: row.name,\n capabilities: row.capabilities ?? [],\n status: row.status as NodeRecord[\"status\"],\n lastHeartbeatAt: row.lastHeartbeatAt ?? null,\n };\n}\n\nfunction toLogRecord(row: typeof runLogs.$inferSelect): RunLogRecord {\n return {\n id: row.id,\n runId: row.runId,\n stepName: row.stepName ?? null,\n stream: row.stream as RunLogRecord[\"stream\"],\n message: row.message,\n timestamp: row.timestamp,\n };\n}\n\nfunction toArtifactRecord(row: typeof artifacts.$inferSelect): ArtifactRecord {\n return {\n id: row.id,\n runId: row.runId,\n type: row.type,\n name: row.name,\n path: row.path,\n metadata: row.metadata ?? null,\n createdAt: row.createdAt,\n };\n}\n\nfunction toInputEventRecord(row: typeof runInputEvents.$inferSelect): RunInputEvent {\n return {\n id: row.id,\n runId: row.runId,\n sequence: row.sequence,\n kind: row.kind as RunInputEvent[\"kind\"],\n payload: row.payload,\n createdAt: row.createdAt,\n deliveredAt: row.deliveredAt ?? null,\n };\n}\n\nfunction toScheduleRecord(row: typeof workplanSchedules.$inferSelect): WorkplanScheduleRecord {\n return {\n id: row.id,\n planId: row.planId,\n name: row.name,\n cronExpression: row.cronExpression,\n timezone: row.timezone,\n inputs: row.inputs ?? {},\n enabled: row.enabled,\n lastRunAt: row.lastRunAt ?? null,\n nextRunAt: row.nextRunAt ?? null,\n createdBy: row.createdBy ?? null,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n\nfunction toWorkplanRunRecord(row: typeof workplanRuns.$inferSelect): WorkplanRunRecord {\n return {\n id: row.id,\n scheduleId: row.scheduleId ?? null,\n planId: row.planId,\n planName: row.planName,\n status: row.status as WorkplanRunRecord[\"status\"],\n idempotencyKey: row.idempotencyKey ?? null,\n createdAt: row.createdAt,\n endedAt: row.endedAt ?? null,\n error: row.error ?? null,\n };\n}\n\nfunction toWorkplanStepResultRecord(row: typeof workplanStepResults.$inferSelect): WorkplanStepResultRecord {\n return {\n id: row.id,\n workplanRunId: row.workplanRunId,\n stepId: row.stepId,\n stepName: row.stepName,\n output: row.output ?? null,\n exitCode: row.exitCode ?? null,\n durationMs: row.durationMs ?? null,\n metadata: row.metadata ?? null,\n createdAt: row.createdAt,\n };\n}\n\n// Raw-row mappers for transaction methods that use $client directly.\n// better-sqlite3 rejects async transaction callbacks so we use the sync\n// $client API with prepared statements for the 4 methods that need atomicity.\n\ninterface RawTaskRow {\n id: string; kind: string; adapter: string;\n payload: string; requires: string; status: string;\n created_at: string; updated_at: string;\n}\n\ninterface RawRunRow {\n id: string; task_id: string; node_id: string; attempt: number; status: string;\n started_at: string | null; ended_at: string | null; error: string | null;\n created_at: string; updated_at: string;\n}\n\nfunction mapRawTask(row: RawTaskRow): TaskRecord {\n return {\n id: row.id, kind: row.kind, adapter: row.adapter,\n payload: JSON.parse(row.payload) as Record<string, unknown>,\n requires: JSON.parse(row.requires) as string[],\n status: row.status as TaskRecord[\"status\"],\n createdAt: row.created_at, updatedAt: row.updated_at,\n };\n}\n\nfunction mapRawRun(row: RawRunRow): RunRecord {\n return {\n id: row.id, taskId: row.task_id, nodeId: row.node_id,\n attempt: row.attempt, status: row.status as RunRecord[\"status\"],\n startedAt: row.started_at ?? null, endedAt: row.ended_at ?? null,\n error: row.error ?? null,\n };\n}\n\nexport class SqliteStore implements WorkplaneStore {\n constructor(private readonly db: SqliteDb) {}\n\n async createTask(input: CreateTaskInput): Promise<TaskRecord> {\n const now = nowIso();\n const [row] = await this.db.insert(tasks).values({\n id: makeId(\"task\"),\n kind: input.kind,\n adapter: input.adapter,\n payload: input.payload,\n requires: input.requires ?? [],\n status: \"queued\",\n createdAt: now,\n updatedAt: now,\n }).returning();\n return toTaskRecord(row);\n }\n\n async listTasks(status?: TaskRecord[\"status\"]): Promise<TaskRecord[]> {\n const rows = await this.db.select().from(tasks)\n .where(status ? eq(tasks.status, status) : undefined)\n .orderBy(desc(tasks.createdAt)).limit(100);\n return rows.map(toTaskRecord);\n }\n\n async getTask(taskId: string): Promise<TaskRecord | null> {\n const [row] = await this.db.select().from(tasks).where(eq(tasks.id, taskId));\n return row ? toTaskRecord(row) : null;\n }\n\n async listRuns(filters?: { taskId?: string; status?: RunRecord[\"status\"] }): Promise<RunRecord[]> {\n const conds = [];\n if (filters?.taskId) conds.push(eq(runs.taskId, filters.taskId));\n if (filters?.status) conds.push(eq(runs.status, filters.status));\n const rows = await this.db.select().from(runs)\n .where(conds.length ? and(...conds) : undefined)\n .orderBy(desc(runs.createdAt)).limit(100);\n return rows.map(toRunRecord);\n }\n\n async getRun(runId: string): Promise<RunRecord | null> {\n const [row] = await this.db.select().from(runs).where(eq(runs.id, runId));\n return row ? toRunRecord(row) : null;\n }\n\n async registerNode(name: string, capabilities: string[], preferredId?: string): Promise<NodeRecord> {\n const now = nowIso();\n const [existing] = await this.db.select().from(nodes).where(eq(nodes.name, name));\n if (existing) {\n const [updated] = await this.db.update(nodes)\n .set({ capabilities, status: \"online\", lastHeartbeatAt: now, updatedAt: now })\n .where(eq(nodes.id, existing.id)).returning();\n return toNodeRecord(updated);\n }\n const [inserted] = await this.db.insert(nodes).values({\n id: preferredId ?? makeId(\"node\"), name, capabilities,\n status: \"online\", lastHeartbeatAt: now, createdAt: now, updatedAt: now,\n }).returning();\n return toNodeRecord(inserted);\n }\n\n async getRunCancellationState(runId: string): Promise<{ runStatus: string; taskStatus: string } | null> {\n const [row] = await this.db\n .select({ runStatus: runs.status, taskStatus: tasks.status })\n .from(runs).innerJoin(tasks, eq(tasks.id, runs.taskId)).where(eq(runs.id, runId));\n return row ? { runStatus: row.runStatus, taskStatus: row.taskStatus ?? \"\" } : null;\n }\n\n // Transaction methods use this.db.$client directly because better-sqlite3\n // rejects async callbacks — all operations inside must be synchronous.\n\n async pollNode(nodeId: string, capabilities: string[]): Promise<NodePollResult | null> {\n const now = nowIso();\n await this.db.update(nodes)\n .set({ capabilities, status: \"online\", lastHeartbeatAt: now, updatedAt: now })\n .where(eq(nodes.id, nodeId));\n\n const raw = this.db.$client;\n return raw.transaction(() => {\n const capSet = new Set(capabilities);\n const queued = raw.prepare(\n \"SELECT * FROM tasks WHERE status = 'queued' ORDER BY created_at ASC\",\n ).all() as RawTaskRow[];\n\n const task = queued.find((t) =>\n (JSON.parse(t.requires) as string[]).every((r) => capSet.has(r)),\n );\n if (!task) return null;\n\n const { max_attempt } = raw.prepare(\n \"SELECT COALESCE(MAX(attempt), 0) AS max_attempt FROM runs WHERE task_id = ?\",\n ).get(task.id) as { max_attempt: number };\n\n const runId = makeId(\"run\");\n raw.prepare(\n \"INSERT INTO runs (id, task_id, node_id, attempt, status, started_at, created_at, updated_at) VALUES (?, ?, ?, ?, 'assigned', ?, ?, ?)\",\n ).run(runId, task.id, nodeId, max_attempt + 1, now, now, now);\n\n raw.prepare(\"UPDATE tasks SET status = 'assigned', updated_at = ? WHERE id = ?\").run(now, task.id);\n\n return {\n task: mapRawTask(task),\n run: mapRawRun(raw.prepare(\"SELECT * FROM runs WHERE id = ?\").get(runId) as RawRunRow),\n };\n })();\n }\n\n async updateRunStatus(runId: string, status: RunRecord[\"status\"], error?: string): Promise<RunRecord | null> {\n const raw = this.db.$client;\n return raw.transaction(() => {\n const current = raw.prepare(\n \"SELECT r.*, t.status AS task_status FROM runs r JOIN tasks t ON t.id = r.task_id WHERE r.id = ?\",\n ).get(runId) as (RawRunRow & { task_status: string }) | undefined;\n if (!current) return null;\n\n if (current.task_status === \"cancelled\" && status !== \"cancelled\") {\n return mapRawRun(current);\n }\n\n const now = nowIso();\n const isTerminal = status === \"succeeded\" || status === \"failed\" || status === \"cancelled\";\n if (isTerminal) {\n raw.prepare(\n \"UPDATE runs SET status = ?, error = ?, ended_at = ?, updated_at = ? WHERE id = ?\",\n ).run(status, error ?? null, now, now, runId);\n } else {\n raw.prepare(\n \"UPDATE runs SET status = ?, error = ?, updated_at = ? WHERE id = ?\",\n ).run(status, error ?? null, now, runId);\n }\n\n const updatedRun = raw.prepare(\"SELECT * FROM runs WHERE id = ?\").get(runId) as RawRunRow;\n const nextTaskStatus = isTerminal ? status : \"running\";\n if (current.task_status !== \"cancelled\" || status === \"cancelled\") {\n raw.prepare(\"UPDATE tasks SET status = ?, updated_at = ? WHERE id = ?\").run(\n nextTaskStatus, now, updatedRun.task_id,\n );\n }\n return mapRawRun(updatedRun);\n })();\n }\n\n async appendRunLogs(runId: string, logs: RunLogInput[]): Promise<number> {\n if (logs.length === 0) return 0;\n const now = nowIso();\n await this.db.insert(runLogs).values(\n logs.map((log) => ({\n runId, stepName: log.stepName ?? null,\n stream: log.stream, message: log.message, timestamp: now,\n })),\n );\n return logs.length;\n }\n\n async getRunLogs(runId: string): Promise<RunLogRecord[]> {\n const rows = await this.db.select().from(runLogs)\n .where(eq(runLogs.runId, runId)).orderBy(asc(runLogs.timestamp));\n return rows.map(toLogRecord);\n }\n\n async createArtifact(runId: string, input: ArtifactInput): Promise<ArtifactRecord> {\n const now = nowIso();\n const [row] = await this.db.insert(artifacts).values({\n id: makeId(\"artifact\"), runId, type: input.type, name: input.name,\n path: input.path, metadata: input.metadata ?? null, createdAt: now,\n }).returning();\n return toArtifactRecord(row);\n }\n\n async listRunArtifacts(runId: string): Promise<ArtifactRecord[]> {\n const rows = await this.db.select().from(artifacts)\n .where(eq(artifacts.runId, runId)).orderBy(asc(artifacts.createdAt));\n return rows.map(toArtifactRecord);\n }\n\n async retryTask(taskId: string): Promise<TaskRecord | null> {\n const [row] = await this.db.update(tasks)\n .set({ status: \"queued\", updatedAt: nowIso() })\n .where(and(eq(tasks.id, taskId), eq(tasks.status, \"failed\"))).returning();\n return row ? toTaskRecord(row) : null;\n }\n\n async cancelTask(taskId: string): Promise<TaskRecord | null> {\n const raw = this.db.$client;\n return raw.transaction(() => {\n const taskRow = raw.prepare(\"SELECT * FROM tasks WHERE id = ?\").get(taskId) as RawTaskRow | undefined;\n if (!taskRow) return null;\n\n const terminal = new Set([\"succeeded\", \"failed\", \"cancelled\"]);\n if (terminal.has(taskRow.status)) return null;\n\n const now = nowIso();\n if (taskRow.status === \"assigned\" || taskRow.status === \"running\") {\n const latestRun = raw.prepare(\n \"SELECT * FROM runs WHERE task_id = ? ORDER BY attempt DESC LIMIT 1\",\n ).get(taskId) as RawRunRow | undefined;\n\n if (latestRun && !terminal.has(latestRun.status)) {\n raw.prepare(\n \"UPDATE runs SET status = 'cancelled', ended_at = ?, updated_at = ?, error = COALESCE(error, 'task cancelled') WHERE id = ?\",\n ).run(now, now, latestRun.id);\n }\n }\n\n raw.prepare(\"UPDATE tasks SET status = 'cancelled', updated_at = ? WHERE id = ?\").run(now, taskId);\n return mapRawTask(raw.prepare(\"SELECT * FROM tasks WHERE id = ?\").get(taskId) as RawTaskRow);\n })();\n }\n\n async appendInputEvent(runId: string, input: AppendInputEventInput): Promise<RunInputEvent> {\n const raw = this.db.$client;\n const now = nowIso();\n const event = raw.transaction(() => {\n const { next_seq } = raw.prepare(\n \"SELECT COALESCE(MAX(sequence), 0) + 1 AS next_seq FROM run_input_events WHERE run_id = ?\",\n ).get(runId) as { next_seq: number };\n\n raw.prepare(\n \"INSERT INTO run_input_events (run_id, sequence, kind, payload, created_at) VALUES (?, ?, ?, ?, ?)\",\n ).run(runId, next_seq, input.kind, JSON.stringify(input.payload), now);\n\n return raw.prepare(\n \"SELECT * FROM run_input_events WHERE run_id = ? AND sequence = ?\",\n ).get(runId, next_seq) as {\n id: number; run_id: string; sequence: number; kind: string;\n payload: string; created_at: string; delivered_at: string | null;\n };\n })();\n\n return {\n id: event.id, runId: event.run_id, sequence: event.sequence,\n kind: event.kind as RunInputEvent[\"kind\"],\n payload: JSON.parse(event.payload) as Record<string, unknown>,\n createdAt: event.created_at, deliveredAt: event.delivered_at ?? null,\n };\n }\n\n async getInputEvents(runId: string, afterSequence: number): Promise<RunInputEvent[]> {\n const rows = await this.db.select().from(runInputEvents)\n .where(and(eq(runInputEvents.runId, runId), gt(runInputEvents.sequence, afterSequence)))\n .orderBy(asc(runInputEvents.sequence));\n return rows.map(toInputEventRecord);\n }\n\n async markInputDelivered(runId: string, sequence: number): Promise<void> {\n await this.db.update(runInputEvents)\n .set({ deliveredAt: nowIso() })\n .where(and(eq(runInputEvents.runId, runId), eq(runInputEvents.sequence, sequence)));\n }\n\n async createWorkplanSchedule(input: CreateWorkplanScheduleInput): Promise<WorkplanScheduleRecord> {\n const now = nowIso();\n const [row] = await this.db.insert(workplanSchedules).values({\n id: makeId(\"sched\"),\n planId: input.planId,\n name: input.name,\n cronExpression: input.cronExpression,\n timezone: input.timezone,\n inputs: input.inputs ?? {},\n enabled: input.enabled ?? true,\n nextRunAt: input.enabled === false ? null : input.nextRunAt ?? null,\n createdBy: input.createdBy ?? null,\n createdAt: now,\n updatedAt: now,\n }).returning();\n return toScheduleRecord(row);\n }\n\n async listWorkplanSchedules(enabled?: boolean): Promise<WorkplanScheduleRecord[]> {\n const rows = await this.db.select().from(workplanSchedules)\n .where(enabled === undefined ? undefined : eq(workplanSchedules.enabled, enabled))\n .orderBy(desc(workplanSchedules.createdAt));\n return rows.map(toScheduleRecord);\n }\n\n async getWorkplanSchedule(scheduleId: string): Promise<WorkplanScheduleRecord | null> {\n const [row] = await this.db.select().from(workplanSchedules).where(eq(workplanSchedules.id, scheduleId));\n return row ? toScheduleRecord(row) : null;\n }\n\n async updateWorkplanSchedule(\n scheduleId: string,\n input: UpdateWorkplanScheduleInput,\n ): Promise<WorkplanScheduleRecord | null> {\n const patch: Partial<typeof workplanSchedules.$inferInsert> = { updatedAt: nowIso() };\n if (input.name !== undefined) patch.name = input.name;\n if (input.cronExpression !== undefined) patch.cronExpression = input.cronExpression;\n if (input.timezone !== undefined) patch.timezone = input.timezone;\n if (input.inputs !== undefined) patch.inputs = input.inputs;\n if (input.enabled !== undefined) patch.enabled = input.enabled;\n if (input.nextRunAt !== undefined) patch.nextRunAt = input.nextRunAt;\n\n const [row] = await this.db.update(workplanSchedules)\n .set(patch)\n .where(eq(workplanSchedules.id, scheduleId))\n .returning();\n return row ? toScheduleRecord(row) : null;\n }\n\n async deleteWorkplanSchedule(scheduleId: string): Promise<boolean> {\n const raw = this.db.$client;\n return raw.transaction(() => {\n raw.prepare(\"UPDATE workplan_runs SET schedule_id = NULL WHERE schedule_id = ?\").run(scheduleId);\n const result = raw.prepare(\"DELETE FROM workplan_schedules WHERE id = ?\").run(scheduleId);\n return (result.changes ?? 0) > 0;\n })();\n }\n\n async listDueWorkplanSchedules(asOf: string): Promise<WorkplanScheduleRecord[]> {\n const rows = await this.db.select().from(workplanSchedules)\n .where(and(\n eq(workplanSchedules.enabled, true),\n sql`${workplanSchedules.nextRunAt} IS NOT NULL`,\n lte(workplanSchedules.nextRunAt, asOf),\n ))\n .orderBy(asc(workplanSchedules.nextRunAt));\n return rows.map(toScheduleRecord);\n }\n\n async markWorkplanScheduleRan(scheduleId: string, lastRunAt: string, nextRunAt: string): Promise<void> {\n await this.db.update(workplanSchedules)\n .set({ lastRunAt, nextRunAt, updatedAt: nowIso() })\n .where(eq(workplanSchedules.id, scheduleId));\n }\n\n async tryCreateWorkplanRun(input: CreateWorkplanRunInput): Promise<WorkplanRunRecord | null> {\n const now = nowIso();\n try {\n const [row] = await this.db.insert(workplanRuns).values({\n id: makeId(\"wprun\"),\n scheduleId: input.scheduleId ?? null,\n planId: input.planId,\n planName: input.planName,\n status: \"running\",\n idempotencyKey: input.idempotencyKey ?? null,\n createdAt: now,\n }).returning();\n return toWorkplanRunRecord(row);\n } catch (err) {\n if (input.idempotencyKey && err instanceof Error && err.message.includes(\"UNIQUE constraint failed\")) {\n return null;\n }\n throw err;\n }\n }\n\n async updateWorkplanRun(\n runId: string,\n status: WorkplanRunRecord[\"status\"],\n error?: string,\n endedAt?: string,\n ): Promise<WorkplanRunRecord | null> {\n const [row] = await this.db.update(workplanRuns)\n .set({\n status,\n error: error ?? null,\n endedAt: endedAt ?? (status !== \"running\" ? nowIso() : null),\n })\n .where(eq(workplanRuns.id, runId))\n .returning();\n return row ? toWorkplanRunRecord(row) : null;\n }\n\n async getWorkplanRun(runId: string): Promise<WorkplanRunRecord | null> {\n const [row] = await this.db.select().from(workplanRuns).where(eq(workplanRuns.id, runId));\n return row ? toWorkplanRunRecord(row) : null;\n }\n\n async listWorkplanRuns(filters?: { scheduleId?: string }): Promise<WorkplanRunRecord[]> {\n const rows = await this.db.select().from(workplanRuns)\n .where(filters?.scheduleId ? eq(workplanRuns.scheduleId, filters.scheduleId) : undefined)\n .orderBy(desc(workplanRuns.createdAt))\n .limit(100);\n return rows.map(toWorkplanRunRecord);\n }\n\n async appendWorkplanStepResults(\n runId: string,\n steps: WorkplanStepResultInput[],\n ): Promise<WorkplanStepResultRecord[]> {\n if (steps.length === 0) return [];\n const now = nowIso();\n const rows = await this.db.insert(workplanStepResults).values(\n steps.map((step) => ({\n id: makeId(\"wpstep\"),\n workplanRunId: runId,\n stepId: step.stepId,\n stepName: step.stepName,\n output: step.output ?? null,\n exitCode: step.exitCode ?? null,\n durationMs: step.durationMs ?? null,\n metadata: step.metadata ?? null,\n createdAt: now,\n })),\n ).returning();\n return rows.map(toWorkplanStepResultRecord);\n }\n\n async listWorkplanStepResults(runId: string): Promise<WorkplanStepResultRecord[]> {\n const rows = await this.db.select().from(workplanStepResults)\n .where(eq(workplanStepResults.workplanRunId, runId))\n .orderBy(asc(workplanStepResults.createdAt));\n return rows.map(toWorkplanStepResultRecord);\n }\n}\n","import { mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { drizzle as drizzlePg } from \"drizzle-orm/node-postgres\";\nimport type { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport { drizzle as drizzleSqlite } from \"drizzle-orm/better-sqlite3\";\nimport Database from \"better-sqlite3\";\nimport { Pool } from \"pg\";\nimport { PgStore } from \"./pg-store.js\";\nimport { SqliteStore } from \"./sqlite-store.js\";\nimport * as pgSchema from \"./schema/pg.js\";\nimport * as sqliteSchema from \"./schema/sqlite.js\";\nimport type { WorkplaneStore } from \"./store-interface.js\";\n\nexport type DrizzleDb = NodePgDatabase<typeof pgSchema>;\n\nexport interface StoreHandle {\n store: WorkplaneStore;\n close: () => Promise<void>;\n}\n\nconst pools = new Map<string, Pool>();\n\nexport function getPool(databaseUrl: string): Pool {\n const existing = pools.get(databaseUrl);\n if (existing) return existing;\n const pool = new Pool({ connectionString: databaseUrl });\n pools.set(databaseUrl, pool);\n return pool;\n}\n\nexport function getDrizzle(pool: Pool): DrizzleDb {\n return drizzlePg(pool, { schema: pgSchema });\n}\n\nfunction parseSqlitePath(url: string): string {\n const path = url.startsWith(\"sqlite://\") ? url.slice(\"sqlite://\".length) : url;\n if (!path) return join(homedir(), \".workplane\", \"workplane.db\");\n if (path.startsWith(\"~/\")) return join(homedir(), path.slice(2));\n return path;\n}\n\nexport function createStore(databaseUrl?: string): StoreHandle {\n const url = databaseUrl ?? \"\";\n\n if (url.startsWith(\"postgres://\") || url.startsWith(\"postgresql://\")) {\n const pool = getPool(url);\n return {\n store: new PgStore(getDrizzle(pool)),\n close: () => pool.end(),\n };\n }\n\n const filePath = parseSqlitePath(url);\n mkdirSync(dirname(filePath), { recursive: true });\n const sqlite = new Database(filePath);\n sqlite.pragma(\"journal_mode = WAL\");\n sqlite.pragma(\"synchronous = NORMAL\");\n const db = drizzleSqlite(sqlite, { schema: sqliteSchema });\n return {\n store: new SqliteStore(db),\n close: async () => { sqlite.close(); },\n };\n}\n","import { createHash } from \"node:crypto\";\nimport { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport type pg from \"pg\";\n\ninterface JournalEntry {\n idx: number;\n when: number;\n tag: string;\n}\n\ninterface Journal {\n entries: JournalEntry[];\n}\n\nfunction readJournal(migrationsFolder: string): Journal {\n return JSON.parse(\n readFileSync(resolve(migrationsFolder, \"meta/_journal.json\"), \"utf8\"),\n ) as Journal;\n}\n\nfunction migrationHash(migrationsFolder: string, tag: string): string {\n const sql = readFileSync(resolve(migrationsFolder, `${tag}.sql`), \"utf8\");\n return createHash(\"sha256\").update(sql).digest(\"hex\");\n}\n\nfunction baselineEntries(migrationsFolder: string, upToIdx: number): Array<{ hash: string; createdAt: number; tag: string }> {\n const journal = readJournal(migrationsFolder);\n return journal.entries\n .filter((entry) => entry.idx <= upToIdx)\n .map((entry) => ({\n hash: migrationHash(migrationsFolder, entry.tag),\n createdAt: entry.when,\n tag: entry.tag,\n }));\n}\n\nasync function tableExistsPg(pool: pg.Pool, tableName: string): Promise<boolean> {\n const result = await pool.query<{ exists: boolean }>(\n `SELECT EXISTS (\n SELECT 1 FROM information_schema.tables\n WHERE table_schema = 'public' AND table_name = $1\n ) AS exists`,\n [tableName],\n );\n return Boolean(result.rows[0]?.exists);\n}\n\nasync function getLastMigrationCreatedAtPg(pool: pg.Pool): Promise<number | null> {\n await pool.query('CREATE SCHEMA IF NOT EXISTS drizzle');\n await pool.query(`\n CREATE TABLE IF NOT EXISTS drizzle.__drizzle_migrations (\n id SERIAL PRIMARY KEY,\n hash text NOT NULL,\n created_at bigint\n )\n `);\n\n const result = await pool.query<{ created_at: string | null }>(\n \"SELECT created_at FROM drizzle.__drizzle_migrations ORDER BY created_at DESC LIMIT 1\",\n );\n const value = result.rows[0]?.created_at;\n return value === null || value === undefined ? null : Number(value);\n}\n\nasync function insertBaselinePg(\n pool: pg.Pool,\n entries: Array<{ hash: string; createdAt: number; tag: string }>,\n): Promise<string[]> {\n const inserted: string[] = [];\n const lastCreatedAt = await getLastMigrationCreatedAtPg(pool);\n\n for (const entry of entries) {\n if (lastCreatedAt !== null && lastCreatedAt >= entry.createdAt) {\n continue;\n }\n\n const existing = await pool.query<{ id: number }>(\n \"SELECT id FROM drizzle.__drizzle_migrations WHERE created_at = $1 LIMIT 1\",\n [entry.createdAt],\n );\n if (existing.rowCount) {\n continue;\n }\n\n await pool.query(\n 'INSERT INTO drizzle.__drizzle_migrations (\"hash\", \"created_at\") VALUES ($1, $2)',\n [entry.hash, entry.createdAt],\n );\n inserted.push(entry.tag);\n }\n\n return inserted;\n}\n\nexport async function baselineLegacyPostgresMigrations(\n pool: pg.Pool,\n migrationsFolder: string,\n): Promise<void> {\n const hasTasks = await tableExistsPg(pool, \"tasks\");\n if (!hasTasks) {\n return;\n }\n\n const hasWorkplanSchedules = await tableExistsPg(pool, \"workplan_schedules\");\n const upToIdx = hasWorkplanSchedules ? 1 : 0;\n const inserted = await insertBaselinePg(pool, baselineEntries(migrationsFolder, upToIdx));\n\n if (inserted.length > 0) {\n process.stdout.write(\n `Baselined existing Postgres schema for migration(s): ${inserted.join(\", \")}\\n`,\n );\n }\n}\n\nfunction tableExistsSqlite(db: import(\"better-sqlite3\").Database, tableName: string): boolean {\n const row = db.prepare(\n \"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ? LIMIT 1\",\n ).get(tableName);\n return Boolean(row);\n}\n\nfunction getLastMigrationCreatedAtSqlite(db: import(\"better-sqlite3\").Database): number | null {\n db.exec(`\n CREATE TABLE IF NOT EXISTS __drizzle_migrations (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n hash text NOT NULL,\n created_at numeric\n )\n `);\n\n const row = db.prepare(\n \"SELECT created_at FROM __drizzle_migrations ORDER BY created_at DESC LIMIT 1\",\n ).get() as { created_at: number } | undefined;\n\n return row?.created_at ?? null;\n}\n\nfunction insertBaselineSqlite(\n db: import(\"better-sqlite3\").Database,\n entries: Array<{ hash: string; createdAt: number; tag: string }>,\n): string[] {\n const inserted: string[] = [];\n const lastCreatedAt = getLastMigrationCreatedAtSqlite(db);\n\n const insert = db.prepare(\n 'INSERT INTO __drizzle_migrations (\"hash\", \"created_at\") VALUES (?, ?)',\n );\n const exists = db.prepare(\n \"SELECT 1 FROM __drizzle_migrations WHERE created_at = ? LIMIT 1\",\n );\n\n for (const entry of entries) {\n if (lastCreatedAt !== null && lastCreatedAt >= entry.createdAt) {\n continue;\n }\n if (exists.get(entry.createdAt)) {\n continue;\n }\n insert.run(entry.hash, entry.createdAt);\n inserted.push(entry.tag);\n }\n\n return inserted;\n}\n\nexport function baselineLegacySqliteMigrations(\n db: import(\"better-sqlite3\").Database,\n migrationsFolder: string,\n): void {\n if (!tableExistsSqlite(db, \"tasks\")) {\n return;\n }\n\n const hasWorkplanSchedules = tableExistsSqlite(db, \"workplan_schedules\");\n const upToIdx = hasWorkplanSchedules ? 1 : 0;\n const inserted = insertBaselineSqlite(db, baselineEntries(migrationsFolder, upToIdx));\n\n if (inserted.length > 0) {\n process.stdout.write(\n `Baselined existing SQLite schema for migration(s): ${inserted.join(\", \")}\\n`,\n );\n }\n}\n","// Side-effect-free module — safe to import from both the migrate CLI and setup wizard.\nimport { mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { getDrizzle, getPool } from \"./client.js\";\nimport { baselineLegacyPostgresMigrations, baselineLegacySqliteMigrations } from \"./baseline-migrations.js\";\n\nconst distDir = dirname(fileURLToPath(import.meta.url));\n\nasync function migratePostgres(databaseUrl: string): Promise<void> {\n const adminUrl = new URL(databaseUrl);\n const dbName = adminUrl.pathname.replace(/^\\//, \"\");\n if (!dbName) throw new Error(\"DATABASE_URL must include a database name\");\n\n adminUrl.pathname = \"/postgres\";\n const adminPool = getPool(adminUrl.toString());\n try {\n const { rowCount } = await adminPool.query(\n \"SELECT 1 FROM pg_database WHERE datname = $1\",\n [dbName],\n );\n if (!rowCount) {\n await adminPool.query(`CREATE DATABASE \"${dbName.replaceAll('\"', '\"\"')}\"`);\n process.stdout.write(`Created database: ${dbName}\\n`);\n }\n } catch (err) {\n const e = err as { code?: string };\n if (e.code === \"42501\") {\n throw new Error(\n `Database \"${dbName}\" does not exist and the current role lacks CREATE DATABASE permissions.`,\n );\n }\n throw err;\n } finally {\n await adminPool.end();\n }\n\n const pool = getPool(databaseUrl);\n const migrationsFolder = resolve(distDir, \"migrations/pg\");\n await baselineLegacyPostgresMigrations(pool, migrationsFolder);\n const { migrate } = await import(\"drizzle-orm/node-postgres/migrator\");\n await migrate(getDrizzle(pool), { migrationsFolder });\n await pool.end();\n process.stdout.write(\"Postgres migrations applied.\\n\");\n}\n\nasync function migrateSqlite(databaseUrl: string): Promise<void> {\n const { default: Database } = await import(\"better-sqlite3\");\n const { drizzle } = await import(\"drizzle-orm/better-sqlite3\");\n const { migrate } = await import(\"drizzle-orm/better-sqlite3/migrator\");\n const sqliteSchema = await import(\"./schema/sqlite.js\");\n\n const rawPath = databaseUrl.startsWith(\"sqlite://\")\n ? databaseUrl.slice(\"sqlite://\".length)\n : databaseUrl;\n const filePath = !rawPath\n ? join(homedir(), \".workplane\", \"workplane.db\")\n : rawPath.startsWith(\"~/\") ? join(homedir(), rawPath.slice(2)) : rawPath;\n\n mkdirSync(dirname(filePath), { recursive: true });\n const sqlite = new Database(filePath);\n sqlite.pragma(\"journal_mode = WAL\");\n sqlite.pragma(\"synchronous = NORMAL\");\n const migrationsFolder = resolve(distDir, \"migrations/sqlite\");\n baselineLegacySqliteMigrations(sqlite, migrationsFolder);\n const db = drizzle(sqlite, { schema: sqliteSchema });\n migrate(db, { migrationsFolder });\n sqlite.close();\n process.stdout.write(`SQLite migrations applied: ${filePath}\\n`);\n}\n\nexport async function runMigration(databaseUrl: string): Promise<void> {\n if (databaseUrl.startsWith(\"postgres://\") || databaseUrl.startsWith(\"postgresql://\")) {\n await migratePostgres(databaseUrl);\n } else {\n await migrateSqlite(databaseUrl);\n }\n}\n","import { randomBytes } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { createInterface } from \"node:readline/promises\";\n\nconst workplaneDir = join(homedir(), \".workplane\");\nconst configPath = join(workplaneDir, \".env\");\n\nfunction generateToken(): string {\n return randomBytes(32).toString(\"hex\");\n}\n\nfunction parseEnvFile(path: string): Record<string, string> {\n try {\n const result: Record<string, string> = {};\n for (const line of readFileSync(path, \"utf8\").split(\"\\n\")) {\n const t = line.trim();\n if (!t || t.startsWith(\"#\")) continue;\n const eq = t.indexOf(\"=\");\n if (eq !== -1) result[t.slice(0, eq).trim()] = t.slice(eq + 1).trim();\n }\n return result;\n } catch {\n return {};\n }\n}\n\nasync function ask(\n rl: Awaited<ReturnType<typeof createInterface>>,\n label: string,\n def: string,\n): Promise<string> {\n const answer = await rl.question(` ${label} [${def}]: `);\n return answer.trim() || def;\n}\n\nasync function main(): Promise<void> {\n const existing = parseEnvFile(configPath);\n const isUpdate = existsSync(configPath);\n\n process.stdout.write(\"Workplane Setup\\n\");\n process.stdout.write(\n isUpdate\n ? `Updating config at ${configPath}\\n`\n : `Creating config at ${configPath}\\n`,\n );\n process.stdout.write(\"Press Enter to accept the value shown in brackets.\\n\\n\");\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n\n const dbUrl = await ask(\n rl, \"DATABASE_URL \",\n existing.DATABASE_URL ?? \"sqlite://~/.workplane/workplane.db\",\n );\n const port = await ask(\n rl, \"Server port \",\n existing.WORKPLANE_SERVER_PORT ?? \"8787\",\n );\n const serverUrl = await ask(\n rl, \"Server URL (for nodes)\",\n existing.WORKPLANE_SERVER_URL ?? `http://localhost:${port}`,\n );\n const nodeToken = await ask(\n rl, \"Node token \",\n existing.WORKPLANE_NODE_TOKEN ?? generateToken(),\n );\n const operatorToken = await ask(\n rl, \"Operator token \",\n existing.WORKPLANE_OPERATOR_TOKEN ?? generateToken(),\n );\n\n rl.close();\n\n const config = [\n `DATABASE_URL=${dbUrl}`,\n `WORKPLANE_SERVER_PORT=${port}`,\n `WORKPLANE_SERVER_URL=${serverUrl}`,\n `WORKPLANE_NODE_TOKEN=${nodeToken}`,\n `WORKPLANE_OPERATOR_TOKEN=${operatorToken}`,\n ].join(\"\\n\") + \"\\n\";\n\n mkdirSync(workplaneDir, { recursive: true });\n writeFileSync(configPath, config);\n process.stdout.write(`\\nConfig saved: ${configPath}\\n`);\n\n process.stdout.write(\"Running migrations...\\n\");\n const { runMigration } = await import(\"../../db/src/migration.js\");\n await runMigration(dbUrl);\n\n process.stdout.write(`\nReady. Start your fleet:\n\n workplane-server # control plane\n workplane-node # worker (this machine or another)\n workplane tasks # inspect submitted work\n\nTokens for remote machines:\n WORKPLANE_NODE_TOKEN=${nodeToken}\n WORKPLANE_OPERATOR_TOKEN=${operatorToken}\n`);\n}\n\nmain().catch((error) => {\n process.stderr.write(`${String(error)}\\n`);\n process.exit(1);\n});\n"],"mappings":";;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAAS,QAAQ,WAAW;AAC5B,SAAS,QAAQ,WAAW,SAAS,OAAO,SAAS,OAAO,SAAS,MAAM,WAAW,mBAAmB;AADzG,IAGa,OAaA,OAUA,MAgBA,SAWA,WAYA,mBAiBA,cAeA,qBAcA;AA/Gb;AAAA;AAAA;AAGO,IAAM,QAAQ,QAAQ,SAAS;AAAA,MACpC,IAAI,KAAK,IAAI,EAAE,WAAW;AAAA,MAC1B,MAAM,KAAK,MAAM,EAAE,QAAQ;AAAA,MAC3B,SAAS,KAAK,SAAS,EAAE,QAAQ;AAAA,MACjC,SAAS,MAAM,SAAS,EAAE,MAA+B,EAAE,QAAQ;AAAA,MACnE,UAAU,KAAK,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,iBAAiB;AAAA,MACtE,QAAQ,KAAK,QAAQ,EAAE,QAAQ;AAAA,MAC/B,WAAW,UAAU,cAAc,EAAE,cAAc,KAAK,CAAC,EAAE,QAAQ,EAAE,WAAW;AAAA,MAChF,WAAW,UAAU,cAAc,EAAE,cAAc,KAAK,CAAC,EAAE,QAAQ,EAAE,WAAW;AAAA,IAClF,GAAG,CAAC,WAAW;AAAA,MACb,oBAAoB,MAAM,6BAA6B,EAAE,GAAG,MAAM,QAAQ,MAAM,SAAS;AAAA,IAC3F,EAAE;AAEK,IAAM,QAAQ,QAAQ,SAAS;AAAA,MACpC,IAAI,KAAK,IAAI,EAAE,WAAW;AAAA,MAC1B,MAAM,KAAK,MAAM,EAAE,QAAQ;AAAA,MAC3B,cAAc,KAAK,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,iBAAiB;AAAA,MAC9E,QAAQ,KAAK,QAAQ,EAAE,QAAQ,EAAE,QAAQ,QAAQ;AAAA,MACjD,iBAAiB,UAAU,qBAAqB,EAAE,cAAc,KAAK,CAAC;AAAA,MACtE,WAAW,UAAU,cAAc,EAAE,cAAc,KAAK,CAAC,EAAE,QAAQ,EAAE,WAAW;AAAA,MAChF,WAAW,UAAU,cAAc,EAAE,cAAc,KAAK,CAAC,EAAE,QAAQ,EAAE,WAAW;AAAA,IAClF,CAAC;AAEM,IAAM,OAAO,QAAQ,QAAQ;AAAA,MAClC,IAAI,KAAK,IAAI,EAAE,WAAW;AAAA,MAC1B,QAAQ,KAAK,SAAS,EAAE,QAAQ,EAAE,WAAW,MAAM,MAAM,EAAE;AAAA,MAC3D,QAAQ,KAAK,SAAS,EAAE,QAAQ,EAAE,WAAW,MAAM,MAAM,EAAE;AAAA,MAC3D,SAAS,QAAQ,SAAS,EAAE,QAAQ;AAAA,MACpC,QAAQ,KAAK,QAAQ,EAAE,QAAQ;AAAA,MAC/B,WAAW,UAAU,cAAc,EAAE,cAAc,KAAK,CAAC;AAAA,MACzD,SAAS,UAAU,YAAY,EAAE,cAAc,KAAK,CAAC;AAAA,MACrD,OAAO,KAAK,OAAO;AAAA,MACnB,WAAW,UAAU,cAAc,EAAE,cAAc,KAAK,CAAC,EAAE,QAAQ,EAAE,WAAW;AAAA,MAChF,WAAW,UAAU,cAAc,EAAE,cAAc,KAAK,CAAC,EAAE,QAAQ,EAAE,WAAW;AAAA,IAClF,GAAG,CAAC,WAAW;AAAA,MACb,WAAW,MAAM,kBAAkB,EAAE,GAAG,MAAM,MAAM;AAAA,MACpD,WAAW,MAAM,kBAAkB,EAAE,GAAG,MAAM,MAAM;AAAA,IACtD,EAAE;AAEK,IAAM,UAAU,QAAQ,YAAY;AAAA,MACzC,IAAI,UAAU,MAAM,EAAE,MAAM,SAAS,CAAC,EAAE,WAAW;AAAA,MACnD,OAAO,KAAK,QAAQ,EAAE,QAAQ,EAAE,WAAW,MAAM,KAAK,EAAE;AAAA,MACxD,UAAU,KAAK,WAAW;AAAA,MAC1B,QAAQ,KAAK,QAAQ,EAAE,QAAQ;AAAA,MAC/B,SAAS,KAAK,SAAS,EAAE,QAAQ;AAAA,MACjC,WAAW,UAAU,aAAa,EAAE,cAAc,KAAK,CAAC,EAAE,QAAQ,EAAE,WAAW;AAAA,IACjF,GAAG,CAAC,WAAW;AAAA,MACb,mBAAmB,MAAM,+BAA+B,EAAE,GAAG,MAAM,OAAO,MAAM,SAAS;AAAA,IAC3F,EAAE;AAEK,IAAM,YAAY,QAAQ,aAAa;AAAA,MAC5C,IAAI,KAAK,IAAI,EAAE,WAAW;AAAA,MAC1B,OAAO,KAAK,QAAQ,EAAE,QAAQ,EAAE,WAAW,MAAM,KAAK,EAAE;AAAA,MACxD,MAAM,KAAK,MAAM,EAAE,QAAQ;AAAA,MAC3B,MAAM,KAAK,MAAM,EAAE,QAAQ;AAAA,MAC3B,MAAM,KAAK,MAAM,EAAE,QAAQ;AAAA,MAC3B,UAAU,MAAM,UAAU,EAAE,MAA+B;AAAA,MAC3D,WAAW,UAAU,cAAc,EAAE,cAAc,KAAK,CAAC,EAAE,QAAQ,EAAE,WAAW;AAAA,IAClF,GAAG,CAAC,WAAW;AAAA,MACb,UAAU,MAAM,sBAAsB,EAAE,GAAG,MAAM,KAAK;AAAA,IACxD,EAAE;AAEK,IAAM,oBAAoB,QAAQ,sBAAsB;AAAA,MAC7D,IAAI,KAAK,IAAI,EAAE,WAAW;AAAA,MAC1B,QAAQ,KAAK,SAAS,EAAE,QAAQ;AAAA,MAChC,MAAM,KAAK,MAAM,EAAE,QAAQ;AAAA,MAC3B,gBAAgB,KAAK,iBAAiB,EAAE,QAAQ;AAAA,MAChD,UAAU,KAAK,UAAU,EAAE,QAAQ;AAAA,MACnC,QAAQ,MAAM,QAAQ,EAAE,MAA+B,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAAA,MAC7E,SAAS,QAAQ,SAAS,EAAE,QAAQ,EAAE,QAAQ,IAAI;AAAA,MAClD,WAAW,UAAU,eAAe,EAAE,cAAc,KAAK,CAAC;AAAA,MAC1D,WAAW,UAAU,eAAe,EAAE,cAAc,KAAK,CAAC;AAAA,MAC1D,WAAW,KAAK,YAAY;AAAA,MAC5B,WAAW,UAAU,cAAc,EAAE,cAAc,KAAK,CAAC,EAAE,QAAQ,EAAE,WAAW;AAAA,MAChF,WAAW,UAAU,cAAc,EAAE,cAAc,KAAK,CAAC,EAAE,QAAQ,EAAE,WAAW;AAAA,IAClF,GAAG,CAAC,WAAW;AAAA,MACb,QAAQ,MAAM,4BAA4B,EAAE,GAAG,MAAM,SAAS;AAAA,IAChE,EAAE;AAEK,IAAM,eAAe,QAAQ,iBAAiB;AAAA,MACnD,IAAI,KAAK,IAAI,EAAE,WAAW;AAAA,MAC1B,YAAY,KAAK,aAAa,EAAE,WAAW,MAAM,kBAAkB,EAAE;AAAA,MACrE,QAAQ,KAAK,SAAS,EAAE,QAAQ;AAAA,MAChC,UAAU,KAAK,WAAW,EAAE,QAAQ;AAAA,MACpC,QAAQ,KAAK,QAAQ,EAAE,QAAQ,EAAE,QAAQ,SAAS;AAAA,MAClD,gBAAgB,KAAK,iBAAiB;AAAA,MACtC,WAAW,UAAU,cAAc,EAAE,cAAc,KAAK,CAAC,EAAE,QAAQ,EAAE,WAAW;AAAA,MAChF,SAAS,UAAU,YAAY,EAAE,cAAc,KAAK,CAAC;AAAA,MACrD,OAAO,KAAK,OAAO;AAAA,IACrB,GAAG,CAAC,WAAW;AAAA,MACb,aAAa,MAAM,+BAA+B,EAAE,GAAG,MAAM,UAAU;AAAA,MACvE,mBAAmB,YAAY,+BAA+B,EAAE,GAAG,MAAM,cAAc;AAAA,IACzF,EAAE;AAEK,IAAM,sBAAsB,QAAQ,yBAAyB;AAAA,MAClE,IAAI,KAAK,IAAI,EAAE,WAAW;AAAA,MAC1B,eAAe,KAAK,iBAAiB,EAAE,QAAQ,EAAE,WAAW,MAAM,aAAa,EAAE;AAAA,MACjF,QAAQ,KAAK,SAAS,EAAE,QAAQ;AAAA,MAChC,UAAU,KAAK,WAAW,EAAE,QAAQ;AAAA,MACpC,QAAQ,KAAK,QAAQ;AAAA,MACrB,UAAU,QAAQ,WAAW;AAAA,MAC7B,YAAY,QAAQ,aAAa;AAAA,MACjC,UAAU,MAAM,UAAU,EAAE,MAA+B;AAAA,MAC3D,WAAW,UAAU,cAAc,EAAE,cAAc,KAAK,CAAC,EAAE,QAAQ,EAAE,WAAW;AAAA,IAClF,GAAG,CAAC,WAAW;AAAA,MACb,QAAQ,MAAM,kCAAkC,EAAE,GAAG,MAAM,aAAa;AAAA,IAC1E,EAAE;AAEK,IAAM,iBAAiB,QAAQ,oBAAoB;AAAA,MACxD,IAAI,UAAU,MAAM,EAAE,MAAM,SAAS,CAAC,EAAE,WAAW;AAAA,MACnD,OAAO,KAAK,QAAQ,EAAE,QAAQ,EAAE,WAAW,MAAM,KAAK,EAAE;AAAA,MACxD,UAAU,OAAO,YAAY,EAAE,MAAM,SAAS,CAAC,EAAE,QAAQ;AAAA,MACzD,MAAM,KAAK,MAAM,EAAE,QAAQ;AAAA,MAC3B,SAAS,MAAM,SAAS,EAAE,MAA+B,EAAE,QAAQ;AAAA,MACnE,WAAW,UAAU,cAAc,EAAE,cAAc,KAAK,CAAC,EAAE,QAAQ,EAAE,WAAW;AAAA,MAChF,aAAa,UAAU,gBAAgB,EAAE,cAAc,KAAK,CAAC;AAAA,IAC/D,GAAG,CAAC,WAAW;AAAA,MACb,sBAAsB,YAAY,mCAAmC,EAAE,GAAG,MAAM,OAAO,MAAM,QAAQ;AAAA,MACrG,gBAAgB,MAAM,kCAAkC,EAAE,GAAG,MAAM,OAAO,MAAM,QAAQ,EAAE,MAAM,OAAO,MAAM,WAAW,CAAC;AAAA,IAC3H,EAAE;AAAA;AAAA;;;AC1HF,SAAS,KAAK,eAAe,KAAK,MAAM,IAAI,IAAI,WAAW,KAAK,OAAAA,YAAW;AAA3E;AAAA;AAAA;AAGA;AAAA;AAAA;;;ACHA;AAAA;AAAA,mBAAAC;AAAA,EAAA,aAAAC;AAAA,EAAA,sBAAAC;AAAA,EAAA,eAAAC;AAAA,EAAA,YAAAC;AAAA,EAAA,aAAAC;AAAA,EAAA,oBAAAC;AAAA,EAAA,yBAAAC;AAAA,EAAA,2BAAAC;AAAA;AAAA,SAAS,UAAAC,SAAQ,OAAAC,YAAW;AAC5B,SAAS,SAAAC,QAAO,WAAAC,UAAS,aAAa,QAAAC,OAAM,eAAAC,oBAAmB;AAD/D,IAGM,YAEOT,QAaAJ,QAUAG,OAgBAD,UAWAH,YAYAO,oBAiBAD,eAeAE,sBAcAN;AAjHb;AAAA;AAAA;AAGA,IAAM,aAAaQ;AAEZ,IAAML,SAAQ,YAAY,SAAS;AAAA,MACxC,IAAIQ,MAAK,IAAI,EAAE,WAAW;AAAA,MAC1B,MAAMA,MAAK,MAAM,EAAE,QAAQ;AAAA,MAC3B,SAASA,MAAK,SAAS,EAAE,QAAQ;AAAA,MACjC,SAASA,MAAK,WAAW,EAAE,MAAM,OAAO,CAAC,EAAE,MAA+B,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAAA,MAChG,UAAUA,MAAK,YAAY,EAAE,MAAM,OAAO,CAAC,EAAE,MAAgB,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAAA,MACnF,QAAQA,MAAK,QAAQ,EAAE,QAAQ;AAAA,MAC/B,WAAWA,MAAK,YAAY,EAAE,QAAQ,EAAE,QAAQ,UAAU;AAAA,MAC1D,WAAWA,MAAK,YAAY,EAAE,QAAQ,EAAE,QAAQ,UAAU;AAAA,IAC5D,GAAG,CAAC,WAAW;AAAA,MACb,oBAAoBF,OAAM,6BAA6B,EAAE,GAAG,MAAM,QAAQ,MAAM,SAAS;AAAA,IAC3F,EAAE;AAEK,IAAMV,SAAQ,YAAY,SAAS;AAAA,MACxC,IAAIY,MAAK,IAAI,EAAE,WAAW;AAAA,MAC1B,MAAMA,MAAK,MAAM,EAAE,QAAQ;AAAA,MAC3B,cAAcA,MAAK,gBAAgB,EAAE,MAAM,OAAO,CAAC,EAAE,MAAgB,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAAA,MAC3F,QAAQA,MAAK,QAAQ,EAAE,QAAQ,EAAE,QAAQ,QAAQ;AAAA,MACjD,iBAAiBA,MAAK,mBAAmB;AAAA,MACzC,WAAWA,MAAK,YAAY,EAAE,QAAQ,EAAE,QAAQ,UAAU;AAAA,MAC1D,WAAWA,MAAK,YAAY,EAAE,QAAQ,EAAE,QAAQ,UAAU;AAAA,IAC5D,CAAC;AAEM,IAAMT,QAAO,YAAY,QAAQ;AAAA,MACtC,IAAIS,MAAK,IAAI,EAAE,WAAW;AAAA,MAC1B,QAAQA,MAAK,SAAS,EAAE,QAAQ,EAAE,WAAW,MAAMR,OAAM,EAAE;AAAA,MAC3D,QAAQQ,MAAK,SAAS,EAAE,QAAQ,EAAE,WAAW,MAAMZ,OAAM,EAAE;AAAA,MAC3D,SAASW,SAAQ,SAAS,EAAE,QAAQ;AAAA,MACpC,QAAQC,MAAK,QAAQ,EAAE,QAAQ;AAAA,MAC/B,WAAWA,MAAK,YAAY;AAAA,MAC5B,SAASA,MAAK,UAAU;AAAA,MACxB,OAAOA,MAAK,OAAO;AAAA,MACnB,WAAWA,MAAK,YAAY,EAAE,QAAQ,EAAE,QAAQ,UAAU;AAAA,MAC1D,WAAWA,MAAK,YAAY,EAAE,QAAQ,EAAE,QAAQ,UAAU;AAAA,IAC5D,GAAG,CAAC,WAAW;AAAA,MACb,WAAWF,OAAM,kBAAkB,EAAE,GAAG,MAAM,MAAM;AAAA,MACpD,WAAWA,OAAM,kBAAkB,EAAE,GAAG,MAAM,MAAM;AAAA,IACtD,EAAE;AAEK,IAAMR,WAAU,YAAY,YAAY;AAAA,MAC7C,IAAIS,SAAQ,IAAI,EAAE,WAAW,EAAE,eAAe,KAAK,CAAC;AAAA,MACpD,OAAOC,MAAK,QAAQ,EAAE,QAAQ,EAAE,WAAW,MAAMT,MAAK,EAAE;AAAA,MACxD,UAAUS,MAAK,WAAW;AAAA,MAC1B,QAAQA,MAAK,QAAQ,EAAE,QAAQ;AAAA,MAC/B,SAASA,MAAK,SAAS,EAAE,QAAQ;AAAA,MACjC,WAAWA,MAAK,WAAW,EAAE,QAAQ,EAAE,QAAQ,UAAU;AAAA,IAC3D,GAAG,CAAC,WAAW;AAAA,MACb,mBAAmBF,OAAM,+BAA+B,EAAE,GAAG,MAAM,OAAO,MAAM,SAAS;AAAA,IAC3F,EAAE;AAEK,IAAMX,aAAY,YAAY,aAAa;AAAA,MAChD,IAAIa,MAAK,IAAI,EAAE,WAAW;AAAA,MAC1B,OAAOA,MAAK,QAAQ,EAAE,QAAQ,EAAE,WAAW,MAAMT,MAAK,EAAE;AAAA,MACxD,MAAMS,MAAK,MAAM,EAAE,QAAQ;AAAA,MAC3B,MAAMA,MAAK,MAAM,EAAE,QAAQ;AAAA,MAC3B,MAAMA,MAAK,MAAM,EAAE,QAAQ;AAAA,MAC3B,UAAUA,MAAK,YAAY,EAAE,MAAM,OAAO,CAAC,EAAE,MAA+B;AAAA,MAC5E,WAAWA,MAAK,YAAY,EAAE,QAAQ,EAAE,QAAQ,UAAU;AAAA,IAC5D,GAAG,CAAC,WAAW;AAAA,MACb,UAAUF,OAAM,sBAAsB,EAAE,GAAG,MAAM,KAAK;AAAA,IACxD,EAAE;AAEK,IAAMJ,qBAAoB,YAAY,sBAAsB;AAAA,MACjE,IAAIM,MAAK,IAAI,EAAE,WAAW;AAAA,MAC1B,QAAQA,MAAK,SAAS,EAAE,QAAQ;AAAA,MAChC,MAAMA,MAAK,MAAM,EAAE,QAAQ;AAAA,MAC3B,gBAAgBA,MAAK,iBAAiB,EAAE,QAAQ;AAAA,MAChD,UAAUA,MAAK,UAAU,EAAE,QAAQ;AAAA,MACnC,QAAQA,MAAK,UAAU,EAAE,MAAM,OAAO,CAAC,EAAE,MAA+B,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAAA,MAC9F,SAASD,SAAQ,WAAW,EAAE,MAAM,UAAU,CAAC,EAAE,QAAQ,EAAE,QAAQ,IAAI;AAAA,MACvE,WAAWC,MAAK,aAAa;AAAA,MAC7B,WAAWA,MAAK,aAAa;AAAA,MAC7B,WAAWA,MAAK,YAAY;AAAA,MAC5B,WAAWA,MAAK,YAAY,EAAE,QAAQ,EAAE,QAAQ,UAAU;AAAA,MAC1D,WAAWA,MAAK,YAAY,EAAE,QAAQ,EAAE,QAAQ,UAAU;AAAA,IAC5D,GAAG,CAAC,WAAW;AAAA,MACb,QAAQF,OAAM,4BAA4B,EAAE,GAAG,MAAM,SAAS;AAAA,IAChE,EAAE;AAEK,IAAML,gBAAe,YAAY,iBAAiB;AAAA,MACvD,IAAIO,MAAK,IAAI,EAAE,WAAW;AAAA,MAC1B,YAAYA,MAAK,aAAa,EAAE,WAAW,MAAMN,mBAAkB,EAAE;AAAA,MACrE,QAAQM,MAAK,SAAS,EAAE,QAAQ;AAAA,MAChC,UAAUA,MAAK,WAAW,EAAE,QAAQ;AAAA,MACpC,QAAQA,MAAK,QAAQ,EAAE,QAAQ,EAAE,QAAQ,SAAS;AAAA,MAClD,gBAAgBA,MAAK,iBAAiB;AAAA,MACtC,WAAWA,MAAK,YAAY,EAAE,QAAQ,EAAE,QAAQ,UAAU;AAAA,MAC1D,SAASA,MAAK,UAAU;AAAA,MACxB,OAAOA,MAAK,OAAO;AAAA,IACrB,GAAG,CAAC,WAAW;AAAA,MACb,aAAaF,OAAM,+BAA+B,EAAE,GAAG,MAAM,UAAU;AAAA,MACvE,mBAAmBG,aAAY,+BAA+B,EAAE,GAAG,MAAM,cAAc;AAAA,IACzF,EAAE;AAEK,IAAMN,uBAAsB,YAAY,yBAAyB;AAAA,MACtE,IAAIK,MAAK,IAAI,EAAE,WAAW;AAAA,MAC1B,eAAeA,MAAK,iBAAiB,EAAE,QAAQ,EAAE,WAAW,MAAMP,cAAa,EAAE;AAAA,MACjF,QAAQO,MAAK,SAAS,EAAE,QAAQ;AAAA,MAChC,UAAUA,MAAK,WAAW,EAAE,QAAQ;AAAA,MACpC,QAAQA,MAAK,QAAQ;AAAA,MACrB,UAAUD,SAAQ,WAAW;AAAA,MAC7B,YAAYA,SAAQ,aAAa;AAAA,MACjC,UAAUC,MAAK,YAAY,EAAE,MAAM,OAAO,CAAC,EAAE,MAA+B;AAAA,MAC5E,WAAWA,MAAK,YAAY,EAAE,QAAQ,EAAE,QAAQ,UAAU;AAAA,IAC5D,GAAG,CAAC,WAAW;AAAA,MACb,QAAQF,OAAM,kCAAkC,EAAE,GAAG,MAAM,aAAa;AAAA,IAC1E,EAAE;AAEK,IAAMT,kBAAiB,YAAY,oBAAoB;AAAA,MAC5D,IAAIU,SAAQ,IAAI,EAAE,WAAW,EAAE,eAAe,KAAK,CAAC;AAAA,MACpD,OAAOC,MAAK,QAAQ,EAAE,QAAQ,EAAE,WAAW,MAAMT,MAAK,EAAE;AAAA,MACxD,UAAUQ,SAAQ,UAAU,EAAE,QAAQ;AAAA,MACtC,MAAMC,MAAK,MAAM,EAAE,QAAQ;AAAA,MAC3B,SAASA,MAAK,WAAW,EAAE,MAAM,OAAO,CAAC,EAAE,MAA+B,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAAA,MAChG,WAAWA,MAAK,YAAY,EAAE,QAAQ,EAAE,QAAQ,UAAU;AAAA,MAC1D,aAAaA,MAAK,cAAc;AAAA,IAClC,GAAG,CAAC,WAAW;AAAA,MACb,sBAAsBC,aAAY,mCAAmC,EAAE,GAAG,MAAM,OAAO,MAAM,QAAQ;AAAA,MACrG,gBAAgBH,OAAM,kCAAkC,EAAE,GAAG,MAAM,OAAO,MAAM,QAAQ,EAAE,MAAMF,QAAO,MAAM,WAAW,CAAC;AAAA,IAC3H,EAAE;AAAA;AAAA;;;AC5HF,SAAS,OAAAM,MAAK,OAAAC,MAAK,QAAAC,OAAM,MAAAC,KAAI,MAAAC,KAAI,OAAAC,MAAK,OAAAC,YAAW;AAAjD;AAAA;AAAA;AAGA;AAAA;AAAA;;;ACHA,SAAS,iBAAiB;AAC1B,SAAS,eAAe;AACxB,SAAS,SAAS,YAAY;AAC9B,SAAS,WAAW,iBAAiB;AAErC,SAAS,WAAW,qBAAqB;AACzC,OAAO,cAAc;AACrB,SAAS,YAAY;AAgBd,SAAS,QAAQ,aAA2B;AACjD,QAAM,WAAW,MAAM,IAAI,WAAW;AACtC,MAAI,SAAU,QAAO;AACrB,QAAM,OAAO,IAAI,KAAK,EAAE,kBAAkB,YAAY,CAAC;AACvD,QAAM,IAAI,aAAa,IAAI;AAC3B,SAAO;AACT;AAEO,SAAS,WAAW,MAAuB;AAChD,SAAO,UAAU,MAAM,EAAE,QAAQ,WAAS,CAAC;AAC7C;AAjCA,IAqBM;AArBN;AAAA;AAAA;AAQA;AACA;AACA;AACA;AAUA,IAAM,QAAQ,oBAAI,IAAkB;AAAA;AAAA;;;ACrBpC,SAAS,kBAAkB;AAC3B,SAAS,oBAAoB;AAC7B,SAAS,eAAe;AAaxB,SAAS,YAAY,kBAAmC;AACtD,SAAO,KAAK;AAAA,IACV,aAAa,QAAQ,kBAAkB,oBAAoB,GAAG,MAAM;AAAA,EACtE;AACF;AAEA,SAAS,cAAc,kBAA0B,KAAqB;AACpE,QAAMC,OAAM,aAAa,QAAQ,kBAAkB,GAAG,GAAG,MAAM,GAAG,MAAM;AACxE,SAAO,WAAW,QAAQ,EAAE,OAAOA,IAAG,EAAE,OAAO,KAAK;AACtD;AAEA,SAAS,gBAAgB,kBAA0B,SAA0E;AAC3H,QAAM,UAAU,YAAY,gBAAgB;AAC5C,SAAO,QAAQ,QACZ,OAAO,CAAC,UAAU,MAAM,OAAO,OAAO,EACtC,IAAI,CAAC,WAAW;AAAA,IACf,MAAM,cAAc,kBAAkB,MAAM,GAAG;AAAA,IAC/C,WAAW,MAAM;AAAA,IACjB,KAAK,MAAM;AAAA,EACb,EAAE;AACN;AAEA,eAAe,cAAc,MAAe,WAAqC;AAC/E,QAAM,SAAS,MAAM,KAAK;AAAA,IACxB;AAAA;AAAA;AAAA;AAAA,IAIA,CAAC,SAAS;AAAA,EACZ;AACA,SAAO,QAAQ,OAAO,KAAK,CAAC,GAAG,MAAM;AACvC;AAEA,eAAe,4BAA4B,MAAuC;AAChF,QAAM,KAAK,MAAM,qCAAqC;AACtD,QAAM,KAAK,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAMhB;AAED,QAAM,SAAS,MAAM,KAAK;AAAA,IACxB;AAAA,EACF;AACA,QAAM,QAAQ,OAAO,KAAK,CAAC,GAAG;AAC9B,SAAO,UAAU,QAAQ,UAAU,SAAY,OAAO,OAAO,KAAK;AACpE;AAEA,eAAe,iBACb,MACA,SACmB;AACnB,QAAM,WAAqB,CAAC;AAC5B,QAAM,gBAAgB,MAAM,4BAA4B,IAAI;AAE5D,aAAW,SAAS,SAAS;AAC3B,QAAI,kBAAkB,QAAQ,iBAAiB,MAAM,WAAW;AAC9D;AAAA,IACF;AAEA,UAAM,WAAW,MAAM,KAAK;AAAA,MAC1B;AAAA,MACA,CAAC,MAAM,SAAS;AAAA,IAClB;AACA,QAAI,SAAS,UAAU;AACrB;AAAA,IACF;AAEA,UAAM,KAAK;AAAA,MACT;AAAA,MACA,CAAC,MAAM,MAAM,MAAM,SAAS;AAAA,IAC9B;AACA,aAAS,KAAK,MAAM,GAAG;AAAA,EACzB;AAEA,SAAO;AACT;AAEA,eAAsB,iCACpB,MACA,kBACe;AACf,QAAM,WAAW,MAAM,cAAc,MAAM,OAAO;AAClD,MAAI,CAAC,UAAU;AACb;AAAA,EACF;AAEA,QAAM,uBAAuB,MAAM,cAAc,MAAM,oBAAoB;AAC3E,QAAM,UAAU,uBAAuB,IAAI;AAC3C,QAAM,WAAW,MAAM,iBAAiB,MAAM,gBAAgB,kBAAkB,OAAO,CAAC;AAExF,MAAI,SAAS,SAAS,GAAG;AACvB,YAAQ,OAAO;AAAA,MACb,wDAAwD,SAAS,KAAK,IAAI,CAAC;AAAA;AAAA,IAC7E;AAAA,EACF;AACF;AAEA,SAAS,kBAAkB,IAAuC,WAA4B;AAC5F,QAAM,MAAM,GAAG;AAAA,IACb;AAAA,EACF,EAAE,IAAI,SAAS;AACf,SAAO,QAAQ,GAAG;AACpB;AAEA,SAAS,gCAAgC,IAAsD;AAC7F,KAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAMP;AAED,QAAM,MAAM,GAAG;AAAA,IACb;AAAA,EACF,EAAE,IAAI;AAEN,SAAO,KAAK,cAAc;AAC5B;AAEA,SAAS,qBACP,IACA,SACU;AACV,QAAM,WAAqB,CAAC;AAC5B,QAAM,gBAAgB,gCAAgC,EAAE;AAExD,QAAM,SAAS,GAAG;AAAA,IAChB;AAAA,EACF;AACA,QAAM,SAAS,GAAG;AAAA,IAChB;AAAA,EACF;AAEA,aAAW,SAAS,SAAS;AAC3B,QAAI,kBAAkB,QAAQ,iBAAiB,MAAM,WAAW;AAC9D;AAAA,IACF;AACA,QAAI,OAAO,IAAI,MAAM,SAAS,GAAG;AAC/B;AAAA,IACF;AACA,WAAO,IAAI,MAAM,MAAM,MAAM,SAAS;AACtC,aAAS,KAAK,MAAM,GAAG;AAAA,EACzB;AAEA,SAAO;AACT;AAEO,SAAS,+BACd,IACA,kBACM;AACN,MAAI,CAAC,kBAAkB,IAAI,OAAO,GAAG;AACnC;AAAA,EACF;AAEA,QAAM,uBAAuB,kBAAkB,IAAI,oBAAoB;AACvE,QAAM,UAAU,uBAAuB,IAAI;AAC3C,QAAM,WAAW,qBAAqB,IAAI,gBAAgB,kBAAkB,OAAO,CAAC;AAEpF,MAAI,SAAS,SAAS,GAAG;AACvB,YAAQ,OAAO;AAAA,MACb,sDAAsD,SAAS,KAAK,IAAI,CAAC;AAAA;AAAA,IAC3E;AAAA,EACF;AACF;AAvLA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AACA,SAAS,aAAAC,kBAAiB;AAC1B,SAAS,WAAAC,gBAAe;AACxB,SAAS,WAAAC,UAAS,QAAAC,OAAM,WAAAC,gBAAe;AACvC,SAAS,qBAAqB;AAM9B,eAAe,gBAAgB,aAAoC;AACjE,QAAM,WAAW,IAAI,IAAI,WAAW;AACpC,QAAM,SAAS,SAAS,SAAS,QAAQ,OAAO,EAAE;AAClD,MAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,2CAA2C;AAExE,WAAS,WAAW;AACpB,QAAM,YAAY,QAAQ,SAAS,SAAS,CAAC;AAC7C,MAAI;AACF,UAAM,EAAE,SAAS,IAAI,MAAM,UAAU;AAAA,MACnC;AAAA,MACA,CAAC,MAAM;AAAA,IACT;AACA,QAAI,CAAC,UAAU;AACb,YAAM,UAAU,MAAM,oBAAoB,OAAO,WAAW,KAAK,IAAI,CAAC,GAAG;AACzE,cAAQ,OAAO,MAAM,qBAAqB,MAAM;AAAA,CAAI;AAAA,IACtD;AAAA,EACF,SAAS,KAAK;AACZ,UAAM,IAAI;AACV,QAAI,EAAE,SAAS,SAAS;AACtB,YAAM,IAAI;AAAA,QACR,aAAa,MAAM;AAAA,MACrB;AAAA,IACF;AACA,UAAM;AAAA,EACR,UAAE;AACA,UAAM,UAAU,IAAI;AAAA,EACtB;AAEA,QAAM,OAAO,QAAQ,WAAW;AAChC,QAAM,mBAAmBA,SAAQ,SAAS,eAAe;AACzD,QAAM,iCAAiC,MAAM,gBAAgB;AAC7D,QAAM,EAAE,QAAQ,IAAI,MAAM,OAAO,oCAAoC;AACrE,QAAM,QAAQ,WAAW,IAAI,GAAG,EAAE,iBAAiB,CAAC;AACpD,QAAM,KAAK,IAAI;AACf,UAAQ,OAAO,MAAM,gCAAgC;AACvD;AAEA,eAAe,cAAc,aAAoC;AAC/D,QAAM,EAAE,SAASC,UAAS,IAAI,MAAM,OAAO,gBAAgB;AAC3D,QAAM,EAAE,QAAQ,IAAI,MAAM,OAAO,4BAA4B;AAC7D,QAAM,EAAE,QAAQ,IAAI,MAAM,OAAO,qCAAqC;AACtE,QAAM,eAAe,MAAM;AAE3B,QAAM,UAAU,YAAY,WAAW,WAAW,IAC9C,YAAY,MAAM,YAAY,MAAM,IACpC;AACJ,QAAM,WAAW,CAAC,UACdF,MAAKF,SAAQ,GAAG,cAAc,cAAc,IAC5C,QAAQ,WAAW,IAAI,IAAIE,MAAKF,SAAQ,GAAG,QAAQ,MAAM,CAAC,CAAC,IAAI;AAEnE,EAAAD,WAAUE,SAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAChD,QAAM,SAAS,IAAIG,UAAS,QAAQ;AACpC,SAAO,OAAO,oBAAoB;AAClC,SAAO,OAAO,sBAAsB;AACpC,QAAM,mBAAmBD,SAAQ,SAAS,mBAAmB;AAC7D,iCAA+B,QAAQ,gBAAgB;AACvD,QAAM,KAAK,QAAQ,QAAQ,EAAE,QAAQ,aAAa,CAAC;AACnD,UAAQ,IAAI,EAAE,iBAAiB,CAAC;AAChC,SAAO,MAAM;AACb,UAAQ,OAAO,MAAM,8BAA8B,QAAQ;AAAA,CAAI;AACjE;AAEA,eAAsB,aAAa,aAAoC;AACrE,MAAI,YAAY,WAAW,aAAa,KAAK,YAAY,WAAW,eAAe,GAAG;AACpF,UAAM,gBAAgB,WAAW;AAAA,EACnC,OAAO;AACL,UAAM,cAAc,WAAW;AAAA,EACjC;AACF;AA9EA,IAQM;AARN;AAAA;AAAA;AAKA;AACA;AAEA,IAAM,UAAUF,SAAQ,cAAc,YAAY,GAAG,CAAC;AAAA;AAAA;;;ACRtD,SAAS,mBAAmB;AAC5B,SAAS,YAAY,aAAAI,YAAW,gBAAAC,eAAc,qBAAqB;AACnE,SAAS,WAAAC,gBAAe;AACxB,SAAS,QAAAC,aAAY;AACrB,SAAS,uBAAuB;AAEhC,IAAM,eAAeA,MAAKD,SAAQ,GAAG,YAAY;AACjD,IAAM,aAAaC,MAAK,cAAc,MAAM;AAE5C,SAAS,gBAAwB;AAC/B,SAAO,YAAY,EAAE,EAAE,SAAS,KAAK;AACvC;AAEA,SAAS,aAAa,MAAsC;AAC1D,MAAI;AACF,UAAM,SAAiC,CAAC;AACxC,eAAW,QAAQF,cAAa,MAAM,MAAM,EAAE,MAAM,IAAI,GAAG;AACzD,YAAM,IAAI,KAAK,KAAK;AACpB,UAAI,CAAC,KAAK,EAAE,WAAW,GAAG,EAAG;AAC7B,YAAMG,MAAK,EAAE,QAAQ,GAAG;AACxB,UAAIA,QAAO,GAAI,QAAO,EAAE,MAAM,GAAGA,GAAE,EAAE,KAAK,CAAC,IAAI,EAAE,MAAMA,MAAK,CAAC,EAAE,KAAK;AAAA,IACtE;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEA,eAAe,IACb,IACA,OACA,KACiB;AACjB,QAAM,SAAS,MAAM,GAAG,SAAS,KAAK,KAAK,KAAK,GAAG,KAAK;AACxD,SAAO,OAAO,KAAK,KAAK;AAC1B;AAEA,eAAe,OAAsB;AACnC,QAAM,WAAW,aAAa,UAAU;AACxC,QAAM,WAAW,WAAW,UAAU;AAEtC,UAAQ,OAAO,MAAM,mBAAmB;AACxC,UAAQ,OAAO;AAAA,IACb,WACI,sBAAsB,UAAU;AAAA,IAChC,sBAAsB,UAAU;AAAA;AAAA,EACtC;AACA,UAAQ,OAAO,MAAM,wDAAwD;AAE7E,QAAM,KAAK,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAE3E,QAAM,QAAQ,MAAM;AAAA,IAClB;AAAA,IAAI;AAAA,IACJ,SAAS,gBAAgB;AAAA,EAC3B;AACA,QAAM,OAAO,MAAM;AAAA,IACjB;AAAA,IAAI;AAAA,IACJ,SAAS,yBAAyB;AAAA,EACpC;AACA,QAAM,YAAY,MAAM;AAAA,IACtB;AAAA,IAAI;AAAA,IACJ,SAAS,wBAAwB,oBAAoB,IAAI;AAAA,EAC3D;AACA,QAAM,YAAY,MAAM;AAAA,IACtB;AAAA,IAAI;AAAA,IACJ,SAAS,wBAAwB,cAAc;AAAA,EACjD;AACA,QAAM,gBAAgB,MAAM;AAAA,IAC1B;AAAA,IAAI;AAAA,IACJ,SAAS,4BAA4B,cAAc;AAAA,EACrD;AAEA,KAAG,MAAM;AAET,QAAM,SAAS;AAAA,IACb,gBAAgB,KAAK;AAAA,IACrB,yBAAyB,IAAI;AAAA,IAC7B,wBAAwB,SAAS;AAAA,IACjC,wBAAwB,SAAS;AAAA,IACjC,4BAA4B,aAAa;AAAA,EAC3C,EAAE,KAAK,IAAI,IAAI;AAEf,EAAAJ,WAAU,cAAc,EAAE,WAAW,KAAK,CAAC;AAC3C,gBAAc,YAAY,MAAM;AAChC,UAAQ,OAAO,MAAM;AAAA,gBAAmB,UAAU;AAAA,CAAI;AAEtD,UAAQ,OAAO,MAAM,yBAAyB;AAC9C,QAAM,EAAE,cAAAK,cAAa,IAAI,MAAM;AAC/B,QAAMA,cAAa,KAAK;AAExB,UAAQ,OAAO,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yBAQE,SAAS;AAAA,6BACL,aAAa;AAAA,CACzC;AACD;AAEA,KAAK,EAAE,MAAM,CAAC,UAAU;AACtB,UAAQ,OAAO,MAAM,GAAG,OAAO,KAAK,CAAC;AAAA,CAAI;AACzC,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":["sql","artifacts","nodes","runInputEvents","runLogs","runs","tasks","workplanRuns","workplanSchedules","workplanStepResults","isNull","sql","index","integer","text","uniqueIndex","and","asc","desc","eq","gt","lte","sql","sql","mkdirSync","homedir","dirname","join","resolve","Database","mkdirSync","readFileSync","homedir","join","eq","runMigration"]}
|
|
1
|
+
{"version":3,"sources":["../../../libs/db/src/schema/pg.ts","../../../libs/db/src/pg-store.ts","../../../libs/db/src/schema/sqlite.ts","../../../libs/db/src/sqlite-store.ts","../../../libs/db/src/client.ts","../../../libs/db/src/baseline-migrations.ts","../../../libs/db/src/migration.ts","../../cli/src/setup.ts"],"sourcesContent":["import { isNull, sql } from \"drizzle-orm\";\nimport { bigint, bigserial, boolean, index, integer, jsonb, pgTable, text, timestamp, uniqueIndex } from \"drizzle-orm/pg-core\";\n\nexport const tasks = pgTable(\"tasks\", {\n id: text(\"id\").primaryKey(),\n kind: text(\"kind\").notNull(),\n adapter: text(\"adapter\").notNull(),\n payload: jsonb(\"payload\").$type<Record<string, unknown>>().notNull(),\n requires: text(\"requires\").array().notNull().default(sql`'{}'::text[]`),\n status: text(\"status\").notNull(),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp(\"updated_at\", { withTimezone: true }).notNull().defaultNow(),\n}, (table) => ({\n statusCreatedAtIdx: index(\"idx_tasks_status_created_at\").on(table.status, table.createdAt),\n}));\n\nexport const nodes = pgTable(\"nodes\", {\n id: text(\"id\").primaryKey(),\n name: text(\"name\").notNull(),\n capabilities: text(\"capabilities\").array().notNull().default(sql`'{}'::text[]`),\n status: text(\"status\").notNull().default(\"online\"),\n lastHeartbeatAt: timestamp(\"last_heartbeat_at\", { withTimezone: true }),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp(\"updated_at\", { withTimezone: true }).notNull().defaultNow(),\n});\n\nexport const runs = pgTable(\"runs\", {\n id: text(\"id\").primaryKey(),\n taskId: text(\"task_id\").notNull().references(() => tasks.id),\n nodeId: text(\"node_id\").notNull().references(() => nodes.id),\n attempt: integer(\"attempt\").notNull(),\n status: text(\"status\").notNull(),\n startedAt: timestamp(\"started_at\", { withTimezone: true }),\n endedAt: timestamp(\"ended_at\", { withTimezone: true }),\n error: text(\"error\"),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp(\"updated_at\", { withTimezone: true }).notNull().defaultNow(),\n}, (table) => ({\n taskIdIdx: index(\"idx_runs_task_id\").on(table.taskId),\n nodeIdIdx: index(\"idx_runs_node_id\").on(table.nodeId),\n}));\n\nexport const runLogs = pgTable(\"run_logs\", {\n id: bigserial(\"id\", { mode: \"number\" }).primaryKey(),\n runId: text(\"run_id\").notNull().references(() => runs.id),\n stepName: text(\"step_name\"),\n stream: text(\"stream\").notNull(),\n message: text(\"message\").notNull(),\n timestamp: timestamp(\"timestamp\", { withTimezone: true }).notNull().defaultNow(),\n}, (table) => ({\n runIdTimestampIdx: index(\"idx_run_logs_run_id_timestamp\").on(table.runId, table.timestamp),\n}));\n\nexport const artifacts = pgTable(\"artifacts\", {\n id: text(\"id\").primaryKey(),\n runId: text(\"run_id\").notNull().references(() => runs.id),\n type: text(\"type\").notNull(),\n name: text(\"name\").notNull(),\n path: text(\"path\").notNull(),\n metadata: jsonb(\"metadata\").$type<Record<string, unknown>>(),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n}, (table) => ({\n runIdIdx: index(\"idx_artifacts_run_id\").on(table.runId),\n}));\n\nexport const workplanSchedules = pgTable(\"workplan_schedules\", {\n id: text(\"id\").primaryKey(),\n planId: text(\"plan_id\").notNull(),\n name: text(\"name\").notNull(),\n cronExpression: text(\"cron_expression\").notNull(),\n timezone: text(\"timezone\").notNull(),\n inputs: jsonb(\"inputs\").$type<Record<string, unknown>>().notNull().default({}),\n enabled: boolean(\"enabled\").notNull().default(true),\n lastRunAt: timestamp(\"last_run_at\", { withTimezone: true }),\n nextRunAt: timestamp(\"next_run_at\", { withTimezone: true }),\n createdBy: text(\"created_by\"),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp(\"updated_at\", { withTimezone: true }).notNull().defaultNow(),\n}, (table) => ({\n dueIdx: index(\"idx_workplan_schedules_due\").on(table.nextRunAt),\n}));\n\nexport const workplanRuns = pgTable(\"workplan_runs\", {\n id: text(\"id\").primaryKey(),\n scheduleId: text(\"schedule_id\").references(() => workplanSchedules.id),\n planId: text(\"plan_id\").notNull(),\n planName: text(\"plan_name\").notNull(),\n status: text(\"status\").notNull().default(\"running\"),\n idempotencyKey: text(\"idempotency_key\"),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n endedAt: timestamp(\"ended_at\", { withTimezone: true }),\n error: text(\"error\"),\n}, (table) => ({\n scheduleIdx: index(\"idx_workplan_runs_schedule_id\").on(table.scheduleId),\n idempotencyUnique: uniqueIndex(\"idx_workplan_runs_idempotency\").on(table.idempotencyKey),\n}));\n\nexport const workplanStepResults = pgTable(\"workplan_step_results\", {\n id: text(\"id\").primaryKey(),\n workplanRunId: text(\"workplan_run_id\").notNull().references(() => workplanRuns.id),\n stepId: text(\"step_id\").notNull(),\n stepName: text(\"step_name\").notNull(),\n output: text(\"output\"),\n exitCode: integer(\"exit_code\"),\n durationMs: integer(\"duration_ms\"),\n metadata: jsonb(\"metadata\").$type<Record<string, unknown>>(),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n}, (table) => ({\n runIdx: index(\"idx_workplan_step_results_run_id\").on(table.workplanRunId),\n}));\n\nexport const runInputEvents = pgTable(\"run_input_events\", {\n id: bigserial(\"id\", { mode: \"number\" }).primaryKey(),\n runId: text(\"run_id\").notNull().references(() => runs.id),\n sequence: bigint(\"sequence\", { mode: \"number\" }).notNull(),\n kind: text(\"kind\").notNull(),\n payload: jsonb(\"payload\").$type<Record<string, unknown>>().notNull(),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n deliveredAt: timestamp(\"delivered_at\", { withTimezone: true }),\n}, (table) => ({\n runSequenceUniqueIdx: uniqueIndex(\"idx_run_input_events_run_sequence\").on(table.runId, table.sequence),\n undeliveredIdx: index(\"idx_run_input_events_undelivered\").on(table.runId, table.sequence).where(isNull(table.deliveredAt)),\n}));\n","import { and, arrayContains, asc, desc, eq, gt, isNotNull, lte, sql } from \"drizzle-orm\";\nimport { makeId } from \"@workplane/core\";\nimport type { DrizzleDb } from \"./client.js\";\nimport { artifacts, nodes, runInputEvents, runLogs, runs, tasks, workplanRuns, workplanSchedules, workplanStepResults } from \"./schema/pg.js\";\nimport type { WorkplaneStore } from \"./store-interface.js\";\nimport type {\n AppendInputEventInput,\n ArtifactInput,\n ArtifactRecord,\n CreateTaskInput,\n CreateWorkplanRunInput,\n CreateWorkplanScheduleInput,\n NodePollResult,\n NodeRecord,\n RunInputEvent,\n RunLogInput,\n RunLogRecord,\n RunRecord,\n TaskRecord,\n UpdateWorkplanScheduleInput,\n WorkplanRunRecord,\n WorkplanScheduleRecord,\n WorkplanStepResultInput,\n WorkplanStepResultRecord,\n} from \"@workplane/types\";\n\nfunction toTaskRecord(row: typeof tasks.$inferSelect): TaskRecord {\n return {\n id: row.id,\n kind: row.kind,\n adapter: row.adapter,\n payload: row.payload,\n requires: row.requires ?? [],\n status: row.status as TaskRecord[\"status\"],\n createdAt: row.createdAt.toISOString(),\n updatedAt: row.updatedAt.toISOString(),\n };\n}\n\nfunction toRunRecord(row: typeof runs.$inferSelect): RunRecord {\n return {\n id: row.id,\n taskId: row.taskId,\n nodeId: row.nodeId,\n attempt: row.attempt,\n status: row.status as RunRecord[\"status\"],\n startedAt: row.startedAt?.toISOString() ?? null,\n endedAt: row.endedAt?.toISOString() ?? null,\n error: row.error ?? null,\n };\n}\n\nfunction toNodeRecord(row: typeof nodes.$inferSelect): NodeRecord {\n return {\n id: row.id,\n name: row.name,\n capabilities: row.capabilities ?? [],\n status: row.status as NodeRecord[\"status\"],\n lastHeartbeatAt: row.lastHeartbeatAt?.toISOString() ?? null,\n };\n}\n\nfunction toLogRecord(row: typeof runLogs.$inferSelect): RunLogRecord {\n return {\n id: row.id,\n runId: row.runId,\n stepName: row.stepName ?? null,\n stream: row.stream as RunLogRecord[\"stream\"],\n message: row.message,\n timestamp: row.timestamp.toISOString(),\n };\n}\n\nfunction toArtifactRecord(row: typeof artifacts.$inferSelect): ArtifactRecord {\n return {\n id: row.id,\n runId: row.runId,\n type: row.type,\n name: row.name,\n path: row.path,\n metadata: row.metadata ?? null,\n createdAt: row.createdAt.toISOString(),\n };\n}\n\nfunction toInputEventRecord(row: typeof runInputEvents.$inferSelect): RunInputEvent {\n return {\n id: row.id,\n runId: row.runId,\n sequence: row.sequence,\n kind: row.kind as RunInputEvent[\"kind\"],\n payload: row.payload,\n createdAt: row.createdAt.toISOString(),\n deliveredAt: row.deliveredAt?.toISOString() ?? null,\n };\n}\n\nfunction toScheduleRecord(row: typeof workplanSchedules.$inferSelect): WorkplanScheduleRecord {\n return {\n id: row.id,\n planId: row.planId,\n name: row.name,\n cronExpression: row.cronExpression,\n timezone: row.timezone,\n inputs: row.inputs ?? {},\n enabled: row.enabled,\n lastRunAt: row.lastRunAt?.toISOString() ?? null,\n nextRunAt: row.nextRunAt?.toISOString() ?? null,\n createdBy: row.createdBy ?? null,\n createdAt: row.createdAt.toISOString(),\n updatedAt: row.updatedAt.toISOString(),\n };\n}\n\nfunction toWorkplanRunRecord(row: typeof workplanRuns.$inferSelect): WorkplanRunRecord {\n return {\n id: row.id,\n scheduleId: row.scheduleId ?? null,\n planId: row.planId,\n planName: row.planName,\n status: row.status as WorkplanRunRecord[\"status\"],\n idempotencyKey: row.idempotencyKey ?? null,\n createdAt: row.createdAt.toISOString(),\n endedAt: row.endedAt?.toISOString() ?? null,\n error: row.error ?? null,\n };\n}\n\nfunction toWorkplanStepResultRecord(row: typeof workplanStepResults.$inferSelect): WorkplanStepResultRecord {\n return {\n id: row.id,\n workplanRunId: row.workplanRunId,\n stepId: row.stepId,\n stepName: row.stepName,\n output: row.output ?? null,\n exitCode: row.exitCode ?? null,\n durationMs: row.durationMs ?? null,\n metadata: row.metadata ?? null,\n createdAt: row.createdAt.toISOString(),\n };\n}\n\nfunction mapRawTask(row: Record<string, unknown>): TaskRecord {\n return {\n id: String(row.id),\n kind: String(row.kind),\n adapter: String(row.adapter),\n payload: (row.payload as Record<string, unknown>) ?? {},\n requires: (row.requires as string[]) ?? [],\n status: row.status as TaskRecord[\"status\"],\n createdAt: new Date(String(row.created_at)).toISOString(),\n updatedAt: new Date(String(row.updated_at)).toISOString(),\n };\n}\n\nfunction mapRawRun(row: Record<string, unknown>): RunRecord {\n return {\n id: String(row.id),\n taskId: String(row.task_id),\n nodeId: String(row.node_id),\n attempt: Number(row.attempt),\n status: row.status as RunRecord[\"status\"],\n startedAt: row.started_at ? new Date(String(row.started_at)).toISOString() : null,\n endedAt: row.ended_at ? new Date(String(row.ended_at)).toISOString() : null,\n error: row.error ? String(row.error) : null,\n };\n}\n\nexport class PgStore implements WorkplaneStore {\n constructor(private readonly db: DrizzleDb) {}\n\n async createTask(input: CreateTaskInput): Promise<TaskRecord> {\n const taskId = makeId(\"task\");\n const [row] = await this.db.insert(tasks).values({\n id: taskId,\n kind: input.kind,\n adapter: input.adapter,\n payload: input.payload,\n requires: input.requires ?? [],\n status: \"queued\",\n }).returning();\n return toTaskRecord(row);\n }\n\n async listTasks(status?: TaskRecord[\"status\"]): Promise<TaskRecord[]> {\n const rows = await this.db\n .select()\n .from(tasks)\n .where(status ? eq(tasks.status, status) : undefined)\n .orderBy(desc(tasks.createdAt))\n .limit(100);\n return rows.map(toTaskRecord);\n }\n\n async getTask(taskId: string): Promise<TaskRecord | null> {\n const [row] = await this.db.select().from(tasks).where(eq(tasks.id, taskId));\n return row ? toTaskRecord(row) : null;\n }\n\n async listRuns(filters?: { taskId?: string; status?: RunRecord[\"status\"] }): Promise<RunRecord[]> {\n const conditions = [];\n if (filters?.taskId) conditions.push(eq(runs.taskId, filters.taskId));\n if (filters?.status) conditions.push(eq(runs.status, filters.status));\n\n const rows = await this.db\n .select()\n .from(runs)\n .where(conditions.length ? and(...conditions) : undefined)\n .orderBy(desc(runs.createdAt))\n .limit(100);\n return rows.map(toRunRecord);\n }\n\n async getRun(runId: string): Promise<RunRecord | null> {\n const [row] = await this.db.select().from(runs).where(eq(runs.id, runId));\n return row ? toRunRecord(row) : null;\n }\n\n async registerNode(name: string, capabilities: string[], preferredId?: string): Promise<NodeRecord> {\n const [existing] = await this.db.select().from(nodes).where(eq(nodes.name, name));\n\n if (existing) {\n const [updated] = await this.db.update(nodes)\n .set({ capabilities, status: \"online\", lastHeartbeatAt: sql`now()`, updatedAt: sql`now()` })\n .where(eq(nodes.id, existing.id))\n .returning();\n return toNodeRecord(updated);\n }\n\n const nodeId = preferredId ?? makeId(\"node\");\n const [inserted] = await this.db.insert(nodes).values({\n id: nodeId, name, capabilities, status: \"online\", lastHeartbeatAt: sql`now()`,\n }).returning();\n return toNodeRecord(inserted);\n }\n\n async listNodes(): Promise<NodeRecord[]> {\n const rows = await this.db.select().from(nodes).orderBy(asc(nodes.name));\n return rows.map(toNodeRecord);\n }\n\n async getRunCancellationState(runId: string): Promise<{ runStatus: string; taskStatus: string } | null> {\n const [row] = await this.db\n .select({ runStatus: runs.status, taskStatus: tasks.status })\n .from(runs)\n .innerJoin(tasks, eq(tasks.id, runs.taskId))\n .where(eq(runs.id, runId));\n return row ?? null;\n }\n\n async pollNode(nodeId: string, capabilities: string[]): Promise<NodePollResult | null> {\n return await this.db.transaction(async (tx) => {\n await tx.update(nodes)\n .set({\n capabilities,\n status: \"online\",\n lastHeartbeatAt: sql`now()`,\n updatedAt: sql`now()`,\n })\n .where(eq(nodes.id, nodeId));\n\n const [taskRow] = await tx\n .select()\n .from(tasks)\n .where(and(eq(tasks.status, \"queued\"), arrayContains(tasks.requires, capabilities)))\n .orderBy(asc(tasks.createdAt))\n .limit(1)\n .for(\"update\", { skipLocked: true });\n\n if (!taskRow) return null;\n\n const taskId = taskRow.id;\n\n const attemptResult = await tx.execute(sql`\n SELECT COALESCE(MAX(attempt), 0) AS max_attempt FROM runs WHERE task_id = ${taskId}\n `);\n const attempt = Number((attemptResult.rows[0] as Record<string, unknown>).max_attempt) + 1;\n\n const runId = makeId(\"run\");\n\n const runResult = await tx.execute(sql`\n INSERT INTO runs (id, task_id, node_id, attempt, status, started_at)\n VALUES (${runId}, ${taskId}, ${nodeId}, ${attempt}, 'assigned', now())\n RETURNING *\n `);\n\n await tx.execute(sql`\n UPDATE tasks SET status = 'assigned', updated_at = now() WHERE id = ${taskId}\n `);\n\n return {\n task: toTaskRecord(taskRow),\n run: mapRawRun(runResult.rows[0] as Record<string, unknown>),\n };\n });\n }\n\n async updateRunStatus(runId: string, status: RunRecord[\"status\"], error?: string): Promise<RunRecord | null> {\n return await this.db.transaction(async (tx) => {\n const currentResult = await tx.execute(sql`\n SELECT r.*, t.status AS task_status\n FROM runs r JOIN tasks t ON t.id = r.task_id\n WHERE r.id = ${runId}\n FOR UPDATE\n `);\n\n if (currentResult.rows.length === 0) return null;\n\n const currentRow = currentResult.rows[0] as Record<string, unknown>;\n const currentTaskStatus = String(currentRow.task_status);\n\n if (currentTaskStatus === \"cancelled\" && status !== \"cancelled\") {\n return mapRawRun(currentRow);\n }\n\n const isTerminal = status === \"succeeded\" || status === \"failed\" || status === \"cancelled\";\n const [updatedRun] = await tx.update(runs)\n .set(isTerminal\n ? { status, error: error ?? null, endedAt: sql`now()`, updatedAt: sql`now()` }\n : { status, error: error ?? null, updatedAt: sql`now()` })\n .where(eq(runs.id, runId))\n .returning();\n\n if (!updatedRun) return null;\n\n const nextTaskStatus = isTerminal ? status : \"running\";\n if (currentTaskStatus !== \"cancelled\" || status === \"cancelled\") {\n await tx.update(tasks)\n .set({ status: nextTaskStatus, updatedAt: sql`now()` })\n .where(eq(tasks.id, updatedRun.taskId));\n }\n\n return toRunRecord(updatedRun);\n });\n }\n\n async appendRunLogs(runId: string, logs: RunLogInput[]): Promise<number> {\n if (logs.length === 0) return 0;\n await this.db.insert(runLogs).values(\n logs.map((log) => ({\n runId,\n stepName: log.stepName ?? null,\n stream: log.stream,\n message: log.message,\n })),\n );\n return logs.length;\n }\n\n async getRunLogs(runId: string, afterId?: number): Promise<RunLogRecord[]> {\n const rows = await this.db\n .select()\n .from(runLogs)\n .where(afterId === undefined ? eq(runLogs.runId, runId) : and(eq(runLogs.runId, runId), gt(runLogs.id, afterId)))\n .orderBy(asc(runLogs.timestamp), asc(runLogs.id));\n return rows.map(toLogRecord);\n }\n\n async createArtifact(runId: string, input: ArtifactInput): Promise<ArtifactRecord> {\n const artifactId = makeId(\"artifact\");\n const [row] = await this.db.insert(artifacts).values({\n id: artifactId,\n runId,\n type: input.type,\n name: input.name,\n path: input.path,\n metadata: input.metadata ?? null,\n }).returning();\n return toArtifactRecord(row);\n }\n\n async listRunArtifacts(runId: string): Promise<ArtifactRecord[]> {\n const rows = await this.db\n .select()\n .from(artifacts)\n .where(eq(artifacts.runId, runId))\n .orderBy(asc(artifacts.createdAt));\n return rows.map(toArtifactRecord);\n }\n\n async retryTask(taskId: string): Promise<TaskRecord | null> {\n const [row] = await this.db.update(tasks)\n .set({ status: \"queued\", updatedAt: sql`now()` })\n .where(and(eq(tasks.id, taskId), eq(tasks.status, \"failed\")))\n .returning();\n return row ? toTaskRecord(row) : null;\n }\n\n async cancelTask(taskId: string): Promise<TaskRecord | null> {\n return await this.db.transaction(async (tx) => {\n const taskResult = await tx.execute(sql`\n SELECT * FROM tasks WHERE id = ${taskId} FOR UPDATE\n `);\n\n if (taskResult.rows.length === 0) return null;\n\n const taskRow = taskResult.rows[0] as Record<string, unknown>;\n const taskStatus = String(taskRow.status);\n\n if (taskStatus === \"succeeded\" || taskStatus === \"failed\" || taskStatus === \"cancelled\") {\n return null;\n }\n\n if (taskStatus === \"assigned\" || taskStatus === \"running\") {\n const runResult = await tx.execute(sql`\n SELECT * FROM runs WHERE task_id = ${taskId} ORDER BY attempt DESC LIMIT 1 FOR UPDATE\n `);\n\n if (runResult.rows.length > 0) {\n const runRow = runResult.rows[0] as Record<string, unknown>;\n const runStatus = String(runRow.status);\n if (runStatus !== \"succeeded\" && runStatus !== \"failed\" && runStatus !== \"cancelled\") {\n await tx.execute(sql`\n UPDATE runs SET status = 'cancelled', ended_at = now(), updated_at = now(),\n error = COALESCE(error, 'task cancelled')\n WHERE id = ${runRow.id}\n `);\n }\n }\n }\n\n const [updatedTask] = await tx.update(tasks)\n .set({ status: \"cancelled\", updatedAt: sql`now()` })\n .where(eq(tasks.id, taskId))\n .returning();\n\n return toTaskRecord(updatedTask);\n });\n }\n\n async appendInputEvent(runId: string, input: AppendInputEventInput): Promise<RunInputEvent> {\n return await this.db.transaction(async (tx) => {\n const seqResult = await tx.execute(sql`\n SELECT COALESCE(MAX(sequence), 0) + 1 AS next_seq FROM run_input_events WHERE run_id = ${runId}\n `);\n const sequence = Number((seqResult.rows[0] as Record<string, unknown>).next_seq);\n\n const [inserted] = await tx.insert(runInputEvents)\n .values({ runId, sequence, kind: input.kind, payload: input.payload })\n .returning();\n\n return toInputEventRecord(inserted);\n });\n }\n\n async getInputEvents(runId: string, afterSequence: number): Promise<RunInputEvent[]> {\n const rows = await this.db\n .select()\n .from(runInputEvents)\n .where(and(eq(runInputEvents.runId, runId), gt(runInputEvents.sequence, afterSequence)))\n .orderBy(asc(runInputEvents.sequence));\n return rows.map(toInputEventRecord);\n }\n\n async markInputDelivered(runId: string, sequence: number): Promise<void> {\n await this.db.update(runInputEvents)\n .set({ deliveredAt: sql`now()` })\n .where(and(eq(runInputEvents.runId, runId), eq(runInputEvents.sequence, sequence)));\n }\n\n async createWorkplanSchedule(input: CreateWorkplanScheduleInput): Promise<WorkplanScheduleRecord> {\n const [row] = await this.db.insert(workplanSchedules).values({\n id: makeId(\"sched\"),\n planId: input.planId,\n name: input.name,\n cronExpression: input.cronExpression,\n timezone: input.timezone,\n inputs: input.inputs ?? {},\n enabled: input.enabled ?? true,\n nextRunAt: input.enabled === false ? null : input.nextRunAt ? new Date(input.nextRunAt) : null,\n createdBy: input.createdBy ?? null,\n }).returning();\n return toScheduleRecord(row);\n }\n\n async listWorkplanSchedules(enabled?: boolean): Promise<WorkplanScheduleRecord[]> {\n const rows = await this.db.select().from(workplanSchedules)\n .where(enabled === undefined ? undefined : eq(workplanSchedules.enabled, enabled))\n .orderBy(desc(workplanSchedules.createdAt));\n return rows.map(toScheduleRecord);\n }\n\n async getWorkplanSchedule(scheduleId: string): Promise<WorkplanScheduleRecord | null> {\n const [row] = await this.db.select().from(workplanSchedules).where(eq(workplanSchedules.id, scheduleId));\n return row ? toScheduleRecord(row) : null;\n }\n\n async updateWorkplanSchedule(\n scheduleId: string,\n input: UpdateWorkplanScheduleInput,\n ): Promise<WorkplanScheduleRecord | null> {\n const patch: Partial<typeof workplanSchedules.$inferInsert> = {};\n if (input.name !== undefined) patch.name = input.name;\n if (input.cronExpression !== undefined) patch.cronExpression = input.cronExpression;\n if (input.timezone !== undefined) patch.timezone = input.timezone;\n if (input.inputs !== undefined) patch.inputs = input.inputs;\n if (input.enabled !== undefined) patch.enabled = input.enabled;\n if (input.nextRunAt !== undefined) patch.nextRunAt = input.nextRunAt ? new Date(input.nextRunAt) : null;\n patch.updatedAt = new Date();\n\n const [row] = await this.db.update(workplanSchedules)\n .set(patch)\n .where(eq(workplanSchedules.id, scheduleId))\n .returning();\n return row ? toScheduleRecord(row) : null;\n }\n\n async deleteWorkplanSchedule(scheduleId: string): Promise<boolean> {\n return await this.db.transaction(async (tx) => {\n await tx.update(workplanRuns)\n .set({ scheduleId: null })\n .where(eq(workplanRuns.scheduleId, scheduleId));\n\n const deleted = await tx.delete(workplanSchedules)\n .where(eq(workplanSchedules.id, scheduleId))\n .returning({ id: workplanSchedules.id });\n return deleted.length > 0;\n });\n }\n\n async listDueWorkplanSchedules(asOf: string): Promise<WorkplanScheduleRecord[]> {\n const asOfDate = new Date(asOf);\n const rows = await this.db.select().from(workplanSchedules)\n .where(and(\n eq(workplanSchedules.enabled, true),\n isNotNull(workplanSchedules.nextRunAt),\n lte(workplanSchedules.nextRunAt, asOfDate),\n ))\n .orderBy(asc(workplanSchedules.nextRunAt));\n return rows.map(toScheduleRecord);\n }\n\n async markWorkplanScheduleRan(scheduleId: string, lastRunAt: string, nextRunAt: string): Promise<void> {\n await this.db.update(workplanSchedules)\n .set({\n lastRunAt: new Date(lastRunAt),\n nextRunAt: new Date(nextRunAt),\n updatedAt: sql`now()`,\n })\n .where(eq(workplanSchedules.id, scheduleId));\n }\n\n async tryCreateWorkplanRun(input: CreateWorkplanRunInput): Promise<WorkplanRunRecord | null> {\n try {\n const [row] = await this.db.insert(workplanRuns).values({\n id: makeId(\"wprun\"),\n scheduleId: input.scheduleId ?? null,\n planId: input.planId,\n planName: input.planName,\n status: \"running\",\n idempotencyKey: input.idempotencyKey ?? null,\n }).returning();\n return toWorkplanRunRecord(row);\n } catch (err) {\n const code = (err as { code?: string }).code;\n if (input.idempotencyKey && code === \"23505\") {\n return null;\n }\n throw err;\n }\n }\n\n async updateWorkplanRun(\n runId: string,\n status: WorkplanRunRecord[\"status\"],\n error?: string,\n endedAt?: string,\n ): Promise<WorkplanRunRecord | null> {\n const [row] = await this.db.update(workplanRuns)\n .set({\n status,\n error: error ?? null,\n endedAt: endedAt ? new Date(endedAt) : status !== \"running\" ? new Date() : null,\n })\n .where(eq(workplanRuns.id, runId))\n .returning();\n return row ? toWorkplanRunRecord(row) : null;\n }\n\n async getWorkplanRun(runId: string): Promise<WorkplanRunRecord | null> {\n const [row] = await this.db.select().from(workplanRuns).where(eq(workplanRuns.id, runId));\n return row ? toWorkplanRunRecord(row) : null;\n }\n\n async listWorkplanRuns(filters?: { scheduleId?: string }): Promise<WorkplanRunRecord[]> {\n const rows = await this.db.select().from(workplanRuns)\n .where(filters?.scheduleId ? eq(workplanRuns.scheduleId, filters.scheduleId) : undefined)\n .orderBy(desc(workplanRuns.createdAt))\n .limit(100);\n return rows.map(toWorkplanRunRecord);\n }\n\n async appendWorkplanStepResults(\n runId: string,\n steps: WorkplanStepResultInput[],\n ): Promise<WorkplanStepResultRecord[]> {\n if (steps.length === 0) return [];\n const rows = await this.db.insert(workplanStepResults).values(\n steps.map((step) => ({\n id: makeId(\"wpstep\"),\n workplanRunId: runId,\n stepId: step.stepId,\n stepName: step.stepName,\n output: step.output ?? null,\n exitCode: step.exitCode ?? null,\n durationMs: step.durationMs ?? null,\n metadata: step.metadata ?? null,\n })),\n ).returning();\n return rows.map(toWorkplanStepResultRecord);\n }\n\n async listWorkplanStepResults(runId: string): Promise<WorkplanStepResultRecord[]> {\n const rows = await this.db.select().from(workplanStepResults)\n .where(eq(workplanStepResults.workplanRunId, runId))\n .orderBy(asc(workplanStepResults.createdAt));\n return rows.map(toWorkplanStepResultRecord);\n }\n}\n","import { isNull, sql } from \"drizzle-orm\";\nimport { index, integer, sqliteTable, text, uniqueIndex } from \"drizzle-orm/sqlite-core\";\n\nconst nowDefault = sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`;\n\nexport const tasks = sqliteTable(\"tasks\", {\n id: text(\"id\").primaryKey(),\n kind: text(\"kind\").notNull(),\n adapter: text(\"adapter\").notNull(),\n payload: text(\"payload\", { mode: \"json\" }).$type<Record<string, unknown>>().notNull().default({}),\n requires: text(\"requires\", { mode: \"json\" }).$type<string[]>().notNull().default([]),\n status: text(\"status\").notNull(),\n createdAt: text(\"created_at\").notNull().default(nowDefault),\n updatedAt: text(\"updated_at\").notNull().default(nowDefault),\n}, (table) => ({\n statusCreatedAtIdx: index(\"idx_tasks_status_created_at\").on(table.status, table.createdAt),\n}));\n\nexport const nodes = sqliteTable(\"nodes\", {\n id: text(\"id\").primaryKey(),\n name: text(\"name\").notNull(),\n capabilities: text(\"capabilities\", { mode: \"json\" }).$type<string[]>().notNull().default([]),\n status: text(\"status\").notNull().default(\"online\"),\n lastHeartbeatAt: text(\"last_heartbeat_at\"),\n createdAt: text(\"created_at\").notNull().default(nowDefault),\n updatedAt: text(\"updated_at\").notNull().default(nowDefault),\n});\n\nexport const runs = sqliteTable(\"runs\", {\n id: text(\"id\").primaryKey(),\n taskId: text(\"task_id\").notNull().references(() => tasks.id),\n nodeId: text(\"node_id\").notNull().references(() => nodes.id),\n attempt: integer(\"attempt\").notNull(),\n status: text(\"status\").notNull(),\n startedAt: text(\"started_at\"),\n endedAt: text(\"ended_at\"),\n error: text(\"error\"),\n createdAt: text(\"created_at\").notNull().default(nowDefault),\n updatedAt: text(\"updated_at\").notNull().default(nowDefault),\n}, (table) => ({\n taskIdIdx: index(\"idx_runs_task_id\").on(table.taskId),\n nodeIdIdx: index(\"idx_runs_node_id\").on(table.nodeId),\n}));\n\nexport const runLogs = sqliteTable(\"run_logs\", {\n id: integer(\"id\").primaryKey({ autoIncrement: true }),\n runId: text(\"run_id\").notNull().references(() => runs.id),\n stepName: text(\"step_name\"),\n stream: text(\"stream\").notNull(),\n message: text(\"message\").notNull(),\n timestamp: text(\"timestamp\").notNull().default(nowDefault),\n}, (table) => ({\n runIdTimestampIdx: index(\"idx_run_logs_run_id_timestamp\").on(table.runId, table.timestamp),\n}));\n\nexport const artifacts = sqliteTable(\"artifacts\", {\n id: text(\"id\").primaryKey(),\n runId: text(\"run_id\").notNull().references(() => runs.id),\n type: text(\"type\").notNull(),\n name: text(\"name\").notNull(),\n path: text(\"path\").notNull(),\n metadata: text(\"metadata\", { mode: \"json\" }).$type<Record<string, unknown>>(),\n createdAt: text(\"created_at\").notNull().default(nowDefault),\n}, (table) => ({\n runIdIdx: index(\"idx_artifacts_run_id\").on(table.runId),\n}));\n\nexport const workplanSchedules = sqliteTable(\"workplan_schedules\", {\n id: text(\"id\").primaryKey(),\n planId: text(\"plan_id\").notNull(),\n name: text(\"name\").notNull(),\n cronExpression: text(\"cron_expression\").notNull(),\n timezone: text(\"timezone\").notNull(),\n inputs: text(\"inputs\", { mode: \"json\" }).$type<Record<string, unknown>>().notNull().default({}),\n enabled: integer(\"enabled\", { mode: \"boolean\" }).notNull().default(true),\n lastRunAt: text(\"last_run_at\"),\n nextRunAt: text(\"next_run_at\"),\n createdBy: text(\"created_by\"),\n createdAt: text(\"created_at\").notNull().default(nowDefault),\n updatedAt: text(\"updated_at\").notNull().default(nowDefault),\n}, (table) => ({\n dueIdx: index(\"idx_workplan_schedules_due\").on(table.nextRunAt),\n}));\n\nexport const workplanRuns = sqliteTable(\"workplan_runs\", {\n id: text(\"id\").primaryKey(),\n scheduleId: text(\"schedule_id\").references(() => workplanSchedules.id),\n planId: text(\"plan_id\").notNull(),\n planName: text(\"plan_name\").notNull(),\n status: text(\"status\").notNull().default(\"running\"),\n idempotencyKey: text(\"idempotency_key\"),\n createdAt: text(\"created_at\").notNull().default(nowDefault),\n endedAt: text(\"ended_at\"),\n error: text(\"error\"),\n}, (table) => ({\n scheduleIdx: index(\"idx_workplan_runs_schedule_id\").on(table.scheduleId),\n idempotencyUnique: uniqueIndex(\"idx_workplan_runs_idempotency\").on(table.idempotencyKey),\n}));\n\nexport const workplanStepResults = sqliteTable(\"workplan_step_results\", {\n id: text(\"id\").primaryKey(),\n workplanRunId: text(\"workplan_run_id\").notNull().references(() => workplanRuns.id),\n stepId: text(\"step_id\").notNull(),\n stepName: text(\"step_name\").notNull(),\n output: text(\"output\"),\n exitCode: integer(\"exit_code\"),\n durationMs: integer(\"duration_ms\"),\n metadata: text(\"metadata\", { mode: \"json\" }).$type<Record<string, unknown>>(),\n createdAt: text(\"created_at\").notNull().default(nowDefault),\n}, (table) => ({\n runIdx: index(\"idx_workplan_step_results_run_id\").on(table.workplanRunId),\n}));\n\nexport const runInputEvents = sqliteTable(\"run_input_events\", {\n id: integer(\"id\").primaryKey({ autoIncrement: true }),\n runId: text(\"run_id\").notNull().references(() => runs.id),\n sequence: integer(\"sequence\").notNull(),\n kind: text(\"kind\").notNull(),\n payload: text(\"payload\", { mode: \"json\" }).$type<Record<string, unknown>>().notNull().default({}),\n createdAt: text(\"created_at\").notNull().default(nowDefault),\n deliveredAt: text(\"delivered_at\"),\n}, (table) => ({\n runSequenceUniqueIdx: uniqueIndex(\"idx_run_input_events_run_sequence\").on(table.runId, table.sequence),\n undeliveredIdx: index(\"idx_run_input_events_undelivered\").on(table.runId, table.sequence).where(isNull(table.deliveredAt)),\n}));\n","import { and, asc, desc, eq, gt, lte, sql } from \"drizzle-orm\";\nimport type { BetterSQLite3Database } from \"drizzle-orm/better-sqlite3\";\nimport type BetterSqlite3Client from \"better-sqlite3\";\nimport { makeId } from \"@workplane/core\";\nimport { artifacts, nodes, runInputEvents, runLogs, runs, tasks, workplanRuns, workplanSchedules, workplanStepResults } from \"./schema/sqlite.js\";\nimport type * as sqliteSchema from \"./schema/sqlite.js\";\nimport type { WorkplaneStore } from \"./store-interface.js\";\nimport type {\n AppendInputEventInput,\n ArtifactInput,\n ArtifactRecord,\n CreateTaskInput,\n CreateWorkplanRunInput,\n CreateWorkplanScheduleInput,\n NodePollResult,\n NodeRecord,\n RunInputEvent,\n RunLogInput,\n RunLogRecord,\n RunRecord,\n TaskRecord,\n UpdateWorkplanScheduleInput,\n WorkplanRunRecord,\n WorkplanScheduleRecord,\n WorkplanStepResultInput,\n WorkplanStepResultRecord,\n} from \"@workplane/types\";\n\ntype SqliteDb = BetterSQLite3Database<typeof sqliteSchema> & { $client: BetterSqlite3Client.Database };\n\nfunction nowIso(): string {\n return new Date().toISOString();\n}\n\n// Drizzle-typed mappers for simple CRUD methods (camelCase, text timestamps)\n\nfunction toTaskRecord(row: typeof tasks.$inferSelect): TaskRecord {\n return {\n id: row.id,\n kind: row.kind,\n adapter: row.adapter,\n payload: row.payload,\n requires: row.requires ?? [],\n status: row.status as TaskRecord[\"status\"],\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n\nfunction toRunRecord(row: typeof runs.$inferSelect): RunRecord {\n return {\n id: row.id,\n taskId: row.taskId,\n nodeId: row.nodeId,\n attempt: row.attempt,\n status: row.status as RunRecord[\"status\"],\n startedAt: row.startedAt ?? null,\n endedAt: row.endedAt ?? null,\n error: row.error ?? null,\n };\n}\n\nfunction toNodeRecord(row: typeof nodes.$inferSelect): NodeRecord {\n return {\n id: row.id,\n name: row.name,\n capabilities: row.capabilities ?? [],\n status: row.status as NodeRecord[\"status\"],\n lastHeartbeatAt: row.lastHeartbeatAt ?? null,\n };\n}\n\nfunction toLogRecord(row: typeof runLogs.$inferSelect): RunLogRecord {\n return {\n id: row.id,\n runId: row.runId,\n stepName: row.stepName ?? null,\n stream: row.stream as RunLogRecord[\"stream\"],\n message: row.message,\n timestamp: row.timestamp,\n };\n}\n\nfunction toArtifactRecord(row: typeof artifacts.$inferSelect): ArtifactRecord {\n return {\n id: row.id,\n runId: row.runId,\n type: row.type,\n name: row.name,\n path: row.path,\n metadata: row.metadata ?? null,\n createdAt: row.createdAt,\n };\n}\n\nfunction toInputEventRecord(row: typeof runInputEvents.$inferSelect): RunInputEvent {\n return {\n id: row.id,\n runId: row.runId,\n sequence: row.sequence,\n kind: row.kind as RunInputEvent[\"kind\"],\n payload: row.payload,\n createdAt: row.createdAt,\n deliveredAt: row.deliveredAt ?? null,\n };\n}\n\nfunction toScheduleRecord(row: typeof workplanSchedules.$inferSelect): WorkplanScheduleRecord {\n return {\n id: row.id,\n planId: row.planId,\n name: row.name,\n cronExpression: row.cronExpression,\n timezone: row.timezone,\n inputs: row.inputs ?? {},\n enabled: row.enabled,\n lastRunAt: row.lastRunAt ?? null,\n nextRunAt: row.nextRunAt ?? null,\n createdBy: row.createdBy ?? null,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n\nfunction toWorkplanRunRecord(row: typeof workplanRuns.$inferSelect): WorkplanRunRecord {\n return {\n id: row.id,\n scheduleId: row.scheduleId ?? null,\n planId: row.planId,\n planName: row.planName,\n status: row.status as WorkplanRunRecord[\"status\"],\n idempotencyKey: row.idempotencyKey ?? null,\n createdAt: row.createdAt,\n endedAt: row.endedAt ?? null,\n error: row.error ?? null,\n };\n}\n\nfunction toWorkplanStepResultRecord(row: typeof workplanStepResults.$inferSelect): WorkplanStepResultRecord {\n return {\n id: row.id,\n workplanRunId: row.workplanRunId,\n stepId: row.stepId,\n stepName: row.stepName,\n output: row.output ?? null,\n exitCode: row.exitCode ?? null,\n durationMs: row.durationMs ?? null,\n metadata: row.metadata ?? null,\n createdAt: row.createdAt,\n };\n}\n\n// Raw-row mappers for transaction methods that use $client directly.\n// better-sqlite3 rejects async transaction callbacks so we use the sync\n// $client API with prepared statements for the 4 methods that need atomicity.\n\ninterface RawTaskRow {\n id: string; kind: string; adapter: string;\n payload: string; requires: string; status: string;\n created_at: string; updated_at: string;\n}\n\ninterface RawRunRow {\n id: string; task_id: string; node_id: string; attempt: number; status: string;\n started_at: string | null; ended_at: string | null; error: string | null;\n created_at: string; updated_at: string;\n}\n\nfunction mapRawTask(row: RawTaskRow): TaskRecord {\n return {\n id: row.id, kind: row.kind, adapter: row.adapter,\n payload: JSON.parse(row.payload) as Record<string, unknown>,\n requires: JSON.parse(row.requires) as string[],\n status: row.status as TaskRecord[\"status\"],\n createdAt: row.created_at, updatedAt: row.updated_at,\n };\n}\n\nfunction mapRawRun(row: RawRunRow): RunRecord {\n return {\n id: row.id, taskId: row.task_id, nodeId: row.node_id,\n attempt: row.attempt, status: row.status as RunRecord[\"status\"],\n startedAt: row.started_at ?? null, endedAt: row.ended_at ?? null,\n error: row.error ?? null,\n };\n}\n\nexport class SqliteStore implements WorkplaneStore {\n constructor(private readonly db: SqliteDb) {}\n\n async createTask(input: CreateTaskInput): Promise<TaskRecord> {\n const now = nowIso();\n const [row] = await this.db.insert(tasks).values({\n id: makeId(\"task\"),\n kind: input.kind,\n adapter: input.adapter,\n payload: input.payload,\n requires: input.requires ?? [],\n status: \"queued\",\n createdAt: now,\n updatedAt: now,\n }).returning();\n return toTaskRecord(row);\n }\n\n async listTasks(status?: TaskRecord[\"status\"]): Promise<TaskRecord[]> {\n const rows = await this.db.select().from(tasks)\n .where(status ? eq(tasks.status, status) : undefined)\n .orderBy(desc(tasks.createdAt)).limit(100);\n return rows.map(toTaskRecord);\n }\n\n async getTask(taskId: string): Promise<TaskRecord | null> {\n const [row] = await this.db.select().from(tasks).where(eq(tasks.id, taskId));\n return row ? toTaskRecord(row) : null;\n }\n\n async listRuns(filters?: { taskId?: string; status?: RunRecord[\"status\"] }): Promise<RunRecord[]> {\n const conds = [];\n if (filters?.taskId) conds.push(eq(runs.taskId, filters.taskId));\n if (filters?.status) conds.push(eq(runs.status, filters.status));\n const rows = await this.db.select().from(runs)\n .where(conds.length ? and(...conds) : undefined)\n .orderBy(desc(runs.createdAt)).limit(100);\n return rows.map(toRunRecord);\n }\n\n async getRun(runId: string): Promise<RunRecord | null> {\n const [row] = await this.db.select().from(runs).where(eq(runs.id, runId));\n return row ? toRunRecord(row) : null;\n }\n\n async registerNode(name: string, capabilities: string[], preferredId?: string): Promise<NodeRecord> {\n const now = nowIso();\n const [existing] = await this.db.select().from(nodes).where(eq(nodes.name, name));\n if (existing) {\n const [updated] = await this.db.update(nodes)\n .set({ capabilities, status: \"online\", lastHeartbeatAt: now, updatedAt: now })\n .where(eq(nodes.id, existing.id)).returning();\n return toNodeRecord(updated);\n }\n const [inserted] = await this.db.insert(nodes).values({\n id: preferredId ?? makeId(\"node\"), name, capabilities,\n status: \"online\", lastHeartbeatAt: now, createdAt: now, updatedAt: now,\n }).returning();\n return toNodeRecord(inserted);\n }\n\n async listNodes(): Promise<NodeRecord[]> {\n const rows = await this.db.select().from(nodes).orderBy(asc(nodes.name));\n return rows.map(toNodeRecord);\n }\n\n async getRunCancellationState(runId: string): Promise<{ runStatus: string; taskStatus: string } | null> {\n const [row] = await this.db\n .select({ runStatus: runs.status, taskStatus: tasks.status })\n .from(runs).innerJoin(tasks, eq(tasks.id, runs.taskId)).where(eq(runs.id, runId));\n return row ? { runStatus: row.runStatus, taskStatus: row.taskStatus ?? \"\" } : null;\n }\n\n // Transaction methods use this.db.$client directly because better-sqlite3\n // rejects async callbacks — all operations inside must be synchronous.\n\n async pollNode(nodeId: string, capabilities: string[]): Promise<NodePollResult | null> {\n const now = nowIso();\n await this.db.update(nodes)\n .set({ capabilities, status: \"online\", lastHeartbeatAt: now, updatedAt: now })\n .where(eq(nodes.id, nodeId));\n\n const raw = this.db.$client;\n return raw.transaction(() => {\n const capSet = new Set(capabilities);\n const queued = raw.prepare(\n \"SELECT * FROM tasks WHERE status = 'queued' ORDER BY created_at ASC\",\n ).all() as RawTaskRow[];\n\n const task = queued.find((t) =>\n (JSON.parse(t.requires) as string[]).every((r) => capSet.has(r)),\n );\n if (!task) return null;\n\n const { max_attempt } = raw.prepare(\n \"SELECT COALESCE(MAX(attempt), 0) AS max_attempt FROM runs WHERE task_id = ?\",\n ).get(task.id) as { max_attempt: number };\n\n const runId = makeId(\"run\");\n raw.prepare(\n \"INSERT INTO runs (id, task_id, node_id, attempt, status, started_at, created_at, updated_at) VALUES (?, ?, ?, ?, 'assigned', ?, ?, ?)\",\n ).run(runId, task.id, nodeId, max_attempt + 1, now, now, now);\n\n raw.prepare(\"UPDATE tasks SET status = 'assigned', updated_at = ? WHERE id = ?\").run(now, task.id);\n\n return {\n task: mapRawTask(task),\n run: mapRawRun(raw.prepare(\"SELECT * FROM runs WHERE id = ?\").get(runId) as RawRunRow),\n };\n })();\n }\n\n async updateRunStatus(runId: string, status: RunRecord[\"status\"], error?: string): Promise<RunRecord | null> {\n const raw = this.db.$client;\n return raw.transaction(() => {\n const current = raw.prepare(\n \"SELECT r.*, t.status AS task_status FROM runs r JOIN tasks t ON t.id = r.task_id WHERE r.id = ?\",\n ).get(runId) as (RawRunRow & { task_status: string }) | undefined;\n if (!current) return null;\n\n if (current.task_status === \"cancelled\" && status !== \"cancelled\") {\n return mapRawRun(current);\n }\n\n const now = nowIso();\n const isTerminal = status === \"succeeded\" || status === \"failed\" || status === \"cancelled\";\n if (isTerminal) {\n raw.prepare(\n \"UPDATE runs SET status = ?, error = ?, ended_at = ?, updated_at = ? WHERE id = ?\",\n ).run(status, error ?? null, now, now, runId);\n } else {\n raw.prepare(\n \"UPDATE runs SET status = ?, error = ?, updated_at = ? WHERE id = ?\",\n ).run(status, error ?? null, now, runId);\n }\n\n const updatedRun = raw.prepare(\"SELECT * FROM runs WHERE id = ?\").get(runId) as RawRunRow;\n const nextTaskStatus = isTerminal ? status : \"running\";\n if (current.task_status !== \"cancelled\" || status === \"cancelled\") {\n raw.prepare(\"UPDATE tasks SET status = ?, updated_at = ? WHERE id = ?\").run(\n nextTaskStatus, now, updatedRun.task_id,\n );\n }\n return mapRawRun(updatedRun);\n })();\n }\n\n async appendRunLogs(runId: string, logs: RunLogInput[]): Promise<number> {\n if (logs.length === 0) return 0;\n const now = nowIso();\n await this.db.insert(runLogs).values(\n logs.map((log) => ({\n runId, stepName: log.stepName ?? null,\n stream: log.stream, message: log.message, timestamp: now,\n })),\n );\n return logs.length;\n }\n\n async getRunLogs(runId: string, afterId?: number): Promise<RunLogRecord[]> {\n const rows = await this.db.select().from(runLogs)\n .where(afterId === undefined ? eq(runLogs.runId, runId) : and(eq(runLogs.runId, runId), gt(runLogs.id, afterId)))\n .orderBy(asc(runLogs.timestamp), asc(runLogs.id));\n return rows.map(toLogRecord);\n }\n\n async createArtifact(runId: string, input: ArtifactInput): Promise<ArtifactRecord> {\n const now = nowIso();\n const [row] = await this.db.insert(artifacts).values({\n id: makeId(\"artifact\"), runId, type: input.type, name: input.name,\n path: input.path, metadata: input.metadata ?? null, createdAt: now,\n }).returning();\n return toArtifactRecord(row);\n }\n\n async listRunArtifacts(runId: string): Promise<ArtifactRecord[]> {\n const rows = await this.db.select().from(artifacts)\n .where(eq(artifacts.runId, runId)).orderBy(asc(artifacts.createdAt));\n return rows.map(toArtifactRecord);\n }\n\n async retryTask(taskId: string): Promise<TaskRecord | null> {\n const [row] = await this.db.update(tasks)\n .set({ status: \"queued\", updatedAt: nowIso() })\n .where(and(eq(tasks.id, taskId), eq(tasks.status, \"failed\"))).returning();\n return row ? toTaskRecord(row) : null;\n }\n\n async cancelTask(taskId: string): Promise<TaskRecord | null> {\n const raw = this.db.$client;\n return raw.transaction(() => {\n const taskRow = raw.prepare(\"SELECT * FROM tasks WHERE id = ?\").get(taskId) as RawTaskRow | undefined;\n if (!taskRow) return null;\n\n const terminal = new Set([\"succeeded\", \"failed\", \"cancelled\"]);\n if (terminal.has(taskRow.status)) return null;\n\n const now = nowIso();\n if (taskRow.status === \"assigned\" || taskRow.status === \"running\") {\n const latestRun = raw.prepare(\n \"SELECT * FROM runs WHERE task_id = ? ORDER BY attempt DESC LIMIT 1\",\n ).get(taskId) as RawRunRow | undefined;\n\n if (latestRun && !terminal.has(latestRun.status)) {\n raw.prepare(\n \"UPDATE runs SET status = 'cancelled', ended_at = ?, updated_at = ?, error = COALESCE(error, 'task cancelled') WHERE id = ?\",\n ).run(now, now, latestRun.id);\n }\n }\n\n raw.prepare(\"UPDATE tasks SET status = 'cancelled', updated_at = ? WHERE id = ?\").run(now, taskId);\n return mapRawTask(raw.prepare(\"SELECT * FROM tasks WHERE id = ?\").get(taskId) as RawTaskRow);\n })();\n }\n\n async appendInputEvent(runId: string, input: AppendInputEventInput): Promise<RunInputEvent> {\n const raw = this.db.$client;\n const now = nowIso();\n const event = raw.transaction(() => {\n const { next_seq } = raw.prepare(\n \"SELECT COALESCE(MAX(sequence), 0) + 1 AS next_seq FROM run_input_events WHERE run_id = ?\",\n ).get(runId) as { next_seq: number };\n\n raw.prepare(\n \"INSERT INTO run_input_events (run_id, sequence, kind, payload, created_at) VALUES (?, ?, ?, ?, ?)\",\n ).run(runId, next_seq, input.kind, JSON.stringify(input.payload), now);\n\n return raw.prepare(\n \"SELECT * FROM run_input_events WHERE run_id = ? AND sequence = ?\",\n ).get(runId, next_seq) as {\n id: number; run_id: string; sequence: number; kind: string;\n payload: string; created_at: string; delivered_at: string | null;\n };\n })();\n\n return {\n id: event.id, runId: event.run_id, sequence: event.sequence,\n kind: event.kind as RunInputEvent[\"kind\"],\n payload: JSON.parse(event.payload) as Record<string, unknown>,\n createdAt: event.created_at, deliveredAt: event.delivered_at ?? null,\n };\n }\n\n async getInputEvents(runId: string, afterSequence: number): Promise<RunInputEvent[]> {\n const rows = await this.db.select().from(runInputEvents)\n .where(and(eq(runInputEvents.runId, runId), gt(runInputEvents.sequence, afterSequence)))\n .orderBy(asc(runInputEvents.sequence));\n return rows.map(toInputEventRecord);\n }\n\n async markInputDelivered(runId: string, sequence: number): Promise<void> {\n await this.db.update(runInputEvents)\n .set({ deliveredAt: nowIso() })\n .where(and(eq(runInputEvents.runId, runId), eq(runInputEvents.sequence, sequence)));\n }\n\n async createWorkplanSchedule(input: CreateWorkplanScheduleInput): Promise<WorkplanScheduleRecord> {\n const now = nowIso();\n const [row] = await this.db.insert(workplanSchedules).values({\n id: makeId(\"sched\"),\n planId: input.planId,\n name: input.name,\n cronExpression: input.cronExpression,\n timezone: input.timezone,\n inputs: input.inputs ?? {},\n enabled: input.enabled ?? true,\n nextRunAt: input.enabled === false ? null : input.nextRunAt ?? null,\n createdBy: input.createdBy ?? null,\n createdAt: now,\n updatedAt: now,\n }).returning();\n return toScheduleRecord(row);\n }\n\n async listWorkplanSchedules(enabled?: boolean): Promise<WorkplanScheduleRecord[]> {\n const rows = await this.db.select().from(workplanSchedules)\n .where(enabled === undefined ? undefined : eq(workplanSchedules.enabled, enabled))\n .orderBy(desc(workplanSchedules.createdAt));\n return rows.map(toScheduleRecord);\n }\n\n async getWorkplanSchedule(scheduleId: string): Promise<WorkplanScheduleRecord | null> {\n const [row] = await this.db.select().from(workplanSchedules).where(eq(workplanSchedules.id, scheduleId));\n return row ? toScheduleRecord(row) : null;\n }\n\n async updateWorkplanSchedule(\n scheduleId: string,\n input: UpdateWorkplanScheduleInput,\n ): Promise<WorkplanScheduleRecord | null> {\n const patch: Partial<typeof workplanSchedules.$inferInsert> = { updatedAt: nowIso() };\n if (input.name !== undefined) patch.name = input.name;\n if (input.cronExpression !== undefined) patch.cronExpression = input.cronExpression;\n if (input.timezone !== undefined) patch.timezone = input.timezone;\n if (input.inputs !== undefined) patch.inputs = input.inputs;\n if (input.enabled !== undefined) patch.enabled = input.enabled;\n if (input.nextRunAt !== undefined) patch.nextRunAt = input.nextRunAt;\n\n const [row] = await this.db.update(workplanSchedules)\n .set(patch)\n .where(eq(workplanSchedules.id, scheduleId))\n .returning();\n return row ? toScheduleRecord(row) : null;\n }\n\n async deleteWorkplanSchedule(scheduleId: string): Promise<boolean> {\n const raw = this.db.$client;\n return raw.transaction(() => {\n raw.prepare(\"UPDATE workplan_runs SET schedule_id = NULL WHERE schedule_id = ?\").run(scheduleId);\n const result = raw.prepare(\"DELETE FROM workplan_schedules WHERE id = ?\").run(scheduleId);\n return (result.changes ?? 0) > 0;\n })();\n }\n\n async listDueWorkplanSchedules(asOf: string): Promise<WorkplanScheduleRecord[]> {\n const rows = await this.db.select().from(workplanSchedules)\n .where(and(\n eq(workplanSchedules.enabled, true),\n sql`${workplanSchedules.nextRunAt} IS NOT NULL`,\n lte(workplanSchedules.nextRunAt, asOf),\n ))\n .orderBy(asc(workplanSchedules.nextRunAt));\n return rows.map(toScheduleRecord);\n }\n\n async markWorkplanScheduleRan(scheduleId: string, lastRunAt: string, nextRunAt: string): Promise<void> {\n await this.db.update(workplanSchedules)\n .set({ lastRunAt, nextRunAt, updatedAt: nowIso() })\n .where(eq(workplanSchedules.id, scheduleId));\n }\n\n async tryCreateWorkplanRun(input: CreateWorkplanRunInput): Promise<WorkplanRunRecord | null> {\n const now = nowIso();\n try {\n const [row] = await this.db.insert(workplanRuns).values({\n id: makeId(\"wprun\"),\n scheduleId: input.scheduleId ?? null,\n planId: input.planId,\n planName: input.planName,\n status: \"running\",\n idempotencyKey: input.idempotencyKey ?? null,\n createdAt: now,\n }).returning();\n return toWorkplanRunRecord(row);\n } catch (err) {\n if (input.idempotencyKey && err instanceof Error && err.message.includes(\"UNIQUE constraint failed\")) {\n return null;\n }\n throw err;\n }\n }\n\n async updateWorkplanRun(\n runId: string,\n status: WorkplanRunRecord[\"status\"],\n error?: string,\n endedAt?: string,\n ): Promise<WorkplanRunRecord | null> {\n const [row] = await this.db.update(workplanRuns)\n .set({\n status,\n error: error ?? null,\n endedAt: endedAt ?? (status !== \"running\" ? nowIso() : null),\n })\n .where(eq(workplanRuns.id, runId))\n .returning();\n return row ? toWorkplanRunRecord(row) : null;\n }\n\n async getWorkplanRun(runId: string): Promise<WorkplanRunRecord | null> {\n const [row] = await this.db.select().from(workplanRuns).where(eq(workplanRuns.id, runId));\n return row ? toWorkplanRunRecord(row) : null;\n }\n\n async listWorkplanRuns(filters?: { scheduleId?: string }): Promise<WorkplanRunRecord[]> {\n const rows = await this.db.select().from(workplanRuns)\n .where(filters?.scheduleId ? eq(workplanRuns.scheduleId, filters.scheduleId) : undefined)\n .orderBy(desc(workplanRuns.createdAt))\n .limit(100);\n return rows.map(toWorkplanRunRecord);\n }\n\n async appendWorkplanStepResults(\n runId: string,\n steps: WorkplanStepResultInput[],\n ): Promise<WorkplanStepResultRecord[]> {\n if (steps.length === 0) return [];\n const now = nowIso();\n const rows = await this.db.insert(workplanStepResults).values(\n steps.map((step) => ({\n id: makeId(\"wpstep\"),\n workplanRunId: runId,\n stepId: step.stepId,\n stepName: step.stepName,\n output: step.output ?? null,\n exitCode: step.exitCode ?? null,\n durationMs: step.durationMs ?? null,\n metadata: step.metadata ?? null,\n createdAt: now,\n })),\n ).returning();\n return rows.map(toWorkplanStepResultRecord);\n }\n\n async listWorkplanStepResults(runId: string): Promise<WorkplanStepResultRecord[]> {\n const rows = await this.db.select().from(workplanStepResults)\n .where(eq(workplanStepResults.workplanRunId, runId))\n .orderBy(asc(workplanStepResults.createdAt));\n return rows.map(toWorkplanStepResultRecord);\n }\n}\n","import { mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { drizzle as drizzlePg } from \"drizzle-orm/node-postgres\";\nimport type { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport { drizzle as drizzleSqlite } from \"drizzle-orm/better-sqlite3\";\nimport Database from \"better-sqlite3\";\nimport { Pool } from \"pg\";\nimport { PgStore } from \"./pg-store.js\";\nimport { SqliteStore } from \"./sqlite-store.js\";\nimport * as pgSchema from \"./schema/pg.js\";\nimport * as sqliteSchema from \"./schema/sqlite.js\";\nimport type { WorkplaneStore } from \"./store-interface.js\";\n\nexport type DrizzleDb = NodePgDatabase<typeof pgSchema>;\n\nexport interface StoreHandle {\n store: WorkplaneStore;\n close: () => Promise<void>;\n}\n\nconst pools = new Map<string, Pool>();\n\nexport function getPool(databaseUrl: string): Pool {\n const existing = pools.get(databaseUrl);\n if (existing) return existing;\n const pool = new Pool({ connectionString: databaseUrl });\n pools.set(databaseUrl, pool);\n return pool;\n}\n\nexport function getDrizzle(pool: Pool): DrizzleDb {\n return drizzlePg(pool, { schema: pgSchema });\n}\n\nfunction parseSqlitePath(url: string): string {\n const path = url.startsWith(\"sqlite://\") ? url.slice(\"sqlite://\".length) : url;\n if (!path) return join(homedir(), \".workplane\", \"workplane.db\");\n if (path.startsWith(\"~/\")) return join(homedir(), path.slice(2));\n return path;\n}\n\nexport function createStore(databaseUrl?: string): StoreHandle {\n const url = databaseUrl ?? \"\";\n\n if (url.startsWith(\"postgres://\") || url.startsWith(\"postgresql://\")) {\n const pool = getPool(url);\n return {\n store: new PgStore(getDrizzle(pool)),\n close: () => pool.end(),\n };\n }\n\n const filePath = parseSqlitePath(url);\n mkdirSync(dirname(filePath), { recursive: true });\n const sqlite = new Database(filePath);\n sqlite.pragma(\"journal_mode = WAL\");\n sqlite.pragma(\"synchronous = NORMAL\");\n const db = drizzleSqlite(sqlite, { schema: sqliteSchema });\n return {\n store: new SqliteStore(db),\n close: async () => { sqlite.close(); },\n };\n}\n","import { createHash } from \"node:crypto\";\nimport { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport type pg from \"pg\";\n\ninterface JournalEntry {\n idx: number;\n when: number;\n tag: string;\n}\n\ninterface Journal {\n entries: JournalEntry[];\n}\n\nfunction readJournal(migrationsFolder: string): Journal {\n return JSON.parse(\n readFileSync(resolve(migrationsFolder, \"meta/_journal.json\"), \"utf8\"),\n ) as Journal;\n}\n\nfunction migrationHash(migrationsFolder: string, tag: string): string {\n const sql = readFileSync(resolve(migrationsFolder, `${tag}.sql`), \"utf8\");\n return createHash(\"sha256\").update(sql).digest(\"hex\");\n}\n\nfunction baselineEntries(migrationsFolder: string, upToIdx: number): Array<{ hash: string; createdAt: number; tag: string }> {\n const journal = readJournal(migrationsFolder);\n return journal.entries\n .filter((entry) => entry.idx <= upToIdx)\n .map((entry) => ({\n hash: migrationHash(migrationsFolder, entry.tag),\n createdAt: entry.when,\n tag: entry.tag,\n }));\n}\n\nasync function tableExistsPg(pool: pg.Pool, tableName: string): Promise<boolean> {\n const result = await pool.query<{ exists: boolean }>(\n `SELECT EXISTS (\n SELECT 1 FROM information_schema.tables\n WHERE table_schema = 'public' AND table_name = $1\n ) AS exists`,\n [tableName],\n );\n return Boolean(result.rows[0]?.exists);\n}\n\nasync function getLastMigrationCreatedAtPg(pool: pg.Pool): Promise<number | null> {\n await pool.query('CREATE SCHEMA IF NOT EXISTS drizzle');\n await pool.query(`\n CREATE TABLE IF NOT EXISTS drizzle.__drizzle_migrations (\n id SERIAL PRIMARY KEY,\n hash text NOT NULL,\n created_at bigint\n )\n `);\n\n const result = await pool.query<{ created_at: string | null }>(\n \"SELECT created_at FROM drizzle.__drizzle_migrations ORDER BY created_at DESC LIMIT 1\",\n );\n const value = result.rows[0]?.created_at;\n return value === null || value === undefined ? null : Number(value);\n}\n\nasync function insertBaselinePg(\n pool: pg.Pool,\n entries: Array<{ hash: string; createdAt: number; tag: string }>,\n): Promise<string[]> {\n const inserted: string[] = [];\n const lastCreatedAt = await getLastMigrationCreatedAtPg(pool);\n\n for (const entry of entries) {\n if (lastCreatedAt !== null && lastCreatedAt >= entry.createdAt) {\n continue;\n }\n\n const existing = await pool.query<{ id: number }>(\n \"SELECT id FROM drizzle.__drizzle_migrations WHERE created_at = $1 LIMIT 1\",\n [entry.createdAt],\n );\n if (existing.rowCount) {\n continue;\n }\n\n await pool.query(\n 'INSERT INTO drizzle.__drizzle_migrations (\"hash\", \"created_at\") VALUES ($1, $2)',\n [entry.hash, entry.createdAt],\n );\n inserted.push(entry.tag);\n }\n\n return inserted;\n}\n\nexport async function baselineLegacyPostgresMigrations(\n pool: pg.Pool,\n migrationsFolder: string,\n): Promise<void> {\n const hasTasks = await tableExistsPg(pool, \"tasks\");\n if (!hasTasks) {\n return;\n }\n\n const hasWorkplanSchedules = await tableExistsPg(pool, \"workplan_schedules\");\n const upToIdx = hasWorkplanSchedules ? 1 : 0;\n const inserted = await insertBaselinePg(pool, baselineEntries(migrationsFolder, upToIdx));\n\n if (inserted.length > 0) {\n process.stdout.write(\n `Baselined existing Postgres schema for migration(s): ${inserted.join(\", \")}\\n`,\n );\n }\n}\n\nfunction tableExistsSqlite(db: import(\"better-sqlite3\").Database, tableName: string): boolean {\n const row = db.prepare(\n \"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ? LIMIT 1\",\n ).get(tableName);\n return Boolean(row);\n}\n\nfunction getLastMigrationCreatedAtSqlite(db: import(\"better-sqlite3\").Database): number | null {\n db.exec(`\n CREATE TABLE IF NOT EXISTS __drizzle_migrations (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n hash text NOT NULL,\n created_at numeric\n )\n `);\n\n const row = db.prepare(\n \"SELECT created_at FROM __drizzle_migrations ORDER BY created_at DESC LIMIT 1\",\n ).get() as { created_at: number } | undefined;\n\n return row?.created_at ?? null;\n}\n\nfunction insertBaselineSqlite(\n db: import(\"better-sqlite3\").Database,\n entries: Array<{ hash: string; createdAt: number; tag: string }>,\n): string[] {\n const inserted: string[] = [];\n const lastCreatedAt = getLastMigrationCreatedAtSqlite(db);\n\n const insert = db.prepare(\n 'INSERT INTO __drizzle_migrations (\"hash\", \"created_at\") VALUES (?, ?)',\n );\n const exists = db.prepare(\n \"SELECT 1 FROM __drizzle_migrations WHERE created_at = ? LIMIT 1\",\n );\n\n for (const entry of entries) {\n if (lastCreatedAt !== null && lastCreatedAt >= entry.createdAt) {\n continue;\n }\n if (exists.get(entry.createdAt)) {\n continue;\n }\n insert.run(entry.hash, entry.createdAt);\n inserted.push(entry.tag);\n }\n\n return inserted;\n}\n\nexport function baselineLegacySqliteMigrations(\n db: import(\"better-sqlite3\").Database,\n migrationsFolder: string,\n): void {\n if (!tableExistsSqlite(db, \"tasks\")) {\n return;\n }\n\n const hasWorkplanSchedules = tableExistsSqlite(db, \"workplan_schedules\");\n const upToIdx = hasWorkplanSchedules ? 1 : 0;\n const inserted = insertBaselineSqlite(db, baselineEntries(migrationsFolder, upToIdx));\n\n if (inserted.length > 0) {\n process.stdout.write(\n `Baselined existing SQLite schema for migration(s): ${inserted.join(\", \")}\\n`,\n );\n }\n}\n","// Side-effect-free module — safe to import from both the migrate CLI and setup wizard.\nimport { mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { getDrizzle, getPool } from \"./client.js\";\nimport { baselineLegacyPostgresMigrations, baselineLegacySqliteMigrations } from \"./baseline-migrations.js\";\n\nconst distDir = dirname(fileURLToPath(import.meta.url));\n\nasync function migratePostgres(databaseUrl: string): Promise<void> {\n const adminUrl = new URL(databaseUrl);\n const dbName = adminUrl.pathname.replace(/^\\//, \"\");\n if (!dbName) throw new Error(\"DATABASE_URL must include a database name\");\n\n adminUrl.pathname = \"/postgres\";\n const adminPool = getPool(adminUrl.toString());\n try {\n const { rowCount } = await adminPool.query(\n \"SELECT 1 FROM pg_database WHERE datname = $1\",\n [dbName],\n );\n if (!rowCount) {\n await adminPool.query(`CREATE DATABASE \"${dbName.replaceAll('\"', '\"\"')}\"`);\n process.stdout.write(`Created database: ${dbName}\\n`);\n }\n } catch (err) {\n const e = err as { code?: string };\n if (e.code === \"42501\") {\n throw new Error(\n `Database \"${dbName}\" does not exist and the current role lacks CREATE DATABASE permissions.`,\n );\n }\n throw err;\n } finally {\n await adminPool.end();\n }\n\n const pool = getPool(databaseUrl);\n const migrationsFolder = resolve(distDir, \"migrations/pg\");\n await baselineLegacyPostgresMigrations(pool, migrationsFolder);\n const { migrate } = await import(\"drizzle-orm/node-postgres/migrator\");\n await migrate(getDrizzle(pool), { migrationsFolder });\n await pool.end();\n process.stdout.write(\"Postgres migrations applied.\\n\");\n}\n\nasync function migrateSqlite(databaseUrl: string): Promise<void> {\n const { default: Database } = await import(\"better-sqlite3\");\n const { drizzle } = await import(\"drizzle-orm/better-sqlite3\");\n const { migrate } = await import(\"drizzle-orm/better-sqlite3/migrator\");\n const sqliteSchema = await import(\"./schema/sqlite.js\");\n\n const rawPath = databaseUrl.startsWith(\"sqlite://\")\n ? databaseUrl.slice(\"sqlite://\".length)\n : databaseUrl;\n const filePath = !rawPath\n ? join(homedir(), \".workplane\", \"workplane.db\")\n : rawPath.startsWith(\"~/\") ? join(homedir(), rawPath.slice(2)) : rawPath;\n\n mkdirSync(dirname(filePath), { recursive: true });\n const sqlite = new Database(filePath);\n sqlite.pragma(\"journal_mode = WAL\");\n sqlite.pragma(\"synchronous = NORMAL\");\n const migrationsFolder = resolve(distDir, \"migrations/sqlite\");\n baselineLegacySqliteMigrations(sqlite, migrationsFolder);\n const db = drizzle(sqlite, { schema: sqliteSchema });\n migrate(db, { migrationsFolder });\n sqlite.close();\n process.stdout.write(`SQLite migrations applied: ${filePath}\\n`);\n}\n\nexport async function runMigration(databaseUrl: string): Promise<void> {\n if (databaseUrl.startsWith(\"postgres://\") || databaseUrl.startsWith(\"postgresql://\")) {\n await migratePostgres(databaseUrl);\n } else {\n await migrateSqlite(databaseUrl);\n }\n}\n","import { randomBytes } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { createInterface } from \"node:readline/promises\";\n\nconst workplaneDir = join(homedir(), \".workplane\");\nconst configPath = join(workplaneDir, \".env\");\n\nfunction generateToken(): string {\n return randomBytes(32).toString(\"hex\");\n}\n\nfunction parseEnvFile(path: string): Record<string, string> {\n try {\n const result: Record<string, string> = {};\n for (const line of readFileSync(path, \"utf8\").split(\"\\n\")) {\n const t = line.trim();\n if (!t || t.startsWith(\"#\")) continue;\n const eq = t.indexOf(\"=\");\n if (eq !== -1) result[t.slice(0, eq).trim()] = t.slice(eq + 1).trim();\n }\n return result;\n } catch {\n return {};\n }\n}\n\nasync function ask(\n rl: Awaited<ReturnType<typeof createInterface>>,\n label: string,\n def: string,\n): Promise<string> {\n const answer = await rl.question(` ${label} [${def}]: `);\n return answer.trim() || def;\n}\n\nasync function main(): Promise<void> {\n const existing = parseEnvFile(configPath);\n const isUpdate = existsSync(configPath);\n\n process.stdout.write(\"Workplane Setup\\n\");\n process.stdout.write(\n isUpdate\n ? `Updating config at ${configPath}\\n`\n : `Creating config at ${configPath}\\n`,\n );\n process.stdout.write(\"Press Enter to accept the value shown in brackets.\\n\\n\");\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n\n const dbUrl = await ask(\n rl, \"DATABASE_URL \",\n existing.DATABASE_URL ?? \"sqlite://~/.workplane/workplane.db\",\n );\n const port = await ask(\n rl, \"Server port \",\n existing.WORKPLANE_SERVER_PORT ?? \"8787\",\n );\n const serverUrl = await ask(\n rl, \"Server URL (for nodes)\",\n existing.WORKPLANE_SERVER_URL ?? `http://localhost:${port}`,\n );\n const nodeToken = await ask(\n rl, \"Node token \",\n existing.WORKPLANE_NODE_TOKEN ?? generateToken(),\n );\n const operatorToken = await ask(\n rl, \"Operator token \",\n existing.WORKPLANE_OPERATOR_TOKEN ?? generateToken(),\n );\n\n rl.close();\n\n const config = [\n `DATABASE_URL=${dbUrl}`,\n `WORKPLANE_SERVER_PORT=${port}`,\n `WORKPLANE_SERVER_URL=${serverUrl}`,\n `WORKPLANE_NODE_TOKEN=${nodeToken}`,\n `WORKPLANE_OPERATOR_TOKEN=${operatorToken}`,\n ].join(\"\\n\") + \"\\n\";\n\n mkdirSync(workplaneDir, { recursive: true });\n writeFileSync(configPath, config);\n process.stdout.write(`\\nConfig saved: ${configPath}\\n`);\n\n process.stdout.write(\"Running migrations...\\n\");\n const { runMigration } = await import(\"@workplane/db/migration\");\n await runMigration(dbUrl);\n\n process.stdout.write(`\nReady. Start your fleet:\n\n workplane-server # control plane\n workplane-node # worker (this machine or another)\n workplane tasks # inspect submitted work\n\nTokens for remote machines:\n WORKPLANE_NODE_TOKEN=${nodeToken}\n WORKPLANE_OPERATOR_TOKEN=${operatorToken}\n`);\n}\n\nmain().catch((error) => {\n process.stderr.write(`${String(error)}\\n`);\n process.exit(1);\n});\n"],"mappings":"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","names":["sql","artifacts","nodes","runInputEvents","runLogs","runs","tasks","workplanRuns","workplanSchedules","workplanStepResults","isNull","sql","index","integer","text","uniqueIndex","and","asc","desc","eq","gt","lte","sql","sql","mkdirSync","homedir","dirname","join","resolve","Database","mkdirSync","readFileSync","homedir","join","eq","runMigration"]}
|