@smithers-orchestrator/db 0.25.1 → 0.25.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smithers-orchestrator/db",
3
- "version": "0.25.1",
3
+ "version": "0.25.2",
4
4
  "description": "SQLite and Drizzle persistence adapter for Smithers workflows",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -30,10 +30,10 @@
30
30
  "drizzle-zod": "^0.8.3",
31
31
  "effect": "^3.21.1",
32
32
  "zod": "^4.3.6",
33
- "@smithers-orchestrator/errors": "0.25.1",
34
- "@smithers-orchestrator/graph": "0.25.1",
35
- "@smithers-orchestrator/observability": "0.25.1",
36
- "@smithers-orchestrator/scheduler": "0.25.1"
33
+ "@smithers-orchestrator/graph": "0.25.2",
34
+ "@smithers-orchestrator/errors": "0.25.2",
35
+ "@smithers-orchestrator/observability": "0.25.2",
36
+ "@smithers-orchestrator/scheduler": "0.25.2"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@electric-sql/pglite": "0.5.1",
package/src/adapter.js CHANGED
@@ -2556,6 +2556,35 @@ export class SmithersDb {
2556
2556
  }));
2557
2557
  }
2558
2558
  /**
2559
+ * Delete durable snapshot rows for frames after `frameNo`. Snapshots are keyed
2560
+ * (run_id, frame_no) and are the hydration/fork source, so a rewind that
2561
+ * truncates frames must truncate the matching snapshots too — otherwise
2562
+ * fork/replay/loadLatestSnapshot can resurrect logically-discarded state.
2563
+ * @param {string} runId
2564
+ * @param {number} frameNo
2565
+ * @returns {RunnableEffect<void, SmithersError>}
2566
+ */
2567
+ deleteSnapshotsAfter(runId, frameNo) {
2568
+ const self = this;
2569
+ return runnableEffect(Effect.gen(function* () {
2570
+ yield* self.write(`delete snapshots after ${frameNo}`, () => self.internalStorage.deleteWhere("_smithers_snapshots", "run_id = ? AND frame_no > ?", [runId, frameNo]));
2571
+ }));
2572
+ }
2573
+ /**
2574
+ * Delete VCS tag rows for frames after `frameNo`, keyed (run_id, frame_no).
2575
+ * A rewind truncates frames; the matching vcs-tags must go too or
2576
+ * rerunAtRevision/loadVcsTag can restore a discarded working-copy revision.
2577
+ * @param {string} runId
2578
+ * @param {number} frameNo
2579
+ * @returns {RunnableEffect<void, SmithersError>}
2580
+ */
2581
+ deleteVcsTagsAfter(runId, frameNo) {
2582
+ const self = this;
2583
+ return runnableEffect(Effect.gen(function* () {
2584
+ yield* self.write(`delete vcs tags after ${frameNo}`, () => self.internalStorage.deleteWhere("_smithers_vcs_tags", "run_id = ? AND frame_no > ?", [runId, frameNo]));
2585
+ }));
2586
+ }
2587
+ /**
2559
2588
  * @param {string} runId
2560
2589
  * @param {number} limit
2561
2590
  * @param {number} [afterFrameNo]
@@ -75,6 +75,9 @@ const LEGACY_COLUMN_MIGRATIONS = [
75
75
  name: "Add legacy run operational columns",
76
76
  table: "_smithers_runs",
77
77
  columns: [
78
+ ["workflow_path", "workflow_path TEXT"],
79
+ ["started_at_ms", "started_at_ms INTEGER"],
80
+ ["finished_at_ms", "finished_at_ms INTEGER"],
78
81
  ["workflow_hash", "workflow_hash TEXT"],
79
82
  ["heartbeat_at_ms", "heartbeat_at_ms INTEGER"],
80
83
  ["runtime_owner_id", "runtime_owner_id TEXT"],
@@ -126,6 +129,12 @@ const LEGACY_COLUMN_MIGRATIONS = [
126
129
  table: "_smithers_frames",
127
130
  columns: [["encoding", "encoding TEXT NOT NULL DEFAULT 'full'"]],
128
131
  },
132
+ {
133
+ id: "0007_event_timestamp_column",
134
+ name: "Add event timestamp column",
135
+ table: "_smithers_events",
136
+ columns: [["timestamp_ms", "timestamp_ms INTEGER NOT NULL DEFAULT 0"]],
137
+ },
129
138
  ];
130
139
 
131
140
  const EXTRA_INDEX_STATEMENTS = [
@@ -516,6 +525,14 @@ function buildMigrations(context) {
516
525
  return config.columns.every(([column]) => columns.has(column));
517
526
  },
518
527
  up: (sqlite) => {
528
+ // A column migration only adds columns to an existing table; the base
529
+ // 0001 migration owns table creation. If the table is absent (a store
530
+ // whose ledger recorded 0001 but predates this table), skip rather than
531
+ // throw "no such table" — mirrors how the index migrations skip indexes
532
+ // whose table is absent and let the create-table migration own it.
533
+ if (!tableExists(sqlite, config.table)) {
534
+ return { table: config.table, addedColumns: [], skipped: "missing_table" };
535
+ }
519
536
  const addedColumns = [];
520
537
  for (const [column, definition] of config.columns) {
521
538
  if (addColumnIfMissing(sqlite, config.table, column, definition)) {
@@ -525,6 +542,9 @@ function buildMigrations(context) {
525
542
  return { table: config.table, addedColumns };
526
543
  },
527
544
  upPostgres: async (pgConn) => {
545
+ if (!(await tableExistsPostgres(pgConn, config.table))) {
546
+ return { table: config.table, addedColumns: [], skipped: "missing_table" };
547
+ }
528
548
  const addedColumns = [];
529
549
  for (const [column, definition] of config.columns) {
530
550
  if (await addColumnIfMissingPostgres(pgConn, config.table, column, definition)) {
@@ -810,7 +830,8 @@ export async function runSmithersSchemaMigrationsPostgres(pgConn, context) {
810
830
  await pgConn.query({ text: translateDdl(POSTGRES, MIGRATION_TABLE_SQL) });
811
831
  const applied = await loadAppliedMigrationIdsPostgres(pgConn);
812
832
  for (const migration of buildMigrations(context)) {
813
- if (applied.has(migration.id)) {
833
+ const alreadyAppliedInLedger = applied.has(migration.id);
834
+ if (alreadyAppliedInLedger && migration.isAppliedPostgres && await migration.isAppliedPostgres(pgConn)) {
814
835
  continue;
815
836
  }
816
837
  const details = await runPostgresMigrationSpan(migration, async () => {