@palbase/backend 25.0.0 → 25.0.1

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.
Files changed (43) hide show
  1. package/dist/bin/palbase-backend.cjs +2 -0
  2. package/dist/bin/palbase-backend.cjs.map +1 -1
  3. package/dist/bin/palbase-backend.js +2 -2
  4. package/dist/{chunk-43A3KGWL.js → chunk-34I4GB7D.js} +4 -2
  5. package/dist/{chunk-43A3KGWL.js.map → chunk-34I4GB7D.js.map} +1 -1
  6. package/dist/{chunk-ZQRWW37O.js → chunk-35PNTIRN.js} +5 -1
  7. package/dist/chunk-35PNTIRN.js.map +1 -0
  8. package/dist/{chunk-OEQBHE2Z.js → chunk-HBOJLP2Z.js} +19 -4
  9. package/dist/chunk-HBOJLP2Z.js.map +1 -0
  10. package/dist/{chunk-5CMLOAEF.js → chunk-YOY5DFQS.js} +2 -2
  11. package/dist/db/env.cjs.map +1 -1
  12. package/dist/db/env.d.cts +2 -1
  13. package/dist/db/env.d.ts +2 -1
  14. package/dist/db/index.cjs +17 -3
  15. package/dist/db/index.cjs.map +1 -1
  16. package/dist/db/index.d.cts +1 -1
  17. package/dist/db/index.d.ts +1 -1
  18. package/dist/db/index.js +1 -1
  19. package/dist/engine/index.cjs +2 -0
  20. package/dist/engine/index.cjs.map +1 -1
  21. package/dist/engine/index.d.cts +2 -2
  22. package/dist/engine/index.d.ts +2 -2
  23. package/dist/engine/index.js +2 -2
  24. package/dist/{index-Ct1iiB4N.d.ts → index-B4CcpqLb.d.ts} +23 -16
  25. package/dist/{index-BF1f0DfA.d.ts → index-B8v6hVyU.d.ts} +1 -1
  26. package/dist/{index-CwaWRhyc.d.cts → index-DmVyY6N7.d.cts} +23 -16
  27. package/dist/{index-CoaDN9dL.d.cts → index-VsjBQ4Kw.d.cts} +1 -1
  28. package/dist/index.cjs +75 -49
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.cts +8 -7
  31. package/dist/index.d.ts +8 -7
  32. package/dist/index.js +58 -49
  33. package/dist/index.js.map +1 -1
  34. package/dist/openapi/index.cjs +1 -1
  35. package/dist/openapi/index.cjs.map +1 -1
  36. package/dist/openapi/index.js +3 -3
  37. package/dist/openapi/index.js.map +1 -1
  38. package/docs/llms-full.txt +36 -5
  39. package/docs/schema.md +36 -5
  40. package/package.json +1 -1
  41. package/dist/chunk-OEQBHE2Z.js.map +0 -1
  42. package/dist/chunk-ZQRWW37O.js.map +0 -1
  43. /package/dist/{chunk-5CMLOAEF.js.map → chunk-YOY5DFQS.js.map} +0 -0
@@ -1,8 +1,8 @@
1
- export { A as App, m as AuthVerifier, B as BootRefused, n as CreateAppOptions, E as EgressPolicy, p as EngineConfig, M as ModuleClients, q as RateLimiter, r as RequestDatabase, s as RouteEntry, t as RuntimeHooks, u as ScrubResult, v as SqlDriver, w as SqlTx, x as buildRouteTable, y as createApp, z as createLazyTransaction, G as createOps, H as createRequestDatabase, I as effectiveAuth, J as hostAllowed, K as installEgressFence, O as loadConfig, P as makeMemoryCache, Q as matchRoute, T as quoteIdent, U as scrubSecrets, V as withTables } from '../index-CoaDN9dL.cjs';
1
+ export { A as App, m as AuthVerifier, B as BootRefused, n as CreateAppOptions, E as EgressPolicy, p as EngineConfig, M as ModuleClients, q as RateLimiter, r as RequestDatabase, s as RouteEntry, t as RuntimeHooks, u as ScrubResult, v as SqlDriver, w as SqlTx, x as buildRouteTable, y as createApp, z as createLazyTransaction, G as createOps, H as createRequestDatabase, I as effectiveAuth, J as hostAllowed, K as installEgressFence, O as loadConfig, P as makeMemoryCache, Q as matchRoute, T as quoteIdent, U as scrubSecrets, V as withTables } from '../index-VsjBQ4Kw.cjs';
2
2
  import '../endpoint-BSGw1pTu.cjs';
