@noormdev/sdk 1.0.2 → 1.1.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**.
package/dist/index.d.ts CHANGED
@@ -3820,6 +3820,31 @@ export declare class Context<DB = unknown, Procs = object, Funcs = object, Tvfs
3820
3820
  get dialect(): Dialect;
3821
3821
  get connected(): boolean;
3822
3822
  get kysely(): Kysely<DB>;
3823
+ /**
3824
+ * Derive a schema-scoped Context sharing this context's connection,
3825
+ * pool, and #heldConnections — same lifecycle, fresh generics for the
3826
+ * schema's own table/routine shape.
3827
+ *
3828
+ * Replaces rather than stacks: calling withSchema() again on an
3829
+ * already-derived context swaps the schema instead of nesting it,
3830
+ * because every derived context re-derives its `.kysely` from the
3831
+ * shared connection's bare instance, never from another derived
3832
+ * context's already-wrapped one.
3833
+ *
3834
+ * `sql`-tagged raw fragments are not rewritten by this — they resolve
3835
+ * against the connection's default schema regardless of this call.
3836
+ *
3837
+ * @throws Error synchronously if `name` fails identifier validation,
3838
+ * before any connection is borrowed or shared state is touched.
3839
+ *
3840
+ * @example
3841
+ * ```typescript
3842
+ * const acct = ctx.withSchema<AcctDB>('acct');
3843
+ * const rows = await acct.kysely.selectFrom('ledger').selectAll().execute();
3844
+ * await acct.proc('rebuild_ledger', { id: 1 }); // -> acct.rebuild_ledger
3845
+ * ```
3846
+ */
3847
+ withSchema<SDB = DB, SProcs = Procs, SFuncs = Funcs, STvfs = Tvfs>(name: string): Context<SDB, SProcs, SFuncs, STvfs>;
3823
3848
  /**
3824
3849
  * Noorm management operations.
3825
3850
  *
package/dist/index.js CHANGED
@@ -15486,7 +15486,19 @@ function buildScope(db, revertFn, dialect) {
15486
15486
  }
15487
15487
 
15488
15488
  // src/sdk/context.ts
15489
- var Context = class {
15489
+ var VALID_SCHEMA_NAME = /^[a-zA-Z0-9_-]+$/;
15490
+ function validateSchemaName(name) {
15491
+ if (!name || !VALID_SCHEMA_NAME.test(name)) {
15492
+ throw new Error(
15493
+ `Invalid schema name: "${name}". Only alphanumeric characters, underscores, and hyphens are allowed.`
15494
+ );
15495
+ }
15496
+ }
15497
+ function qualifyName(schema, name) {
15498
+ if (!schema || name.includes(".")) return name;
15499
+ return `${schema}.${name}`;
15500
+ }
15501
+ var Context = class _Context {
15490
15502
  #state;
15491
15503
  #noorm = null;
15492
15504
  /**
@@ -15496,6 +15508,12 @@ var Context = class {
15496
15508
  * pool can never reclaim, and the context never closes.
15497
15509
  */
15498
15510
  #heldConnections = /* @__PURE__ */ new Set();
15511
+ /**
15512
+ * Schema this context is scoped to, or `null` for the root context.
15513
+ * Set once by withSchema() and never mutated afterward — a chained
15514
+ * withSchema() call derives a new Context rather than changing this.
15515
+ */
15516
+ #schema = null;
15499
15517
  constructor(config, settings, identity, options, projectRoot) {
15500
15518
  this.#state = {
15501
15519
  connection: null,
@@ -15517,7 +15535,49 @@ var Context = class {
15517
15535
  return this.#state.connection !== null;
15518
15536
  }
15519
15537
  get kysely() {
15520
- return requireConnection(this.#state).db;
15538
+ const db = requireConnection(this.#state).db;
15539
+ return this.#schema === null ? db : db.withSchema(this.#schema);
15540
+ }
15541
+ // ─────────────────────────────────────────────────────────
15542
+ // Schema Scoping
15543
+ // ─────────────────────────────────────────────────────────
15544
+ /**
15545
+ * Derive a schema-scoped Context sharing this context's connection,
15546
+ * pool, and #heldConnections — same lifecycle, fresh generics for the
15547
+ * schema's own table/routine shape.
15548
+ *
15549
+ * Replaces rather than stacks: calling withSchema() again on an
15550
+ * already-derived context swaps the schema instead of nesting it,
15551
+ * because every derived context re-derives its `.kysely` from the
15552
+ * shared connection's bare instance, never from another derived
15553
+ * context's already-wrapped one.
15554
+ *
15555
+ * `sql`-tagged raw fragments are not rewritten by this — they resolve
15556
+ * against the connection's default schema regardless of this call.
15557
+ *
15558
+ * @throws Error synchronously if `name` fails identifier validation,
15559
+ * before any connection is borrowed or shared state is touched.
15560
+ *
15561
+ * @example
15562
+ * ```typescript
15563
+ * const acct = ctx.withSchema<AcctDB>('acct');
15564
+ * const rows = await acct.kysely.selectFrom('ledger').selectAll().execute();
15565
+ * await acct.proc('rebuild_ledger', { id: 1 }); // -> acct.rebuild_ledger
15566
+ * ```
15567
+ */
15568
+ withSchema(name) {
15569
+ validateSchemaName(name);
15570
+ const derived = new _Context(
15571
+ this.#state.config,
15572
+ this.#state.settings,
15573
+ this.#state.identity,
15574
+ this.#state.options,
15575
+ this.#state.projectRoot
15576
+ );
15577
+ derived.#state = this.#state;
15578
+ derived.#heldConnections = this.#heldConnections;
15579
+ derived.#schema = name;
15580
+ return derived;
15521
15581
  }
15522
15582
  // ─────────────────────────────────────────────────────────
15523
15583
  // Noorm Namespace
@@ -15622,10 +15682,11 @@ var Context = class {
15622
15682
  */
15623
15683
  async proc(name, ...args) {
15624
15684
  const params = args[0];
15685
+ const qualifiedName4 = qualifyName(this.#schema, name);
15625
15686
  if (this.dialect === "postgres") {
15626
- return this.#executeProcPostgres(name, params);
15687
+ return this.#executeProcPostgres(qualifiedName4, params);
15627
15688
  }
15628
- const query = buildProcCall(this.dialect, name, params);
15689
+ const query = buildProcCall(this.dialect, qualifiedName4, params);
15629
15690
  const result = await query.execute(this.kysely);
15630
15691
  return result.rows ?? [];
15631
15692
  }
@@ -15678,7 +15739,7 @@ var Context = class {
15678
15739
  const hasParams = !(args.length === 1 && typeof args[0] === "string");
15679
15740
  const params = hasParams ? args[0] : void 0;
15680
15741
  const column = hasParams ? args[1] : args[0];
15681
- const query = buildFuncCall(this.dialect, name, column, params);
15742
+ const query = buildFuncCall(this.dialect, qualifyName(this.#schema, name), column, params);
15682
15743
  const result = await query.execute(this.kysely);
15683
15744
  return result.rows?.[0] ?? null;
15684
15745
  }
@@ -15706,7 +15767,7 @@ var Context = class {
15706
15767
  */
15707
15768
  async tvf(name, ...args) {
15708
15769
  const params = args[0];
15709
- const query = buildTvfCall(this.dialect, name, params);
15770
+ const query = buildTvfCall(this.dialect, qualifyName(this.#schema, name), params);
15710
15771
  const result = await query.execute(this.kysely);
15711
15772
  return result.rows ?? [];
15712
15773
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noormdev/sdk",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "Database schema & changeset manager SDK",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",