@noormdev/sdk 1.0.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -71,6 +71,29 @@ await ctx.proc('refresh_cache');
71
71
  `createContext<DB, Procs, Funcs, Tvfs>` takes a map per routine kind. Plain `void` is shorthand for "no arguments, no meaningful return".
72
72
 
73
73
 
74
+ ## Schema scoping
75
+
76
+ `ctx.withSchema<SDB>(name)` derives a `Context` scoped to one schema — same connection, pool, and lifecycle as the parent, just qualified generics and query builder.
77
+
78
+ ```typescript
79
+ const acct = ctx.withSchema<AcctDB>('accounting');
80
+
81
+ const invoices = await acct.kysely.selectFrom('invoices').selectAll().execute();
82
+ // select * from "accounting"."invoices" — typed against AcctDB
83
+
84
+ await acct.proc('rebuild_ledger', { year: 2026 });
85
+ // CALL "accounting"."rebuild_ledger"("year" => $1)
86
+
87
+ await ctx.kysely.selectFrom('users').execute(); // parent untouched, no prefix
88
+
89
+ await ctx.disconnect(); // one lifecycle closes both
90
+ ```
91
+
92
+ Calling `withSchema` again replaces the schema instead of stacking it, and a `proc`/`func`/`tvf` name that already contains a `.` passes through unqualified. Raw `` sql`…` `` fragments are not rewritten — they resolve against the connection's default schema regardless of `withSchema`.
93
+
94
+ Non-goals: no config/connection-level schema field (the connection stays schema-agnostic); no schema-defaulting of `ctx.noorm.db.describe*`'s `schema?` arg; no per-dialect gating — the qualifier means whatever the dialect makes of it (a schema on postgres/mssql, a database on mysql, an ATTACHed database on sqlite).
95
+
96
+
74
97
  ## Requires
75
98
 
76
99
  Node >= 22.13. Supports **PostgreSQL**, **MySQL**, **SQLite**, and **SQL Server**.
@@ -1,5 +1,5 @@
1
1
  import { createRequire } from 'module';
2
- import { attempt, observer, attemptSync, shouldSkipConfirmations } from './chunk-QAECL4RN.js';
2
+ import { attempt, observer, attemptSync, shouldSkipConfirmations } from './chunk-LY2F7ZFI.js';
3
3
  import { __commonJS, __toESM } from './chunk-D2XW2UOP.js';
4
4
  import { generateKeyPairSync, randomBytes, createCipheriv, createPrivateKey, createPublicKey, diffieHellman, hkdfSync, createDecipheriv } from 'crypto';
5
5
 
@@ -97660,5 +97660,5 @@ async function listVaultSecretKeysChecked(gate, db, dialect) {
97660
97660
  }
97661
97661
 
97662
97662
  export { assertPolicy, assertVaultPolicy, checkConfigPolicy, deleteVaultSecret, deleteVaultSecretChecked, detectAgentHarness, encryptVaultKey, getAllVaultSecrets, getNoormTables, getVaultKey, getVaultKeyChecked, getVaultSecret, getVaultStatus, guarded, initializeVault, initializeVaultChecked, listVaultSecretKeys, listVaultSecretKeysChecked, noormDb, resolveLegacyAccess, setVaultSecret, setVaultSecretChecked, vaultSecretExists };
97663
- //# sourceMappingURL=chunk-2RW4SL2O.js.map
97664
- //# sourceMappingURL=chunk-2RW4SL2O.js.map
97663
+ //# sourceMappingURL=chunk-FSVO6NAJ.js.map
97664
+ //# sourceMappingURL=chunk-FSVO6NAJ.js.map
@@ -1,5 +1,5 @@
1
1
  import { createRequire } from 'module';
2
- import { attemptSync, observer, attempt } from './chunk-QAECL4RN.js';
2
+ import { attemptSync, observer, attempt } from './chunk-LY2F7ZFI.js';
3
3
  import { dist_default } from './chunk-TBRUDUBQ.js';
4
4
  import { readFile as readFile$1, readdir, stat } from 'fs/promises';
5
5
  import * as fs from 'fs';
@@ -2505,7 +2505,7 @@ function createIncludeHelper(templateDir, projectRoot, options) {
2505
2505
  throw new Error(`Include path escapes project root: ${includePath}`);
2506
2506
  }
2507
2507
  if (resolved.endsWith(".tmpl")) {
2508
- const { processFile: processFile2 } = await import('./engine-S4DX3V33.js');
2508
+ const { processFile: processFile2 } = await import('./engine-MZQ364EH.js');
2509
2509
  const result = await processFile2(resolved, options);
2510
2510
  return result.sql;
2511
2511
  }
@@ -2587,5 +2587,5 @@ voca/es/index.js:
2587
2587
  */
2588
2588
 
2589
2589
  export { DT_EXTENSIONS, DtReader, ENCODED_TYPES, FORMAT_VERSION, encryptWithPassphrase, es_default, eta, isTemplate, processFile, processFiles, renderTemplate };