3
3
  import '../stack.cjs';
4
4
  import 'node:async_hooks';
5
- import '../index-CwaWRhyc.cjs';
5
+ import '../index-DmVyY6N7.cjs';
6
6
  import '../db/env.cjs';
7
7
  import '../registry-CgJvDYUW.cjs';
8
8
  import 'zod';
@@ -1,8 +1,8 @@
1
- export { A as App, m as AuthVerifier, B as BootRefused, n as CreateAppOptions, E as EgressPolicy, p as EngineConfig, M as ModuleClients, q as RateLimiter, r as RequestDatabase, s as RouteEntry, t as RuntimeHooks, u as ScrubResult, v as SqlDriver, w as SqlTx, x as buildRouteTable, y as createApp, z as createLazyTransaction, G as createOps, H as createRequestDatabase, I as effectiveAuth, J as hostAllowed, K as installEgressFence, O as loadConfig, P as makeMemoryCache, Q as matchRoute, T as quoteIdent, U as scrubSecrets, V as withTables } from '../index-BF1f0DfA.js';
1
+ export { A as App, m as AuthVerifier, B as BootRefused, n as CreateAppOptions, E as EgressPolicy, p as EngineConfig, M as ModuleClients, q as RateLimiter, r as RequestDatabase, s as RouteEntry, t as RuntimeHooks, u as ScrubResult, v as SqlDriver, w as SqlTx, x as buildRouteTable, y as createApp, z as createLazyTransaction, G as createOps, H as createRequestDatabase, I as effectiveAuth, J as hostAllowed, K as installEgressFence, O as loadConfig, P as makeMemoryCache, Q as matchRoute, T as quoteIdent, U as scrubSecrets, V as withTables } from '../index-B8v6hVyU.js';
2
2
  import '../endpoint-DMwJjEHQ.js';
3
3
  import '../stack.js';
4
4
  import 'node:async_hooks';
5
- import '../index-Ct1iiB4N.js';
5
+ import '../index-B4CcpqLb.js';
6
6
  import '../db/env.js';
7
7
  import '../registry-B8pld3fj.js';
8
8
  import 'zod';
@@ -16,11 +16,11 @@ import {
16
16
  quoteIdent,
17
17
  scrubSecrets,
18
18
  withTables
19
- } from "../chunk-43A3KGWL.js";
19
+ } from "../chunk-34I4GB7D.js";
20
20
  import "../chunk-XJ2RSHEU.js";
21
21
  import "../chunk-XABBC7JP.js";
22
22
  import "../chunk-P2Q27SGP.js";
23
- import "../chunk-ZQRWW37O.js";
23
+ import "../chunk-35PNTIRN.js";
24
24
  import "../chunk-7D4SUZUM.js";
