@palbase/backend 25.0.1 → 25.0.2

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 (36) hide show
  1. package/dist/{chunk-HBOJLP2Z.js → chunk-7JSEN7UR.js} +2 -1
  2. package/dist/chunk-7JSEN7UR.js.map +1 -0
  3. package/dist/db/index.cjs.map +1 -1
  4. package/dist/db/index.d.cts +2 -2
  5. package/dist/db/index.d.ts +2 -2
  6. package/dist/db/index.js +1 -1
  7. package/dist/{endpoint-DMwJjEHQ.d.ts → endpoint-CTEHhb7A.d.ts} +19 -8
  8. package/dist/{endpoint-BSGw1pTu.d.cts → endpoint-DYHMo6cC.d.cts} +19 -8
  9. package/dist/engine/index.d.cts +4 -4
  10. package/dist/engine/index.d.ts +4 -4
  11. package/dist/{index-B8v6hVyU.d.ts → index-CUomTA3e.d.ts} +3 -3
  12. package/dist/{index-B4CcpqLb.d.ts → index-CW21M9Z3.d.ts} +1 -1
  13. package/dist/{index-VsjBQ4Kw.d.cts → index-ClpDeSos.d.cts} +3 -3
  14. package/dist/{index-DmVyY6N7.d.cts → index-CmBK76nx.d.cts} +1 -1
  15. package/dist/index.cjs +5 -2
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +9 -8
  18. package/dist/index.d.ts +9 -8
  19. package/dist/index.js +5 -3
  20. package/dist/index.js.map +1 -1
  21. package/dist/openapi/index.d.cts +2 -2
  22. package/dist/openapi/index.d.ts +2 -2
  23. package/dist/{registry-CgJvDYUW.d.cts → registry-CC0WBQq6.d.cts} +1 -1
  24. package/dist/{registry-B8pld3fj.d.ts → registry-dZZ5JKYg.d.ts} +1 -1
  25. package/dist/test/index.d.cts +1 -1
  26. package/dist/test/index.d.ts +1 -1
  27. package/docs/README.md +9 -8
  28. package/docs/database.md +2 -2
  29. package/docs/endpoints.md +6 -5
  30. package/docs/getting-started.md +1 -1
  31. package/docs/llms-full.txt +38 -33
  32. package/docs/migrations.md +16 -14
  33. package/docs/schema.md +2 -2
  34. package/docs/services.md +2 -1
  35. package/package.json +1 -1
  36. package/dist/chunk-HBOJLP2Z.js.map +0 -1
@@ -1,7 +1,7 @@
1
1
  import { OpenApiGeneratorV31 } from '@asteasolutions/zod-to-openapi';
2
2
  import { ZodTypeAny } from 'zod';
3
- import { A as AuthSpec, R as RateLimitConfig } from '../endpoint-BSGw1pTu.cjs';
4
- import { P as ParamMeta, T as ThrowDescriptor, U as UploadConfig, S as SseConfig } from '../registry-CgJvDYUW.cjs';
3
+ import { A as AuthSpec, R as RateLimitConfig } from '../endpoint-DYHMo6cC.cjs';
4
+ import { P as ParamMeta, T as ThrowDescriptor, U as UploadConfig, S as SseConfig } from '../registry-CC0WBQq6.cjs';
5
5
  import '../stack.cjs';
6
6
 
