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/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
- // ../db/src/schema/pg.ts
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
- "../db/src/schema/pg.ts"() {
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
- // ../db/src/pg-store.ts
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
- "../db/src/pg-store.ts"() {
149
+ "../../libs/db/src/pg-store.ts"() {
150
150
  "use strict";
151
151
  init_pg();
152
152
  }
153
153
  });
154
154
 
155
- // ../db/src/schema/sqlite.ts
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
- "../db/src/schema/sqlite.ts"() {
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
- // ../db/src/sqlite-store.ts
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
- "../db/src/sqlite-store.ts"() {
293
+ "../../libs/db/src/sqlite-store.ts"() {
294
294
  "use strict";
295
295
  init_sqlite();
296
296
  }
297
297
  });
298
298
 
299
- // ../db/src/client.ts
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
- "../db/src/client.ts"() {
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
- // ../db/src/baseline-migrations.ts
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
- "../db/src/baseline-migrations.ts"() {
467
+ "../../libs/db/src/baseline-migrations.ts"() {
468
468
  "use strict";
469
469
  }
470
470
  });
471
471
 
472
- // ../db/src/migration.ts
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
- "../db/src/migration.ts"() {
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":"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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"]}
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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workplane",
3
- "version": "0.4.3",
3
+ "version": "0.5.0",
4
4
  "description": "Durable execution plane for routing work across trusted nodes",
5
5
  "type": "module",
6
6
  "license": "MIT",