@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.
- package/dist/{chunk-HBOJLP2Z.js → chunk-7JSEN7UR.js} +2 -1
- package/dist/chunk-7JSEN7UR.js.map +1 -0
- package/dist/db/index.cjs.map +1 -1
- package/dist/db/index.d.cts +2 -2
- package/dist/db/index.d.ts +2 -2
- package/dist/db/index.js +1 -1
- package/dist/{endpoint-DMwJjEHQ.d.ts → endpoint-CTEHhb7A.d.ts} +19 -8
- package/dist/{endpoint-BSGw1pTu.d.cts → endpoint-DYHMo6cC.d.cts} +19 -8
- package/dist/engine/index.d.cts +4 -4
- package/dist/engine/index.d.ts +4 -4
- package/dist/{index-B8v6hVyU.d.ts → index-CUomTA3e.d.ts} +3 -3
- package/dist/{index-B4CcpqLb.d.ts → index-CW21M9Z3.d.ts} +1 -1
- package/dist/{index-VsjBQ4Kw.d.cts → index-ClpDeSos.d.cts} +3 -3
- package/dist/{index-DmVyY6N7.d.cts → index-CmBK76nx.d.cts} +1 -1
- package/dist/index.cjs +5 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -8
- package/dist/index.d.ts +9 -8
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/openapi/index.d.cts +2 -2
- package/dist/openapi/index.d.ts +2 -2
- package/dist/{registry-CgJvDYUW.d.cts → registry-CC0WBQq6.d.cts} +1 -1
- package/dist/{registry-B8pld3fj.d.ts → registry-dZZ5JKYg.d.ts} +1 -1
- package/dist/test/index.d.cts +1 -1
- package/dist/test/index.d.ts +1 -1
- package/docs/README.md +9 -8
- package/docs/database.md +2 -2
- package/docs/endpoints.md +6 -5
- package/docs/getting-started.md +1 -1
- package/docs/llms-full.txt +38 -33
- package/docs/migrations.md +16 -14
- package/docs/schema.md +2 -2
- package/docs/services.md +2 -1
- package/package.json +1 -1
- package/dist/chunk-HBOJLP2Z.js.map +0 -1
package/dist/openapi/index.d.cts
CHANGED
|
@@ -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-
|
|
4
|
-
import { P as ParamMeta, T as ThrowDescriptor, U as UploadConfig, S as SseConfig } from '../registry-
|
|
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
|
package/dist/openapi/index.d.ts
CHANGED
|
@@ -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-
|
|
4
|
-
import { P as ParamMeta, T as ThrowDescriptor, U as UploadConfig, S as SseConfig } from '../registry-
|
|
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
|
package/dist/test/index.d.cts
CHANGED
package/dist/test/index.d.ts
CHANGED
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
|
|
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
|
|
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/
|
|
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
|
|
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`
|
|
265
|
-
|
|
266
|
-
`ctx` anywhere on a path that runs
|
|
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
|
|
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
|
|
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
|
|
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) => { … })`
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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`
|
package/docs/getting-started.md
CHANGED
|
@@ -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
|
|
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.
|
package/docs/llms-full.txt
CHANGED
|
@@ -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
|
|
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
|
|
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/
|
|
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
|
|
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`
|
|
273
|
-
|
|
274
|
-
`ctx` anywhere on a path that runs
|
|
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
|
|
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
|
|
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) => { … })`
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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/
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
});
|
package/docs/migrations.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Migrations
|
|
2
2
|
|
|
3
|
-
`db
|
|
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/
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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