7
7
  /** A controller class (constructor). Kept loose — the only contract is that it
@@ -1,7 +1,7 @@
1
1
  import { OpenApiGeneratorV31 } from '@asteasolutions/zod-to-openapi';
2
2
  import { ZodTypeAny } from 'zod';
3
- import { A as AuthSpec, R as RateLimitConfig } from '../endpoint-DMwJjEHQ.js';
4
- import { P as ParamMeta, T as ThrowDescriptor, U as UploadConfig, S as SseConfig } from '../registry-B8pld3fj.js';
3
+ import { A as AuthSpec, R as RateLimitConfig } from '../endpoint-CTEHhb7A.js';
4
+ import { P as ParamMeta, T as ThrowDescriptor, U as UploadConfig, S as SseConfig } from '../registry-dZZ5JKYg.js';
5
5
  import '../stack.js';
6
6
 
7
7
  /** A controller class (constructor). Kept loose — the only contract is that it
@@ -1,4 +1,4 @@
1
- import { A as AuthSpec, R as RateLimitConfig } from './endpoint-BSGw1pTu.cjs';
1
+ import { A as AuthSpec, R as RateLimitConfig } from './endpoint-DYHMo6cC.cjs';
2
2
  import { ZodTypeAny } from 'zod';
3
3
  import { PalbaseBucketName } from './stack.cjs';
4
4
 
@@ -1,4 +1,4 @@
1
- import { A as AuthSpec, R as RateLimitConfig } from './endpoint-DMwJjEHQ.js';
1
+ import { A as AuthSpec, R as RateLimitConfig } from './endpoint-CTEHhb7A.js';
2
2
  import { ZodTypeAny } from 'zod';
3
3
  import { PalbaseBucketName } from './stack.js';
4
4
 
@@ -1,4 +1,4 @@
1
- import { D as DBClient } from '../endpoint-BSGw1pTu.cjs';
1
+ import { D as DBClient } from '../endpoint-DYHMo6cC.cjs';
2
2
  import 'zod';
3
3
  import '../stack.cjs';
4
4
 
@@ -1,4 +1,4 @@
1
- import { D as DBClient } from '../endpoint-DMwJjEHQ.js';
1
+ import { D as DBClient } from '../endpoint-CTEHhb7A.js';
2
2
  import 'zod';
3
3
  import '../stack.js';
4
4
 
package/docs/README.md CHANGED
@@ -20,7 +20,7 @@ deploy gate or the TypeScript compile.
20
20
  controllers/<name>.controller.ts # @Controller class + @Get/@Post/… route methods (the API surface)
21
21
  models/<controller>/<endpoint>.ts # zod schemas — one folder per controller, one file per endpoint (+ shared.ts)
22
22
  services/<name>.service.ts # plain class + singleton — the real logic (controllers stay thin)
23
- db/schema.ts # config-as-code Postgres schema (tables, columns, RLS) auto-migrated on deploy
23
+ db/<schema>.ts # one file per Postgres schema (db/public.ts in a fresh scaffold)
24
24
  ```
25
25
 
26
26
  The four folders above are the daily surface. Three more are discovered by name,
@@ -40,7 +40,7 @@ service the controllers call.
40
40
  that side effect alone — the shipped scaffold's own `HealthController` and
41
41
  `NotesController` are not exported. Exporting is harmless and reads well, so
42
42
  these examples do it; it is not a requirement. (`export default` **is**
43
- required for `jobs/`, `webhooks/`, `hooks/` and `db/public.ts`, one class per
43
+ required for `jobs/`, `webhooks/`, `hooks/` and every `db/*.ts`, one class per
44
44
  file.) What IS fatal is a `@Controller` class that collected zero routes —
45
45
  usually `experimentalDecorators` missing from `tsconfig.json`.
46
46
  2. **Methods that call a service are `async` and return `Promise<T>`.** Services
@@ -177,7 +177,7 @@ export default class TodosController {
177
177
  ```
178
178
 
179
179
  ```ts
180
- // db/schema.ts — config-as-code; the deploy auto-migrates additive changes.
180
+ // db/public.ts — config-as-code; the deploy auto-migrates additive changes.
181
181
  import { defineSchema, uuid, text, boolean, timestamp, policy } from "@palbase/backend";
182
182
  export default defineSchema({
183
183
  tables: {
@@ -218,7 +218,7 @@ generated client surface) changes; the verb/path do not affect it.
218
218
  (stage, bundle, extract controller metadata). Exits non-zero on a decorator,
219
219
  return-type or version-skew error, so a push that would deploy zero endpoints
220
220
  fails on your machine instead. It also regenerates `palbase-env.d.ts` from
221
- `db/schema.ts`, which is what types `Database.tables.*` (no import, no
221
+ your `db/*.ts` files, which is what types `Database.tables.*` (no import, no
222
222
  generic) — so run it after editing the schema. There is no separate command
223
223
  for that: `build` regenerates everything derived.
224
224
  - `palbase push` — deploy the current backend to the selected Environment. For a
@@ -261,9 +261,10 @@ The **only difference** is the trigger argument:
261
261
  | **Hooks** (`hooks/**`) | `(event, meta)` | typed event + `HookMeta` |
262
262
  | **Webhooks** (`webhooks/**`) | `(event, meta)` | typed event + `WebhookMeta` |
263
263
 
264
- `defineMiddleware` is still exported and takes `(ctx, next)`, but **no bundler
265
- reads a `middleware/` directory and the engine never calls one** there is no
266
- `ctx` anywhere on a path that runs. Every handler above imports its services.
264
+ `defineMiddleware(fn)` **throws when called**: no bundler reads a `middleware/`
265
+ directory and the engine never calls one, so a handler written against it would
266
+ deploy and never run. There is no `ctx` anywhere on a path that runs every
267
+ handler above imports its services.
267
268
 
268
269
  `meta` carries non-service data: `env` (Environment variables),
269
270
  `environmentId`, and for webhooks `requestId`. Services always come from
@@ -287,7 +288,7 @@ my-backend/
287
288
  ```
288
289
 
289
290
  There is **no `db/migrations/`**. Nothing generates a migration file, nothing
290
- commits one and nothing replays one: `db/schema.ts` is diffed against the live
291
+ commits one and nothing replays one: `db/*.ts` is diffed against the live
291
292
  database and applied — see [migrations.md](./migrations.md).
292
293
 
293
294
  HTTP endpoints are **not** file-path routed. You author a class controller
package/docs/database.md CHANGED
@@ -12,7 +12,7 @@ and [events.md](./events.md) for worker/job/hook/webhook examples).
12
12
 
13
13
  ## Typed by default — `Database.tables`
14
14
 
15
- When you declare `db/schema.ts`, `Database.tables.<name>` is typed everywhere
15
+ When you declare a schema under `db/*.ts`, `Database.tables.<name>` is typed everywhere
16
16
  with no import and no generic. `insert` demands the right columns; rows come
17
17
  back typed; nullable columns are `T | null`. This is the path you should use:
18
18
 
@@ -274,7 +274,7 @@ default.
274
274
 
275
275
  The Postgres role it connects as is **`backend_authenticated`** (or
276
276
  `backend_anon` when there is no signed-in user) — not `authenticated`. You
277
- rarely need to know that, because a policy declared in `db/schema.ts` is
277
+ rarely need to know that, because a policy declared in `db/*.ts` is
278
278
  deployed targeting both. It matters in exactly one place: **hand-written
279
279
  `CREATE POLICY` SQL in a migration must name both roles**, or it applies to
280
280
  nothing your code does. See
package/docs/endpoints.md CHANGED
@@ -155,11 +155,12 @@ See [errors.md](./errors.md) for the full set + the wire envelope shape.
155
155
 
156
156
  ## There is no middleware
157
157
 
158
- `defineMiddleware(async (ctx, next) => { … })` is still exported and still
159
- type-checks, but **nothing mounts a `middleware/` directory and the engine has no
160
- middleware pipeline** a handler written against it deploys, never runs, and
161
- nothing reports it. There is no `ctx` object anywhere on a path that executes, so
162
- treat the export as a leftover rather than a seam.
158
+ `defineMiddleware(async (ctx, next) => { … })` **throws**. Nothing mounts a
159
+ `middleware/` directory and the engine has no middleware pipeline, so a handler
160
+ written against it used to deploy and then never run — silently. The call now
161
+ refuses and names where the work belongs, rather than handing you a shell.
162
+
163
+ There is no `ctx` object anywhere on a path that executes.
163
164
 
164
165
  Cross-cutting work goes in a service the controllers call, and the route concerns
165
166
  that used to live in a wrapper are route options instead: `auth` on `@Controller`
@@ -47,7 +47,7 @@ start. The loop is: edit, validate, push to a dev Environment.
47
47
  own metadata extractor over the result, so a bad decorator, an illegal return
48
48
  type or an SDK major skew fails here rather than shipping a deploy that
49
49
  serves zero endpoints. It is wired into a `pre-push` git hook for you. It also
50
- regenerates `palbase-env.d.ts` from `db/schema.ts`, which is what types
50
+ regenerates `palbase-env.d.ts` from your `db/*.ts` files, which is what types
51
51
  `Database.tables.*` (no import, no generic), so run it after editing the
52
52
  schema — there is no separate command for that. See
53
53
  [migrations.md](./migrations.md) for the schema side.
@@ -28,7 +28,7 @@ deploy gate or the TypeScript compile.
28
28
  controllers/<name>.controller.ts # @Controller class + @Get/@Post/… route methods (the API surface)
29
29
  models/<controller>/<endpoint>.ts # zod schemas — one folder per controller, one file per endpoint (+ shared.ts)
30
30
  services/<name>.service.ts # plain class + singleton — the real logic (controllers stay thin)
31
- db/schema.ts # config-as-code Postgres schema (tables, columns, RLS) auto-migrated on deploy
31
+ db/<schema>.ts # one file per Postgres schema (db/public.ts in a fresh scaffold)
32
32
  ```
33
33
 
34
34
  The four folders above are the daily surface. Three more are discovered by name,
@@ -48,7 +48,7 @@ service the controllers call.
48
48
  that side effect alone — the shipped scaffold's own `HealthController` and
49
49
  `NotesController` are not exported. Exporting is harmless and reads well, so
50
50
  these examples do it; it is not a requirement. (`export default` **is**
51
- required for `jobs/`, `webhooks/`, `hooks/` and `db/public.ts`, one class per
51
+ required for `jobs/`, `webhooks/`, `hooks/` and every `db/*.ts`, one class per
52
52
  file.) What IS fatal is a `@Controller` class that collected zero routes —
53
53
  usually `experimentalDecorators` missing from `tsconfig.json`.
54
54
  2. **Methods that call a service are `async` and return `Promise<T>`.** Services
@@ -185,7 +185,7 @@ export default class TodosController {
185
185
  ```
186
186
 
187
187
  ```ts
188
- // db/schema.ts — config-as-code; the deploy auto-migrates additive changes.
188
+ // db/public.ts — config-as-code; the deploy auto-migrates additive changes.
189
189
  import { defineSchema, uuid, text, boolean, timestamp, policy } from "@palbase/backend";
190
190
  export default defineSchema({
191
191
  tables: {
@@ -226,7 +226,7 @@ generated client surface) changes; the verb/path do not affect it.
226
226
  (stage, bundle, extract controller metadata). Exits non-zero on a decorator,
227
227
  return-type or version-skew error, so a push that would deploy zero endpoints
228
228
  fails on your machine instead. It also regenerates `palbase-env.d.ts` from
229
- `db/schema.ts`, which is what types `Database.tables.*` (no import, no
229
+ your `db/*.ts` files, which is what types `Database.tables.*` (no import, no
230
230
  generic) — so run it after editing the schema. There is no separate command
231
231
  for that: `build` regenerates everything derived.
232
232
  - `palbase push` — deploy the current backend to the selected Environment. For a
@@ -269,9 +269,10 @@ The **only difference** is the trigger argument:
269
269
  | **Hooks** (`hooks/**`) | `(event, meta)` | typed event + `HookMeta` |
270
270
  | **Webhooks** (`webhooks/**`) | `(event, meta)` | typed event + `WebhookMeta` |
271
271
 
272
- `defineMiddleware` is still exported and takes `(ctx, next)`, but **no bundler
273
- reads a `middleware/` directory and the engine never calls one** there is no
274
- `ctx` anywhere on a path that runs. Every handler above imports its services.
272
+ `defineMiddleware(fn)` **throws when called**: no bundler reads a `middleware/`
273
+ directory and the engine never calls one, so a handler written against it would
274
+ deploy and never run. There is no `ctx` anywhere on a path that runs every
275
+ handler above imports its services.
275
276
 
276
277
  `meta` carries non-service data: `env` (Environment variables),
277
278
  `environmentId`, and for webhooks `requestId`. Services always come from
@@ -295,7 +296,7 @@ my-backend/
295
296
  ```
296
297
 
297
298
  There is **no `db/migrations/`**. Nothing generates a migration file, nothing
298
- commits one and nothing replays one: `db/schema.ts` is diffed against the live
299
+ commits one and nothing replays one: `db/*.ts` is diffed against the live
299
300
  database and applied — see [migrations.md](./migrations.md).
300
301
 
301
302
  HTTP endpoints are **not** file-path routed. You author a class controller
@@ -375,7 +376,7 @@ start. The loop is: edit, validate, push to a dev Environment.
375
376
  own metadata extractor over the result, so a bad decorator, an illegal return
376
377
  type or an SDK major skew fails here rather than shipping a deploy that
377
378
  serves zero endpoints. It is wired into a `pre-push` git hook for you. It also
378
- regenerates `palbase-env.d.ts` from `db/schema.ts`, which is what types
379
+ regenerates `palbase-env.d.ts` from your `db/*.ts` files, which is what types
379
380
  `Database.tables.*` (no import, no generic), so run it after editing the
380
381
  schema — there is no separate command for that. See
381
382
  [migrations.md](./migrations.md) for the schema side.
@@ -669,11 +670,12 @@ See [errors.md](./errors.md) for the full set + the wire envelope shape.
669
670
 
670
671
  ## There is no middleware
671
672
 
672
- `defineMiddleware(async (ctx, next) => { … })` is still exported and still
673
- type-checks, but **nothing mounts a `middleware/` directory and the engine has no
674
- middleware pipeline** a handler written against it deploys, never runs, and
675
- nothing reports it. There is no `ctx` object anywhere on a path that executes, so
676
- treat the export as a leftover rather than a seam.
673
+ `defineMiddleware(async (ctx, next) => { … })` **throws**. Nothing mounts a
674
+ `middleware/` directory and the engine has no middleware pipeline, so a handler
675
+ written against it used to deploy and then never run — silently. The call now
676
+ refuses and names where the work belongs, rather than handing you a shell.
677
+
678
+ There is no `ctx` object anywhere on a path that executes.
677
679
 
678
680
  Cross-cutting work goes in a service the controllers call, and the route concerns
679
681
  that used to live in a wrapper are route options instead: `auth` on `@Controller`
@@ -881,7 +883,7 @@ and [events.md](./events.md) for worker/job/hook/webhook examples).
881
883
 
882
884
  ## Typed by default — `Database.tables`
883
885
 
884
- When you declare `db/schema.ts`, `Database.tables.<name>` is typed everywhere
886
+ When you declare a schema under `db/*.ts`, `Database.tables.<name>` is typed everywhere
885
887
  with no import and no generic. `insert` demands the right columns; rows come
886
888
  back typed; nullable columns are `T | null`. This is the path you should use:
887
889
 
@@ -1143,7 +1145,7 @@ default.
1143
1145
 
1144
1146
  The Postgres role it connects as is **`backend_authenticated`** (or
1145
1147
  `backend_anon` when there is no signed-in user) — not `authenticated`. You
1146
- rarely need to know that, because a policy declared in `db/schema.ts` is
1148
+ rarely need to know that, because a policy declared in `db/*.ts` is
1147
1149
  deployed targeting both. It matters in exactly one place: **hand-written
1148
1150
  `CREATE POLICY` SQL in a migration must name both roles**, or it applies to
1149
1151
  nothing your code does. See
@@ -1520,7 +1522,7 @@ Add a value instead, and let the old one die:
1520
1522
 
1521
1523
  ```sql
1522
1524
  -- 1. Add the new label. This IS safe while the previous release serves.
1523
- -- (Declare it in db/public.ts; the rail emits ALTER TYPE … ADD VALUE.)
1525
+ -- (Declare it in the schema file; the rail emits ALTER TYPE … ADD VALUE.)
1524
1526
  -- 2. Move the data:
1525
1527
  UPDATE posts SET status = 'review' WHERE status = 'onay';
1526
1528
  -- 3. Stop naming the old value in the next release.
@@ -1620,7 +1622,7 @@ of the schema, no generic, no cast:
1620
1622
  // services/room.service.ts — the layer that touches the database.
1621
1623
  import { Database } from "@palbase/backend";
1622
1624
 
1623
- type RoomsTable = typeof Database.tables.rooms; // typed from db/schema.ts
1625
+ type RoomsTable = typeof Database.tables.rooms; // typed from your db/*.ts
1624
1626
 
1625
1627
  export class RoomService {
1626
1628
  private readonly rooms: RoomsTable;
@@ -1804,7 +1806,9 @@ so they apply without the `acceptDataLoss` confirmation that column drops need.
1804
1806
 
1805
1807
  # Migrations
1806
1808
 
1807
- `db/schema.ts` is the single source of truth for your Postgres schema. You change
1809
+ Your `db/*.ts` files are the single source of truth for your Postgres schema one
1810
+ file per schema, `db/public.ts` in a fresh scaffold. (It was a single
1811
+ `db/schema.ts` until 2026-08-31.) You change
1808
1812
  the schema by editing that file — and **nothing generates a migration from it**.
1809
1813
  There is no diff-to-file step: the change is computed against the database *as it
1810
1814
  is right now*, by the stack that owns it, which is what lets it cover type
@@ -1825,7 +1829,7 @@ It is not where an ordinary column change goes any more.
1825
1829
  ## The workflow
1826
1830
 
1827
1831
  ```bash
1828
- # 1. Edit db/schema.ts (add a column, a table, a policy, …)
1832
+ # 1. Edit the schema file — db/public.ts in a fresh scaffold (add a column, a table, a policy, …)
1829
1833
 
1830
1834
  # 2. See what it would take. THIS APPLIES NOTHING.
1831
1835
  palbase db plan
@@ -1841,9 +1845,9 @@ palbase db apply
1841
1845
  palbase push
1842
1846
  ```
1843
1847
 
1844
- `palbase db plan` hands `db/schema.ts` to the local stack, which computes the plan
1848
+ `palbase db plan` hands your `db/*.ts` declarations to the local stack, which computes the plan
1845
1849
  against its own database and answers with what it would change. When there is
1846
- nothing to do it says `✓ the database matches db/schema.ts`. It writes nothing,
1850
+ nothing to do it says `✓ the database matches db/*.ts`. It writes nothing,
1847
1851
  ever — neither a file nor a row — so it is safe to run at any moment, including
1848
1852
  from a script (`--detailed-exitcode` exits 2 when the plan would change
1849
1853
  something, 0 when it is in sync).
@@ -1885,19 +1889,19 @@ Code and schema move at different speeds, so three things keep them together:
1885
1889
  validation the deploy runs, so a push that would produce a failed deploy is
1886
1890
  caught on your machine first. (Bypass with `git push --no-verify` — the server
1887
1891
  still gates it.)
1888
- - On deploy, the migrations run and Palbase then asserts `db/schema.ts` matches
1892
+ - On deploy, the migrations run and Palbase then asserts `db/*.ts` matches
1889
1893
  the live database. Unresolved drift **fails the deploy** and keeps the previous
1890
1894
  version live — a broken schema never goes out silently.
1891
1895
 
1892
1896
  ## Your schema change is not live anywhere you have not applied it
1893
1897
 
1894
1898
  The local stack and every Environment hold their own database, and editing
1895
- `db/schema.ts` changes none of them: `palbase db apply` is what moves the local
1899
+ Editing `db/*.ts` changes none of them: `palbase db apply` is what moves the local
1896
1900
  one, `palbase push` is what moves an Environment's. Until then the declaration is
1897
1901
  ahead of the tables.
1898
1902
 
1899
1903
  The TYPES move separately again. `palbase build` regenerates `palbase-env.d.ts`
1900
- from `db/schema.ts`, which is what types `Database.tables.<name>` in your
1904
+ from `db/*.ts`, which is what types `Database.tables.<name>` in your
1901
1905
  services — so after a schema edit, run it. (There is no second command for this:
1902
1906
  `build` regenerates everything derived, because one verb to remember is one verb
1903
1907
  to forget.) Typed and applied are independent, and knowing which one you are
@@ -1912,7 +1916,7 @@ trigger, a `CHECK` constraint, an `EXCLUDE` constraint, a composite/multi-column
1912
1916
  `UNIQUE`, an index (`CREATE INDEX`), or `CREATE EXTENSION` for an extension that
1913
1917
  isn't in the DSL allowlist (e.g. `btree_gist`). The DSL column builders cover
1914
1918
  single-column constraints only; everything above is hand-written SQL. Where the
1915
- DSL *can* model the table, keep `db/schema.ts` as the declared end-state and add
1919
+ DSL *can* model the table, keep `db/*.ts` as the declared end-state and add
1916
1920
  only the extra object in raw SQL so the drift gate passes (see below); a table
1917
1921
  the DSL can't model at all lives entirely in raw SQL.
1918
1922
 
@@ -1920,7 +1924,7 @@ Migrations run on the privileged DDL connection, so `CREATE EXTENSION` and
1920
1924
  `ALTER TABLE ... ADD CONSTRAINT` work. Example — race-free, DB-enforced
1921
1925
  double-booking prevention (no instructor in two overlapping sessions), which the
1922
1926
  DSL can't express. The `during` column is a `tstzrange` (no DSL builder), so the
1923
- whole table is created in raw SQL — it can't come from `db/schema.ts`:
1927
+ whole table is created in raw SQL — it can't come from `db/*.ts`:
1924
1928
 
1925
1929
  ```sql
1926
1930
  CREATE EXTENSION IF NOT EXISTS btree_gist;
@@ -1941,7 +1945,7 @@ different instructor in the same slot are accepted. No application-level check.
1941
1945
  ### Drift gate and raw-SQL objects
1942
1946
 
1943
1947
  The deploy-time drift gate only asserts that everything **declared** in
1944
- `db/schema.ts` exists in the live DB (declared ⊆ live), and it inspects tables,
1948
+ `db/*.ts` declares exists in the live DB (declared ⊆ live), and it inspects tables,
1945
1949
  columns, and column types **only** — not constraints or indexes. So a
1946
1950
  hand-written constraint or index is invisible to the gate, and a live object you
1947
1951
  never declared is tolerated (not drift, not dropped — it survives redeploys).
@@ -1949,12 +1953,12 @@ never declared is tolerated (not drift, not dropped — it survives redeploys).
1949
1953
  Two patterns follow from this:
1950
1954
 
1951
1955
  - **Extra constraint/index on a DSL-expressible table** — declare the table in
1952
- `db/schema.ts` and add **only** the constraint/index via the migration. The
1956
+ `db/*.ts` and add **only** the constraint/index via the migration. The
1953
1957
  base table stays modeled, the gate ignores the constraint, and `palbase db
1954
1958
  plan` stays clean.
1955
1959
  - **A whole object the DSL can't model** (like `instructor_sessions` above — its
1956
1960
  `tstzrange` column has no DSL builder) — the table lives entirely in raw SQL,
1957
- not in `db/schema.ts`. The deploy still succeeds, but `palbase db plan` will
1961
+ not in `db/*.ts`. The deploy still succeeds, but `palbase db plan` will
1958
1962
  list it as a ⚠ would-be-drop for the local database; that is expected, and the
1959
1963
  deploy-time gate does not block on it. Never approve that one.
1960
1964
 
@@ -1962,12 +1966,12 @@ Two patterns follow from this:
1962
1966
 
1963
1967
  There's nothing to put in existing rows, so do it in two migrations: first add it
1964
1968
  nullable (or with a default) and backfill, then a follow-up `ALTER ... SET NOT
1965
- NULL`. `db/schema.ts` describes the end state; the migrations describe how
1969
+ NULL`. `db/*.ts` describes the end state; the migrations describe how
1966
1970
  existing data gets there.
1967
1971
 
1968
1972
  ## Row-Level Security
1969
1973
 
1970
- Add `rls: true` + `policies: [policy(...)]` to a table in `db/schema.ts`; the
1974
+ Add `rls: true` + `policies: [policy(...)]` to a table in `db/*.ts`; the
1971
1975
  generated migration emits the `ENABLE ROW LEVEL SECURITY` + `CREATE POLICY` DDL.
1972
1976
  See [schema.md](./schema.md) for the column builders, the policy DSL, and typed
1973
1977
  `Database.tables.*` access.
@@ -2051,7 +2055,8 @@ const v = await Cache.get<{ hits: number }>("k"); // typed, null on miss
2051
2055
  await Cache.incr("counter");
2052
2056
  await Cache.del("k");
2053
2057
 
2054
- // Stampede-safe read-through: only one caller across all pods runs fn.
2058
+ // Stampede-safe read-through: concurrent callers wait on ONE execution of fn
2059
+ // and share its result, however many of them there are.
2055
2060
  const profile = await Cache.getOrSet("user:42", 300, async () => {
2056
2061
  return Database.findById("users", "42");
2057
2062
  });
@@ -1,6 +1,8 @@
1
1
  # Migrations
2
2
 
3
- `db/schema.ts` is the single source of truth for your Postgres schema. You change
3
+ Your `db/*.ts` files are the single source of truth for your Postgres schema one
4
+ file per schema, `db/public.ts` in a fresh scaffold. (It was a single
5
+ `db/schema.ts` until 2026-08-31.) You change
4
6
  the schema by editing that file — and **nothing generates a migration from it**.
5
7
  There is no diff-to-file step: the change is computed against the database *as it
6
8
  is right now*, by the stack that owns it, which is what lets it cover type
@@ -21,7 +23,7 @@ It is not where an ordinary column change goes any more.
21
23
  ## The workflow
22
24
 
23
25
  ```bash
24
- # 1. Edit db/schema.ts (add a column, a table, a policy, …)
26
+ # 1. Edit the schema file — db/public.ts in a fresh scaffold (add a column, a table, a policy, …)
25
27
 
26
28
  # 2. See what it would take. THIS APPLIES NOTHING.
27
29
  palbase db plan
@@ -37,9 +39,9 @@ palbase db apply
37
39
  palbase push
38
40
  ```
39
41
 
40
- `palbase db plan` hands `db/schema.ts` to the local stack, which computes the plan
42
+ `palbase db plan` hands your `db/*.ts` declarations to the local stack, which computes the plan
41
43
  against its own database and answers with what it would change. When there is
42
- nothing to do it says `✓ the database matches db/schema.ts`. It writes nothing,
44
+ nothing to do it says `✓ the database matches db/*.ts`. It writes nothing,
43
45
  ever — neither a file nor a row — so it is safe to run at any moment, including
44
46
  from a script (`--detailed-exitcode` exits 2 when the plan would change
45
47
  something, 0 when it is in sync).
@@ -81,19 +83,19 @@ Code and schema move at different speeds, so three things keep them together:
81
83
  validation the deploy runs, so a push that would produce a failed deploy is
82
84
  caught on your machine first. (Bypass with `git push --no-verify` — the server
83
85
  still gates it.)
84
- - On deploy, the migrations run and Palbase then asserts `db/schema.ts` matches
86
+ - On deploy, the migrations run and Palbase then asserts `db/*.ts` matches
85
87
  the live database. Unresolved drift **fails the deploy** and keeps the previous
86
88
  version live — a broken schema never goes out silently.
87
89
 
88
90
  ## Your schema change is not live anywhere you have not applied it
89
91
 
90
92
  The local stack and every Environment hold their own database, and editing
91
- `db/schema.ts` changes none of them: `palbase db apply` is what moves the local
93
+ Editing `db/*.ts` changes none of them: `palbase db apply` is what moves the local
92
94
  one, `palbase push` is what moves an Environment's. Until then the declaration is
93
95
  ahead of the tables.
94
96
 
95
97
  The TYPES move separately again. `palbase build` regenerates `palbase-env.d.ts`
96
- from `db/schema.ts`, which is what types `Database.tables.<name>` in your
98
+ from `db/*.ts`, which is what types `Database.tables.<name>` in your
97
99
  services — so after a schema edit, run it. (There is no second command for this:
98
100
  `build` regenerates everything derived, because one verb to remember is one verb
99
101
  to forget.) Typed and applied are independent, and knowing which one you are
@@ -108,7 +110,7 @@ trigger, a `CHECK` constraint, an `EXCLUDE` constraint, a composite/multi-column
108
110
  `UNIQUE`, an index (`CREATE INDEX`), or `CREATE EXTENSION` for an extension that
109
111
  isn't in the DSL allowlist (e.g. `btree_gist`). The DSL column builders cover
110
112
  single-column constraints only; everything above is hand-written SQL. Where the
111
- DSL *can* model the table, keep `db/schema.ts` as the declared end-state and add
113
+ DSL *can* model the table, keep `db/*.ts` as the declared end-state and add
112
114
  only the extra object in raw SQL so the drift gate passes (see below); a table
113
115
  the DSL can't model at all lives entirely in raw SQL.
114
116
 
@@ -116,7 +118,7 @@ Migrations run on the privileged DDL connection, so `CREATE EXTENSION` and
116
118
  `ALTER TABLE ... ADD CONSTRAINT` work. Example — race-free, DB-enforced
117
119
  double-booking prevention (no instructor in two overlapping sessions), which the
118
120
  DSL can't express. The `during` column is a `tstzrange` (no DSL builder), so the
119
- whole table is created in raw SQL — it can't come from `db/schema.ts`:
121
+ whole table is created in raw SQL — it can't come from `db/*.ts`:
120
122
 
121
123
  ```sql
122
124
  CREATE EXTENSION IF NOT EXISTS btree_gist;
@@ -137,7 +139,7 @@ different instructor in the same slot are accepted. No application-level check.
137
139
  ### Drift gate and raw-SQL objects
138
140
 
139
141
  The deploy-time drift gate only asserts that everything **declared** in
140
- `db/schema.ts` exists in the live DB (declared ⊆ live), and it inspects tables,
142
+ `db/*.ts` declares exists in the live DB (declared ⊆ live), and it inspects tables,
141
143
  columns, and column types **only** — not constraints or indexes. So a
142
144
  hand-written constraint or index is invisible to the gate, and a live object you
143
145
  never declared is tolerated (not drift, not dropped — it survives redeploys).
@@ -145,12 +147,12 @@ never declared is tolerated (not drift, not dropped — it survives redeploys).
145
147
  Two patterns follow from this:
146
148
 
147
149
  - **Extra constraint/index on a DSL-expressible table** — declare the table in
148
- `db/schema.ts` and add **only** the constraint/index via the migration. The
150
+ `db/*.ts` and add **only** the constraint/index via the migration. The
149
151
  base table stays modeled, the gate ignores the constraint, and `palbase db
150
152
  plan` stays clean.
151
153
  - **A whole object the DSL can't model** (like `instructor_sessions` above — its
152
154
  `tstzrange` column has no DSL builder) — the table lives entirely in raw SQL,
153
- not in `db/schema.ts`. The deploy still succeeds, but `palbase db plan` will
155
+ not in `db/*.ts`. The deploy still succeeds, but `palbase db plan` will
154
156
  list it as a ⚠ would-be-drop for the local database; that is expected, and the
155
157
  deploy-time gate does not block on it. Never approve that one.
156
158
 
@@ -158,12 +160,12 @@ Two patterns follow from this:
158
160
 
159
161
  There's nothing to put in existing rows, so do it in two migrations: first add it
160
162
  nullable (or with a default) and backfill, then a follow-up `ALTER ... SET NOT
161
- NULL`. `db/schema.ts` describes the end state; the migrations describe how
163
+ NULL`. `db/*.ts` describes the end state; the migrations describe how
162
164
  existing data gets there.
163
165
 
164
166
  ## Row-Level Security
165
167
 
166
- Add `rls: true` + `policies: [policy(...)]` to a table in `db/schema.ts`; the
168
+ Add `rls: true` + `policies: [policy(...)]` to a table in `db/*.ts`; the
167
169
  generated migration emits the `ENABLE ROW LEVEL SECURITY` + `CREATE POLICY` DDL.
168
170
  See [schema.md](./schema.md) for the column builders, the policy DSL, and typed
169
171
  `Database.tables.*` access.
package/docs/schema.md CHANGED
@@ -318,7 +318,7 @@ Add a value instead, and let the old one die:
318
318
 
319
319
  ```sql
320
320
  -- 1. Add the new label. This IS safe while the previous release serves.
321
- -- (Declare it in db/public.ts; the rail emits ALTER TYPE … ADD VALUE.)
321
+ -- (Declare it in the schema file; the rail emits ALTER TYPE … ADD VALUE.)
322
322
  -- 2. Move the data:
323
323
  UPDATE posts SET status = 'review' WHERE status = 'onay';
324
324
  -- 3. Stop naming the old value in the next release.
@@ -418,7 +418,7 @@ of the schema, no generic, no cast:
418
418
  // services/room.service.ts — the layer that touches the database.
419
419
  import { Database } from "@palbase/backend";
420
420
 
421
- type RoomsTable = typeof Database.tables.rooms; // typed from db/schema.ts
421
+ type RoomsTable = typeof Database.tables.rooms; // typed from your db/*.ts
422
422
 
423
423
  export class RoomService {
424
424
  private readonly rooms: RoomsTable;
package/docs/services.md CHANGED
@@ -42,7 +42,8 @@ const v = await Cache.get<{ hits: number }>("k"); // typed, null on miss
42
42
  await Cache.incr("counter");
43
43
  await Cache.del("k");
44
44
 
45
- // Stampede-safe read-through: only one caller across all pods runs fn.
45
+ // Stampede-safe read-through: concurrent callers wait on ONE execution of fn
46
+ // and share its result, however many of them there are.
46
47
  const profile = await Cache.getOrSet("user:42", 300, async () => {
47
48
  return Database.findById("users", "42");
48
49
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@palbase/backend",
3
- "version": "25.0.1",
3
+ "version": "25.0.2",
4
4
  "description": "Palbase Backend SDK — class controllers (@Controller/@Get/@Post + @Body/@QueryParams/@Param), error classes, schema DSL",
5
5
  "license": "MIT",
6
6
  "repository": {