@palbase/backend 24.3.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 (61) hide show
  1. package/dist/bin/palbase-backend.cjs +103 -60
  2. package/dist/bin/palbase-backend.cjs.map +1 -1
  3. package/dist/bin/palbase-backend.js +17 -13
  4. package/dist/bin/palbase-backend.js.map +1 -1
  5. package/dist/{chunk-EIXCY4SS.js → chunk-34I4GB7D.js} +82 -49
  6. package/dist/chunk-34I4GB7D.js.map +1 -0
  7. package/dist/{chunk-UWSYTUGM.js → chunk-35PNTIRN.js} +48 -1
  8. package/dist/chunk-35PNTIRN.js.map +1 -0
  9. package/dist/chunk-HBOJLP2Z.js +840 -0
  10. package/dist/chunk-HBOJLP2Z.js.map +1 -0
  11. package/dist/{chunk-7Z6MGMXQ.js → chunk-XJ2RSHEU.js} +11 -5
  12. package/dist/chunk-XJ2RSHEU.js.map +1 -0
  13. package/dist/{chunk-ERDL5VAE.js → chunk-YOY5DFQS.js} +2 -2
  14. package/dist/db/env.cjs.map +1 -1
  15. package/dist/db/env.d.cts +31 -14
  16. package/dist/db/env.d.ts +31 -14
  17. package/dist/db/index.cjs +226 -111
  18. package/dist/db/index.cjs.map +1 -1
  19. package/dist/db/index.d.cts +1 -1
  20. package/dist/db/index.d.ts +1 -1
  21. package/dist/db/index.js +11 -1
  22. package/dist/engine/index.cjs +89 -50
  23. package/dist/engine/index.cjs.map +1 -1
  24. package/dist/engine/index.d.cts +2 -2
  25. package/dist/engine/index.d.ts +2 -2
  26. package/dist/engine/index.js +3 -3
  27. package/dist/{index-BTMYod_l.d.ts → index-B4CcpqLb.d.ts} +224 -75
  28. package/dist/{index-DEneI8Mn.d.ts → index-B8v6hVyU.d.ts} +5 -2
  29. package/dist/{index-BLAbr9ZH.d.cts → index-DmVyY6N7.d.cts} +224 -75
  30. package/dist/{index-C-ALG22n.d.cts → index-VsjBQ4Kw.d.cts} +5 -2
  31. package/dist/index.cjs +580 -301
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.d.cts +125 -22
  34. package/dist/index.d.ts +125 -22
  35. package/dist/index.js +173 -217
  36. package/dist/index.js.map +1 -1
  37. package/dist/openapi/index.cjs +100 -36
  38. package/dist/openapi/index.cjs.map +1 -1
  39. package/dist/openapi/index.js +59 -2
  40. package/dist/openapi/index.js.map +1 -1
  41. package/docs/README.md +64 -31
  42. package/docs/endpoints.md +25 -28
  43. package/docs/llms-full.txt +430 -153
  44. package/docs/schema.md +303 -91
  45. package/docs/services.md +39 -4
  46. package/package.json +1 -1
  47. package/template/AGENTS.md +119 -314
  48. package/template/CLAUDE.md +13 -0
  49. package/template/controllers/notes.controller.ts +6 -13
  50. package/template/db/public.ts +38 -0
  51. package/template/models/notes/create.ts +38 -0
  52. package/template/package.json +6 -3
  53. package/template/services/note.service.test.ts +45 -0
  54. package/template/services/note.service.ts +2 -2
  55. package/dist/chunk-7Z6MGMXQ.js.map +0 -1
  56. package/dist/chunk-D5CQES25.js +0 -556
  57. package/dist/chunk-D5CQES25.js.map +0 -1
  58. package/dist/chunk-EIXCY4SS.js.map +0 -1
  59. package/dist/chunk-UWSYTUGM.js.map +0 -1
  60. package/template/db/schema.ts +0 -35
  61. /package/dist/{chunk-ERDL5VAE.js.map → chunk-YOY5DFQS.js.map} +0 -0