2590
- //# sourceMappingURL=chunk-DSXHOXDE.js.map
2591
- //# sourceMappingURL=chunk-DSXHOXDE.js.map
2590
+ //# sourceMappingURL=chunk-G4B7EHL7.js.map
2591
+ //# sourceMappingURL=chunk-G4B7EHL7.js.map
@@ -3367,6 +3367,6 @@ var observer = new ObserverEngine({
3367
3367
  spy: isDebug() ? (action) => console.error(`[noorm:${action.fn}] ${String(action.event)}`) : void 0
3368
3368
  });
3369
3369
 
3370
- export { ObserverRelay, attempt, attemptSync, clone, equals, getEnvConfigName, gigabytes, isDebug, makeNestedConfig, merge, observer, retry, runWithTimeout, shouldSkipConfirmations, wait };
3371
- //# sourceMappingURL=chunk-QAECL4RN.js.map
3372
- //# sourceMappingURL=chunk-QAECL4RN.js.map
3370
+ export { ObserverRelay, attempt, attemptSync, clone, equals, getEnvConfigName, gigabytes, isDebug, isTimeoutError, makeNestedConfig, merge, observer, retry, runWithTimeout, shouldSkipConfirmations, wait };
3371
+ //# sourceMappingURL=chunk-LY2F7ZFI.js.map
3372
+ //# sourceMappingURL=chunk-LY2F7ZFI.js.map
@@ -13772,8 +13772,16 @@ var DEFAULT_PORTS = {
13772
13772
  // Not applicable
13773
13773
  mssql: 1433
13774
13774
  };
13775
+ var DEFAULT_CONNECT_TIMEOUT_MS = 15e3;
13776
+ function connectTimeoutFor(config2) {
13777
+ const configured = config2.connectTimeoutMs;
13778
+ if (typeof configured === "number" && Number.isFinite(configured) && configured > 0) {
13779
+ return configured;
13780
+ }
13781
+ return DEFAULT_CONNECT_TIMEOUT_MS;
13782
+ }
13775
13783
  var PortSchema = external_exports.number().int().min(1, "Port must be at least 1").max(65535, "Port must be at most 65535");
13776
13784
 
13777
- export { DEFAULT_PORTS, PortSchema, external_exports };
13778
- //# sourceMappingURL=chunk-SPU42W75.js.map
13779
- //# sourceMappingURL=chunk-SPU42W75.js.map
13785
+ export { DEFAULT_PORTS, PortSchema, connectTimeoutFor, external_exports };
13786
+ //# sourceMappingURL=chunk-WE2JLJMT.js.map
13787
+ //# sourceMappingURL=chunk-WE2JLJMT.js.map
@@ -1,11 +1,11 @@
1
1
  import { createRequire } from 'module';
2
- export { eta, isTemplate, processFile, processFiles, renderTemplate } from './chunk-DSXHOXDE.js';
2
+ export { eta, isTemplate, processFile, processFiles, renderTemplate } from './chunk-G4B7EHL7.js';
3
3
  import './chunk-UOFJT5PY.js';
4
- import './chunk-QAECL4RN.js';
4
+ import './chunk-LY2F7ZFI.js';
5
5
  import './chunk-TBRUDUBQ.js';
6
6
  import './chunk-YJBKNUTP.js';
7
7
  import './chunk-D2XW2UOP.js';
8
8
 
9
9
  createRequire(import.meta.url);
10
- //# sourceMappingURL=engine-S4DX3V33.js.map
11
- //# sourceMappingURL=engine-S4DX3V33.js.map
10
+ //# sourceMappingURL=engine-MZQ364EH.js.map
11
+ //# sourceMappingURL=engine-MZQ364EH.js.map
package/dist/index.d.ts CHANGED
@@ -38,6 +38,15 @@ export interface ConnectionConfig {
38
38
  min?: number;
39
39
  max?: number;
40
40
  };
41
+ /**
42
+ * Milliseconds a single connection attempt may spend before the driver
43
+ * gives up. Defaults to `DEFAULT_CONNECT_TIMEOUT_MS`.
44
+ *
45
+ * Raise it for a link that is slow but working — a serverless database
46
+ * resuming from auto-pause is the case that motivates it, since the resume
47
+ * can outlast any timeout tuned for a handshake.
48
+ */
49
+ connectTimeoutMs?: number;
41
50
  ssl?: boolean | {
42
51
  rejectUnauthorized?: boolean;
43
52
  ca?: string;
@@ -1035,6 +1044,31 @@ export interface TeardownConfig {
1035
1044
  /** SQL script to run after schema teardown (relative to project root) */
1036
1045
  postScript?: string;
1037
1046
  }
1047
+ /**
1048
+ * Terminal UI configuration.
1049
+ *
1050
+ * Only affects `noorm ui`; headless commands ignore it.
1051
+ *
1052
+ * @example
1053
+ * ```yaml
1054
+ * ui:
1055
+ * mouse: false
1056
+ * ```
1057
+ */
1058
+ export interface UiConfig {
1059
+ /**
1060
+ * Answer mouse clicks and wheel notches in lists and result grids.
1061
+ *
1062
+ * Absent means on — `isMouseEnabled` in `./defaults.js` is where that is
1063
+ * decided, so read it rather than this field. Written `false` is the only
1064
+ * thing that turns tracking off, and it is worth writing: enabling any
1065
+ * tracking mode takes click-drag text selection away from the terminal
1066
+ * unless the user holds a modifier (Option on macOS Terminal and iTerm2,
1067
+ * Shift elsewhere). The symptom a user reports is "text selection stopped
1068
+ * working", which does not point at noorm on its own.
1069
+ */
1070
+ mouse?: boolean;
1071
+ }
1038
1072
  /**
1039
1073
  * Complete settings configuration.
1040
1074
  *
@@ -1057,6 +1091,8 @@ export interface Settings {
1057
1091
  secrets?: StageSecret[];
1058
1092
  /** Database teardown/reset configuration */
1059
1093
  teardown?: TeardownConfig;
1094
+ /** Terminal UI behaviour */
1095
+ ui?: UiConfig;
1060
1096
  }
