@noormdev/sdk 1.0.0-alpha.34 → 1.0.0-alpha.36

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,8 +1,8 @@
1
1
  import { createRequire } from 'module';
2
2
  import { processFile, DtReader, isTemplate, FORMAT_VERSION, DT_EXTENSIONS, encryptWithPassphrase, ENCODED_TYPES } from './chunk-5AYPF5D5.js';
3
3
  import './chunk-UOFJT5PY.js';
4
- import { initializeVault, getVaultStatus, setVaultSecret, getVaultSecret, getAllVaultSecrets, listVaultSecretKeys, deleteVaultSecret, vaultSecretExists, getVaultKey, noormDb, getNoormTables, encryptVaultKey } from './chunk-DE3NJ22A.js';
5
- import { makeNestedConfig, gigabytes, observer, attempt, retry, attemptSync, clone, merge, ObserverRelay } from './chunk-DLQES4PM.js';
4
+ import { initializeVault, getVaultStatus, setVaultSecret, getVaultSecret, getAllVaultSecrets, listVaultSecretKeys, deleteVaultSecret, vaultSecretExists, getVaultKey, noormDb, getNoormTables, encryptVaultKey } from './chunk-WFVWNXTV.js';
5
+ import { makeNestedConfig, gigabytes, observer, attempt, attemptSync, retry, clone, merge, ObserverRelay } from './chunk-DLQES4PM.js';
6
6
  import { dist_default } from './chunk-TBRUDUBQ.js';
7
7
  import { require_dist } from './chunk-YJBKNUTP.js';
8
8
  import { __commonJS, __export, __toESM } from './chunk-D2XW2UOP.js';
@@ -15797,7 +15797,7 @@ async function getDialectFactory(dialect) {
15797
15797
  case "mysql":
15798
15798
  return (await import('./mysql-UPMQUBLE.js')).createMysqlConnection;
15799
15799
  case "mssql":
15800
- return (await import('./mssql-3NNVV77C.js')).createMssqlConnection;
15800
+ return (await import('./mssql-NIABT3Z4.js')).createMssqlConnection;
15801
15801
  default:
15802
15802
  throw new Error(`Unsupported dialect: ${dialect}`);
15803
15803
  }
@@ -16938,6 +16938,16 @@ function isTvp(value) {
16938
16938
  }
16939
16939
 
16940
16940
  // src/sdk/sql.ts