@@ -1,4 +1,4 @@
1
- import { Tables, TableTypes } from './db/env.cjs';
1
+ import { Tables, TableTypes, Schemas } from './db/env.cjs';
2
2
  import { D as DBClient, b8 as TxPlanHandle, bh as TxTable, M as Materialized } from './endpoint-BSGw1pTu.cjs';
3
3
 
4
4
  /** On delete action for foreign key references. */
@@ -26,6 +26,31 @@ interface ColumnDef {
26
26
  table: string;
27
27
  column: string;
28
28
  };
29
+ /** Pending FK target, resolved by `defineSchema` once every binding exists. */
30
+ referencesThunk?: () => ColumnBuilder;
31
+ /** FK onto THIS table — no thunk needed, the target is the declaring table. */
32
+ selfRefColumn?: string;
33
+ /** This column OWNS the row: erasure, RLS and the `owner` relation read it. */
34
+ owns?: true;
35
+ /** Explicit name for the FORWARD relation (child → parent), when the one
36
+ * derived from the column would be ambiguous or unclear. */
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;
49
+ /** The table this column belongs to; set by `defineTable`. */
50
+ ownerTable?: {
51
+ name: string;
52
+ columns: Record<string, ColumnBuilder>;
53
+ };
29
54
  /**
30
55
  * The name this column used to have. A diff cannot tell a rename from a drop and
31
56
  * an add — both leave one name gone and another present — so the intent has to be
@@ -171,47 +196,44 @@ declare class ColumnBuilder<K extends ColumnType = ColumnType, N extends boolean
171
196
  * safety gate in silence.
172
197
  */
173
198
  ignored(): ColumnBuilder<K, N, D, E, P, T>;
174
- references(table: string, column: string): ColumnBuilder<K, N, D, E, P, T>;
175
199
  /**
176
- * Add a real DB-level foreign key to the built-in auth users
177
- * (`REFERENCES auth.users(id)`), so a column like `user_id` gets true
178
- * database cascade/integrity instead of app-layer-only. Sugar for
179
- * `.references("auth.users", "id")`.
200
+ * Foreign key onto another table's column.
201
+ *
202
+ * The target is a THUNK, not a direct reference. In a cycle (`x → y`, `y → x`)
203
+ * the second table does not exist yet when the first is built; a direct
204
+ * reference makes TypeScript chase its own tail (TS7022 — measured, and making
205
+ * the return type independent of the target does NOT help). The thunk is
206
+ * invoked in `defineSchema`, where every binding exists and every table
207
+ * already knows its name.
180
208
  *
181
- * `auth.users` lives in the SAME tenant database (palauth-owned), so this is
182
- * a genuine cross-schema integrity constraint scoped to THIS tenant's users.
183
- * The referenced `auth.users.id` is `text` (palauth ids are `usr_<uuid>`), so
184
- * the referencing column must be `text()` too.
209
+ * In a cycle, ONE side needs an explicit return type:
210
+ * `references((): AnyColumn => y.id)`. One side is enough measured.
211
+ * For a self-reference use `selfReferences(column)`: no thunk, no annotation.
185
212
  *
186
- * ON DELETE is REQUIRED here and may only be `cascade` or `set null`: an
187
- * account-erasure request must never be blocked by a lingering FK, so
188
- * `restrict` / `no action` are not accepted (they don't type-check). Example:
189
- * `text().notNull().referencesAuthUser("cascade")`, or
190
- * `text().nullable().referencesAuthUser("set null")`. The server
191
- * (validateAuthUserFK) enforces this — and the remaining rules the type can't
192
- * express (referencing column is text, `set null` needs a nullable column) —
193
- * as the real boundary; this signature is the compile-time DX mirror.
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.
194
218
  */