1061
1097
  /**
1062
1098
  * Settings events emitted by the settings module.
@@ -3820,6 +3856,31 @@ export declare class Context<DB = unknown, Procs = object, Funcs = object, Tvfs
3820
3856
  get dialect(): Dialect;
3821
3857
  get connected(): boolean;
3822
3858
  get kysely(): Kysely<DB>;
3859
+ /**
3860
+ * Derive a schema-scoped Context sharing this context's connection,
3861
+ * pool, and #heldConnections — same lifecycle, fresh generics for the
3862
+ * schema's own table/routine shape.
3863
+ *
3864
+ * Replaces rather than stacks: calling withSchema() again on an
3865
+ * already-derived context swaps the schema instead of nesting it,
3866
+ * because every derived context re-derives its `.kysely` from the
3867
+ * shared connection's bare instance, never from another derived
3868
+ * context's already-wrapped one.
3869
+ *
3870
+ * `sql`-tagged raw fragments are not rewritten by this — they resolve
3871
+ * against the connection's default schema regardless of this call.
3872
+ *
3873
+ * @throws Error synchronously if `name` fails identifier validation,
3874
+ * before any connection is borrowed or shared state is touched.
3875
+ *
3876
+ * @example
3877
+ * ```typescript
3878
+ * const acct = ctx.withSchema<AcctDB>('acct');
3879
+ * const rows = await acct.kysely.selectFrom('ledger').selectAll().execute();
3880
+ * await acct.proc('rebuild_ledger', { id: 1 }); // -> acct.rebuild_ledger
3881
+ * ```
3882
+ */
3883
+ withSchema<SDB = DB, SProcs = Procs, SFuncs = Funcs, STvfs = Tvfs>(name: string): Context<SDB, SProcs, SFuncs, STvfs>;
3823
3884
  /**
3824
3885
  * Noorm management operations.
3825
3886
  *
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { createRequire } from 'module';
2
- import { processFile, es_default, DtReader, isTemplate, FORMAT_VERSION, DT_EXTENSIONS, encryptWithPassphrase, ENCODED_TYPES } from './chunk-DSXHOXDE.js';
2
+ import { processFile, es_default, DtReader, isTemplate, FORMAT_VERSION, DT_EXTENSIONS, encryptWithPassphrase, ENCODED_TYPES } from './chunk-G4B7EHL7.js';
3
3
  import './chunk-UOFJT5PY.js';
4
- import { resolveLegacyAccess, checkConfigPolicy, initializeVault, getVaultStatus, setVaultSecret, getVaultSecret, getAllVaultSecrets, listVaultSecretKeys, deleteVaultSecret, vaultSecretExists, getVaultKey, noormDb, getNoormTables, encryptVaultKey, assertVaultPolicy, assertPolicy, detectAgentHarness, guarded } from './chunk-2RW4SL2O.js';
5
- import { makeNestedConfig, gigabytes, attempt, observer, attemptSync, retry, getEnvConfigName, clone, merge, equals, ObserverRelay, isDebug, runWithTimeout, wait } from './chunk-QAECL4RN.js';
6
- export { observer as noormObserver } from './chunk-QAECL4RN.js';
7
- import { external_exports, PortSchema, DEFAULT_PORTS } from './chunk-SPU42W75.js';
4
+ import { resolveLegacyAccess, checkConfigPolicy, initializeVault, getVaultStatus, setVaultSecret, getVaultSecret, getAllVaultSecrets, listVaultSecretKeys, deleteVaultSecret, vaultSecretExists, getVaultKey, noormDb, getNoormTables, encryptVaultKey, assertVaultPolicy, assertPolicy, detectAgentHarness, guarded } from './chunk-FSVO6NAJ.js';
5
+ import { makeNestedConfig, gigabytes, attempt, observer, attemptSync, getEnvConfigName, clone, merge, equals, retry, runWithTimeout, isTimeoutError, ObserverRelay, isDebug, wait } from './chunk-LY2F7ZFI.js';
6
+ export { observer as noormObserver } from './chunk-LY2F7ZFI.js';
7
+ import { external_exports, PortSchema, connectTimeoutFor, DEFAULT_PORTS } from './chunk-WE2JLJMT.js';
8
8
  import { dist_default } from './chunk-TBRUDUBQ.js';
9
9
  import { require_dist } from './chunk-YJBKNUTP.js';
10
10
  import { __commonJS, __toESM } from './chunk-D2XW2UOP.js';
@@ -353,7 +353,8 @@ var ConnectionSchema = external_exports.object({
353
353
  password: external_exports.string().optional(),
354
354
  ssl: SSLSchema.optional(),
355
355
  pool: PoolSchema.optional(),
356
- tlsServerName: external_exports.string().optional()
356
+ tlsServerName: external_exports.string().optional(),
357
+ connectTimeoutMs: external_exports.number().int().positive().optional()
357
358
  }).refine((conn) => conn.dialect === "sqlite" || conn.host, {
358
359
  message: "Host is required for non-SQLite databases",
359
360
  path: ["host"]
@@ -385,7 +386,8 @@ var PartialConnectionSchema = external_exports.object({
385
386
  password: external_exports.string().optional(),
386
387
  ssl: SSLSchema.optional(),
387
388
  pool: PoolSchema.optional(),
388
- tlsServerName: external_exports.string().optional()
389
+ tlsServerName: external_exports.string().optional(),
390
+ connectTimeoutMs: external_exports.number().int().positive().optional()
389
391
  });
390
392
  external_exports.object({
391
393
  name: external_exports.string().regex(/^[a-z0-9_-]+$/i).optional(),
@@ -1722,6 +1724,48 @@ async function initState(projectRoot) {
1722
1724
  return manager;
1723
1725
  }
1724
1726
 
1727
+ // src/core/settings/defaults.ts
1728
+ var DEFAULT_BUILD_CONFIG = {
1729
+ include: [],
1730
+ exclude: []
1731
+ };
1732
+ var DEFAULT_PATH_CONFIG = {
1733
+ sql: "./sql",
1734
+ changes: "./changes"
1735
+ };
1736
+ var DEFAULT_STRICT_CONFIG = {
1737
+ enabled: false,
1738
+ stages: []
1739
+ };
1740
+ var DEFAULT_LOGGING_CONFIG = {
1741
+ enabled: true,
1742
+ level: "info",
1743
+ file: ".noorm/state/noorm.log",
1744
+ maxSize: "10mb",
1745
+ maxFiles: 5
1746
+ };
1747
+ var DEFAULT_UI_MOUSE = true;
1748
+ var DEFAULT_SETTINGS = {
1749
+ build: DEFAULT_BUILD_CONFIG,
1750
+ paths: DEFAULT_PATH_CONFIG,
1751
+ strict: DEFAULT_STRICT_CONFIG,
1752
+ logging: DEFAULT_LOGGING_CONFIG
1753
+ };
1754
+ function createDefaultSettings() {
1755
+ return {
1756
+ build: {
1757
+ include: [...DEFAULT_BUILD_CONFIG.include ?? []],
1758
+ exclude: [...DEFAULT_BUILD_CONFIG.exclude ?? []]
1759
+ },
1760
+ paths: { ...DEFAULT_PATH_CONFIG },
1761
+ rules: [],
1762
+ stages: {},
1763
+ strict: { ...DEFAULT_STRICT_CONFIG, stages: [...DEFAULT_STRICT_CONFIG.stages ?? []] },
1764
+ logging: { ...DEFAULT_LOGGING_CONFIG }
1765
+ };
1766
+ }
1767
+ var SETTINGS_DIR_PATH = ".noorm";
1768
+
1725
1769
  // src/core/settings/schema.ts
1726
1770
  var DialectSchema2 = external_exports.enum(["postgres", "mysql", "sqlite", "mssql"]);
1727
1771
  var SecretTypeSchema = external_exports.enum(["string", "password", "api_key", "connection_string"]);
@@ -1791,6 +1835,9 @@ var TeardownConfigSchema = external_exports.object({
1791
1835
  preserveTables: external_exports.array(external_exports.string()).optional(),
1792
1836
  postScript: external_exports.string().optional()
1793
1837
  });
1838
+ var UiConfigSchema = external_exports.object({
1839
+ mouse: external_exports.boolean().default(DEFAULT_UI_MOUSE)
1840
+ });
1794
1841
  var SettingsSchema = external_exports.object({
1795
1842
  build: BuildConfigSchema.optional(),
1796
1843
  paths: PathConfigSchema.optional(),
@@ -1799,7 +1846,8 @@ var SettingsSchema = external_exports.object({
1799
1846
  strict: StrictConfigSchema.optional(),
1800
1847
  logging: LoggingConfigSchema.optional(),
1801
1848
  secrets: external_exports.array(StageSecretSchema).optional(),
1802
- teardown: TeardownConfigSchema.optional()
1849
+ teardown: TeardownConfigSchema.optional(),
1850
+ ui: UiConfigSchema.optional()
1803
1851
  });
1804
1852
  var SettingsValidationError = class extends Error {
1805
1853
  constructor(message, field, issues) {
@@ -1833,47 +1881,6 @@ function parseSettings(settings) {
1833
1881
  return result.data;
1834
1882
  }
1835
1883
 
1836
- // src/core/settings/defaults.ts
1837
- var DEFAULT_BUILD_CONFIG = {
1838
- include: [],
1839
- exclude: []
1840
- };
1841
- var DEFAULT_PATH_CONFIG = {
1842
- sql: "./sql",
1843
- changes: "./changes"
1844
- };
1845
- var DEFAULT_STRICT_CONFIG = {
1846
- enabled: false,
1847
- stages: []
1848
- };
1849
- var DEFAULT_LOGGING_CONFIG = {
1850
- enabled: true,
1851
- level: "info",
1852
- file: ".noorm/state/noorm.log",
1853
- maxSize: "10mb",
1854
- maxFiles: 5
1855
- };
1856
- var DEFAULT_SETTINGS = {
1857
- build: DEFAULT_BUILD_CONFIG,
1858
- paths: DEFAULT_PATH_CONFIG,
1859
- strict: DEFAULT_STRICT_CONFIG,
1860
- logging: DEFAULT_LOGGING_CONFIG
1861
- };
1862
- function createDefaultSettings() {
1863
- return {
1864
- build: {
1865
- include: [...DEFAULT_BUILD_CONFIG.include ?? []],
1866
- exclude: [...DEFAULT_BUILD_CONFIG.exclude ?? []]
1867
- },
1868
- paths: { ...DEFAULT_PATH_CONFIG },
1869
- rules: [],
1870
- stages: {},
1871
- strict: { ...DEFAULT_STRICT_CONFIG, stages: [...DEFAULT_STRICT_CONFIG.stages ?? []] },
1872
- logging: { ...DEFAULT_LOGGING_CONFIG }
1873
- };
1874
- }
1875
- var SETTINGS_DIR_PATH = ".noorm";
1876
-
1877
1884
  // src/core/settings/rules.ts
1878
1885
  function isConfigGuarded(config) {
1879
1886
  return guarded(config);
@@ -2692,6 +2699,47 @@ async function loadExistingIdentity() {
2692
2699
  };
2693
2700
  }
2694
2701
 
2702
+ // src/core/shared/abort.ts
2703
+ var OperationAbortedError = class extends Error {
2704
+ name = "OperationAbortedError";
2705
+ constructor(message = "Stopped waiting for the database") {
2706
+ super(message);
2707
+ }
2708
+ };
2709
+ function throwIfAborted(signal, message) {
2710
+ if (signal?.aborted) {
2711
+ throw new OperationAbortedError(message);
2712
+ }
2713
+ }
2714
+ function raceAbort(work, signal, onAbandoned) {
2715
+ if (!signal) return work;
2716
+ let abandoned = signal.aborted;
2717
+ void work.then(
2718
+ (value) => {
2719
+ if (abandoned) onAbandoned?.(value);
2720
+ },
2721
+ () => void 0
2722
+ );
2723
+ throwIfAborted(signal);
2724
+ return new Promise((resolve3, reject) => {
2725
+ const onAbort = () => {
2726
+ abandoned = true;
2727
+ reject(new OperationAbortedError());
2728
+ };
2729
+ signal.addEventListener("abort", onAbort, { once: true });
2730
+ void work.then(
2731
+ (value) => {
2732
+ signal.removeEventListener("abort", onAbort);
2733
+ resolve3(value);
2734
+ },
2735
+ (error) => {
2736
+ signal.removeEventListener("abort", onAbort);
2737
+ reject(error);
2738
+ }
2739
+ );
2740
+ });
2741
+ }
2742
+
2695
2743
  // src/core/connection/manager.ts
2696
2744
  var ConnectionManager = class {
2697
2745
  #cached = /* @__PURE__ */ new Map();
