@noormdev/sdk 1.0.0-alpha.18 → 1.0.0-alpha.19

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/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import { processFile, DtReader, isTemplate, FORMAT_VERSION, DT_EXTENSIONS, encryptWithPassphrase, ENCODED_TYPES } from './chunk-KF7GE2VM.js';
2
- import { initializeVault, getVaultStatus, setVaultSecret, getVaultSecret, getAllVaultSecrets, listVaultSecretKeys, deleteVaultSecret, vaultSecretExists, getVaultKey, noormDb, getNoormTables, encryptVaultKey } from './chunk-LJDHQEWW.js';
3
- import { observer } from './chunk-4HW4CS2C.js';
1
+ import { processFile, DtReader, isTemplate, FORMAT_VERSION, DT_EXTENSIONS, encryptWithPassphrase, ENCODED_TYPES } from './chunk-UZARPCOH.js';
2
+ import { initializeVault, getVaultStatus, setVaultSecret, getVaultSecret, getAllVaultSecrets, listVaultSecretKeys, deleteVaultSecret, vaultSecretExists, getVaultKey, noormDb, getNoormTables, encryptVaultKey } from './chunk-HB3JXUJV.js';
3
+ import { observer } from './chunk-BP4PMFJF.js';
4
4
  import { constants, existsSync, readFileSync, mkdirSync, writeFileSync, createWriteStream } from 'fs';
5
5
  import path, { join, dirname, resolve } from 'path';
6
6
  import { makeNestedConfig, gigabytes, attempt, retry, clone, merge, attemptSync } from '@logosdx/utils';
@@ -1757,11 +1757,17 @@ var ConnectionManager = class {
1757
1757
  }