16941
+ function quoteIdent(dialect, name) {
16942
+ const parts = name.includes(".") ? name.split(".", 2) : [name];
16943
+ if (dialect === "postgres") {
16944
+ return parts.map((p) => '"' + p.replace(/"/g, '""') + '"').join(".");
16945
+ }
16946
+ if (dialect === "mssql") {
16947
+ return parts.map((p) => "[" + p.replace(/]/g, "]]") + "]").join(".");
16948
+ }
16949
+ return parts.map((p) => "`" + p.replace(/`/g, "``") + "`").join(".");
16950
+ }
16941
16951
  function buildProcCall(dialect, name, params) {
16942
16952
  if (dialect === "sqlite") {
16943
16953
  throw new Error("SQLite does not support stored procedures.");
@@ -16945,7 +16955,7 @@ function buildProcCall(dialect, name, params) {
16945
16955
  if (params && hasTvpParam(params) && dialect !== "mssql") {
16946
16956
  throw new Error("Table-valued parameters (TVP) are only supported on MSSQL.");
16947
16957
  }
16948
- const rawName = sql.raw(name);
16958
+ const rawName = sql.raw(quoteIdent(dialect, name));
16949
16959
  if (!params || Array.isArray(params) && params.length === 0 || !Array.isArray(params) && Object.keys(params).length === 0) {
16950
16960
  if (dialect === "mssql") {
16951
16961
  return sql`EXEC ${rawName}`;
@@ -16967,8 +16977,8 @@ function buildFuncCall(dialect, name, column, params) {
16967
16977
  if (params && hasTvpParam(params) && dialect !== "mssql") {
16968
16978
  throw new Error("Table-valued parameters (TVP) are only supported on MSSQL.");
16969
16979
  }
16970
- const rawName = sql.raw(name);
16971
- const rawCol = sql.raw(column);
16980
+ const rawName = sql.raw(quoteIdent(dialect, name));
16981
+ const rawCol = sql.raw(quoteIdent(dialect, column));
16972
16982
  if (!params || Array.isArray(params) && params.length === 0 || !Array.isArray(params) && Object.keys(params).length === 0) {
16973
16983
  return sql`SELECT ${rawName}() AS ${rawCol}`;
16974
16984
  }
@@ -16977,7 +16987,7 @@ function buildFuncCall(dialect, name, column, params) {
16977
16987
  }
16978
16988
  if (dialect === "postgres" && !Array.isArray(params)) {
16979
16989
  const parts = Object.entries(params).map(
16980
- ([key, val]) => sql`${sql.raw(key)} => ${val}`
16990
+ ([key, val]) => sql`${sql.raw(quoteIdent("postgres", key))} => ${val}`
16981
16991
  );
16982
16992
  return sql`SELECT ${rawName}(${sql.join(parts)}) AS ${rawCol}`;
16983
16993
  }
@@ -17004,7 +17014,7 @@ function buildTvfCall(dialect, name, params) {
17004
17014
  if (params && hasTvpParam(params) && dialect !== "mssql") {
17005
17015
  throw new Error("Table-valued parameters (TVP) are only supported on MSSQL.");
17006
17016
  }
17007
- const rawName = sql.raw(name);
17017
+ const rawName = sql.raw(quoteIdent(dialect, name));
17008
17018
  if (!params || Array.isArray(params) && params.length === 0 || !Array.isArray(params) && Object.keys(params).length === 0) {
17009
17019
  return sql`SELECT * FROM ${rawName}()`;
17010
17020
  }
@@ -17013,7 +17023,7 @@ function buildTvfCall(dialect, name, params) {
17013
17023
  }
17014
17024
  if (dialect === "postgres" && !Array.isArray(params)) {
17015
17025
  const parts = Object.entries(params).map(
17016
- ([key, val]) => sql`${sql.raw(key)} => ${val}`
17026
+ ([key, val]) => sql`${sql.raw(quoteIdent("postgres", key))} => ${val}`
17017
17027
  );
17018
17028
  return sql`SELECT * FROM ${rawName}(${sql.join(parts)})`;
17019
17029
  }
@@ -17040,7 +17050,7 @@ function buildPostgresProc(rawName, params) {
17040
17050
  return sql`CALL ${rawName}(${joined})`;
17041
17051
  }
17042
17052
  const parts = Object.entries(params).map(
17043
- ([key, val]) => sql`${sql.raw(key)} => ${val}`
17053
+ ([key, val]) => sql`${sql.raw(quoteIdent("postgres", key))} => ${val}`
17044
17054
  );
17045
17055
  return sql`CALL ${rawName}(${sql.join(parts)})`;
17046
17056
  }
@@ -20233,9 +20243,16 @@ var ChangesNamespace = class {
20233
20243
  /**
20234
20244
  * Apply a specific change.
20235
20245
  *
20246
+ * Pass `dryRun: true` to render the change to `tmp/` without
20247
+ * touching the database, or `preview: true` to emit rendered SQL.
20248
+ *
20236
20249
  * @example
20237
20250
  * ```typescript
20238
20251
  * const result = await ctx.noorm.changes.apply('2024-01-15-add-users')
20252
+ * const dry = await ctx.noorm.changes.apply(
20253
+ * '2024-01-15-add-users',
20254
+ * { dryRun: true },
20255
+ * )
20239
20256
  * ```
20240
20257
  */
20241
20258
  async apply(name, options) {
@@ -20244,9 +20261,16 @@ var ChangesNamespace = class {
20244
20261
  /**
20245
20262
  * Revert a specific change.
20246
20263
  *
20264
+ * Pass `dryRun: true` to render the revert files to `tmp/` without
20265
+ * touching the database, or `preview: true` to emit rendered SQL.
20266
+ *
20247
20267
  * @example
20248
20268
  * ```typescript
20249
20269
  * const result = await ctx.noorm.changes.revert('2024-01-15-add-users')
20270
+ * const dry = await ctx.noorm.changes.revert(
20271
+ * '2024-01-15-add-users',
20272
+ * { dryRun: true },
20273
+ * )
20250
20274
  * ```
20251
20275
  */
20252
20276
  async revert(name, options) {
@@ -20256,25 +20280,37 @@ var ChangesNamespace = class {
20256
20280
  /**
20257
20281
  * Apply all pending changes.
20258
20282
  *
20283
+ * Pass `dryRun: true` to render each change to `tmp/` without
20284
+ * touching the database. Pass `preview: true` to emit rendered
20285
+ * SQL (also without DB writes). When omitted, both default to
20286
+ * `false` and changes are applied normally.
20287
+ *
20259
20288
  * @example
20260
20289
  * ```typescript
20261
20290
  * const result = await ctx.noorm.changes.ff()
20291
+ * const dry = await ctx.noorm.changes.ff({ dryRun: true })
20292
+ * const forced = await ctx.noorm.changes.ff({ force: true })
20262
20293
  * ```
20263
20294
  */
20264
- async ff() {
20265
- return this.#getManager().ff();
20295
+ async ff(options) {
20296
+ return this.#getManager().ff(options);
20266
20297
  }
20267
20298
  /**
20268
20299
  * Apply the next N pending changes (default 1).
20269
20300
  *
20301
+ * Pass `dryRun: true` to render the changes to `tmp/` without
20302
+ * touching the database. Pass `preview: true` to emit rendered
20303
+ * SQL (also without DB writes).
20304
+ *
20270
20305
  * @example
20271
20306
  * ```typescript
20272
20307
  * const result = await ctx.noorm.changes.next()
20273
20308
  * const result = await ctx.noorm.changes.next(3)
20309
+ * const dry = await ctx.noorm.changes.next(2, { dryRun: true })
20274
20310
  * ```
20275
20311
  */
20276
- async next(count = 1) {
20277
- return this.#getManager().next(count);
20312
+ async next(count = 1, options) {
20313
+ return this.#getManager().next(count, options);
20278
20314
  }
20279
20315
  // ─────────────────────────────────────────────────────
20280
20316
  // Status & history (connected)
@@ -20405,6 +20441,48 @@ var ChangesNamespace = class {
20405
20441
  return this.#manager;
20406
20442
  }
20407
20443
  };
20444
+ function splitMssqlBatches(content) {
20445
+ const batches = content.split(/^\s*GO\s*$/mi);
20446
+ return batches.map((b) => b.trim()).filter((b) => b.length > 0 && !isCommentOnly(b));
20447
+ }
20448
+ function isCommentOnly(batch) {
20449
+ for (const line of batch.split("\n")) {
20450
+ const trimmed = line.trim();
20451
+ if (trimmed.length > 0 && !trimmed.startsWith("--")) {
20452
+ return false;
20453
+ }
20454
+ }
20455
+ return true;
20456
+ }
20457
+ async function executeSqlBody(context, sqlContent) {
20458
+ if (context.dialect !== "mssql") {
20459
+ const [, execErr] = await attempt(() => sql.raw(sqlContent).execute(context.db));
20460
+ if (execErr) {
20461
+ return getSqlErrorMessage(execErr);
20462
+ }
20463
+ return null;
20464
+ }
20465
+ const batches = splitMssqlBatches(sqlContent);
20466
+ if (batches.length === 0) {
20467
+ return null;
20468
+ }
20469
+ if (batches.length === 1) {
20470
+ const [, execErr] = await attempt(() => sql.raw(batches[0]).execute(context.db));
20471
+ if (execErr) {
20472
+ return getSqlErrorMessage(execErr);
20473
+ }
20474
+ return null;
20475
+ }
20476
+ for (let i = 0; i < batches.length; i++) {
20477
+ const [, execErr] = await attempt(() => sql.raw(batches[i]).execute(context.db));
20478
+ if (execErr) {
20479
+ return `[batch ${i + 1} of ${batches.length}] ${getSqlErrorMessage(execErr)}`;
20480
+ }
20481
+ }
20482
+ return null;
20483
+ }
20484
+
20485
+ // src/core/runner/runner.ts
20408
20486
  var SQL_EXTENSIONS2 = [".sql", ".sql.tmpl"];
20409
20487
  var FILE_HEADER_TEMPLATE = `-- ============================================================
20410
20488
  -- File: %FILE%
@@ -20770,14 +20848,14 @@ async function executeSingleFileWithUpdate(context, filepath, checksum, options,
20770
20848
  return result2;
20771
20849
  }
20772
20850
  }
20773
- const [, execErr] = await attempt(() => sql.raw(sqlContent).execute(context.db));
20851
+ const execErrMsg = await executeSqlBody(context, sqlContent);
20774
20852
  const durationMs = performance.now() - start;
20775
- if (execErr) {
20853
+ if (execErrMsg) {
20776
20854
  const result2 = {
20777
20855
  filepath,
20778
20856
  checksum: finalChecksum,
20779
20857
  status: "failed",
20780
- error: getSqlErrorMessage(execErr),
20858
+ error: execErrMsg,
20781
20859
  durationMs
20782
20860
  };
20783
20861
  await tracker.updateFileExecution(
@@ -20785,13 +20863,13 @@ async function executeSingleFileWithUpdate(context, filepath, checksum, options,
20785
20863
  relFilepath,
20786
20864
  "failed",
20787
20865
  Math.round(durationMs),
20788
- getSqlErrorMessage(execErr)
20866
+ execErrMsg
20789
20867
  );
20790
20868
  observer.emit("file:after", {
20791
20869
  filepath,
20792
20870
  status: "failed",
20793
20871
  durationMs,
20794
- error: getSqlErrorMessage(execErr)
20872
+ error: execErrMsg
20795
20873
  });
20796
20874
  return result2;
20797
20875
  }
@@ -20894,14 +20972,14 @@ async function executeSingleFile(context, filepath, options, tracker, operationI
20894
20972
  });
20895
20973
  return result2;
20896
20974
  }
20897
- const [, execErr] = await attempt(() => sql.raw(sqlContent).execute(context.db));
20975
+ const execErrMsg = await executeSqlBody(context, sqlContent);
20898
20976
  const durationMs = performance.now() - start;
20899
- if (execErr) {
20977
+ if (execErrMsg) {
20900
20978
  const result2 = {
20901
20979
  filepath,
20902
20980
  checksum,
20903
20981
  status: "failed",
20904
- error: getSqlErrorMessage(execErr),
20982
+ error: execErrMsg,
20905
20983
  durationMs
20906
20984
  };
20907
20985
  await tracker.recordExecution({
@@ -20909,14 +20987,14 @@ async function executeSingleFile(context, filepath, options, tracker, operationI
20909
20987
  filepath: relFilepath,
20910
20988
  checksum,
20911
20989
  status: "failed",
20912
- errorMessage: getSqlErrorMessage(execErr),
20990
+ errorMessage: execErrMsg,
20913
20991
  durationMs: Math.round(durationMs)
20914
20992
  });
20915
20993
  observer.emit("file:after", {
20916
20994
  filepath,
20917
20995
  status: "failed",
20918
20996
  durationMs,
20919
- error: getSqlErrorMessage(execErr)
20997
+ error: execErrMsg
20920
20998
  });
20921
20999
  return result2;
20922
21000
  }
@@ -23442,10 +23520,10 @@ var { quote, qualifiedName } = createDialectQuoting({
23442
23520
  escape: '""'
23443
23521
  });
23444
23522
  var postgresTeardownOperations = {
23445
- disableForeignKeyChecks() {
23523
+ disableForeignKeyChecks(_tables) {
23446
23524
  return "SET session_replication_role = 'replica'";
23447
23525
  },
23448
- enableForeignKeyChecks() {
23526
+ enableForeignKeyChecks(_tables) {
23449
23527
  return "SET session_replication_role = 'origin'";
23450
23528
  },
23451
23529
  truncateTable(tableName, schema, restartIdentity = true) {
@@ -23480,10 +23558,10 @@ var { quote: quote2, qualifiedName: qualifiedName2 } = createDialectQuoting({
23480
23558
  escape: "``"
23481
23559
  });
23482
23560
  var mysqlTeardownOperations = {
23483
- disableForeignKeyChecks() {
23561
+ disableForeignKeyChecks(_tables) {
23484
23562
  return "SET FOREIGN_KEY_CHECKS = 0";
23485
23563
  },
23486
- enableForeignKeyChecks() {
23564
+ enableForeignKeyChecks(_tables) {
23487
23565
  return "SET FOREIGN_KEY_CHECKS = 1";
23488
23566
  },
23489
23567
  truncateTable(tableName, schema, _restartIdentity = true) {
@@ -23516,10 +23594,16 @@ var { quote: quote3, qualifiedName: qualifiedName3 } = createDialectQuoting({
23516
23594
  escape: "]]"
23517
23595
  });
23518
23596
  var mssqlTeardownOperations = {
23519
- disableForeignKeyChecks() {
23597
+ disableForeignKeyChecks(tables) {
23598
+ if (tables && tables.length > 0) {
23599
+ return tables.map((t) => `ALTER TABLE ${qualifiedName3(t)} NOCHECK CONSTRAINT ALL`);
23600
+ }
23520
23601
  return "EXEC sp_MSforeachtable 'ALTER TABLE ? NOCHECK CONSTRAINT ALL'";
23521
23602
  },
23522
- enableForeignKeyChecks() {
23603
+ enableForeignKeyChecks(tables) {
23604
+ if (tables && tables.length > 0) {
23605
+ return tables.map((t) => `ALTER TABLE ${qualifiedName3(t)} CHECK CONSTRAINT ALL`);
23606
+ }
23523
23607
  return "EXEC sp_MSforeachtable 'ALTER TABLE ? CHECK CONSTRAINT ALL'";
23524
23608
  },
23525
23609
  truncateTable(tableName, schema, restartIdentity = true) {
@@ -23548,6 +23632,15 @@ var mssqlTeardownOperations = {
23548
23632
  },
23549
23633
  dropForeignKey(constraintName, tableName, schema) {
23550
23634
  return `ALTER TABLE ${qualifiedName3(tableName, schema)} DROP CONSTRAINT ${quote3(constraintName)}`;
23635
+ },
23636
+ dropCheckConstraints() {
23637
+ return [
23638
+ "DECLARE @noorm_drop_checks NVARCHAR(MAX) = N'';",
23639
+ "SELECT @noorm_drop_checks += N'ALTER TABLE ' + QUOTENAME(SCHEMA_NAME(t.schema_id)) + N'.' + QUOTENAME(t.name) + N' DROP CONSTRAINT ' + QUOTENAME(cc.name) + N';'",
23640
+ "FROM sys.check_constraints cc INNER JOIN sys.tables t ON cc.parent_object_id = t.object_id",
23641
+ "WHERE SCHEMA_NAME(t.schema_id) <> 'noorm';",
23642
+ "EXEC sp_executesql @noorm_drop_checks;"
23643
+ ].join("\n");
23551
23644
  }
23552
23645
  };
23553
23646
 
@@ -23558,10 +23651,10 @@ var { quote: quote4 } = createDialectQuoting({
23558
23651
  escape: '""'
23559
23652
  });
23560
23653
  var sqliteTeardownOperations = {
23561
- disableForeignKeyChecks() {
23654
+ disableForeignKeyChecks(_tables) {
23562
23655
  return "PRAGMA foreign_keys = OFF";
23563
23656
  },
23564
- enableForeignKeyChecks() {
23657
+ enableForeignKeyChecks(_tables) {
23565
23658
  return "PRAGMA foreign_keys = ON";
23566
23659
  },
23567
23660
  truncateTable(tableName, _schema, _restartIdentity = false) {
@@ -23611,6 +23704,13 @@ function isNoormTable2(name) {
23611
23704
  if (name.startsWith("__noorm_")) return true;
23612
23705
  return false;
23613
23706
  }
23707
+ function pushFlat(out, value) {
23708
+ if (Array.isArray(value)) {
23709
+ out.push(...value);
23710
+ return;
23711
+ }
23712
+ out.push(value);
23713
+ }
23614
23714
  async function truncateData(db, dialect, options = {}) {
23615
23715
  const startTime = performance.now();
23616
23716
  const ops = getTeardownOperations(dialect);
@@ -23640,11 +23740,13 @@ async function truncateData(db, dialect, options = {}) {
23640
23740
  }
23641
23741
  truncated.push(tableName);
23642
23742
  }
23643
- statements.push(ops.disableForeignKeyChecks());
23644
- for (const tableName of truncated) {
23645
- statements.push(ops.truncateTable(tableName, void 0, options.restartIdentity ?? true));
23743
+ if (truncated.length > 0) {
23744
+ pushFlat(statements, ops.disableForeignKeyChecks(truncated));
23745
+ for (const tableName of truncated) {
23746
+ statements.push(ops.truncateTable(tableName, void 0, options.restartIdentity ?? true));
23747
+ }
23748
+ pushFlat(statements, ops.enableForeignKeyChecks(truncated));
23646
23749
  }
23647
- statements.push(ops.enableForeignKeyChecks());
23648
23750
  if (!options.dryRun) {
23649
23751
  for (const stmt of statements) {
23650
23752
  if (stmt.startsWith("--")) continue;
@@ -23716,6 +23818,21 @@ async function teardownSchema(db, dialect, options = {}) {
23716
23818
  dropped.foreignKeys.push(fk.name);
23717
23819
  statements.push(ops.dropForeignKey(fk.name, tableName, fk.schema));
23718
23820
  }
23821
+ if (!options.keepFunctions && ops.dropCheckConstraints) {
23822
+ statements.push(ops.dropCheckConstraints());
23823
+ }
23824
+ if (!options.keepProcedures) {
23825
+ for (const proc of procedures) {
23826
+ dropped.procedures.push(proc.name);
23827
+ statements.push(ops.dropProcedure(proc.name, proc.schema));
23828
+ }
23829
+ }
23830
+ if (!options.keepFunctions) {
23831
+ for (const fn of functions) {
23832
+ dropped.functions.push(fn.name);
23833
+ statements.push(ops.dropFunction(fn.name, fn.schema));
23834
+ }
23835
+ }
23719
23836
  if (!options.keepViews) {
23720
23837
  for (const view of views) {
23721
23838
  dropped.views.push(view.name);
@@ -23735,18 +23852,6 @@ async function teardownSchema(db, dialect, options = {}) {
23735
23852
  dropped.tables.push(tableName);
23736
23853
  statements.push(ops.dropTable(tableName, table.schema));
23737
23854
  }
23738
- if (!options.keepFunctions) {
23739
- for (const fn of functions) {
23740
- dropped.functions.push(fn.name);
23741
- statements.push(ops.dropFunction(fn.name, fn.schema));
23742
- }
23743
- }
23744
- if (!options.keepProcedures) {
23745
- for (const proc of procedures) {
23746
- dropped.procedures.push(proc.name);
23747
- statements.push(ops.dropProcedure(proc.name, proc.schema));
23748
- }
23749
- }
23750
23855
  const skipTypesDueToKeptRoutines = dialect === "mssql" && (options.keepFunctions || options.keepProcedures);
23751
23856
  if (!options.keepTypes && !skipTypesDueToKeptRoutines) {
23752
23857
  const sortedTypes = [...types].sort((a, b) => {
@@ -24058,7 +24163,11 @@ var DbNamespace = class {
24058
24163
  */
24059
24164
  async reset() {
24060
24165
  checkProtectedConfig(this.#state.config, "reset");
24061
- await this.teardown();
24166
+ await teardownSchema(this.#kysely, this.#dialect, {
24167
+ configName: this.#state.config.name,
24168
+ executedBy: formatIdentity(this.#state.identity),
24169
+ postScript: this.#state.settings.teardown?.postScript
24170
+ });
24062
24171
  if (this.#buildFn) {
24063
24172
  await this.#buildFn({ force: true });
24064
24173
  }
@@ -24406,25 +24515,60 @@ async function copyVaultSecrets(sourceConfig, destConfig, keys, identityHash, pr
24406
24515
  // src/sdk/namespaces/vault.ts
24407
24516
  var VaultNamespace = class {
24408
24517
  #state;
24518
+ #cryptoIdentity = null;
24409
24519
  constructor(state) {
24410
24520
  this.#state = state;
24411
24521
  }
24522
+ /**
24523
+ * Load the cryptographic identity (with publicKey + identityHash) on
24524
+ * demand. The Context's state.identity is the audit identity (name +
24525
+ * email only); the crypto fields needed for vault encryption live in
24526
+ * `~/.noorm/identity.json` (or a NOORM_IDENTITY_* env override).
24527
+ *
24528
+ * Cached after the first call so repeated vault ops don't re-read disk.
24529
+ */
24530
+ async #getCryptoIdentity() {
24531
+ if (this.#cryptoIdentity) return this.#cryptoIdentity;
24532
+ const loaded = await loadIdentityMetadata();
24533
+ if (!loaded) {
24534
+ throw new Error(
24535
+ "Vault operations require a cryptographic identity at ~/.noorm/identity.json. Run `noorm identity init` or set NOORM_IDENTITY_* env vars in CI."
24536
+ );
24537
+ }
24538
+ this.#cryptoIdentity = loaded;
24539
+ return loaded;
24540
+ }
24412
24541
  // ─────────────────────────────────────────────────────
24413
24542
  // Lifecycle
24414
24543
  // ─────────────────────────────────────────────────────
24415
24544
  /**
24416
24545
  * Initialize the vault for this database.
24417
24546
  *
24547
+ * Idempotent. Returns the vault key on first init; returns [null, null]
24548
+ * on subsequent calls when the vault already exists. Returns [null, Error]
24549
+ * only on real failures (DB errors, encryption errors).
24550
+ *
24551
+ * The `vault:initialized` observer event fires only on first init, never
24552
+ * on repeat calls.
24553
+ *
24418
24554
  * @example
24419
24555
  * ```typescript
24420
- * const [vaultKey, err] = await ctx.noorm.vault.init()
24556
+ * const [vaultKey, err] = await ctx.noorm.vault.init();
24557
+ * if (err) throw err;
24558
+ * if (vaultKey) {
24559
+ * // first-time init — set initial secrets, etc.
24560
+ * }
24561
+ * else {
24562
+ * // already initialized — vault.get / vault.set with private key
24563
+ * }
24421
24564
  * ```
24422
24565
  */
24423
24566
  async init() {
24567
+ const crypto = await this.#getCryptoIdentity();
24424
24568
  return initializeVault(
24425
24569
  this.#kysely,
24426
- this.#identityHash,
24427
- this.#publicKey,
24570
+ crypto.identityHash,
24571
+ crypto.publicKey,
24428
24572
  this.#dialect
24429
24573
  );
24430
24574
  }
@@ -24437,9 +24581,10 @@ var VaultNamespace = class {
24437
24581
  * ```
24438
24582
  */
24439
24583
  async status() {
24584
+ const crypto = await this.#getCryptoIdentity();
24440
24585
  return getVaultStatus(
24441
24586
  this.#kysely,
24442
- this.#identityHash,
24587
+ crypto.identityHash,
24443
24588
  this.#dialect
24444
24589
  );
24445
24590
  }
@@ -24576,13 +24721,14 @@ var VaultNamespace = class {
24576
24721
  * ```
24577
24722
  */
24578
24723
  async copy(destConfig, keys, privateKey, options) {
24724
+ const crypto = await this.#getCryptoIdentity();
24579
24725
  return copyVaultSecrets(
24580
24726
  this.#state.config,
24581
24727
  destConfig,
24582
24728
  keys,
24583
- this.#identityHash,
24729
+ crypto.identityHash,
24584
24730
  privateKey,
24585
- this.#publicKey,
24731
+ crypto.publicKey,
24586
24732
  options
24587
24733
  );
24588
24734
  }
@@ -24595,21 +24741,14 @@ var VaultNamespace = class {
24595
24741
  }
24596
24742
  return this.#state.connection.db;
24597
24743
  }
24598
- get #identityHash() {
24599
- const identity = this.#state.identity;
24600
- return identity["identityHash"] ?? "";
24601
- }
24602
- get #publicKey() {
24603
- const identity = this.#state.identity;
24604
- return identity["publicKey"] ?? "";
24605
- }
24606
24744
  get #dialect() {
24607
24745
  return this.#state.config.connection.dialect;
24608
24746
  }
24609
24747
  async #getVaultKey(privateKey) {
24748
+ const crypto = await this.#getCryptoIdentity();
24610
24749
  return getVaultKey(
24611
24750
  this.#kysely,
24612
- this.#identityHash,
24751
+ crypto.identityHash,
24613
24752
  privateKey,
24614
24753
  this.#dialect
24615
24754
  );
@@ -24695,7 +24834,7 @@ function isSameServer(source, dest) {
24695
24834
  }
24696
24835
  return true;
24697
24836
  }
24698
- function quoteIdent(name) {
24837
+ function quoteIdent2(name) {
24699
24838
  return `"${name.replace(/"/g, '""')}"`;
24700
24839
  }
24701
24840
  var postgresTransferOperations = {
@@ -24712,20 +24851,20 @@ var postgresTransferOperations = {
24712
24851
  return null;
24713
24852
  },
24714
24853
  getResetSequenceSql(table, column, schema) {
24715
- const fullTable = schema ? `${quoteIdent(schema)}.${quoteIdent(table)}` : quoteIdent(table);
24854
+ const fullTable = schema ? `${quoteIdent2(schema)}.${quoteIdent2(table)}` : quoteIdent2(table);
24716
24855
  return `
24717
24856
  SELECT setval(
24718
24857
  pg_get_serial_sequence('${schema ? schema + "." : ""}${table}', '${column}'),
24719
- COALESCE((SELECT MAX(${quoteIdent(column)}) FROM ${fullTable}), 0) + 1,
24858
+ COALESCE((SELECT MAX(${quoteIdent2(column)}) FROM ${fullTable}), 0) + 1,
24720
24859
  false
24721
24860
  )
24722
24861
  `.trim();
24723
24862
  },
24724
24863
  buildConflictInsert(table, columns, primaryKey, strategy) {
24725
- const quotedCols = columns.map(quoteIdent).join(", ");
24864
+ const quotedCols = columns.map(quoteIdent2).join(", ");
24726
24865
  const placeholders = columns.map((_, i) => `$${i + 1}`).join(", ");
24727
- const pkCols = primaryKey.map(quoteIdent).join(", ");
24728
- let insertSql = `INSERT INTO ${quoteIdent(table)} (${quotedCols}) OVERRIDING SYSTEM VALUE VALUES (${placeholders})`;
24866
+ const pkCols = primaryKey.map(quoteIdent2).join(", ");
24867
+ let insertSql = `INSERT INTO ${quoteIdent2(table)} (${quotedCols}) OVERRIDING SYSTEM VALUE VALUES (${placeholders})`;
24729
24868
  switch (strategy) {
24730
24869
  case "fail":
24731
24870
  break;
@@ -24735,7 +24874,7 @@ var postgresTransferOperations = {
24735
24874
  case "update": {
24736
24875
  const updateCols = columns.filter((c) => !primaryKey.includes(c));
24737
24876
  if (updateCols.length > 0) {
24738
- const setClauses = updateCols.map((c) => `${quoteIdent(c)} = EXCLUDED.${quoteIdent(c)}`).join(", ");
24877
+ const setClauses = updateCols.map((c) => `${quoteIdent2(c)} = EXCLUDED.${quoteIdent2(c)}`).join(", ");
24739
24878
  insertSql += ` ON CONFLICT (${pkCols}) DO UPDATE SET ${setClauses}`;
24740
24879
  } else {
24741
24880
  insertSql += ` ON CONFLICT (${pkCols}) DO NOTHING`;
@@ -24744,7 +24883,7 @@ var postgresTransferOperations = {
24744
24883
  }
24745
24884
  case "replace":
24746
24885
  {
24747
- const setClauses = columns.map((c) => `${quoteIdent(c)} = EXCLUDED.${quoteIdent(c)}`).join(", ");
24886
+ const setClauses = columns.map((c) => `${quoteIdent2(c)} = EXCLUDED.${quoteIdent2(c)}`).join(", ");
24748
24887
  insertSql += ` ON CONFLICT (${pkCols}) DO UPDATE SET ${setClauses}`;
24749
24888
  }
24750
24889
  break;
@@ -24752,9 +24891,9 @@ var postgresTransferOperations = {
24752
24891
  return insertSql;
24753
24892
  },
24754
24893
  buildDirectTransfer(srcDb, srcTable, dstTable, columns, srcSchema = "public", dstSchema = "public") {
24755
- const quotedCols = columns.map(quoteIdent).join(", ");
24756
- const srcFull = `${quoteIdent(srcSchema)}.${quoteIdent(srcTable)}`;
24757
- const dstFull = `${quoteIdent(dstSchema)}.${quoteIdent(dstTable)}`;
24894
+ const quotedCols = columns.map(quoteIdent2).join(", ");
24895
+ const srcFull = `${quoteIdent2(srcSchema)}.${quoteIdent2(srcTable)}`;
24896
+ const dstFull = `${quoteIdent2(dstSchema)}.${quoteIdent2(dstTable)}`;
24758
24897
  return `INSERT INTO ${dstFull} (${quotedCols}) OVERRIDING SYSTEM VALUE SELECT ${quotedCols} FROM ${srcFull}`;
24759
24898
  },
24760
24899
  async executeDisableFK(db) {
@@ -24764,7 +24903,7 @@ var postgresTransferOperations = {
24764
24903
  await sql.raw(this.getEnableFKSql()).execute(db);
24765
24904
  }
24766
24905
  };
24767
- function quoteIdent2(name) {
24906
+ function quoteIdent3(name) {
24768
24907
  return `\`${name.replace(/`/g, "``")}\``;
24769
24908
  }
24770
24909
  var mysqlTransferOperations = {
@@ -24781,32 +24920,32 @@ var mysqlTransferOperations = {
24781
24920
  return null;
24782
24921
  },
24783
24922
  getResetSequenceSql(table, _column, _schema) {
24784
- return `ALTER TABLE ${quoteIdent2(table)} AUTO_INCREMENT = 1`;
24923
+ return `ALTER TABLE ${quoteIdent3(table)} AUTO_INCREMENT = 1`;
24785
24924
  },
24786
24925
  buildConflictInsert(table, columns, primaryKey, strategy) {
24787
- const quotedCols = columns.map(quoteIdent2).join(", ");
24926
+ const quotedCols = columns.map(quoteIdent3).join(", ");
24788
24927
  const placeholders = columns.map(() => "?").join(", ");
24789
24928
  switch (strategy) {
24790
24929
  case "fail":
24791
- return `INSERT INTO ${quoteIdent2(table)} (${quotedCols}) VALUES (${placeholders})`;
24930
+ return `INSERT INTO ${quoteIdent3(table)} (${quotedCols}) VALUES (${placeholders})`;
24792
24931
  case "skip":
24793
- return `INSERT IGNORE INTO ${quoteIdent2(table)} (${quotedCols}) VALUES (${placeholders})`;
24932
+ return `INSERT IGNORE INTO ${quoteIdent3(table)} (${quotedCols}) VALUES (${placeholders})`;
24794
24933
  case "update": {
24795
24934
  const updateCols = columns.filter((c) => !primaryKey.includes(c));
24796
24935
  if (updateCols.length > 0) {
24797
- const setClauses = updateCols.map((c) => `${quoteIdent2(c)} = VALUES(${quoteIdent2(c)})`).join(", ");
24798
- return `INSERT INTO ${quoteIdent2(table)} (${quotedCols}) VALUES (${placeholders}) ON DUPLICATE KEY UPDATE ${setClauses}`;
24936
+ const setClauses = updateCols.map((c) => `${quoteIdent3(c)} = VALUES(${quoteIdent3(c)})`).join(", ");
24937
+ return `INSERT INTO ${quoteIdent3(table)} (${quotedCols}) VALUES (${placeholders}) ON DUPLICATE KEY UPDATE ${setClauses}`;
24799
24938
  }
24800
- return `INSERT IGNORE INTO ${quoteIdent2(table)} (${quotedCols}) VALUES (${placeholders})`;
24939
+ return `INSERT IGNORE INTO ${quoteIdent3(table)} (${quotedCols}) VALUES (${placeholders})`;
24801
24940
  }
24802
24941
  case "replace":
24803
- return `REPLACE INTO ${quoteIdent2(table)} (${quotedCols}) VALUES (${placeholders})`;
24942
+ return `REPLACE INTO ${quoteIdent3(table)} (${quotedCols}) VALUES (${placeholders})`;
24804
24943
  }
24805
24944
  },
24806
24945
  buildDirectTransfer(srcDb, srcTable, dstTable, columns, _srcSchema, _dstSchema) {
24807
- const quotedCols = columns.map(quoteIdent2).join(", ");
24808
- const srcFull = `${quoteIdent2(srcDb)}.${quoteIdent2(srcTable)}`;
24809
- const dstFull = quoteIdent2(dstTable);
24946
+ const quotedCols = columns.map(quoteIdent3).join(", ");
24947
+ const srcFull = `${quoteIdent3(srcDb)}.${quoteIdent3(srcTable)}`;
24948
+ const dstFull = quoteIdent3(dstTable);
24810
24949
  return `INSERT INTO ${dstFull} (${quotedCols}) SELECT ${quotedCols} FROM ${srcFull}`;
24811
24950
  },
24812
24951
  async executeDisableFK(db) {
@@ -24816,7 +24955,7 @@ var mysqlTransferOperations = {
24816
24955
  await sql.raw(this.getEnableFKSql()).execute(db);
24817
24956
  }
24818
24957
  };
24819
- function quoteIdent3(name) {
24958
+ function quoteIdent4(name) {
24820
24959
  return `[${name.replace(/\]/g, "]]")}]`;
24821
24960
  }
24822
24961
  var mssqlTransferOperations = {
@@ -24827,27 +24966,27 @@ var mssqlTransferOperations = {
24827
24966
  return "-- FK enable per-table";
24828
24967
  },
24829
24968
  getEnableIdentityInsertSql(table) {
24830
- return `SET IDENTITY_INSERT ${quoteIdent3(table)} ON`;
24969
+ return `SET IDENTITY_INSERT ${quoteIdent4(table)} ON`;
24831
24970
  },
24832
24971
  getDisableIdentityInsertSql(table) {
24833
- return `SET IDENTITY_INSERT ${quoteIdent3(table)} OFF`;
24972
+ return `SET IDENTITY_INSERT ${quoteIdent4(table)} OFF`;
24834
24973
  },
24835
24974
  getResetSequenceSql(table, _column, _schema) {
24836
24975
  return `DBCC CHECKIDENT ('${table}', RESEED)`;
24837
24976
  },
24838
24977
  buildConflictInsert(table, columns, primaryKey, strategy) {
24839
- const quotedCols = columns.map(quoteIdent3).join(", ");
24978
+ const quotedCols = columns.map(quoteIdent4).join(", ");
24840
24979
  const placeholders = columns.map((_, i) => `@p${i}`).join(", ");
24841
24980
  switch (strategy) {
24842
24981
  case "fail":
24843
- return `INSERT INTO ${quoteIdent3(table)} (${quotedCols}) VALUES (${placeholders})`;
24982
+ return `INSERT INTO ${quoteIdent4(table)} (${quotedCols}) VALUES (${placeholders})`;
24844
24983
  case "skip":
24845
24984
  case "update":
24846
24985
  case "replace": {
24847
- const pkConditions = primaryKey.map((pk) => `target.${quoteIdent3(pk)} = source.${quoteIdent3(pk)}`).join(" AND ");
24848
- const sourceValues = columns.map((c, i) => `@p${i} AS ${quoteIdent3(c)}`).join(", ");
24986
+ const pkConditions = primaryKey.map((pk) => `target.${quoteIdent4(pk)} = source.${quoteIdent4(pk)}`).join(" AND ");
24987
+ const sourceValues = columns.map((c, i) => `@p${i} AS ${quoteIdent4(c)}`).join(", ");
24849
24988
  let mergeSql = `
24850
- MERGE INTO ${quoteIdent3(table)} AS target
24989
+ MERGE INTO ${quoteIdent4(table)} AS target
24851
24990
  USING (SELECT ${sourceValues}) AS source
24852
24991
  ON (${pkConditions})
24853
24992
  `.trim();
@@ -24855,12 +24994,12 @@ var mssqlTransferOperations = {
24855
24994
  mergeSql += `
24856
24995
  WHEN NOT MATCHED THEN
24857
24996
  INSERT (${quotedCols})
24858
- VALUES (${columns.map((c) => `source.${quoteIdent3(c)}`).join(", ")})
24997
+ VALUES (${columns.map((c) => `source.${quoteIdent4(c)}`).join(", ")})
24859
24998
  `;
24860
24999
  } else if (strategy === "update") {
24861
25000
  const updateCols = columns.filter((c) => !primaryKey.includes(c));
24862
25001
  if (updateCols.length > 0) {
24863
- const setClauses = updateCols.map((c) => `target.${quoteIdent3(c)} = source.${quoteIdent3(c)}`).join(", ");
25002
+ const setClauses = updateCols.map((c) => `target.${quoteIdent4(c)} = source.${quoteIdent4(c)}`).join(", ");
24864
25003
  mergeSql += `
24865
25004
  WHEN MATCHED THEN
24866
25005
  UPDATE SET ${setClauses}
@@ -24869,16 +25008,16 @@ var mssqlTransferOperations = {
24869
25008
  mergeSql += `
24870
25009
  WHEN NOT MATCHED THEN
24871
25010
  INSERT (${quotedCols})
24872
- VALUES (${columns.map((c) => `source.${quoteIdent3(c)}`).join(", ")})
25011
+ VALUES (${columns.map((c) => `source.${quoteIdent4(c)}`).join(", ")})
24873
25012
  `;
24874
25013
  } else {
24875
- const setClauses = columns.map((c) => `target.${quoteIdent3(c)} = source.${quoteIdent3(c)}`).join(", ");
25014
+ const setClauses = columns.map((c) => `target.${quoteIdent4(c)} = source.${quoteIdent4(c)}`).join(", ");
24876
25015
  mergeSql += `
24877
25016
  WHEN MATCHED THEN
24878
25017
  UPDATE SET ${setClauses}
24879
25018
  WHEN NOT MATCHED THEN
24880
25019
  INSERT (${quotedCols})
24881
- VALUES (${columns.map((c) => `source.${quoteIdent3(c)}`).join(", ")})
25020
+ VALUES (${columns.map((c) => `source.${quoteIdent4(c)}`).join(", ")})
24882
25021
  `;
24883
25022
  }
24884
25023
  mergeSql += ";";
@@ -24887,19 +25026,19 @@ var mssqlTransferOperations = {
24887
25026
  }
24888
25027
  },
24889
25028
  buildDirectTransfer(srcDb, srcTable, dstTable, columns, srcSchema = "dbo", dstSchema = "dbo") {
24890
- const quotedCols = columns.map(quoteIdent3).join(", ");
24891
- const srcFull = `${quoteIdent3(srcDb)}.${quoteIdent3(srcSchema)}.${quoteIdent3(srcTable)}`;
24892
- const dstFull = `${quoteIdent3(dstSchema)}.${quoteIdent3(dstTable)}`;
25029
+ const quotedCols = columns.map(quoteIdent4).join(", ");
25030
+ const srcFull = `${quoteIdent4(srcDb)}.${quoteIdent4(srcSchema)}.${quoteIdent4(srcTable)}`;
25031
+ const dstFull = `${quoteIdent4(dstSchema)}.${quoteIdent4(dstTable)}`;
24893
25032
  return `INSERT INTO ${dstFull} (${quotedCols}) SELECT ${quotedCols} FROM ${srcFull}`;
24894
25033
  },
24895
25034
  async executeDisableFK(db, tables) {
24896
25035
  for (const table of tables) {
24897
- await sql.raw(`ALTER TABLE ${quoteIdent3(table)} NOCHECK CONSTRAINT ALL`).execute(db);
25036
+ await sql.raw(`ALTER TABLE ${quoteIdent4(table)} NOCHECK CONSTRAINT ALL`).execute(db);
24898
25037
  }
24899
25038
  },
24900
25039
  async executeEnableFK(db, tables) {
24901
25040
  for (const table of tables) {
24902
- await sql.raw(`ALTER TABLE ${quoteIdent3(table)} WITH CHECK CHECK CONSTRAINT ALL`).execute(db);
25041
+ await sql.raw(`ALTER TABLE ${quoteIdent4(table)} WITH CHECK CHECK CONSTRAINT ALL`).execute(db);
24903
25042
  }
24904
25043
  }
24905
25044
  };
@@ -26439,10 +26578,10 @@ async function transferTableCrossDialect(ctx, plan, transferPlan, options) {
26439
26578
  }, null];
26440
26579
  }
26441
26580
  async function fetchBatch(db, dialect, table, columns, limit, offset, _schema) {
26442
- const quoteIdent4 = dialect === "mssql" ? (c) => `[${c}]` : dialect === "mysql" ? (c) => `\`${c}\`` : (c) => `"${c}"`;
26443
- const columnList = columns.map(quoteIdent4).join(", ");
26581
+ const quoteIdent5 = dialect === "mssql" ? (c) => `[${c}]` : dialect === "mysql" ? (c) => `\`${c}\`` : (c) => `"${c}"`;
26582
+ const columnList = columns.map(quoteIdent5).join(", ");
26444
26583
  if (dialect === "mssql") {
26445
- const orderCol = quoteIdent4(columns[0]);
26584
+ const orderCol = quoteIdent5(columns[0]);
26446
26585
  const result2 = await sql`
26447
26586
  SELECT ${sql.raw(columnList)}
26448
26587
  FROM ${sql.table(table)}
@@ -27108,9 +27247,9 @@ async function exportTableWithWorkers(ctx) {
27108
27247
  totalRows = Number(firstRow?.["cnt"] ?? 0);
27109
27248
  }
27110
27249
  const columns = dtSchema.columns.map((c) => c.name);
27111
- const quoteIdent4 = getQuoteIdent(dialect);
27112
- const columnList = columns.map(quoteIdent4).join(", ");
27113
- const orderCol = quoteIdent4(columns[0]);
27250
+ const quoteIdent5 = getQuoteIdent(dialect);
27251
+ const columnList = columns.map(quoteIdent5).join(", ");
27252
+ const orderCol = quoteIdent5(columns[0]);
27114
27253
  let globalIndex = 0;
27115
27254
  let loaded = 0;
27116
27255
  let processed = 0;
@@ -27868,10 +28007,40 @@ var Context = class {
27868
28007
  throw new Error("SQLite does not support stored procedures.");
27869
28008
  }
27870
28009
  const params = args[0];
28010
+ if (this.dialect === "postgres") {
28011
+ return this.#executeProcPostgres(name, params);
28012
+ }
27871
28013
  const query = buildProcCall(this.dialect, name, params);
27872
28014
  const result = await query.execute(this.kysely);
27873
28015
  return result.rows ?? [];
27874
28016
  }
28017
+ /**
28018
+ * Execute a stored procedure on PostgreSQL with FUNCTION fallback.
28019
+ *
28020
+ * PostgreSQL distinguishes between PROCEDURE and FUNCTION objects:
28021
+ * `CALL` only works against true procedures and raises SQLSTATE 42809
28022
+ * (`<name>(...) is not a procedure`) when the target is a function.
28023
+ *
28024
+ * The noorm playbook tells schema authors to use `CREATE PROCEDURE`
28025
+ * for void-returning ops and `CREATE FUNCTION` for row-returning ops,
28026
+ * and `ctx.proc()` is expected to handle both. So if `CALL` fails
28027
+ * with the wrong-object-type error, we retry with
28028
+ * `SELECT * FROM <name>(...)`, which works for both scalar and
28029
+ * set-returning PG functions.
28030
+ */
28031
+ async #executeProcPostgres(name, params) {
28032
+ const callQuery = buildProcCall("postgres", name, params);
28033
+ const [callResult, callErr] = await attempt(() => callQuery.execute(this.kysely));
28034
+ if (!callErr) {
28035
+ return callResult.rows ?? [];
28036
+ }
28037
+ if (!isFunctionNotProcedureError(callErr)) {
28038
+ throw callErr;
28039
+ }
28040
+ const tvfQuery = buildTvfCall("postgres", name, params);
28041
+ const tvfResult = await tvfQuery.execute(this.kysely);
28042
+ return tvfResult.rows ?? [];
28043
+ }
27875
28044
  /**
27876
28045
  * Call a database function and return the scalar result.
27877
28046
  *
@@ -27987,6 +28156,14 @@ var Context = class {
27987
28156
  return ready;
27988
28157
  }
27989
28158
  };
28159
+ function isFunctionNotProcedureError(err) {
28160
+ if (!err || typeof err !== "object") return false;
28161
+ const obj = err;
28162
+ if (obj.code !== "42809" && obj.code !== "42883") return false;
28163
+ const msg = String(obj.message ?? "");
28164
+ const hint = String(obj.hint ?? "");
28165
+ return msg.includes("is not a procedure") || msg.includes("procedure") || hint.includes("use SELECT");
28166
+ }
27990
28167
 
27991
28168
  // src/sdk/index.ts
27992
28169
  function toSettingsProvider(manager) {