@@ -2874,11 +2922,11 @@ async function getDialectFactory(dialect) {
2874
2922
  }
2875
2923
  return (await import('./sqlite-F3ZSSDUM.js')).createSqliteConnection;
2876
2924
  case "postgres":
2877
- return (await import('./postgres-NIL5BE3O.js')).createPostgresConnection;
2925
+ return (await import('./postgres-MXXRYPOY.js')).createPostgresConnection;
2878
2926
  case "mysql":
2879
- return (await import('./mysql-5P43DYBY.js')).createMysqlConnection;
2927
+ return (await import('./mysql-32B4SXWA.js')).createMysqlConnection;
2880
2928
  case "mssql":
2881
- return (await import('./mssql-NL6OHOMO.js')).createMssqlConnection;
2929
+ return (await import('./mssql-3BQB6ACD.js')).createMssqlConnection;
2882
2930
  default:
2883
2931
  throw new Error(`Unsupported dialect: ${dialect}`);
2884
2932
  }
@@ -2892,8 +2940,19 @@ function getInstallCommand(dialect) {
2892
2940
  };
2893
2941
  return commands[dialect];
2894
2942
  }
2895
- async function createConnection(config, configName = "__default__", retryOptions = {}) {
2943
+ var DISCARD_TIMEOUT_MS = 5e3;
2944
+ var PROBE_GRACE_MS = 2e3;
2945
+ async function discardConnection(conn, timeoutMs = DISCARD_TIMEOUT_MS) {
2946
+ const [, err] = await attempt(
2947
+ () => runWithTimeout(() => conn.destroy(), { timeout: timeoutMs, throws: true })
2948
+ );
2949
+ if (err) {
2950
+ observer.emit("error", { source: "connection", error: err });
2951
+ }
2952
+ }
2953
+ async function openConnection(config, configName, retryOptions, signal) {
2896
2954
  const { retries = 3, delay = 1e3, backoff = 2 } = retryOptions;
2955
+ const connectTimeout = connectTimeoutFor(config);
2897
2956
  const [conn, err] = await attempt(
2898
2957
  () => retry(
2899
2958
  async () => {
@@ -2911,7 +2970,25 @@ async function createConnection(config, configName = "__default__", retryOptions
2911
2970
  throw importErr;
2912
2971
  }
2913
2972
  const conn2 = await createFn(config);
2914
- await sql`SELECT 1`.execute(conn2.db);
2973
+ if (signal?.aborted) {
2974
+ void discardConnection(conn2);
2975
+ throw new OperationAbortedError();
2976
+ }
2977
+ const [, probeErr] = await attempt(
2978
+ () => runWithTimeout(() => sql`SELECT 1`.execute(conn2.db), {
2979
+ timeout: connectTimeout + PROBE_GRACE_MS,
2980
+ throws: true
2981
+ })
2982
+ );
2983
+ if (probeErr) {
2984
+ void discardConnection(conn2);
2985
+ if (isTimeoutError(probeErr)) {
2986
+ throw new Error(
2987
+ `Database did not respond within ${connectTimeout + PROBE_GRACE_MS}ms`
2988
+ );
2989
+ }
2990
+ throw probeErr;
2991
+ }
2915
2992
  return conn2;
2916
2993
  },
2917
2994
  {
@@ -2919,7 +2996,9 @@ async function createConnection(config, configName = "__default__", retryOptions
2919
2996
  delay,
2920
2997
  backoff,
2921
2998
  jitterFactor: 0.1,
2999
+ signal,
2922
3000
  shouldRetry: (err2) => {
3001
+ if (err2 instanceof OperationAbortedError) return false;
2923
3002
  const msg = err2.message.toLowerCase();
2924
3003
  if (msg.includes("authentication")) return false;
2925
3004
  if (msg.includes("password")) return false;
@@ -2960,6 +3039,14 @@ async function createConnection(config, configName = "__default__", retryOptions
2960
3039
  });
2961
3040
  return trackedConn;
2962
3041
  }
3042
+ async function createConnection(config, configName = "__default__", retryOptions = {}, signal) {
3043
+ throwIfAborted(signal);
3044
+ return raceAbort(
3045
+ openConnection(config, configName, retryOptions, signal),
3046
+ signal,
3047
+ discardConnection
3048
+ );
3049
+ }
2963
3050
  var SYSTEM_DATABASES = {
2964
3051
  postgres: "postgres",
2965
3052
  mysql: void 0,
@@ -2987,8 +3074,13 @@ async function testConnection(config, options = {}) {
2987
3074
  return { ok: true };
2988
3075
  }
2989
3076
  }
2990
- const [conn, err] = await attempt(() => createConnection(testConfig, "__test__"));
3077
+ const [conn, err] = await attempt(
3078
+ () => createConnection(testConfig, "__test__", {}, options.signal)
3079
+ );
2991
3080
  if (err) {
3081
+ if (err instanceof OperationAbortedError) {
3082
+ return { ok: false, error: err.message, aborted: true };
3083
+ }
2992
3084
  return { ok: false, error: err.message };
2993
3085
  }
2994
3086
  await conn.destroy();
@@ -7454,7 +7546,7 @@ async function resolveVaultKey(db, dialect) {
7454
7546
  async function buildSecretsContext(stateManager, configName, db, vaultKey, dialect) {
7455
7547
  const secrets = {};
7456
7548
  if (db && vaultKey) {
7457
- const { getAllVaultSecrets: getAllVaultSecrets2 } = await import('./storage-6QBJ55SB.js');
7549
+ const { getAllVaultSecrets: getAllVaultSecrets2 } = await import('./storage-V5I67ANZ.js');
7458
7550
  const vaultSecrets = await getAllVaultSecrets2(db, vaultKey, dialect ?? "postgres");
7459
7551
  for (const [key, secret] of Object.entries(vaultSecrets)) {
7460
7552
  secrets[key] = secret.value;
@@ -15486,7 +15578,19 @@ function buildScope(db, revertFn, dialect) {
15486
15578
  }
15487
15579
 
15488
15580
  // src/sdk/context.ts
15489
- var Context = class {
15581
+ var VALID_SCHEMA_NAME = /^[a-zA-Z0-9_-]+$/;
15582
+ function validateSchemaName(name) {
15583
+ if (!name || !VALID_SCHEMA_NAME.test(name)) {
15584
+ throw new Error(
15585
+ `Invalid schema name: "${name}". Only alphanumeric characters, underscores, and hyphens are allowed.`
15586
+ );
15587
+ }
15588
+ }
15589
+ function qualifyName(schema, name) {
15590
+ if (!schema || name.includes(".")) return name;
15591
+ return `${schema}.${name}`;
15592
+ }
15593
+ var Context = class _Context {
15490
15594
  #state;
15491
15595
  #noorm = null;
15492
15596
  /**
@@ -15496,6 +15600,12 @@ var Context = class {
15496
15600
  * pool can never reclaim, and the context never closes.
15497
15601
  */
15498
15602
  #heldConnections = /* @__PURE__ */ new Set();
15603
+ /**
15604
+ * Schema this context is scoped to, or `null` for the root context.
15605
+ * Set once by withSchema() and never mutated afterward — a chained
15606
+ * withSchema() call derives a new Context rather than changing this.
15607
+ */
15608
+ #schema = null;
15499
15609
  constructor(config, settings, identity, options, projectRoot) {
15500
15610
  this.#state = {
15501
15611
  connection: null,
@@ -15517,7 +15627,49 @@ var Context = class {
15517
15627
  return this.#state.connection !== null;
15518
15628
  }
15519
15629
  get kysely() {
15520
- return requireConnection(this.#state).db;
15630
+ const db = requireConnection(this.#state).db;
15631
+ return this.#schema === null ? db : db.withSchema(this.#schema);
15632
+ }
15633
+ // ─────────────────────────────────────────────────────────
15634
+ // Schema Scoping
15635
+ // ─────────────────────────────────────────────────────────
15636
+ /**
15637
+ * Derive a schema-scoped Context sharing this context's connection,
15638
+ * pool, and #heldConnections — same lifecycle, fresh generics for the
15639
+ * schema's own table/routine shape.
15640
+ *
15641
+ * Replaces rather than stacks: calling withSchema() again on an
15642
+ * already-derived context swaps the schema instead of nesting it,
15643
+ * because every derived context re-derives its `.kysely` from the
15644
+ * shared connection's bare instance, never from another derived
15645
+ * context's already-wrapped one.
15646
+ *
15647
+ * `sql`-tagged raw fragments are not rewritten by this — they resolve
15648
+ * against the connection's default schema regardless of this call.
15649
+ *
15650
+ * @throws Error synchronously if `name` fails identifier validation,
15651
+ * before any connection is borrowed or shared state is touched.
15652
+ *
15653
+ * @example
15654
+ * ```typescript
15655
+ * const acct = ctx.withSchema<AcctDB>('acct');
15656
+ * const rows = await acct.kysely.selectFrom('ledger').selectAll().execute();
15657
+ * await acct.proc('rebuild_ledger', { id: 1 }); // -> acct.rebuild_ledger
15658
+ * ```
15659
+ */
15660
+ withSchema(name) {
15661
+ validateSchemaName(name);
15662
+ const derived = new _Context(
15663
+ this.#state.config,
15664
+ this.#state.settings,
15665
+ this.#state.identity,
15666
+ this.#state.options,
15667
+ this.#state.projectRoot
15668
+ );
15669
+ derived.#state = this.#state;
15670
+ derived.#heldConnections = this.#heldConnections;
15671
+ derived.#schema = name;
15672
+ return derived;
15521
15673
  }
15522
15674
  // ─────────────────────────────────────────────────────────
15523
15675
  // Noorm Namespace
@@ -15622,10 +15774,11 @@ var Context = class {
15622
15774
  */
15623
15775
  async proc(name, ...args) {
15624
15776
  const params = args[0];
15777
+ const qualifiedName4 = qualifyName(this.#schema, name);
15625
15778
  if (this.dialect === "postgres") {
15626
- return this.#executeProcPostgres(name, params);
15779
+ return this.#executeProcPostgres(qualifiedName4, params);
15627
15780
  }
15628
- const query = buildProcCall(this.dialect, name, params);
15781
+ const query = buildProcCall(this.dialect, qualifiedName4, params);
15629
15782
  const result = await query.execute(this.kysely);
15630
15783
  return result.rows ?? [];
15631
15784
  }
@@ -15678,7 +15831,7 @@ var Context = class {
15678
15831
  const hasParams = !(args.length === 1 && typeof args[0] === "string");
15679
15832
  const params = hasParams ? args[0] : void 0;
15680
15833
  const column = hasParams ? args[1] : args[0];
15681
- const query = buildFuncCall(this.dialect, name, column, params);
15834
+ const query = buildFuncCall(this.dialect, qualifyName(this.#schema, name), column, params);
15682
15835
  const result = await query.execute(this.kysely);
15683
15836
  return result.rows?.[0] ?? null;
15684
15837
  }
@@ -15706,7 +15859,7 @@ var Context = class {
15706
15859
  */
15707
15860
  async tvf(name, ...args) {
15708
15861
  const params = args[0];
15709
- const query = buildTvfCall(this.dialect, name, params);
15862
+ const query = buildTvfCall(this.dialect, qualifyName(this.#schema, name), params);
15710
15863
  const result = await query.execute(this.kysely);
15711
15864
  return result.rows ?? [];
15712
15865
  }
@@ -1,5 +1,5 @@
1
1
  import { createRequire } from 'module';
2
- import { DEFAULT_PORTS } from './chunk-SPU42W75.js';
2
+ import { DEFAULT_PORTS, connectTimeoutFor } from './chunk-WE2JLJMT.js';
3
3
  import './chunk-D2XW2UOP.js';
4
4
  import { isIP } from 'net';
5
5
  import { Kysely, MssqlDialect, sql, OperationNodeTransformer } from 'kysely';
@@ -88,7 +88,13 @@ function buildTediousOptions(config, database) {
88
88
  database: database ?? config.database,
89
89
  trustServerCertificate: !config.ssl,
90
90
  encrypt: true,
91
- serverName: resolveTlsServerName(config)
91
+ serverName: resolveTlsServerName(config),
92
+ // Matches tedious's own default, so mssql keeps the behaviour it
93
+ // had while becoming overridable alongside the other dialects.
94
+ // `requestTimeout` is deliberately left alone: a long-running
95
+ // query is legitimate, and Escape is the answer for that, not a
96
+ // deadline nobody asked for.
97
+ connectTimeout: connectTimeoutFor(config)
92
98
  }
93
99
  };
94
100
  }
@@ -157,5 +163,5 @@ async function createMssqlConnection(config) {
157
163
  }
158
164
 
159
165
  export { MssqlTlsServerNameError, UNVERIFIED_TLS_SERVER_NAME, buildTediousOptions, createMssqlConnection, resolveTlsServerName };
160
- //# sourceMappingURL=mssql-NL6OHOMO.js.map
161
- //# sourceMappingURL=mssql-NL6OHOMO.js.map
166
+ //# sourceMappingURL=mssql-3BQB6ACD.js.map
167
+ //# sourceMappingURL=mssql-3BQB6ACD.js.map
@@ -1,5 +1,5 @@
1
1
  import { createRequire } from 'module';
2
- import { DEFAULT_PORTS } from './chunk-SPU42W75.js';
2
+ import { DEFAULT_PORTS, connectTimeoutFor } from './chunk-WE2JLJMT.js';
3
3
  import './chunk-D2XW2UOP.js';
4
4
  import { Kysely, MysqlDialect } from 'kysely';
5
5
 
@@ -14,7 +14,10 @@ async function createMysqlConnection(config) {
14
14
  password: config.password,
15
15
  database: config.database,
16
16
  connectionLimit: config.pool?.max ?? 10,
17
- ssl: config.ssl ? {} : void 0
17
+ ssl: config.ssl ? {} : void 0,
18
+ // mysql2 defaults this to 10s. Set explicitly so every dialect gives
19
+ // up on the same schedule and one override moves all of them.
20
+ connectTimeout: connectTimeoutFor(config)
18
21
  });
19
22
  const db = new Kysely({
20
23
  dialect: new MysqlDialect({ pool })
@@ -27,5 +30,5 @@ async function createMysqlConnection(config) {
27
30
  }
28
31
 
29
32
  export { createMysqlConnection };
30
- //# sourceMappingURL=mysql-5P43DYBY.js.map
31
- //# sourceMappingURL=mysql-5P43DYBY.js.map
33
+ //# sourceMappingURL=mysql-32B4SXWA.js.map
34
+ //# sourceMappingURL=mysql-32B4SXWA.js.map
@@ -1,5 +1,5 @@
1
1
  import { createRequire } from 'module';
2
- import { DEFAULT_PORTS } from './chunk-SPU42W75.js';
2
+ import { DEFAULT_PORTS, connectTimeoutFor } from './chunk-WE2JLJMT.js';
3
3
  import './chunk-D2XW2UOP.js';
4
4
  import { Kysely, PostgresDialect } from 'kysely';
5
5
 
@@ -15,7 +15,10 @@ async function createPostgresConnection(config) {
15
15
  database: config.database,
16
16
  min: config.pool?.min ?? 0,
17
17
  max: config.pool?.max ?? 10,
18
- ssl: config.ssl
18
+ ssl: config.ssl,
19
+ // pg's own default is 0, meaning wait forever: an unreachable host
20
+ // never comes back without this.
21
+ connectionTimeoutMillis: connectTimeoutFor(config)
19
22
  });
20
23
  const db = new Kysely({
21
24
  dialect: new PostgresDialect({ pool })
@@ -28,5 +31,5 @@ async function createPostgresConnection(config) {
28
31
  }
29
32
 
30
33
  export { createPostgresConnection };
31
- //# sourceMappingURL=postgres-NIL5BE3O.js.map
32
- //# sourceMappingURL=postgres-NIL5BE3O.js.map
34
+ //# sourceMappingURL=postgres-MXXRYPOY.js.map
35
+ //# sourceMappingURL=postgres-MXXRYPOY.js.map
@@ -1,8 +1,8 @@
1
1
  import { createRequire } from 'module';
2
- export { deleteVaultSecret, deleteVaultSecretChecked, getAllVaultSecrets, getVaultKey, getVaultKeyChecked, getVaultSecret, getVaultStatus, initializeVault, initializeVaultChecked, listVaultSecretKeys, listVaultSecretKeysChecked, setVaultSecret, setVaultSecretChecked, vaultSecretExists } from './chunk-2RW4SL2O.js';
3
- import './chunk-QAECL4RN.js';
2
+ export { deleteVaultSecret, deleteVaultSecretChecked, getAllVaultSecrets, getVaultKey, getVaultKeyChecked, getVaultSecret, getVaultStatus, initializeVault, initializeVaultChecked, listVaultSecretKeys, listVaultSecretKeysChecked, setVaultSecret, setVaultSecretChecked, vaultSecretExists } from './chunk-FSVO6NAJ.js';
3
+ import './chunk-LY2F7ZFI.js';
4
4
  import './chunk-D2XW2UOP.js';
5
5
 
6
6
  createRequire(import.meta.url);
7
- //# sourceMappingURL=storage-6QBJ55SB.js.map
8
- //# sourceMappingURL=storage-6QBJ55SB.js.map
7
+ //# sourceMappingURL=storage-V5I67ANZ.js.map
8
+ //# sourceMappingURL=storage-V5I67ANZ.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noormdev/sdk",
3
- "version": "1.0.2",
3
+ "version": "1.2.0",
4
4
  "description": "Database schema & changeset manager SDK",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",