195
- referencesAuthUser(onDelete: AuthUserOnDelete): ColumnBuilder<K, N, D, E, P, T>;
219
+ references(target: () => AnyColumn, opts?: {
220
+ as?: string;
221
+ reverseAs?: string;
222
+ onDelete?: OnDeleteAction;
223
+ }): ColumnBuilder<K, N, D, E, P, T>;
196
224
  /**
197
- * Add a real DB-level foreign key to the canonical, server-minted installation
198
- * anchor (`REFERENCES auth.installations(id)`) — the app-scoped verified-device
199
- * root (`ins_...`). Sugar for `.references("auth.installations", "id")`.
225
+ * Foreign key onto THIS table (`parent_id id`) — category trees, comment
226
+ * replies, org charts.
200
227
  *
201
- * An installation is an APP INSTALL, not a user: this FK is NOT user ownership.
202
- * A user-owned row STILL needs its own `.referencesAuthUser(...)` FK so account
203
- * erasure removes it — an installation reference alone does not tie a row to a
204
- * user's deletion. Use this only for install-scoped state (device prefs, push
205
- * routing, …), alongside a separate auth-user FK where the row is user-owned.
206
- *
207
- * `auth.installations` lives in the SAME tenant DB (palauth-owned); its `id` is
208
- * `text` (`ins_<uuid>`), so the referencing column must be `text()` too. ON
209
- * DELETE is REQUIRED and may only be `cascade` or `set null` (same allowed set
210
- * as an auth-user FK): an installation revoke / orphan cleanup must never be
211
- * blocked by a lingering FK. The server (validateAuthAnchorFK) is the real
212
- * boundary; this signature is the compile-time DX mirror.
228
+ * No thunk and no type annotation: the target table is the one being declared,
229
+ * so there is nothing to defer and nothing for TypeScript to chase in a circle.
230
+ * Drizzle forces an explicit `(): AnyPgColumn =>` here because its reference
231
+ * always goes through a callback; measured, we do not need one.
213
232
  */
214
- referencesInstallation(onDelete: AuthUserOnDelete): ColumnBuilder<K, N, D, E, P, T>;
233
+ selfReferences(column: string, opts?: {
234
+ as?: string;
235
+ onDelete?: OnDeleteAction;
236
+ }): ColumnBuilder<K, N, D, E, P, T>;
215
237
  /** Set the ON DELETE action for a foreign key reference. */
216
238
  onDelete(action: OnDeleteAction): ColumnBuilder<K, N, D, E, P, T>;
217
239
  /** Add a single-column UNIQUE constraint. */