1758
1758
  const trackedEntries = Array.from(this.#tracked.entries());
1759
1759
  for (const [id, entry] of trackedEntries) {
1760
+ let timer;
1760
1761
  const destroyWithTimeout = Promise.race([
1761
- entry.conn.destroy(),
1762
- new Promise((resolve2) => setTimeout(resolve2, CLOSE_TIMEOUT))
1762
+ entry.conn.destroy().then(() => {
1763
+ clearTimeout(timer);
1764
+ }),
1765
+ new Promise((resolve2) => {
1766
+ timer = setTimeout(resolve2, CLOSE_TIMEOUT);
1767
+ })
1763
1768
  ]);
1764
1769
  const [, err] = await attempt(() => destroyWithTimeout);
1770
+ clearTimeout(timer);
1765
1771
  this.#tracked.delete(id);
1766
1772
  if (err) {
1767
1773
  observer.emit("error", { source: "connection", error: err });
@@ -1841,7 +1847,7 @@ async function getDialectFactory(dialect) {
1841
1847
  case "mysql":
1842
1848
  return (await import('./mysql-C2DR4TF7.js')).createMysqlConnection;
1843
1849
  case "mssql":
1844
- return (await import('./mssql-7VB7EHRX.js')).createMssqlConnection;
1850
+ return (await import('./mssql-65M7MY4Q.js')).createMssqlConnection;
1845
1851
  default:
1846
1852
  throw new Error(`Unsupported dialect: ${dialect}`);
1847
1853
  }
@@ -1915,7 +1921,6 @@ async function createConnection(config, configName = "__default__") {
1915
1921
  destroy: wrappedDestroy
1916
1922
  };
1917
1923
  observer.emit("connection:open", { configName, dialect: config.dialect });
1918
- await waitForIdentityToLoad(trackedConn.db, config.dialect);
1919
1924
  return trackedConn;
1920
1925
  }
1921
1926
  var SYSTEM_DATABASES = {
@@ -2061,79 +2066,170 @@ var INDEXES = [
2061
2066
  { name: "idx_change_name_config", table: "change", columns: ["name", "config_name"] },
2062
2067
  { name: "idx_vault_secret_key", table: "vault", columns: ["secret_key"] }
2063
2068
  ];
2069
+ var PREFIXED_TABLES = {
2070
+ executions: "__noorm_executions__",
2071
+ change: "__noorm_change__",
2072
+ vault: "__noorm_vault__"
2073
+ };
2064
2074
  var v2 = {
2065
2075
  version: 2,
2066
2076
  description: "Move tracking tables to noorm schema",
2067
2077
  async up(db, dialect) {
2068
2078
  if (dialect === "sqlite" || dialect === "mysql") return;
2069
2079
  await db.transaction().execute(async (trx) => {
2070
- if (dialect === "postgres") {
2071
- await sql`CREATE SCHEMA IF NOT EXISTS noorm`.execute(trx);
2072
- } else {
2073
- await sql`IF NOT EXISTS (SELECT 1 FROM sys.schemas WHERE name = 'noorm') EXEC('CREATE SCHEMA noorm')`.execute(trx);
2074
- }
2075
- if (dialect === "postgres") {
2080
+ const src = dialect === "postgres" ? "public" : "dbo";
2081
+ async function tableExistsIn(schema, table) {
2082
+ if (dialect === "postgres") {
2083
+ const { rows: rows2 } = await sql`
2084
+ SELECT 1 AS n FROM information_schema.tables
2085
+ WHERE table_schema = ${schema} AND table_name = ${table}
2086
+ `.execute(trx);
2087
+ return rows2.length > 0;
2088
+ }
2076
2089
  const { rows } = await sql`
2077
- SELECT constraint_name
2078
- FROM information_schema.table_constraints
2079
- WHERE table_name = '__noorm_executions__'
2080
- AND constraint_type = 'FOREIGN KEY'
2090
+ SELECT 1 AS n FROM sys.tables t
2091
+ JOIN sys.schemas s ON t.schema_id = s.schema_id
2092
+ WHERE t.name = ${table} AND s.name = ${schema}
2081
2093
  `.execute(trx);
2082
- for (const row of rows) {
2083
- await sql`ALTER TABLE __noorm_executions__ DROP CONSTRAINT ${sql.ref(row.constraint_name)}`.execute(trx);
2094
+ return rows.length > 0;
2095
+ }
2096
+ async function getForeignKeys(schema, table) {
2097
+ if (!await tableExistsIn(schema, table)) return [];
2098
+ if (dialect === "postgres") {
2099
+ const { rows: rows2 } = await sql`
2100
+ SELECT tc.constraint_name AS name
2101
+ FROM information_schema.table_constraints tc
2102
+ WHERE tc.table_schema = ${schema}
2103
+ AND tc.table_name = ${table}
2104
+ AND tc.constraint_type = 'FOREIGN KEY'
2105
+ `.execute(trx);
2106
+ return rows2.map((r) => r.name);
2084
2107
  }
2085
- } else {
2086
2108
  const { rows } = await sql`
2087
- SELECT fk.name AS fk_name
2109
+ SELECT fk.name
2088
2110
  FROM sys.foreign_keys fk
2089
2111
  JOIN sys.tables t ON fk.parent_object_id = t.object_id
2090
- WHERE t.name = '__noorm_executions__'
2112
+ JOIN sys.schemas s ON t.schema_id = s.schema_id
2113
+ WHERE t.name = ${table} AND s.name = ${schema}
2091
2114
  `.execute(trx);
2092
- for (const row of rows) {
2093
- await sql`ALTER TABLE __noorm_executions__ DROP CONSTRAINT ${sql.ref(row.fk_name)}`.execute(trx);
2115
+ return rows.map((r) => r.name);
2116
+ }
2117
+ async function indexExistsIn(schema, idxName) {
2118
+ if (dialect === "postgres") {
2119
+ const { rows: rows2 } = await sql`
2120
+ SELECT 1 AS n FROM pg_indexes
2121
+ WHERE schemaname = ${schema} AND indexname = ${idxName}
2122
+ `.execute(trx);
2123
+ return rows2.length > 0;
2094
2124
  }
2125
+ const { rows } = await sql`
2126
+ SELECT 1 AS n FROM sys.indexes i
2127
+ JOIN sys.tables t ON i.object_id = t.object_id
2128
+ JOIN sys.schemas s ON t.schema_id = s.schema_id
2129
+ WHERE i.name = ${idxName} AND s.name = ${schema}
2130
+ `.execute(trx);
2131
+ return rows.length > 0;
2095
2132
  }
2096
2133
  if (dialect === "postgres") {
2097
- for (const idx of INDEXES) {
2098
- await sql`DROP INDEX IF EXISTS ${sql.ref(idx.name)}`.execute(trx);
2099
- }
2134
+ await sql`CREATE SCHEMA IF NOT EXISTS noorm`.execute(trx);
2100
2135
  } else {
2101
- const prefixedTables = {
2102
- executions: "__noorm_executions__",
2103
- change: "__noorm_change__",
2104
- vault: "__noorm_vault__"
2105
- };
2106
- for (const idx of INDEXES) {
2107
- const tableName = prefixedTables[idx.table];
2108
- await sql`
2109
- IF EXISTS (SELECT 1 FROM sys.indexes WHERE name = ${idx.name})
2110
- DROP INDEX ${sql.ref(idx.name)} ON ${sql.table(tableName)}
2111
- `.execute(trx);
2136
+ await sql`IF NOT EXISTS (SELECT 1 FROM sys.schemas WHERE name = 'noorm') EXEC('CREATE SCHEMA noorm')`.execute(trx);
2137
+ }
2138
+ const fkLocations = [
2139
+ { schema: src, table: "__noorm_executions__" },
2140
+ { schema: "noorm", table: "__noorm_executions__" },
2141
+ { schema: "noorm", table: "executions" }
2142
+ ];
2143
+ for (const loc of fkLocations) {
2144
+ const fks = await getForeignKeys(loc.schema, loc.table);
2145
+ for (const fkName of fks) {
2146
+ await sql`ALTER TABLE ${sql.table(`${loc.schema}.${loc.table}`)} DROP CONSTRAINT ${sql.ref(fkName)}`.execute(trx);
2112
2147
  }
2113
2148
  }
2114
- for (const t of TABLE_MAP) {
2115
- if (dialect === "postgres") {
2116
- await sql`ALTER TABLE ${sql.table(t.old)} SET SCHEMA noorm`.execute(trx);
2117
- } else {
2118
- await sql`ALTER SCHEMA noorm TRANSFER ${sql.ref(`dbo.${t.old}`)}`.execute(trx);
2149
+ for (const idx of INDEXES) {
2150
+ if (await indexExistsIn(src, idx.name)) {
2151
+ if (dialect === "postgres") {
2152
+ await sql`DROP INDEX IF EXISTS ${sql.ref(idx.name)}`.execute(trx);
2153
+ } else {
2154
+ const tableName = PREFIXED_TABLES[idx.table];
2155
+ await sql`
2156
+ IF EXISTS (SELECT 1 FROM sys.indexes WHERE name = ${idx.name})
2157
+ DROP INDEX ${sql.ref(idx.name)} ON ${sql.table(tableName)}
2158
+ `.execute(trx);
2159
+ }
2160
+ }
2161
+ if (await indexExistsIn("noorm", idx.name)) {
2162
+ if (dialect === "postgres") {
2163
+ await sql`DROP INDEX IF EXISTS ${sql.ref(`noorm.${idx.name}`)}`.execute(trx);
2164
+ } else {
2165
+ const oldName = PREFIXED_TABLES[idx.table];
2166
+ const onOldName = await tableExistsIn("noorm", oldName);
2167
+ const tbl = onOldName ? `noorm.${oldName}` : `noorm.${idx.table}`;
2168
+ await sql`
2169
+ IF EXISTS (SELECT 1 FROM sys.indexes WHERE name = ${idx.name})
2170
+ DROP INDEX ${sql.ref(idx.name)} ON ${sql.table(tbl)}
2171
+ `.execute(trx);
2172
+ }
2119
2173
  }
2120
2174
  }
2121
2175
  for (const t of TABLE_MAP) {
2176
+ const inNoormNew = await tableExistsIn("noorm", t.new);
2177
+ const inNoormOld = await tableExistsIn("noorm", t.old);
2178
+ const inSrc = await tableExistsIn(src, t.old);
2179
+ if (inNoormNew) {
2180
+ if (inSrc) {
2181
+ if (dialect === "postgres") {
2182
+ await sql`DROP TABLE ${sql.table(`${src}.${t.old}`)} CASCADE`.execute(trx);
2183
+ } else {
2184
+ await sql`DROP TABLE ${sql.table(`${src}.${t.old}`)}`.execute(trx);
2185
+ }
2186
+ }
2187
+ if (inNoormOld) {
2188
+ if (dialect === "postgres") {
2189
+ await sql`DROP TABLE ${sql.table(`noorm.${t.old}`)} CASCADE`.execute(trx);
2190
+ } else {
2191
+ await sql`DROP TABLE ${sql.table(`noorm.${t.old}`)}`.execute(trx);
2192
+ }
2193
+ }
2194
+ continue;
2195
+ }
2196
+ if (!inNoormOld && !inSrc) continue;
2197
+ if (!inNoormOld && inSrc) {
2198
+ if (dialect === "postgres") {
2199
+ await sql`ALTER TABLE ${sql.table(t.old)} SET SCHEMA noorm`.execute(trx);
2200
+ } else {
2201
+ await sql`ALTER SCHEMA noorm TRANSFER ${sql.ref(`dbo.${t.old}`)}`.execute(trx);
2202
+ }
2203
+ }
2122
2204
  if (dialect === "postgres") {
2123
2205
  await sql`ALTER TABLE ${sql.table(`noorm.${t.old}`)} RENAME TO ${sql.ref(t.new)}`.execute(trx);
2124
2206
  } else {
2125
2207
  await sql`EXEC sp_rename ${sql.raw(`'noorm.${t.old}'`)}, ${sql.raw(`'${t.new}'`)}`.execute(trx);
2126
2208
  }
2209
+ if (inSrc && inNoormOld) {
2210
+ if (await tableExistsIn(src, t.old)) {
2211
+ if (dialect === "postgres") {
2212
+ await sql`DROP TABLE ${sql.table(`${src}.${t.old}`)} CASCADE`.execute(trx);
2213
+ } else {
2214
+ await sql`DROP TABLE ${sql.table(`${src}.${t.old}`)}`.execute(trx);
2215
+ }
2216
+ }
2217
+ }
2218
+ }
2219
+ const fksOnExec = await getForeignKeys("noorm", "executions");
2220
+ if (fksOnExec.length === 0) {
2221
+ await sql`
2222
+ ALTER TABLE ${sql.table("noorm.executions")}
2223
+ ADD CONSTRAINT fk_executions_change_id
2224
+ FOREIGN KEY (change_id) REFERENCES ${sql.table("noorm.change")}(id)
2225
+ ON DELETE CASCADE
2226
+ `.execute(trx);
2127
2227
  }
2128
- await sql`
2129
- ALTER TABLE ${sql.table("noorm.executions")}
2130
- ADD CONSTRAINT fk_executions_change_id
2131
- FOREIGN KEY (change_id) REFERENCES ${sql.table("noorm.change")}(id)
2132
- ON DELETE CASCADE
2133
- `.execute(trx);
2134
2228
  for (const idx of INDEXES) {
2135
- const cols = idx.columns.join(", ");
2136
- await sql`CREATE INDEX ${sql.ref(idx.name)} ON ${sql.table(`noorm.${idx.table}`)} (${sql.raw(cols)})`.execute(trx);
2229
+ if (!await indexExistsIn("noorm", idx.name)) {
2230
+ const cols = idx.columns.join(", ");
2231
+ await sql`CREATE INDEX ${sql.ref(idx.name)} ON ${sql.table(`noorm.${idx.table}`)} (${sql.raw(cols)})`.execute(trx);
2232
+ }
2137
2233
  }
2138
2234
  });
2139
2235
  },
@@ -2196,12 +2292,7 @@ var v2 = {
2196
2292
  ON DELETE CASCADE
2197
2293
  `.execute(trx);
2198
2294
  for (const idx of INDEXES) {
2199
- const prefixedTables = {
2200
- executions: "__noorm_executions__",
2201
- change: "__noorm_change__",
2202
- vault: "__noorm_vault__"
2203
- };
2204
- const tableName = prefixedTables[idx.table];
2295
+ const tableName = PREFIXED_TABLES[idx.table];
2205
2296
  const cols = idx.columns.join(", ");
2206
2297
  await sql`CREATE INDEX ${sql.ref(idx.name)} ON ${sql.table(tableName)} (${sql.raw(cols)})`.execute(trx);
2207
2298
  }
@@ -2332,17 +2423,22 @@ async function migrateSchema(db, dialect, options) {
2332
2423
  throw new VersionMismatchError("schema", status.current, status.expected);
2333
2424
  }
2334
2425
  if (!status.needsMigration) return;
2335
- if (status.current === 0) {
2336
- await bootstrapSchema(db, dialect);
2337
- return;
2426
+ let currentVersion = status.current;
2427
+ if (currentVersion === 0) {
2428
+ const tableFound = await tablesExist(db, dialect);
2429
+ if (!tableFound) {
2430
+ await bootstrapSchema(db, dialect);
2431
+ return;
2432
+ }
2433
+ currentVersion = 1;
2338
2434
  }
2339
2435
  const start = performance.now();
2340
2436
  observer.emit("version:schema:migrating", {
2341
- from: status.current,
2437
+ from: currentVersion,
2342
2438
  to: CURRENT_VERSIONS.schema
2343
2439
  });
2344
2440
  const existing = await getLatestVersionRecord(db, dialect);
2345
- const pendingMigrations = MIGRATIONS.filter((m) => m.version > status.current);
2441
+ const pendingMigrations = MIGRATIONS.filter((m) => m.version > currentVersion);
2346
2442
  for (const migration of pendingMigrations) {
2347
2443
  const [, err] = await attempt(() => migration.up(db, dialect));
2348
2444
  if (err) {
@@ -2359,7 +2455,7 @@ async function migrateSchema(db, dialect, options) {
2359
2455
  }).execute();
2360
2456
  const durationMs = performance.now() - start;
2361
2457
  observer.emit("version:schema:migrated", {
2362
- from: status.current,
2458
+ from: currentVersion,
2363
2459
  to: CURRENT_VERSIONS.schema,
2364
2460
  durationMs
2365
2461
  });
@@ -3178,11 +3274,13 @@ var ChangeHistory = class {
3178
3274
  #ndb;
3179
3275
  #tables;
3180
3276
  #configName;
3277
+ #dialect;
3181
3278
  constructor(db, configName, dialect = "sqlite") {
3182
3279
  this.#db = db;
3183
3280
  this.#ndb = noormDb(db, dialect);
3184
3281
  this.#tables = getNoormTables(dialect);
3185
3282
  this.#configName = configName;
3283
+ this.#dialect = dialect;
3186
3284
  }
3187
3285
  // ─────────────────────────────────────────────────────────
3188
3286
  // Status Queries
@@ -3358,34 +3456,63 @@ var ChangeHistory = class {
3358
3456
  * @returns The created operation's ID
3359
3457
  */
3360
3458
  async createOperation(data2) {
3361
- const [result, err] = await attempt(
3362
- () => this.#ndb.insertInto(this.#tables.change).values({
3363
- name: data2.name,
3364
- change_type: "change",
3365
- direction: data2.direction,
3366
- status: "pending",
3367
- config_name: this.#configName,
3368
- executed_by: data2.executedBy
3369
- }).returning("id").executeTakeFirstOrThrow()
3370
- );
3371
- if (err) {
3372
- throw new Error("Failed to create change operation record", { cause: err });
3373
- }
3374
- let id = result?.id;
3375
- if (id === null || id === void 0) {
3376
- const [lastIdResult, lastIdErr] = await attempt(
3377
- () => sql`SELECT last_insert_rowid() as id`.execute(this.#db)
3459
+ const insertQuery = this.#ndb.insertInto(this.#tables.change).values({
3460
+ name: data2.name,
3461
+ change_type: "change",
3462
+ direction: data2.direction,
3463
+ status: "pending",
3464
+ config_name: this.#configName,
3465
+ executed_by: data2.executedBy
3466
+ });
3467
+ let id;
3468
+ if (this.#dialect === "mssql") {
3469
+ const [result, insertErr] = await attempt(
3470
+ () => insertQuery.output("inserted.id as id").executeTakeFirstOrThrow()
3471
+ );
3472
+ if (insertErr) {
3473
+ throw new Error("Failed to create change operation record", { cause: insertErr });
3474
+ }
3475
+ id = result?.id;
3476
+ } else {
3477
+ const [result, err] = await attempt(
3478
+ () => insertQuery.returning("id").executeTakeFirstOrThrow()
3378
3479
  );
3379
- if (lastIdErr || !lastIdResult?.rows?.[0]?.id) {
3380
- throw new Error("Failed to retrieve last insert row id");
3480
+ if (err) {
3481
+ throw new Error("Failed to create change operation record", { cause: err });
3482
+ }
3483
+ id = result?.id ?? void 0;
3484
+ if (id === null || id === void 0) {
3485
+ const lastIdQuery = this.#lastInsertIdQuery();
3486
+ if (lastIdQuery) {
3487
+ const [lastIdResult] = await attempt(() => lastIdQuery.execute(this.#db));
3488
+ id = lastIdResult?.rows?.[0]?.id;
3489
+ }
3381
3490
  }
3382
- id = lastIdResult.rows[0].id;
3383
3491
  }
3384
3492
  if (typeof id !== "number" || !Number.isFinite(id) || id <= 0) {
3385
3493
  throw new Error(`Invalid operation ID returned: ${id}`);
3386
3494
  }
3387
3495
  return id;
3388
3496
  }
3497
+ /**
3498
+ * Get dialect-specific last-insert-id query.
3499
+ *
3500
+ * Returns null if the dialect should always use RETURNING/OUTPUT.
3501
+ */
3502
+ #lastInsertIdQuery() {
3503
+ switch (this.#dialect) {
3504
+ case "sqlite":
3505
+ return sql`SELECT last_insert_rowid() as id`;
3506
+ case "mysql":
3507
+ return sql`SELECT LAST_INSERT_ID() as id`;
3508
+ case "mssql":
3509
+ return sql`SELECT SCOPE_IDENTITY() as id`;
3510
+ case "postgres":
3511
+ return sql`SELECT lastval() as id`;
3512
+ default:
3513
+ return null;
3514
+ }
3515
+ }
3389
3516
  /**
3390
3517
  * Create pending file records for all files.
3391
3518
  *
@@ -3463,7 +3590,7 @@ var ChangeHistory = class {
3463
3590
  const [, err] = await attempt(
3464
3591
  () => this.#ndb.updateTable(this.#tables.executions).set({
3465
3592
  status: "skipped",
3466
- skip_reason: reason
3593
+ skip_reason: reason.slice(0, 100)
3467
3594
  }).where("change_id", "=", operationId).where("status", "=", "pending").execute()
3468
3595
  );
3469
3596
  if (err) {
@@ -3524,18 +3651,33 @@ var ChangeHistory = class {
3524
3651
  * @returns The created record's ID
3525
3652
  */
3526
3653
  async recordReset(executedBy, reason) {
3654
+ const insertQuery = this.#ndb.insertInto(this.#tables.change).values({
3655
+ name: "__reset__",
3656
+ change_type: "change",
3657
+ direction: "change",
3658
+ status: "success",
3659
+ config_name: this.#configName,
3660
+ executed_by: executedBy,
3661
+ error_message: reason ?? "",
3662
+ duration_ms: 0,
3663
+ checksum: ""
3664
+ });
3665
+ if (this.#dialect === "mssql") {
3666
+ const [result2, insertErr] = await attempt(
3667
+ () => insertQuery.output("inserted.id as id").executeTakeFirstOrThrow()
3668
+ );
3669
+ if (insertErr) {
3670
+ observer.emit("error", {
3671
+ source: "change",
3672
+ error: insertErr,
3673
+ context: { operation: "record-reset" }
3674
+ });
3675
+ return 0;
3676
+ }
3677
+ return result2?.id ?? 0;
3678
+ }
3527
3679
  const [result, err] = await attempt(
3528
- () => this.#ndb.insertInto(this.#tables.change).values({
3529
- name: "__reset__",
3530
- change_type: "change",
3531
- direction: "change",
3532
- status: "success",
3533
- config_name: this.#configName,
3534
- executed_by: executedBy,
3535
- error_message: reason ?? "",
3536
- duration_ms: 0,
3537
- checksum: ""
3538
- }).returning("id").executeTakeFirstOrThrow()
3680
+ () => insertQuery.returning("id").executeTakeFirstOrThrow()
3539
3681
  );
3540
3682
  if (err) {
3541
3683
  observer.emit("error", {
@@ -3749,11 +3891,13 @@ var Tracker = class {
3749
3891
  #ndb;
3750
3892
  #tables;
3751
3893
  #configName;
3894
+ #dialect;
3752
3895
  constructor(db, configName, dialect = "sqlite") {
3753
3896
  this.#db = db;
3754
3897
  this.#ndb = noormDb(db, dialect);
3755
3898
  this.#tables = getNoormTables(dialect);
3756
3899
  this.#configName = configName;
3900
+ this.#dialect = dialect;
3757
3901
  }
3758
3902
  /**
3759
3903
  * Check if a file needs to run.
@@ -3839,34 +3983,63 @@ var Tracker = class {
3839
3983
  async createOperation(data2) {
3840
3984
  const direction = data2.direction ?? "commit";
3841
3985
  const dbDirection = direction === "commit" ? "change" : "revert";
3842
- const [result, err] = await attempt(
3843
- () => this.#ndb.insertInto(this.#tables.change).values({
3844
- name: data2.name,
3845
- change_type: data2.changeType,
3846
- direction: dbDirection,
3847
- status: "pending",
3848
- config_name: data2.configName,
3849
- executed_by: data2.executedBy
3850
- }).returning("id").executeTakeFirstOrThrow()
3851
- );
3852
- if (err) {
3853
- throw new Error("Failed to create operation record", { cause: err });
3854
- }
3855
- let id = result?.id;
3856
- if (id === null || id === void 0) {
3857
- const [lastIdResult, lastIdErr] = await attempt(
3858
- () => sql`SELECT last_insert_rowid() as id`.execute(this.#db)
3986
+ const insertQuery = this.#ndb.insertInto(this.#tables.change).values({
3987
+ name: data2.name,
3988
+ change_type: data2.changeType,
3989
+ direction: dbDirection,
3990
+ status: "pending",
3991
+ config_name: data2.configName,
3992
+ executed_by: data2.executedBy
3993
+ });
3994
+ let id;
3995
+ if (this.#dialect === "mssql") {
3996
+ const [result, insertErr] = await attempt(
3997
+ () => insertQuery.output("inserted.id as id").executeTakeFirstOrThrow()
3998
+ );
3999
+ if (insertErr) {
4000
+ throw new Error("Failed to create operation record", { cause: insertErr });
4001
+ }
4002
+ id = result?.id;
4003
+ } else {
4004
+ const [result, err] = await attempt(
4005
+ () => insertQuery.returning("id").executeTakeFirstOrThrow()
3859
4006
  );
3860
- if (lastIdErr || !lastIdResult?.rows?.[0]?.id) {
3861
- throw new Error("Failed to retrieve last insert row id");
4007
+ if (err) {
4008
+ throw new Error("Failed to create operation record", { cause: err });
4009
+ }
4010
+ id = result?.id ?? void 0;
4011
+ if (id === null || id === void 0) {
4012
+ const lastIdQuery = this.#lastInsertIdQuery();
4013
+ if (lastIdQuery) {
4014
+ const [lastIdResult] = await attempt(() => lastIdQuery.execute(this.#db));
4015
+ id = lastIdResult?.rows?.[0]?.id;
4016
+ }
3862
4017
  }
3863
- id = lastIdResult.rows[0].id;
3864
4018
  }
3865
4019
  if (typeof id !== "number" || !Number.isFinite(id) || id <= 0) {
3866
4020
  throw new Error(`Invalid operation ID returned: ${id}`);
3867
4021
  }
3868
4022
  return id;
3869
4023
  }
4024
+ /**
4025
+ * Get dialect-specific last-insert-id query.
4026
+ *
4027
+ * Returns null if the dialect should always use RETURNING/OUTPUT.
4028
+ */
4029
+ #lastInsertIdQuery() {
4030
+ switch (this.#dialect) {
4031
+ case "sqlite":
4032
+ return sql`SELECT last_insert_rowid() as id`;
4033
+ case "mysql":
4034
+ return sql`SELECT LAST_INSERT_ID() as id`;
4035
+ case "mssql":
4036
+ return sql`SELECT SCOPE_IDENTITY() as id`;
4037
+ case "postgres":
4038
+ return sql`SELECT lastval() as id`;
4039
+ default:
4040
+ return null;
4041
+ }
4042
+ }
3870
4043
  /**
3871
4044
  * Record a file execution.
3872
4045
  *
@@ -4029,10 +4202,11 @@ var Tracker = class {
4029
4202
  * @returns Error message if skip failed, null on success
4030
4203
  */
4031
4204
  async skipRemainingFiles(operationId, reason) {
4205
+ const truncatedReason = reason.slice(0, 100);
4032
4206
  const [, err] = await attempt(
4033
4207
  () => this.#ndb.updateTable(this.#tables.executions).set({
4034
4208
  status: "skipped",
4035
- skip_reason: reason
4209
+ skip_reason: truncatedReason
4036
4210
  }).where("change_id", "=", operationId).where("status", "=", "pending").execute()
4037
4211
  );
4038
4212
  if (err) {
@@ -5746,7 +5920,10 @@ async function runBuild(context, sqlPath, options = {}, preFilteredFiles) {
5746
5920
  error: discoverErr,
5747
5921
  context: { sqlPath, operation: "discover-files" }
5748
5922
  });
5749
- return createFailedBatchResult(discoverErr.message, performance.now() - start);
5923
+ return createFailedBatchResult(
5924
+ `${discoverErr.message} (${sqlPath})`,
5925
+ performance.now() - start
5926
+ );
5750
5927
  }
5751
5928
  files = discovered;
5752
5929
  }
@@ -5754,13 +5931,14 @@ async function runBuild(context, sqlPath, options = {}, preFilteredFiles) {
5754
5931
  sqlPath,
5755
5932
  fileCount: files.length
5756
5933
  });
5757
- const result = await executeFilesInternal(context, files, opts, "build");
5934
+ const result = await executeFilesInternal(context, files, opts, "build", sqlPath);
5758
5935
  observer.emit("build:complete", {
5759
5936
  status: result.status,
5760
5937
  filesRun: result.filesRun,
5761
5938
  filesSkipped: result.filesSkipped,
5762
5939
  filesFailed: result.filesFailed,
5763
- durationMs: result.durationMs
5940
+ durationMs: result.durationMs,
5941
+ error: result.error
5764
5942
  });
5765
5943
  return result;
5766
5944
  }
@@ -5813,14 +5991,17 @@ async function runDir(context, dirpath, options = {}) {
5813
5991
  error: discoverErr,
5814
5992
  context: { dirpath, operation: "discover-files" }
5815
5993
  });
5816
- return createFailedBatchResult(discoverErr.message, performance.now() - start);
5994
+ return createFailedBatchResult(
5995
+ `${discoverErr.message} (${dirpath})`,
5996
+ performance.now() - start
5997
+ );
5817
5998
  }
5818
5999
  observer.emit("run:dir", {
5819
6000
  dirpath,
5820
6001
  fileCount: files.length,
5821
6002
  configName: context.configName
5822
6003
  });
5823
- return executeFilesInternal(context, files, opts, "run");
6004
+ return executeFilesInternal(context, files, opts, "run", dirpath);
5824
6005
  }
5825
6006
  async function runFiles(context, files, options = {}) {
5826
6007
  const opts = { ...DEFAULT_RUN_OPTIONS_INTERNAL, ...options };
@@ -5916,9 +6097,12 @@ async function executeFiles2(context, files, runOptions, execOptions) {
5916
6097
  observer.emit("error", {
5917
6098
  source: "runner:create-operation",
5918
6099
  error: createErr,
5919
- context: { operationName: execOptions.operationName }
6100
+ context: { operationName: execOptions.operationName, sqlPath: execOptions.sqlPath }
5920
6101
  });
5921
- return createFailedBatchResult(createErr.message, performance.now() - start);
6102
+ return createFailedBatchResult(
6103
+ formatErrorChain(createErr),
6104
+ performance.now() - start
6105
+ );
5922
6106
  }
5923
6107
  const fileRecords = [];
5924
6108
  for (const file of files) {
@@ -5941,7 +6125,10 @@ async function executeFiles2(context, files, runOptions, execOptions) {
5941
6125
  context: { operationId }
5942
6126
  });
5943
6127
  await tracker.finalizeOperation(operationId, "failed", 0, "", createRecordsErr);
5944
- return createFailedBatchResult(createRecordsErr, performance.now() - start);
6128
+ return createFailedBatchResult(
6129
+ createRecordsErr,
6130
+ performance.now() - start
6131
+ );
5945
6132
  }
5946
6133
  const results = [];
5947
6134
  let failed = false;
@@ -5962,7 +6149,7 @@ async function executeFiles2(context, files, runOptions, execOptions) {
5962
6149
  failed = true;
5963
6150
  const skipErr = await tracker.skipRemainingFiles(
5964
6151
  operationId,
5965
- `Skipped due to failure in ${file.path}`
6152
+ `Skipped: failure in ${path.basename(file.path)}`.slice(0, 100)
5966
6153
  );
5967
6154
  if (skipErr) {
5968
6155
  observer.emit("error", {
@@ -6008,7 +6195,7 @@ async function executeFiles2(context, files, runOptions, execOptions) {
6008
6195
  changeId: operationId
6009
6196
  };
6010
6197
  }
6011
- async function executeFilesInternal(context, files, options, changeType) {
6198
+ async function executeFilesInternal(context, files, options, changeType, sqlPath) {
6012
6199
  const fileInputs = files.map((f) => ({
6013
6200
  path: f,
6014
6201
  type: "sql"
@@ -6016,7 +6203,8 @@ async function executeFilesInternal(context, files, options, changeType) {
6016
6203
  const execOptions = {
6017
6204
  changeType,
6018
6205
  direction: "commit",
6019
- operationName: `${changeType}:${(/* @__PURE__ */ new Date()).toISOString()}`
6206
+ operationName: `${changeType}:${(/* @__PURE__ */ new Date()).toISOString()}`,
6207
+ sqlPath
6020
6208
  };
6021
6209
  return executeFiles2(context, fileInputs, options, execOptions);
6022
6210
  }
@@ -6086,7 +6274,7 @@ async function executeSingleFileWithUpdate(context, filepath, checksum, options,
6086
6274
  filepath,
6087
6275
  checksum: finalChecksum,
6088
6276
  status: "failed",
6089
- error: execErr.message,
6277
+ error: getErrorMessage(execErr),
6090
6278
  durationMs
6091
6279
  };
6092
6280
  await tracker.updateFileExecution(
@@ -6094,13 +6282,13 @@ async function executeSingleFileWithUpdate(context, filepath, checksum, options,
6094
6282
  relFilepath,
6095
6283
  "failed",
6096
6284
  Math.round(durationMs),
6097
- execErr.message
6285
+ getErrorMessage(execErr)
6098
6286
  );
6099
6287
  observer.emit("file:after", {
6100
6288
  filepath,
6101
6289
  status: "failed",
6102
6290
  durationMs,
6103
- error: execErr.message
6291
+ error: getErrorMessage(execErr)
6104
6292
  });
6105
6293
  return result2;
6106
6294
  }
@@ -6210,7 +6398,7 @@ async function executeSingleFile(context, filepath, options, tracker, operationI
6210
6398
  filepath,
6211
6399
  checksum,
6212
6400
  status: "failed",
6213
- error: execErr.message,
6401
+ error: getErrorMessage(execErr),
6214
6402
  durationMs
6215
6403
  };
6216
6404
  await tracker.recordExecution({
@@ -6218,14 +6406,14 @@ async function executeSingleFile(context, filepath, options, tracker, operationI
6218
6406
  filepath: relFilepath,
6219
6407
  checksum,
6220
6408
  status: "failed",
6221
- errorMessage: execErr.message,
6409
+ errorMessage: getErrorMessage(execErr),
6222
6410
  durationMs: Math.round(durationMs)
6223
6411
  });
6224
6412
  observer.emit("file:after", {
6225
6413
  filepath,
6226
6414
  status: "failed",
6227
6415
  durationMs,
6228
- error: execErr.message
6416
+ error: getErrorMessage(execErr)
6229
6417
  });
6230
6418
  return result2;
6231
6419
  }
@@ -6368,9 +6556,40 @@ function createFailedBatchResult(error, durationMs) {
6368
6556
  filesRun: 0,
6369
6557
  filesSkipped: 0,
6370
6558
  filesFailed: 0,
6371
- durationMs
6559
+ durationMs,
6560
+ error
6372
6561
  };
6373
6562
  }
6563
+ function formatErrorChain(err) {
6564
+ const parts = [err.message];
6565
+ let current = err.cause;
6566
+ while (current instanceof Error) {
6567
+ parts.push(current.message);
6568
+ current = current.cause;
6569
+ }
6570
+ return parts.join(": ");
6571
+ }
6572
+ function getErrorMessage(err) {
6573
+ if (err instanceof AggregateError && err.errors?.length > 0) {
6574
+ return err.errors.map((e) => getErrorMessage(e)).join("; ");
6575
+ }
6576
+ if (err instanceof Error && err.message) return err.message;
6577
+ if (typeof err === "string") return err;
6578
+ if (typeof err === "object" && err !== null) {
6579
+ const e = err;
6580
+ if (typeof e["message"] === "string" && e["message"]) return e["message"];
6581
+ if (Array.isArray(e["errors"]) && e["errors"].length > 0) {
6582
+ return e["errors"].map((inner) => getErrorMessage(inner)).join("; ");
6583
+ }
6584
+ if (typeof e["originalError"] === "object" && e["originalError"] !== null) {
6585
+ const orig = e["originalError"];
6586
+ if (typeof orig["message"] === "string" && orig["message"]) return orig["message"];
6587
+ }
6588
+ const str = String(err);
6589
+ if (str !== "[object Object]") return str;
6590
+ }
6591
+ return "Unknown error";
6592
+ }
6374
6593
 
6375
6594
  // src/sdk/namespaces/run.ts
6376
6595
  var RunNamespace = class {