25
25
  export {
26
26
  AuthVerifier,
@@ -32,8 +32,20 @@ interface ColumnDef {
32
32
  selfRefColumn?: string;
33
33
  /** This column OWNS the row: erasure, RLS and the `owner` relation read it. */
34
34
  owns?: true;
35
- /** Explicit relation name, when the derived one would be ambiguous. */
35
+ /** Explicit name for the FORWARD relation (child parent), when the one
36
+ * derived from the column would be ambiguous or unclear. */
36
37
  refAs?: string;
38
+ /**
39
+ * Explicit name for the REVERSE relation (parent → children).
40
+ *
41
+ * Separate from {@link refAs} because the two directions are different facts:
42
+ * the forward name describes the parent this row points at (`author`), the
43
+ * reverse one describes the rows hanging off the parent (`posts`). One option
44
+ * naming both made an ordinary schema undeclarable — `posts.author_id` and
45
+ * `comments.author_id` both named `{ as: "author" }` collided on `users`, and
46
+ * the refusal asked for the `{ as }` they had both already written.
47
+ */
48
+ reverseAs?: string;
37
49
  /** The table this column belongs to; set by `defineTable`. */
38
50
  ownerTable?: {
39
51
  name: string;
@@ -197,9 +209,16 @@ declare class ColumnBuilder<K extends ColumnType = ColumnType, N extends boolean
197
209
  * In a cycle, ONE side needs an explicit return type:
198
210
  * `references((): AnyColumn => y.id)`. One side is enough — measured.
199
211
  * For a self-reference use `selfReferences(column)`: no thunk, no annotation.
212
+ *
213
+ * `as` names the FORWARD relation (`author_id` → `author` by default);
214
+ * `reverseAs` names the REVERSE one on the parent, whose default is this
215
+ * table's own name (`users.posts`). Two foreign keys from one table onto one
216
+ * parent therefore need a `reverseAs` on at least one of them — the reverse
217
+ * names would otherwise both be this table's name.
200
218
  */
201
219
  references(target: () => AnyColumn, opts?: {
202
220
  as?: string;
221
+ reverseAs?: string;
203
222
  onDelete?: OnDeleteAction;
204
223
  }): ColumnBuilder<K, N, D, E, P, T>;
205
224
  /**
@@ -489,7 +508,8 @@ declare const EXTENSION_DEPENDENCIES: Partial<Record<PalbaseExtension, PalbaseEx
489
508
  declare function isPalbaseExtension(name: string): name is PalbaseExtension;
490
509
 
491
510
  /**
492
- * A named raw-SQL DDL object declared in db/schema.ts for anything the typed DSL
511
+ * A named raw-SQL DDL object declared in a schema file (`db/public.ts`,
512
+ * `db/billing.ts`, …) for anything the typed DSL
493
513
  * cannot express (EXCLUDE, CHECK, partial/expression indexes, triggers, views).
494
514
  * The deploy emits `up` verbatim on the privileged DDL connection — same trust
495
515
  * posture as policy().using(). Tracked by NAME (not by diffing the body), so a
@@ -714,19 +734,6 @@ interface SchemaDef<T extends Record<string, TableDef> = Record<string, TableDef
714
734
  /** Postgres extensions to install on deploy. Normalized to `[]` when absent. */
715
735
  extensions: PalbaseExtension[];
716
736
  }
717
- /** The author-facing input to `defineSchema` — a `tables` map whose keys are
718
- * the table names and whose values are `{ columns, rls?, policies? }`, plus an
719
- * optional `extensions` allowlist. */
720
- interface SchemaInput<T extends Record<string, TableInput> = Record<string, TableInput>> {
721
- tables: T;
722
- /**
723
- * Postgres extensions to enable for this project, e.g. `["vector"]`.
724
- * Config-as-code: installed by the deploy (CREATE EXTENSION … SCHEMA
725
- * extensions) with the privileged deploy connection. The type is an
726
- * allowlist union, so unsupported names fail typecheck.
727
- */
728
- extensions?: PalbaseExtension[];
729
- }
730
737
  /** Map the author's `{ tables: { <name>: { columns } } }` input to the
731
738
  * `{ tables: { <name>: TableDef<columns> } }` runtime/type shape, threading the
732
739
  * per-table column map `T[K]["columns"]` so column-level inference survives. */
@@ -1212,4 +1219,4 @@ interface EnvTypedDatabase extends Omit<DBClient, "txPlan" | "asService"> {
1212
1219
  asService(): EnvServiceDatabase;
1213
1220
  }
1214
1221
 
1215
- export { type AnyColumn as A, defineSchema as B, ColumnBuilder as C, defineTable as D, type EnvTypedDatabase as E, enumType as F, installationRef as G, integer as H, type InsertShape as I, isPalbaseExtension as J, jsonb as K, makeTypedDB as L, numeric as M, openai as N, type OnDeleteAction as O, PALBASE_EXTENSIONS as P, ownedByUser as Q, type RawConstraintDef as R, type SchemaDef as S, TABLE_META as T, policy as U, raw as V, text as W, timestamp as X, userRef as Y, uuid as Z, vector as _, type ColumnDef as a, type ColumnMap as b, type ColumnType as c, EXTENSION_DEPENDENCIES as d, type EmbeddingModelRef as e, type EnvSchemas as f, type EnvServiceDatabase as g, type EnvTables as h, type EnvTypedTable as i, type PalbaseExtension as j, PolicyBuilder as k, type PolicyCommand as l, type PolicyDef as m, type PolicyMode as n, type RowShape as o, type SchemaInput as p, type TableDef as q, type TableHandle as r, type TableInput as s, type TxPlan as t, type TxTables as u, type TypedDB as v, type TypedTable as w, type TypedTx as x, bigint as y, boolean as z };
1222
+ export { type AnyColumn as A, defineTable as B, ColumnBuilder as C, enumType as D, type EnvTypedDatabase as E, installationRef as F, integer as G, isPalbaseExtension as H, type InsertShape as I, jsonb as J, makeTypedDB as K, numeric as L, openai as M, ownedByUser as N, type OnDeleteAction as O, PALBASE_EXTENSIONS as P, policy as Q, type RawConstraintDef as R, type SchemaDef as S, TABLE_META as T, raw as U, text as V, timestamp as W, userRef as X, uuid as Y, vector as Z, type ColumnDef as a, type ColumnMap as b, type ColumnType as c, EXTENSION_DEPENDENCIES as d, type EmbeddingModelRef as e, type EnvSchemas as f, type EnvServiceDatabase as g, type EnvTables as h, type EnvTypedTable as i, type PalbaseExtension as j, PolicyBuilder as k, type PolicyCommand as l, type PolicyDef as m, type PolicyMode as n, type RowShape as o, type TableDef as p, type TableHandle as q, type TableInput as r, type TxPlan as s, type TxTables as t, type TypedDB as u, type TypedTable as v, type TypedTx as w, bigint as x, boolean as y, defineSchema as z };
@@ -1,7 +1,7 @@
1
1
  import { Buckets, BucketTypes } from './stack.js';
2
2
  import { AsyncLocalStorage } from 'node:async_hooks';
3
3
  import { C as CacheClient, P as PalbaseDocsClient, a as PalbaseFlagsClient, L as Logger, b as PalbaseNotificationsClient, c as PalbaseRealtimeClient, D as DBClient, S as SecretsService, d as PalbaseStorageClient, e as PalbaseBucketClient, f as DBOps, T as TxPlanBody, g as TxPlanResponse, A as AuthSpec } from './endpoint-DMwJjEHQ.js';
4
- import { E as EnvTypedDatabase } from './index-Ct1iiB4N.js';
4
+ import { E as EnvTypedDatabase } from './index-B4CcpqLb.js';
5
5
  import { R as RouteMeta } from './registry-B8pld3fj.js';
6
6
 
7
7
  /**
@@ -32,8 +32,20 @@ interface ColumnDef {
32
32
  selfRefColumn?: string;
33
33
  /** This column OWNS the row: erasure, RLS and the `owner` relation read it. */
34
34
  owns?: true;
35
- /** Explicit relation name, when the derived one would be ambiguous. */
35
+ /** Explicit name for the FORWARD relation (child parent), when the one
36
+ * derived from the column would be ambiguous or unclear. */
36
37
  refAs?: string;
38
+ /**
39
+ * Explicit name for the REVERSE relation (parent → children).
40
+ *
41
+ * Separate from {@link refAs} because the two directions are different facts:
42
+ * the forward name describes the parent this row points at (`author`), the
43
+ * reverse one describes the rows hanging off the parent (`posts`). One option
44
+ * naming both made an ordinary schema undeclarable — `posts.author_id` and
45
+ * `comments.author_id` both named `{ as: "author" }` collided on `users`, and
46
+ * the refusal asked for the `{ as }` they had both already written.
47
+ */
48
+ reverseAs?: string;
37
49
  /** The table this column belongs to; set by `defineTable`. */
38
50
  ownerTable?: {
39
51
  name: string;
@@ -197,9 +209,16 @@ declare class ColumnBuilder<K extends ColumnType = ColumnType, N extends boolean
197
209
  * In a cycle, ONE side needs an explicit return type:
198
210
  * `references((): AnyColumn => y.id)`. One side is enough — measured.
199
211
  * For a self-reference use `selfReferences(column)`: no thunk, no annotation.
212
+ *
213
+ * `as` names the FORWARD relation (`author_id` → `author` by default);
214
+ * `reverseAs` names the REVERSE one on the parent, whose default is this
215
+ * table's own name (`users.posts`). Two foreign keys from one table onto one
216
+ * parent therefore need a `reverseAs` on at least one of them — the reverse
217
+ * names would otherwise both be this table's name.
200
218
  */
201
219
  references(target: () => AnyColumn, opts?: {
202
220
  as?: string;
221
+ reverseAs?: string;
203
222
  onDelete?: OnDeleteAction;
204
223
  }): ColumnBuilder<K, N, D, E, P, T>;
205
224
  /**
@@ -489,7 +508,8 @@ declare const EXTENSION_DEPENDENCIES: Partial<Record<PalbaseExtension, PalbaseEx
489
508
  declare function isPalbaseExtension(name: string): name is PalbaseExtension;
490
509
 
491
510
  /**
492
- * A named raw-SQL DDL object declared in db/schema.ts for anything the typed DSL
511
+ * A named raw-SQL DDL object declared in a schema file (`db/public.ts`,
512
+ * `db/billing.ts`, …) for anything the typed DSL
493
513
  * cannot express (EXCLUDE, CHECK, partial/expression indexes, triggers, views).
494
514
  * The deploy emits `up` verbatim on the privileged DDL connection — same trust
495
515
  * posture as policy().using(). Tracked by NAME (not by diffing the body), so a
@@ -714,19 +734,6 @@ interface SchemaDef<T extends Record<string, TableDef> = Record<string, TableDef
714
734
  /** Postgres extensions to install on deploy. Normalized to `[]` when absent. */
715
735
  extensions: PalbaseExtension[];
716
736
  }
717
- /** The author-facing input to `defineSchema` — a `tables` map whose keys are
718
- * the table names and whose values are `{ columns, rls?, policies? }`, plus an
719
- * optional `extensions` allowlist. */
720
- interface SchemaInput<T extends Record<string, TableInput> = Record<string, TableInput>> {
721
- tables: T;
722
- /**
723
- * Postgres extensions to enable for this project, e.g. `["vector"]`.
724
- * Config-as-code: installed by the deploy (CREATE EXTENSION … SCHEMA
725
- * extensions) with the privileged deploy connection. The type is an
726
- * allowlist union, so unsupported names fail typecheck.
727
- */
728
- extensions?: PalbaseExtension[];
729
- }
730
737
  /** Map the author's `{ tables: { <name>: { columns } } }` input to the
731
738
  * `{ tables: { <name>: TableDef<columns> } }` runtime/type shape, threading the
732
739
  * per-table column map `T[K]["columns"]` so column-level inference survives. */
@@ -1212,4 +1219,4 @@ interface EnvTypedDatabase extends Omit<DBClient, "txPlan" | "asService"> {
1212
1219
  asService(): EnvServiceDatabase;
1213
1220
  }
1214
1221
 
1215
- export { type AnyColumn as A, defineSchema as B, ColumnBuilder as C, defineTable as D, type EnvTypedDatabase as E, enumType as F, installationRef as G, integer as H, type InsertShape as I, isPalbaseExtension as J, jsonb as K, makeTypedDB as L, numeric as M, openai as N, type OnDeleteAction as O, PALBASE_EXTENSIONS as P, ownedByUser as Q, type RawConstraintDef as R, type SchemaDef as S, TABLE_META as T, policy as U, raw as V, text as W, timestamp as X, userRef as Y, uuid as Z, vector as _, type ColumnDef as a, type ColumnMap as b, type ColumnType as c, EXTENSION_DEPENDENCIES as d, type EmbeddingModelRef as e, type EnvSchemas as f, type EnvServiceDatabase as g, type EnvTables as h, type EnvTypedTable as i, type PalbaseExtension as j, PolicyBuilder as k, type PolicyCommand as l, type PolicyDef as m, type PolicyMode as n, type RowShape as o, type SchemaInput as p, type TableDef as q, type TableHandle as r, type TableInput as s, type TxPlan as t, type TxTables as u, type TypedDB as v, type TypedTable as w, type TypedTx as x, bigint as y, boolean as z };
1222
+ export { type AnyColumn as A, defineTable as B, ColumnBuilder as C, enumType as D, type EnvTypedDatabase as E, installationRef as F, integer as G, isPalbaseExtension as H, type InsertShape as I, jsonb as J, makeTypedDB as K, numeric as L, openai as M, ownedByUser as N, type OnDeleteAction as O, PALBASE_EXTENSIONS as P, policy as Q, type RawConstraintDef as R, type SchemaDef as S, TABLE_META as T, raw as U, text as V, timestamp as W, userRef as X, uuid as Y, vector as Z, type ColumnDef as a, type ColumnMap as b, type ColumnType as c, EXTENSION_DEPENDENCIES as d, type EmbeddingModelRef as e, type EnvSchemas as f, type EnvServiceDatabase as g, type EnvTables as h, type EnvTypedTable as i, type PalbaseExtension as j, PolicyBuilder as k, type PolicyCommand as l, type PolicyDef as m, type PolicyMode as n, type RowShape as o, type TableDef as p, type TableHandle as q, type TableInput as r, type TxPlan as s, type TxTables as t, type TypedDB as u, type TypedTable as v, type TypedTx as w, bigint as x, boolean as y, defineSchema as z };
@@ -1,7 +1,7 @@
1
1
  import { Buckets, BucketTypes } from './stack.cjs';
2
2
  import { AsyncLocalStorage } from 'node:async_hooks';
3
3
  import { C as CacheClient, P as PalbaseDocsClient, a as PalbaseFlagsClient, L as Logger, b as PalbaseNotificationsClient, c as PalbaseRealtimeClient, D as DBClient, S as SecretsService, d as PalbaseStorageClient, e as PalbaseBucketClient, f as DBOps, T as TxPlanBody, g as TxPlanResponse, A as AuthSpec } from './endpoint-BSGw1pTu.cjs';
4
- import { E as EnvTypedDatabase } from './index-CwaWRhyc.cjs';
4
+ import { E as EnvTypedDatabase } from './index-DmVyY6N7.cjs';
5
5
  import { R as RouteMeta } from './registry-CgJvDYUW.cjs';
6
6
 
7
7
  /**
package/dist/index.cjs CHANGED
@@ -1965,9 +1965,16 @@ function defineTable(name, input) {
1965
1965
  }
1966
1966
  const policies = (input.policies ?? []).map(toPolicyDef);
1967
1967
  const rls = policies.length > 0 || input.rls !== false;
1968
+ const owned = {};
1969
+ for (const [colName, b] of Object.entries(input.columns)) {
1970
+ const builder = b;
1971
+ const clone = Object.create(Object.getPrototypeOf(builder));
1972
+ Object.defineProperty(clone, "_def", { value: { ...builder._def }, enumerable: true, writable: true });
1973
+ owned[colName] = clone;
1974
+ }
1968
1975
  const def = {
1969
1976
  name,
1970
- columns: input.columns,
1977
+ columns: owned,
1971
1978
  rls,
1972
1979
  policies
1973
1980
  };
@@ -2025,10 +2032,10 @@ function defineTable(name, input) {
2025
2032
  }
2026
2033
  def.memory = input.memory;
2027
2034
  }
2028
- for (const b of Object.values(input.columns)) {
2035
+ for (const b of Object.values(owned)) {
2029
2036
  b._def.ownerTable = def;
2030
2037
  }
2031
- const handle = { ...input.columns };
2038
+ const handle = { ...owned };
2032
2039
  Object.defineProperty(handle, TABLE_META, { value: def, enumerable: false });
2033
2040
  return handle;
2034
2041
  }
@@ -2345,6 +2352,12 @@ var ColumnBuilder = class _ColumnBuilder {
2345
2352
  * In a cycle, ONE side needs an explicit return type:
2346
2353
  * `references((): AnyColumn => y.id)`. One side is enough — measured.
2347
2354
  * For a self-reference use `selfReferences(column)`: no thunk, no annotation.
2355
+ *
2356
+ * `as` names the FORWARD relation (`author_id` → `author` by default);
2357
+ * `reverseAs` names the REVERSE one on the parent, whose default is this
2358
+ * table's own name (`users.posts`). Two foreign keys from one table onto one
2359
+ * parent therefore need a `reverseAs` on at least one of them — the reverse
2360
+ * names would otherwise both be this table's name.
2348
2361
  */
2349
2362
  references(target, opts) {
2350
2363
  refuseOnVector(this._def, "references");
@@ -2355,6 +2368,7 @@ var ColumnBuilder = class _ColumnBuilder {
2355
2368
  }
2356
2369
  this._def.referencesThunk = target;
2357
2370
  if (opts?.as !== void 0) this._def.refAs = opts.as;
2371
+ if (opts?.reverseAs !== void 0) this._def.reverseAs = opts.reverseAs;
2358
2372
  if (opts?.onDelete !== void 0) this._def.onDeleteAction = opts.onDelete;
2359
2373
  return new _ColumnBuilder(this._def.type, this._def);
2360
2374
  }
@@ -2581,64 +2595,73 @@ function optionalOnInsert(def) {
2581
2595
  function forwardName(column) {
2582
2596
  return column.endsWith("_id") ? column.slice(0, -3) : column;
2583
2597
  }
2584
- function qualify(schemaName, tableName) {
2585
- return schemaName === "public" ? tableName : `${schemaName}.${tableName}`;
2598
+ function describeOrigin(o) {
2599
+ return o.direction === "forward" ? `the foreign key "${o.table}.${o.column}"` : `the reverse of "${o.table}.${o.column}"`;
2600
+ }
2601
+ function renameCall(o) {
2602
+ if (o.direction === "reverse") return `references(() => \u2026, { reverseAs: "\u2026" })`;
2603
+ if (o.def.owns === true) {
2604
+ return `ownedByUser() takes no { as } \u2014 if "${o.column}" only POINTS at a user, declare it userRef({ onDelete: \u2026 }) instead`;
2605
+ }
2606
+ const target = o.def.references?.table;
2607
+ if (target === "auth.users") return `userRef({ onDelete: \u2026, as: "\u2026" })`;
2608
+ if (target === "auth.installations") return `installationRef({ onDelete: \u2026, as: "\u2026" })`;
2609
+ if (o.def.selfRefColumn !== void 0) return `selfReferences("\u2026", { as: "\u2026" })`;
2610
+ return `references(() => \u2026, { as: "\u2026" })`;
2586
2611
  }
2587
2612
  function buildRelations(schemas) {
2588
2613
  const out = /* @__PURE__ */ new Map();
2589
- const keyOf = /* @__PURE__ */ new Map();
2614
+ const taken = /* @__PURE__ */ new Map();
2590
2615
  for (const schema of schemas) {
2591
2616
  for (const t of Object.values(schema.tables)) {
2592
- const key = qualify(schema.name, t.name);
2617
+ const key = qualifiedTableKey(schema.name, t.name);
2593
2618
  out.set(key, []);
2594
- keyOf.set(t.name, key);
2619
+ taken.set(key, /* @__PURE__ */ new Map());
2595
2620
  }
2596
2621
  }
2622
+ const claim = (tableKey, name, origin, edge) => {
2623
+ const names = taken.get(tableKey);
2624
+ if (names === void 0) return;
2625
+ const held = names.get(name);
2626
+ if (held !== void 0) {
2627
+ if (held.def.owns === true && origin.def.owns === true) {
2628
+ throw new Error(
2629
+ `table "${tableKey}" declares TWO owner columns (${held.column}, ${origin.column}) \u2014 a table has at most ONE ownedByUser(). If the other column only POINTS at a user, declare it userRef({ onDelete: \u2026 }) instead.`
2630
+ );
2631
+ }
2632
+ const heldFix = renameCall(held);
2633
+ const originFix = renameCall(origin);
2634
+ const remedy = heldFix === originFix ? `rename one of them: ${heldFix}` : `rename one of them \u2014 "${held.table}.${held.column}": ${heldFix}; "${origin.table}.${origin.column}": ${originFix}`;
2635
+ throw new Error(
2636
+ `table "${tableKey}": ${describeOrigin(held)} and ${describeOrigin(origin)} both resolve to the relation name "${name}" \u2014 ${remedy}.`
2637
+ );
2638
+ }
2639
+ names.set(name, origin);
2640
+ out.get(tableKey)?.push(edge);
2641
+ };
2597
2642
  for (const schema of schemas) {
2598
2643
  for (const table of Object.values(schema.tables)) {
2599
- const childKey = qualify(schema.name, table.name);
2600
- const bucket = out.get(childKey);
2601
- if (bucket === void 0) continue;
2602
- const byTarget = /* @__PURE__ */ new Map();
2603
- for (const [col, builder] of Object.entries(table.columns)) {
2604
- const ref = builder._def.references;
2605
- if (ref === void 0) continue;
2606
- const list = byTarget.get(ref.table) ?? [];
2607
- list.push(col);
2608
- byTarget.set(ref.table, list);
2609
- }
2610
- for (const [target, cols] of byTarget) {
2611
- if (cols.length < 2) continue;
2612
- const unnamed = cols.filter((c) => {
2613
- const d = table.columns[c]?._def;
2614
- return d?.refAs === void 0 && d?.owns !== true;
2615
- });
2616
- if (unnamed.length > 0) {
2617
- throw new Error(
2618
- `table "${table.name}" declares ${cols.length} foreign keys onto "${target}" (${cols.join(", ")}) \u2014 name each one: references(() => \u2026, { as: "\u2026" })`
2619
- );
2620
- }
2621
- }
2644
+ const childKey = qualifiedTableKey(schema.name, table.name);
2645
+ if (!out.has(childKey)) continue;
2622
2646
  for (const [col, builder] of Object.entries(table.columns)) {
2623
2647
  const def = builder._def;
2624
2648
  if (def.references === void 0) continue;
2649
+ const targetKey = def.references.table;
2625
2650
  const name = def.owns === true ? "owner" : def.refAs ?? forwardName(col);
2626
- if (bucket.some((r) => r.name === name)) {
2627
- throw new Error(
2628
- name === "owner" ? `table "${table.name}": two columns claim ownership of the row \u2014 a table has at most ONE ownedByUser(). If the other column only POINTS at a user, declare it userRef({ onDelete: \u2026 }) instead.` : `table "${table.name}": two relations resolve to the name "${name}" \u2014 give one an explicit { as: "\u2026" }`
2629
- );
2630
- }
2631
- const targetKey = keyOf.get(def.references.table) ?? def.references.table;
2632
- bucket.push({ name, to: targetKey, kind: "one", via: col });
2633
- const parent = out.get(targetKey);
2634
- if (parent === void 0 || targetKey === childKey) continue;
2635
- const reverse = def.refAs ?? table.name;
2636
- if (parent.some((r) => r.name === reverse)) {
2637
- throw new Error(
2638
- `table "${def.references.table}": two reverse relations resolve to the name "${reverse}" \u2014 give one an explicit { as: "\u2026" }`
2639
- );
2640
- }
2641
- parent.push({ name: reverse, to: childKey, kind: "many", via: col });
2651
+ claim(
2652
+ childKey,
2653
+ name,
2654
+ { table: table.name, column: col, direction: "forward", def },
2655
+ { name, to: targetKey, kind: "one", via: col }
2656
+ );
2657
+ if (!out.has(targetKey) || targetKey === childKey) continue;
2658
+ const reverse = def.reverseAs ?? table.name;
2659
+ claim(
2660
+ targetKey,
2661
+ reverse,
2662
+ { table: table.name, column: col, direction: "reverse", def },
2663
+ { name: reverse, to: childKey, kind: "many", via: col }
2664
+ );
2642
2665
  }
2643
2666
  }
2644
2667
  }
@@ -2675,7 +2698,6 @@ function tableBlock(table, relations, indent) {
2675
2698
  }
2676
2699
  function makeEnvDts(schemas) {
2677
2700
  const relations = buildRelations(schemas);
2678
- const qualify2 = (schemaName, table) => schemaName === "public" ? table : `${schemaName}.${table}`;
2679
2701
  const publicSchema = schemas.find((s) => s.name === "public");
2680
2702
  const others = schemas.filter((s) => s.name !== "public");
2681
2703
  const publicBlocks = Object.keys(publicSchema?.tables ?? {}).map(
@@ -2686,7 +2708,7 @@ ${publicBlocks.join("\n")}
2686
2708
  ` : "";
2687
2709
  const schemaBlocks = others.map((schema) => {
2688
2710
  const inner = Object.keys(schema.tables).map(
2689
- (name) => tableBlock(schema.tables[name], relations.get(qualify2(schema.name, name)) ?? [], " ")
2711
+ (name) => tableBlock(schema.tables[name], relations.get(qualifiedTableKey(schema.name, name)) ?? [], " ")
2690
2712
  );
2691
2713
  return ` ${schema.name}: {
2692
2714
  ${inner.join("\n")}
@@ -2880,6 +2902,10 @@ function recordRoom(ctor, meta) {
2880
2902
  const carrier = carrierOf(ctor);
2881
2903
  const buffer = ownRoomBuffer(carrier);
2882
2904
  carrier[ROOM] = { ...meta, hooks: buffer.hooks, messages: buffer.messages };
2905
+ const g = globalThis;
2906
+ const REGISTRY2 = /* @__PURE__ */ Symbol.for("palbase.backend.allControllers");
2907
+ const all = g[REGISTRY2] ??= [];
2908
+ if (!all.includes(ctor)) all.push(ctor);
2883
2909
  }
2884
2910
 
2885
2911
  // src/decorators/controller.ts