@@ -310,6 +332,54 @@ declare function enumType<const V extends readonly string[]>(name: string, value
310
332
  * 2000 = pgvector'ün HNSW-indekslenebilir tavanı; auto-index bu beyanla bağlı
311
333
  * (spec FR-001, D-3). */
312
334
  declare function vector(dimensions: number): ColumnBuilder<'vector', false, false, unknown, number[]>;
335
+ /**
336
+ * Any column, whatever its type parameters.
337
+ *
338
+ * Exported so a cycle can be broken from ONE side:
339
+ * `references((): AnyColumn => y.id)`.
340
+ */
341
+ type AnyColumn = ColumnBuilder;
342
+ /**
343
+ * The column that OWNS this row: a `text` FK onto `auth.users(id)`, NOT NULL,
344
+ * ON DELETE CASCADE.
345
+ *
346
+ * Ownership drives account erasure, so cascade is the only correct action and
347
+ * takes no argument. The referencing column must be `text` (palauth ids are
348
+ * `usr_<uuid>`) and NOT NULL — both are implied here rather than left to the
349
+ * caller, so three rules the type could not express before become UNWRITABLE.
350
+ *
351
+ * At most ONE per table. The old shape let several columns reference
352
+ * `auth.users` and picked the FIRST IN DECLARATION ORDER as the owner — moving a
353
+ * `created_by` above a `user_id` silently changed which rows an account deletion
354
+ * took with it. A second one is now rejected at push.
355
+ *
356
+ * For a column that merely POINTS at a user without owning the row
357
+ * (`created_by`, `edited_by`), use `userRef({ onDelete })`.
358
+ */
359
+ declare function ownedByUser(): ColumnBuilder<'text', false, false, never>;
360
+ /**
361
+ * A plain FK onto `auth.users(id)` that does NOT own the row.
362
+ *
363
+ * `created_by` / `edited_by`: deleting that user must not delete the row. ON
364
+ * DELETE is required and limited to `cascade | set null` so an erasure request
365
+ * is never blocked by a lingering FK; `set null` needs a nullable column.
366
+ */
367
+ declare function userRef(opts: {
368
+ onDelete: AuthUserOnDelete;
369
+ as?: string;
370
+ }): ColumnBuilder<'text', boolean, false, never>;
371
+ /**
372
+ * A plain FK onto `auth.installations(id)` — the app-scoped verified-device
373
+ * anchor.
374
+ *
375
+ * An installation is an APP INSTALL, not a user: this is NOT ownership. A
376
+ * user-owned row still needs its own `ownedByUser()` so account erasure removes
377
+ * it; an installation reference alone does not tie a row to a user's deletion.
378
+ */
379
+ declare function installationRef(opts: {
380
+ onDelete: AuthUserOnDelete;
381
+ as?: string;
382
+ }): ColumnBuilder<'text', boolean, false, never>;
313
383
 
314
384
  /**
315
385
  * policy.ts — the RLS policy authoring DSL.
@@ -438,7 +508,8 @@ declare const EXTENSION_DEPENDENCIES: Partial<Record<PalbaseExtension, PalbaseEx
438
508
  declare function isPalbaseExtension(name: string): name is PalbaseExtension;
439
509
 
440
510
  /**
441
- * 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
442
513
  * cannot express (EXCLUDE, CHECK, partial/expression indexes, triggers, views).
443
514
  * The deploy emits `up` verbatim on the privileged DDL connection — same trust
444
515
  * posture as policy().using(). Tracked by NAME (not by diffing the body), so a
@@ -482,7 +553,7 @@ declare const openai: {
482
553
 
483
554
  /**
484
555
  * A map of column builders keyed by column name — the value you write under
485
- * the `columns` key of `defineSchema({ tables: { <name>: { columns } } })`.
556
+ * the `columns` key of `defineTable("<name>", { columns })`.
486
557
  *
487
558
  * The default `Record<string, ColumnBuilder>` keeps bare references compiling
488
559
  * without a type argument.
@@ -655,55 +726,50 @@ interface TableDef<C extends ColumnMap = ColumnMap> {
655
726
  * `TableDef<{ id: ColumnBuilder<'uuid', false, true, never>; ... }>`.
656
727
  */
657
728
  interface SchemaDef<T extends Record<string, TableDef> = Record<string, TableDef>> {
729
+ /** The schema's own name — `public`, `billing`, … Declared, never derived. */
730
+ name: string;
731
+ /** Reachable over HTTP? False unless the author opts in. */
732
+ exposed: boolean;
658
733
  tables: T;
659
734
  /** Postgres extensions to install on deploy. Normalized to `[]` when absent. */
660
735
  extensions: PalbaseExtension[];
661
736
  }
662
- /** The author-facing input to `defineSchema` — a `tables` map whose keys are
663
- * the table names and whose values are `{ columns, rls?, policies? }`, plus an
664
- * optional `extensions` allowlist. */
665
- interface SchemaInput<T extends Record<string, TableInput> = Record<string, TableInput>> {
666
- tables: T;
667
- /**
668
- * Postgres extensions to enable for this project, e.g. `["vector"]`.
669
- * Config-as-code: installed by the deploy (CREATE EXTENSION … SCHEMA
670
- * extensions) with the privileged deploy connection. The type is an
671
- * allowlist union, so unsupported names fail typecheck.
672
- */
673
- extensions?: PalbaseExtension[];
674
- }
675
737
  /** Map the author's `{ tables: { <name>: { columns } } }` input to the
676
738
  * `{ tables: { <name>: TableDef<columns> } }` runtime/type shape, threading the
677
739
  * per-table column map `T[K]["columns"]` so column-level inference survives. */
678
- type TablesFromInput<T extends Record<string, TableInput>> = {
679
- [K in keyof T]: TableDef<T[K]["columns"]>;
740
+ type TablesByName<T extends readonly TableHandle[]> = {
741
+ [K in T[number] as K[typeof TABLE_META]["name"]]: K[typeof TABLE_META];
680
742
  };
681
743
  /**
682
- * Define a schema. The table NAME comes from the object key. Each table value
683
- * is `{ columns, rls?, policies? }`:
744
+ * How a schema file reads. One file per schema `db/public.ts`, `db/billing.ts`
745
+ * and the schema says its own name:
684
746
  *
685
- * export default defineSchema({
686
- * tables: {
687
- * todos: {
688
- * columns: {
689
- * id: uuid().primaryKey().defaultRandom(),
690
- * owner: text().notNull(),
691
- * title: text().notNull(),
692
- * },
693
- * rls: true,
694
- * policies: [
695
- * policy("owner_all").for("all").to("authenticated")
696
- * .using("owner = (select auth.uid())")
697
- * .withCheck("owner = (select auth.uid())"),
698
- * ],
699
- * },
747
+ * const todos = defineTable("todos", {
748
+ * columns: {
749
+ * id: uuid().primaryKey().defaultRandom(),
750
+ * owner: text().notNull(),
751
+ * title: text().notNull(),
700
752
  * },
753
+ * rls: true,
754
+ * policies: [
755
+ * policy("owner_all").for("all").to("authenticated")
756
+ * .using("owner = (select auth.uid())")
757
+ * .withCheck("owner = (select auth.uid())"),
758
+ * ],
701
759
  * });
702
760
  *
703
- * The returned value is
704
- * `{ tables: { todos: { name, columns, rls, policies } } }` — the exact shape
705
- * the runtime schema extractor parses. Per-column phantom types are preserved
706
- * so `Database.tables.todos.insert({...})` stays typed.
761
+ * export default defineSchema("public", { tables: [todos] });
762
+ *
763
+ * The dictionary form `defineSchema({ tables: { todos: {…} } })` is RETIRED and
764
+ * refused by name: a table whose name comes from a key does not know what it is
765
+ * called while it is being built, so it cannot resolve `references(() =>
766
+ * other.id)`, and a schema that does not say its own name cannot tell one
767
+ * `invoices` from another schema's `invoices`.
768
+ *
769
+ * The returned value is `{ name, tables: { todos: { name, columns, rls,
770
+ * policies } }, extensions, exposed }` — the exact shape the runtime schema
771
+ * extractor parses. Per-column phantom types are preserved so
772
+ * `Database.tables.todos.insert({...})` stays typed.
707
773
  *
708
774
  * RLS normalization: `rls` defaults to **`true`**, `policies` to `[]`. A table
709
775
  * that declares neither is therefore deny-all — nothing reads it until a policy
@@ -713,7 +779,46 @@ type TablesFromInput<T extends Record<string, TableInput>> = {
713
779
  * `rls` is forced on (ENABLE + FORCE) regardless of the declared flag — a table
714
780
  * with policies must have RLS enabled or the policies would be inert.
715
781
  */
716
- declare function defineSchema<T extends Record<string, TableInput>>(input: SchemaInput<T>): SchemaDef<TablesFromInput<T>>;
782
+ /**
783
+ * Where a table's metadata lives.
784
+ *
785
+ * The table VALUE is its columns, so `lists.id` is the column — which means a
786
+ * column called `name` would overwrite the table's own name if metadata sat in
787
+ * a plain field. Measured, not imagined: the existing `users` / `rooms` tests
788
+ * (both declare a `name` column) produced an `[object Object]` table key.
789
+ * `columns`, `rls`, `search`, `memory`, `unique` and `indexes` are all plausible
790
+ * column names too. A column cannot collide with a symbol.
791
+ */
792
+ declare const TABLE_META: unique symbol;
793
+ /** A value produced by `defineTable`: the columns, plus metadata behind the symbol. */
794
+ type TableHandle<C extends ColumnMap = ColumnMap, N extends string = string> = C & {
795
+ readonly [TABLE_META]: TableDef<C> & {
796
+ name: N;
797
+ };
798
+ };
799
+ /**
800
+ * Declare one table as a standalone value that KNOWS ITS OWN NAME.
801
+ *
802
+ * The name lives here, not in a dictionary key. A table whose name comes from a
803
+ * key cannot resolve `references(() => other.id)` when it is built — it does not
804
+ * know what to call itself — which forces a two-phase resolution with an
805
+ * empty-name intermediate state. One name source, no empty identity.
806
+ *
807
+ * The returned object spreads its own columns, so a sibling can point at it:
808
+ * `references(() => lists.id)`.
809
+ */
810
+ declare function defineTable<const N extends string, C extends ColumnMap>(name: N, input: TableInput<C>): TableHandle<C, N>;
811
+ /**
812
+ * Collect declared tables into one schema.
813
+ *
814
+ * Takes an ARRAY, not a dictionary: the tables already carry their names, and a
815
+ * second name source is a second thing that can disagree.
816
+ */
817
+ declare function defineSchema<const T extends readonly TableHandle[]>(name: string, input: {
818
+ tables: T;
819
+ exposed?: boolean;
820
+ extensions?: PalbaseExtension[];
821
+ }): SchemaDef<TablesByName<T>>;
717
822
 
718
823
  /**
719
824
  * typed-db.ts — Task 2: TypedDB schema-derived insert/row shapes.
@@ -978,6 +1083,26 @@ type EnvTypedTable<T extends TableTypes> = EnvTypedTableBase<T> & (T extends {
978
1083
  type EnvTables = {
979
1084
  [K in keyof Tables]: EnvTypedTable<Tables[K]>;
980
1085
  };
1086
+ /**
1087
+ * The project's NON-public schemas, keyed by schema name, each exposing its own
1088
+ * `tables` map — the shape `Database.schema("billing")` returns.
1089
+ *
1090
+ * The intermediate `tables` is there for the reason {@link EnvTables} sits under
1091
+ * `.tables`: a schema's table names must not share a namespace with anything the
1092
+ * accessor itself might grow.
1093
+ *
1094
+ * Empty by default. The generated `palbase-env.d.ts` augments `Schemas` with one
1095
+ * member per declared schema other than `public`, so a project that declares
1096
+ * none has `keyof Schemas = never` and every `schema(...)` call is a compile
1097
+ * error rather than a runtime surprise.
1098
+ */
1099
+ type EnvSchemas = {
1100
+ [S in keyof Schemas]: {
1101
+ tables: {
1102
+ [T in keyof Schemas[S]]: EnvTypedTable<Extract<Schemas[S][T], TableTypes>>;
1103
+ };
1104
+ };
1105
+ };
981
1106
  /** The project's tables as PLAN operations, keyed by the env `Tables`
982
1107
  * interface. The transaction twin of {@link EnvTables}. */
983
1108
  type TxTables = {
@@ -999,6 +1124,10 @@ type TxPlan = TxPlanHandle<TxTables>;
999
1124
  */
1000
1125
  interface EnvServiceDatabase extends Omit<DBClient, "txPlan" | "asService"> {
1001
1126
  tables: EnvTables;
1127
+ /** See {@link EnvTypedDatabase.schema}. The bypass sibling reaches every
1128
+ * schema the default surface does — a `billing` table is no less reachable
1129
+ * because the caller asked to run as the service role. */
1130
+ schema<S extends keyof Schemas>(name: S): EnvSchemas[S];
1002
1131
  transaction<T>(fn: (tx: TxPlan) => T extends Promise<unknown> ? never : T): Promise<Materialized<T>>;
1003
1132
  }
1004
1133
  /**
@@ -1013,6 +1142,26 @@ interface EnvServiceDatabase extends Omit<DBClient, "txPlan" | "asService"> {
1013
1142
  */
1014
1143
  interface EnvTypedDatabase extends Omit<DBClient, "txPlan" | "asService"> {
1015
1144
  tables: EnvTables;
1145
+ /**
1146
+ * The tables of a schema other than `public`.
1147
+ *
1148
+ * `Database.tables.*` is the PUBLIC schema and only the public schema. Two
1149
+ * schemas may declare the same table name — `public.invoices` and
1150
+ * `billing.invoices` are different tables — so one flat namespace would make
1151
+ * `Database.tables.invoices` resolve by declaration order. It does not: the
1152
+ * bare name is always public's, and everything else is asked for by schema.
1153
+ *
1154
+ * The name is `keyof Schemas`, so a schema the project never declared is a
1155
+ * compile error. On the wire the table travels schema-qualified
1156
+ * (`billing.invoices`); `public` stays bare.
1157
+ *
1158
+ * Declaring a schema does not publish it — reachability over HTTP is the
1159
+ * schema's own `exposed` flag, checked by the broker.
1160
+ *
1161
+ * @example
1162
+ * const inv = await Database.schema("billing").tables.invoices.findById(id);
1163
+ */
1164
+ schema<S extends keyof Schemas>(name: S): EnvSchemas[S];
1016
1165
  /**
1017
1166
  * Run a transaction. The callback DESCRIBES the operations; the whole
1018
1167
  * description travels in one request and the broker runs it inside a single
@@ -1070,4 +1219,4 @@ interface EnvTypedDatabase extends Omit<DBClient, "txPlan" | "asService"> {
1070
1219
  asService(): EnvServiceDatabase;
1071
1220
  }
1072
1221
 
1073
- export { isPalbaseExtension as A, jsonb as B, ColumnBuilder as C, makeTypedDB as D, type EnvTypedDatabase as E, numeric as F, openai as G, policy as H, type InsertShape as I, raw as J, text as K, timestamp as L, uuid as M, vector as N, type OnDeleteAction as O, PALBASE_EXTENSIONS as P, type RawConstraintDef as R, type SchemaDef as S, type TableDef as T, type ColumnDef as a, type ColumnMap as b, type ColumnType as c, EXTENSION_DEPENDENCIES as d, type EmbeddingModelRef as e, type EnvServiceDatabase as f, type EnvTables as g, type EnvTypedTable as h, type PalbaseExtension as i, PolicyBuilder as j, type PolicyCommand as k, type PolicyDef as l, type PolicyMode as m, type RowShape as n, type SchemaInput as o, type TableInput as p, type TxPlan as q, type TxTables as r, type TypedDB as s, type TypedTable as t, type TypedTx as u, bigint as v, boolean as w, defineSchema as x, enumType as y, integer 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-BLAbr9ZH.cjs';
4
+ import { E as EnvTypedDatabase } from './index-DmVyY6N7.cjs';
5
5
  import { R as RouteMeta } from './registry-CgJvDYUW.cjs';
6
6
 
7
7
  /**
@@ -673,6 +673,9 @@ declare function withTables<T extends ReturnType<typeof createOps>>(ops: T, sche
673
673
  }>;
674
674
  }): T & {
675
675
  tables: Record<string, unknown>;
676
+ schema: (name: string) => {
677
+ tables: Record<string, unknown>;
678
+ };
676
679
  };
677
680
  /** The two transactions a request may hold, and the single `Database` over them. */
678
681
  interface RequestDatabase {
@@ -1007,7 +1010,7 @@ interface CreateAppOptions {
1007
1010
  /** `@Controller` classes. A class that collected zero routes is fatal. */
1008
1011
  controllers: readonly unknown[];
1009
1012
  /** The project's `defineSchema()` result, for the typed `.tables` surface. */
1010
- schema?: unknown;
1013
+ schemas?: readonly unknown[];
1011
1014
  /** The SQL driver. Omitted ⇒ built from `Bun.sql` when running under Bun. */
1012
1015
  sql?: SqlDriver;
1013
1016
  /** Module clients. Omitted ⇒ each corresponding singleton throws when used. */