@palbase/backend 23.0.0 → 24.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/bin/palbase-backend.cjs +695 -61
  2. package/dist/bin/palbase-backend.cjs.map +1 -1
  3. package/dist/bin/palbase-backend.js +4 -5
  4. package/dist/bin/palbase-backend.js.map +1 -1
  5. package/dist/{chunk-FSGSB42K.js → chunk-7Z6MGMXQ.js} +64 -4
  6. package/dist/chunk-7Z6MGMXQ.js.map +1 -0
  7. package/dist/{chunk-OMRTHM4X.js → chunk-H3JAISUY.js} +136 -1
  8. package/dist/chunk-H3JAISUY.js.map +1 -0
  9. package/dist/{chunk-REZU6UKT.js → chunk-NXDH6VQJ.js} +549 -42
  10. package/dist/chunk-NXDH6VQJ.js.map +1 -0
  11. package/dist/{chunk-W5ODXPY3.js → chunk-P2Q27SGP.js} +32 -3
  12. package/dist/chunk-P2Q27SGP.js.map +1 -0
  13. package/dist/{chunk-ZC6Q2BRD.js → chunk-T5IOSOE5.js} +7 -2
  14. package/dist/chunk-T5IOSOE5.js.map +1 -0
  15. package/dist/{chunk-HAF67F2H.js → chunk-ZUGY7RGS.js} +86 -3
  16. package/dist/chunk-ZUGY7RGS.js.map +1 -0
  17. package/dist/db/index.cjs +115 -3
  18. package/dist/db/index.cjs.map +1 -1
  19. package/dist/db/index.d.cts +2 -2
  20. package/dist/db/index.d.ts +2 -2
  21. package/dist/db/index.js +2 -2
  22. package/dist/{endpoint-BavvbW4P.d.ts → endpoint-0_DGBajf.d.ts} +168 -9
  23. package/dist/{endpoint-i8TTCohk.d.cts → endpoint-CcQ1a36a.d.cts} +168 -9
  24. package/dist/engine/index.cjs +684 -48
  25. package/dist/engine/index.cjs.map +1 -1
  26. package/dist/engine/index.d.cts +4 -4
  27. package/dist/engine/index.d.ts +4 -4
  28. package/dist/engine/index.js +4 -4
  29. package/dist/{index-B3jmmItD.d.ts → index-CJiJU9ux.d.ts} +209 -36
  30. package/dist/{index-Bmvx1EvJ.d.cts → index-D-4-PNuQ.d.cts} +209 -36
  31. package/dist/{index-B7YBEG5w.d.ts → index-D17r-MKb.d.ts} +177 -7
  32. package/dist/{index-E7OscPJT.d.cts → index-DRFxf07H.d.cts} +177 -7
  33. package/dist/index.cjs +269 -7
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.d.cts +54 -12
  36. package/dist/index.d.ts +54 -12
  37. package/dist/index.js +54 -11
  38. package/dist/index.js.map +1 -1
  39. package/dist/openapi/index.cjs +45 -5
  40. package/dist/openapi/index.cjs.map +1 -1
  41. package/dist/openapi/index.d.cts +9 -4
  42. package/dist/openapi/index.d.ts +9 -4
  43. package/dist/openapi/index.js +35 -11
  44. package/dist/openapi/index.js.map +1 -1
  45. package/dist/{registry-C3H2uPeZ.d.cts → registry-1X-skBNu.d.cts} +101 -7
  46. package/dist/{registry-DY3d9l1k.d.ts → registry-CEod_5sz.d.ts} +101 -7
  47. package/dist/test/index.cjs +509 -9
  48. package/dist/test/index.cjs.map +1 -1
  49. package/dist/test/index.d.cts +35 -3
  50. package/dist/test/index.d.ts +35 -3
  51. package/dist/test/index.js +507 -8
  52. package/dist/test/index.js.map +1 -1
  53. package/docs/README.md +4 -4
  54. package/docs/database.md +115 -11
  55. package/docs/getting-started.md +5 -4
  56. package/docs/llms-full.txt +385 -89
  57. package/docs/migrations.md +81 -59
  58. package/docs/schema.md +82 -2
  59. package/docs/services.md +98 -9
  60. package/package.json +3 -2
  61. package/stager/return_types.js +23 -0
  62. package/template/AGENTS.md +121 -41
  63. package/template/controllers/notes.controller.ts +64 -0
  64. package/template/package.json +1 -1
  65. package/template/services/note.service.ts +74 -0
  66. package/template/tsconfig.json +11 -1
  67. package/dist/chunk-FSGSB42K.js.map +0 -1
  68. package/dist/chunk-HAF67F2H.js.map +0 -1
  69. package/dist/chunk-OMRTHM4X.js.map +0 -1
  70. package/dist/chunk-REZU6UKT.js.map +0 -1
  71. package/dist/chunk-W5ODXPY3.js.map +0 -1
  72. package/dist/chunk-Y5HXVUMP.js +0 -90
  73. package/dist/chunk-Y5HXVUMP.js.map +0 -1
  74. package/dist/chunk-ZC6Q2BRD.js.map +0 -1
@@ -18,10 +18,19 @@ import { Controller, Get, Post, Patch, Delete, Body, QueryParams, Param, User, z
18
18
 
19
19
  ```
20
20
  controllers/<name>.controller.ts a @Controller class; each method is a route
21
+ services/<name>.service.ts the logic, as a plain class
21
22
  db/schema.ts the database: tables, columns, RLS policies
22
- config/secrets.ts the secrets this backend needs (names only)
23
+ palbase-stack.d.ts GENERATED — the names your stack actually holds
24
+ palbase-env.d.ts GENERATED — your tables, as types
23
25
  ```
24
26
 
27
+ Both generated files are written by `palbase build` and **committed**. They are
28
+ what makes `Secrets.get("X")`, `Flags.isEnabled("y")`, `@Upload({ bucket })` and
29
+ `Database.tables.*` compile: the names come from the stack and the schema, so
30
+ there is no second list in this repo to drift out of date. A project that has
31
+ never run `palbase build` has no valid names and those calls will not compile —
32
+ that is the gate working, not a rough edge. Run it once and commit both files.
33
+
25
34
  Two folders appear as the project grows, and both are conventions the tooling
26
35
  already understands:
27
36
 
@@ -137,7 +146,18 @@ throw new TooManyRequests({ retryAfter: 30 }); // data-first
137
146
 
138
147
  Every route requires a signed-in user by default. `@Controller("/x", { auth: false })`
139
148
  opens all of a class's routes; `@Get("", { auth: true })` overrides it for one.
140
- Resolution is route → controller → default `true`.
149
+
150
+ An application-wide default sits under those two:
151
+
152
+ ```ts
153
+ import { defineDefaultAuth } from "@palbase/backend";
154
+ defineDefaultAuth({ verifiedEmail: true }); // every route, unless it says otherwise
155
+ ```
156
+
157
+ Put that call in `auth.ts` at the project root — the bundler imports that file by
158
+ name, so the declaration cannot be silently left out of the build. Resolution is
159
+ route → controller → application → `true`. Writing the same auth setting on ten
160
+ controllers is how the eleventh one gets forgotten.
141
161
 
142
162
  ## Models — schemas, one file per endpoint
143
163
 
@@ -155,27 +175,53 @@ Shared response schemas go in `models/<controller>/shared.ts`.
155
175
 
156
176
  ## Services — business logic
157
177
 
158
- Plain classes exported as a singleton. No DI container, no decorators.
178
+ Plain classes wired as a module-level singleton. No DI container, no decorators
179
+ — and this is the ONLY supported way to hold a dependency anywhere in this
180
+ runtime. A controller, hook, job or webhook is constructed with **no arguments**;
181
+ one that declares a constructor parameter is refused at build with the class
182
+ named, because there is no injector to fill it and the field would simply be
183
+ `undefined` in production.
159
184
 
160
- ```ts
161
- // services/todo.service.ts
162
- import { Database } from "@palbase/backend";
185
+ `services/note.service.ts` in this scaffold is the worked example. The shape:
163
186
 
164
- export class TodoService {
165
- list(userId: string) {
166
- return Database.tables.todos.findMany({ user_id: userId });
167
- }
168
- create(userId: string, title: string) {
169
- return Database.tables.todos.insert({ user_id: userId, title });
170
- }
187
+ ```ts
188
+ export class NoteService {
189
+ private readonly notes: typeof Database.tables.notes;
190
+ // Assigned in the BODY — `constructor(private notes: …)` is a parameter
191
+ // property, and Node's type-stripping test runner refuses those per FILE.
192
+ constructor(notes: typeof Database.tables.notes) { this.notes = notes; }
193
+ list(userId: string) { return this.notes.findMany({ user_id: userId }); }
171
194
  }
172
195
 
173
- export const todoService = new TodoService();
196
+ /** Controllers import THIS, never the class. */
197
+ export const noteService = new NoteService(Database.tables.notes);
174
198
  ```
175
199
 
176
- `Database.tables.<name>` gives `insert`, `update(id, data)`, `delete(id)`,
177
- `findById(id)`, `findMany(filter?)`, and `Database.transaction(fn)` for a plan
178
- that commits or rolls back as one.
200
+ The constructor is the seam: the class is handed the table rather than reaching
201
+ for the singleton, so a test constructs it with a stand-in and never needs a
202
+ database. `fakeDatabase()` from `@palbase/backend/test` is that stand-in when you
203
+ want the whole surface instead of one table.
204
+
205
+ `Database.tables.<name>` gives:
206
+
207
+ | | |
208
+ |---|---|
209
+ | `insert(row)` · `upsert(row, { onConflict })` | write one |
210
+ | `update(id, data)` · `delete(id)` | write one, by id |
211
+ | `updateMany(where, set)` · `deleteMany(where)` | write every row the filter matches, in ONE statement |
212
+ | `findById(id)` · `findMany(where?, opts?)` | read |
213
+ | `count(where?)` | how many match |
214
+
215
+ `where` takes a plain value for equality or an operator object —
216
+ `{ n: { gte: 3 } }`, `{ id: { in: [...] } }`, also `gt` / `lt` / `lte` / `neq`.
217
+ `opts` takes `{ orderBy: { column, direction }, limit, offset }`, so ordering and
218
+ paging happen in SQL rather than in JavaScript after fetching everything.
219
+ `updateMany`/`deleteMany` REFUSE an empty filter — a whole-table write is not
220
+ something to reach by accident.
221
+
222
+ `Database.transaction(fn)` runs a plan that commits or rolls back as one, and
223
+ `Database.attempt(fn)` opens a savepoint so one failed write does not poison the
224
+ rest of the request.
179
225
 
180
226
  ## The database — declared, not migrated
181
227
 
@@ -194,37 +240,62 @@ RLS is on by default and a table with no policies is deny-all. Add
194
240
  `policies: [policy(...)]` so Postgres enforces ownership — a query that forgets
195
241
  its `where user_id = …` still cannot read another user's rows.
196
242
 
197
- After a schema change, regenerate the typed surface so `Database.tables.*` keeps
198
- matching what is actually there (`palbase --help` names the command).
243
+ After a schema change run `palbase build`: it regenerates BOTH generated files —
244
+ `palbase-env.d.ts` (your tables) and `palbase-stack.d.ts` (the stack's names)
245
+ so `Database.tables.*` keeps matching what is actually there. Commit them.
199
246
 
200
- ## Config as code
247
+ ## Secrets, flags, buckets — read from the stack
201
248
 
202
- `config/*.ts` declares module configuration the same git-authoritative way the
203
- schema does, applied on deploy:
249
+ There is no `config/` directory and nothing to declare in this repo. Settings are
250
+ written to the STACK with the CLI, and the code READS them:
204
251
 
205
- - `config/secrets.ts` — `defineSecrets({ secrets: [secret("NAME", …)] })`
206
- - `config/storage.ts` `defineStorage(...)` buckets
207
- - `config/notifications.ts` `defineNotifications(...)` providers
208
- - `config/flags.ts` — `defineFlags(...)` feature flags
252
+ ```ts
253
+ import { Secrets, Flags } from "@palbase/backend";
254
+ const dsn = await Secrets.get("SENTRY_DSN"); // compiles only if the stack holds it
255
+ if (await Flags.isEnabled("newCheckout")) { … } // same
256
+ ```
257
+
258
+ The name in the quotes is checked by the compiler against `palbase-stack.d.ts`,
259
+ which `palbase build` generates from the stack. A secret's VALUE never enters
260
+ this repository, and you never read `process.env` for one.
261
+
262
+ Writing a setting is a CLI job, not a code job — `palbase --help` names the
263
+ commands (`secret`, `flags`, `storage`, `notifications`, `auth`). Settings have
264
+ one door; a second one is how the two come to disagree.
209
265
 
210
- ### Secrets
266
+ ## Lifecycle — for things that are expensive to build
211
267
 
212
- A secret's **value** never enters this repository. `config/secrets.ts` declares
213
- the NAME and whether it is required; the value lives in the environment's vault
214
- and reaches the process through `Secrets.get("NAME")`:
268
+ Most handlers need nothing here: the SDK's singletons are already pooled. But a
269
+ long-lived object you build yourself needs somewhere to close, and a process that
270
+ never closes one leaks it per deploy:
215
271
 
216
272
  ```ts
217
- import { Secrets } from "@palbase/backend";
218
- const dsn = await Secrets.get("SENTRY_DSN"); // string | null
273
+ import { onStart, onShutdown } from "@palbase/backend";
274
+
275
+ let client: SomeClient | undefined;
276
+ onStart("graph", () => { client = SomeClient.create({ maxPoolSize: 8 }); });
277
+ onShutdown("graph", async () => { await client?.close(); });
219
278
  ```
220
279
 
221
- Declaring it is what turns a missing secret into a refused push that names it,
222
- instead of a 500 on the first request that touches it. Never read
223
- `process.env` for one, and never commit a `.env`.
280
+ The NAME is the first argument, and it is what a failure is reported by — a stack
281
+ trace through an anonymous closure names nothing. `onStart` runs ONCE as the app
282
+ comes up, and a hook that throws refuses the boot rather than letting the app
283
+ serve traffic with a half-built dependency. `onShutdown` hooks run in reverse
284
+ order and are best-effort: one that throws is reported and the rest still run.
224
285
 
225
- Provider certs and keys are the same rule: author them with the guided CLI,
226
- which uploads the file to the vault and writes only the non-secret fields into
227
- `config/notifications.ts`.
286
+ **These hooks run OUTSIDE a request, so no Palbase service is reachable from
287
+ them** `Secrets`, `Database`, `Log` and the rest all throw "accessed outside a
288
+ request scope". If your object needs a secret, build it lazily on first use
289
+ inside a handler instead:
290
+
291
+ ```ts
292
+ let client: SomeClient | undefined;
293
+ async function graph() {
294
+ client ??= SomeClient.create({ url: await Secrets.get("GRAPH_URL") });
295
+ return client;
296
+ }
297
+ onShutdown("graph", async () => { await client?.close(); });
298
+ ```
228
299
 
229
300
  ## Tests
230
301
 
@@ -233,9 +304,18 @@ install and nothing to configure — Node discovers the files and strips the typ
233
304
  itself.
234
305
 
235
306
  Test the **service** layer. Controllers are thin by construction, so the logic
236
- that can be wrong lives in a service, which is a plain class you construct
237
- directly. A service that needs `Database` takes it as a constructor argument
238
- instead of importing the singleton, and the test passes a stand-in.
307
+ that can be wrong lives in a service a plain class you construct directly with
308
+ a stand-in, exactly as `services/note.service.ts` is written. For a stand-in
309
+ covering the whole database surface rather than one table:
310
+
311
+ ```ts
312
+ import { fakeDatabase } from "@palbase/backend/test";
313
+ const { db, queries } = fakeDatabase();
314
+ ```
315
+
316
+ It answers `tables.*` from memory and records the `query` calls made. What it
317
+ does NOT do is enforce RLS, constraints or unique violations — those are real
318
+ database behaviour, and the test that proves them runs against a real one.
239
319
 
240
320
  Node **strips** types, it never emits code, so TypeScript features that require
241
321
  emitted code are refused outright: parameter properties (`constructor(private x: T)`),
@@ -0,0 +1,64 @@
1
+ import { Body, Controller, Delete, Get, NotFound, Param, Post, User, z } from "@palbase/backend";
2
+ import type { UserT } from "@palbase/backend";
3
+
4
+ import { noteService } from "../services/note.service";
5
+
6
+ // The other half of the vertical that starts in `services/note.service.ts`.
7
+ //
8
+ // Everything a controller does here is HTTP: validate the body through a named
9
+ // schema, name the 200 shape as the return type, turn a missing row into a
10
+ // status. The logic — which rows, whose, in what order — lives in the service,
11
+ // which is why the service is the thing worth testing and this class has
12
+ // nothing in it to get wrong.
13
+ //
14
+ // So there is one rule, and it is the whole reason the layer exists: a
15
+ // controller does not import `Database`. If a method here starts reaching for a
16
+ // table, the logic has leaked into the layer that is hardest to test.
17
+ //
18
+ // The dependency is a MODULE-LEVEL SINGLETON, imported. It is not a constructor
19
+ // parameter: the runtime constructs this class with no arguments, and a
20
+ // controller that declares one is refused at build with the class named rather
21
+ // than handed `undefined` in production.
22
+
23
+ export const NoteSchema = z.object({
24
+ id: z.string(),
25
+ user_id: z.string(),
26
+ body: z.string(),
27
+ created_at: z.string(),
28
+ });
29
+ export type NoteSchema = z.infer<typeof NoteSchema>;
30
+
31
+ export const CreateNoteBody = z.object({
32
+ body: z.string().min(1),
33
+ });
34
+ export type CreateNoteBody = z.infer<typeof CreateNoteBody>;
35
+
36
+ // Auth is required unless a route opts out, so `@User()` is non-null here and
37
+ // every method below acts as somebody.
38
+ @Controller("/notes")
39
+ class NotesController {
40
+ @Get("")
41
+ list(@User() user: UserT): Promise<NoteSchema[]> {
42
+ return noteService.list(user.id);
43
+ }
44
+
45
+ @Post("")
46
+ create(@Body(CreateNoteBody) body: CreateNoteBody, @User() user: UserT): Promise<NoteSchema> {
47
+ return noteService.create(user.id, body.body);
48
+ }
49
+
50
+ @Get("/{id}")
51
+ async get(@Param("id") id: string): Promise<NoteSchema> {
52
+ // `null` covers both "no such note" and "not yours" — the second is not
53
+ // distinguishable from the outside on purpose, because telling a caller
54
+ // that a row they may not read exists is itself a leak.
55
+ const note = await noteService.get(id);
56
+ if (!note) throw new NotFound("no note with that id");
57
+ return note;
58
+ }
59
+
60
+ @Delete("/{id}")
61
+ remove(@Param("id") id: string): Promise<void> {
62
+ return noteService.remove(id);
63
+ }
64
+ }
@@ -9,7 +9,7 @@
9
9
  "typecheck": "tsc --noEmit"
10
10
  },
11
11
  "dependencies": {
12
- "@palbase/backend": "^23.0.0"
12
+ "@palbase/backend": "^24.0.0"
13
13
  },
14
14
  "devDependencies": {
15
15
  "@types/node": "^22",
@@ -0,0 +1,74 @@
1
+ import { Database } from "@palbase/backend";
2
+ import type { Tables } from "@palbase/backend/env";
3
+
4
+ // A SERVICE is where the logic lives. It is a plain class — no decorator, no DI
5
+ // container, nothing to register. What makes the layer real is the two lines at
6
+ // the bottom of this file, not the folder name.
7
+ //
8
+ // THE SEAM IS THE CONSTRUCTOR. The class is handed the table it works on rather
9
+ // than reaching for the singleton itself, so a test constructs it with a
10
+ // stand-in and never needs a database:
11
+ //
12
+ // const svc = new NoteService(fakeNotesTable);
13
+ // assert.deepEqual(await svc.list("u_1"), []);
14
+ //
15
+ // The module then wires the real one ONCE, on the last line, and that instance
16
+ // is what a controller imports. That module-level singleton is also the
17
+ // supported way to hold a dependency ANYWHERE in this runtime: a controller,
18
+ // hook, job or webhook is constructed with no arguments, and one that declares
19
+ // a constructor parameter is refused at build with the class named — there is
20
+ // no injector to fill it, so the field would simply be `undefined` in
21
+ // production.
22
+ //
23
+ // `Database.tables.notes` is typed from `db/schema.ts` through the generated
24
+ // `palbase-env.d.ts`, which `palbase build` writes. Before the first build that
25
+ // file does not exist yet and the table is unknown to the type checker; build
26
+ // once and the whole surface below is typed with no import and no generic.
27
+
28
+ /** One row of `notes`, exactly as `db/schema.ts` declares it. */
29
+ export type Note = Tables["notes"]["row"];
30
+
31
+ /** The typed surface of one table: `insert`, `update`, `delete`, `findById`,
32
+ * `findMany`. Naming it here is what keeps the seam ONE table wide — a test
33
+ * fake implements five methods, not the whole `Database`. */
34
+ type NotesTable = typeof Database.tables.notes;
35
+
36
+ export class NoteService {
37
+ private readonly notes: NotesTable;
38
+
39
+ // Assigned in the BODY, never as a parameter property (`constructor(private
40
+ // notes: NotesTable)`). `npm test` runs Node's type-stripping test runner,
41
+ // which refuses parameter properties — and refuses them for the whole FILE,
42
+ // so one of them anywhere in a test's import graph reads like a dozen broken
43
+ // tests and is one keyword.
44
+ constructor(notes: NotesTable) {
45
+ this.notes = notes;
46
+ }
47
+
48
+ /** The caller's notes. The filter is written out even though the table's RLS
49
+ * policy already scopes the read to `auth.uid()`: the policy is the backstop
50
+ * that holds when a query forgets, not a reason to stop writing the query. */
51
+ list(userId: string): Promise<Note[]> {
52
+ return this.notes.findMany({ user_id: userId });
53
+ }
54
+
55
+ /** `user_id` is `notNull` with no default, so ownership is written here —
56
+ * it is not something the request body may carry. */
57
+ create(userId: string, body: string): Promise<Note> {
58
+ return this.notes.insert({ user_id: userId, body });
59
+ }
60
+
61
+ /** No `userId` argument, and that is not an oversight: another user's note is
62
+ * invisible to this read, so it comes back `null` exactly as a missing id
63
+ * does. Postgres enforces it, not this method. */
64
+ get(id: string): Promise<Note | null> {
65
+ return this.notes.findById(id);
66
+ }
67
+
68
+ remove(id: string): Promise<void> {
69
+ return this.notes.delete(id);
70
+ }
71
+ }
72
+
73
+ /** The wired instance. Controllers import THIS, never the class. */
74
+ export const noteService = new NoteService(Database.tables.notes);
@@ -19,12 +19,22 @@
19
19
  // it, so imports elsewhere stay extensionless.
20
20
  "allowImportingTsExtensions": true
21
21
  },
22
+ // Every directory the deploy path compiles has to be in here, or a file in it
23
+ // is type-checked by nothing until it is already running: `jobs/`, `hooks/`
24
+ // and `webhooks/` are read straight off disk by the bundler (one default-
25
+ // exported class per file), and `middleware/` only reaches a build through
26
+ // whichever controller imports it. `config/` is gone — 23.0.0 removed the
27
+ // declarations it held, and the settings it used to declare are written to the
28
+ // stack with the CLI.
22
29
  "include": [
23
30
  "controllers/**/*.ts",
24
31
  "models/**/*.ts",
25
32
  "services/**/*.ts",
26
- "config/**/*.ts",
27
33
  "db/**/*.ts",
34
+ "jobs/**/*.ts",
35
+ "hooks/**/*.ts",
36
+ "webhooks/**/*.ts",
37
+ "middleware/**/*.ts",
28
38
  "*.d.ts"
29
39
  ]
30
40
  }
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/runtime.ts"],"sourcesContent":["/**\n * runtime.ts — request-scoped service singletons.\n *\n * The backend SDK no longer threads a `ctx` god-object through every handler.\n * Instead, controller methods import PascalCase service singletons directly:\n *\n * import { Controller, Post, Body, Database } from \"@palbase/backend\";\n *\n * \\@Controller(\"/todos\")\n * export default class TodosController {\n * \\@Post(\"\") create(\\@Body(CreateTodoBody) body: CreateTodoBody): unknown {\n * return Database.insert(\"todos\", { title: body.title });\n * }\n * }\n *\n * The singletons are thin Proxies. Every property access forwards to the live\n * client for the CURRENT request scope, resolved through {@link __getRuntime}.\n *\n * # Request-scope resolution (persistent app-server)\n *\n * The runtime is a long-running Node process that serves many concurrent\n * requests on one event loop (NOT a fresh subprocess per request). A single\n * module-global slot would let one in-flight request's services bleed into\n * another's. So the services are carried in an {@link AsyncLocalStorage} store\n * ({@link __requestALS}) that the runtime sets per request with\n * {@link __runWithRuntime}; every async continuation of that request reads its\n * own store. `__getRuntime` reads the ALS store first; the module-global slot\n * (set by {@link __setRuntime}) is only a fallback for callers that run OUTSIDE\n * an ALS scope (dev-server, unit tests, the legacy single-shot path). Because\n * each `br-<ref>` pod is single-tenant, there is no cross-tenant leakage; the\n * ALS store is what prevents cross-REQUEST leakage within the shared process.\n *\n * The seam that makes `import { Database } from \"@palbase/backend\"` resolve to\n * the runtime-injected client: `@palbase/backend` is marked esbuild-EXTERNAL\n * when the tenant bundle is built, and the package is installed globally in the\n * pod (NODE_PATH=/usr/local/lib/node_modules). So worker.js's\n * `require('@palbase/backend')` and the bundle's `import` resolve to ONE shared\n * module instance — the ALS store and `__setRuntime` slot on that instance are\n * visible to the singletons the bundle imported.\n */\n\nimport type { PalbaseFlagKey } from \"./stack.js\";\nimport type { Buckets, BucketTypes } from \"./db/env.js\";\nimport { AsyncLocalStorage } from \"node:async_hooks\";\n\nimport type {\n DBClient,\n DBOps,\n CacheClient,\n Logger,\n PalbaseDocsClient,\n SecretsService,\n} from \"./endpoint.js\";\nimport type {\n PalbaseStorageClient,\n PalbaseBucketClient,\n PalbaseNotificationsClient,\n PalbaseFlagsClient,\n PalbaseFlagsServiceClient,\n PalbaseFlagContext,\n PalbaseFlagVariant,\n PalbaseFlag,\n PalbaseFlagValue,\n PalbaseSetOverrideResult,\n PalbaseRealtimeClient,\n} from \"./clients.js\";\nimport type { PalbaseResult } from \"./endpoint.js\";\nimport type {\n EnvTypedDatabase,\n EnvServiceDatabase,\n EnvTables,\n TxPlan,\n TxTables,\n} from \"./db/typed-db.js\";\nimport type { Materialized } from \"./db/tx-plan.js\";\nimport { TxPlanBuilder, runTxPlan } from \"./db/tx-plan.js\";\n\n/** The set of live clients the runtime injects per request scope.\n *\n * Realtime is BROADCAST-ONLY here (a stateless handler can push an event but\n * cannot hold a subscription socket — `subscribe()` lives on the client SDK).\n *\n * EXCLUDED on purpose: Functions, Links, Analytics, Auth. They are not\n * exposed as backend handler singletons (auth lives on the client SDK; the rest\n * are out of scope for backend endpoints). */\nexport interface RuntimeServices {\n Database: DBClient;\n Secrets: SecretsService;\n Documents: PalbaseDocsClient;\n Storage: PalbaseStorageClient;\n Cache: CacheClient;\n Log: Logger;\n Notifications: PalbaseNotificationsClient;\n Flags: PalbaseFlagsClient;\n Realtime: PalbaseRealtimeClient;\n}\n\n/**\n * The per-request ALS box.\n *\n * `runtime` is the service bundle. `userId` is the request's authenticated user\n * id, written by the runtime immediately after it assembles the request object\n * (worker.js: `requestALS.getStore().userId = pbReq.user?.id || null`) and\n * `null` on an anonymous request. It was already being written there for the\n * Flags client's auto-bind; declaring it here makes the existing contract typed\n * instead of implicit, which is what lets `currentSubjectId()` read a\n * server-owned identity rather than trusting anything the caller sent.\n */\nexport interface RequestStore {\n runtime: RuntimeServices;\n userId?: string | null;\n /** This request's id (`req_…`), written by the runtime. Used as the spend\n * idempotency scope when the caller supplied no `Idempotency-Key`. */\n requestId?: string;\n /** The caller's `Idempotency-Key` header, or `null` when absent. What makes a\n * client's retry replay its first result instead of charging twice. */\n idempotencyKey?: string | null;\n}\n\n/**\n * Per-request store. The persistent runtime runs each request inside\n * {@link __runWithRuntime}, so every async continuation of that request reads\n * its OWN `runtime` (and any other request-scoped fields the runtime adds).\n *\n * Exported with a `__` prefix so the runtime (worker.js) shares the SAME ALS\n * instance across the one module instance — two ALS instances would silently\n * not see each other's stores. NOT part of the public author-facing API.\n */\nexport const __requestALS = new AsyncLocalStorage<RequestStore>();\n\n/** Process-global fallback slot. Used only OUTSIDE an ALS scope (dev-server,\n * unit tests, legacy single-shot worker). Inside the persistent server every\n * request runs in {@link __requestALS}, which takes precedence. */\nlet runtime: RuntimeServices | null = null;\n\n/** Install the live clients in the process-global fallback slot.\n *\n * Persistent-server requests should use {@link __runWithRuntime} instead; this\n * remains for dev-server / tests / the legacy single-shot path that run without\n * an ALS scope. NOT part of the public author-facing API. */\nexport function __setRuntime(services: RuntimeServices): void {\n runtime = services;\n}\n\n/** Run `fn` with `services` bound as the request-scoped runtime.\n *\n * The persistent worker calls this once per request so concurrent requests\n * never share a services slot. NOT part of the public author-facing API. */\nexport function __runWithRuntime<T>(services: RuntimeServices, fn: () => T): T {\n return __requestALS.run({ runtime: services }, fn);\n}\n\n/** Read the live clients, throwing if accessed outside a request scope.\n *\n * Resolves the ALS store first (persistent server, per-request), then the\n * process-global fallback (dev-server / tests). NOT part of the public\n * author-facing API — used by the runtime and the singleton Proxies. */\nexport function __getRuntime(): RuntimeServices {\n const scoped = __requestALS.getStore();\n if (scoped) return scoped.runtime;\n if (runtime === null) {\n throw new Error(\n \"Palbase services accessed outside a request scope. The Database/Documents/… \" +\n \"singletons are only available inside an endpoint handler (or after the \" +\n \"runtime has called __runWithRuntime / __setRuntime).\",\n );\n }\n return runtime;\n}\n\n/**\n * Build a Proxy singleton that forwards every property access to the live\n * client named `key` on the current runtime.\n *\n * The single `as RuntimeServices[K]` is the only contained cast in the surface:\n * `Reflect.get` on a typed object returns `unknown` for a `string | symbol`\n * key, but `prop` is constrained to keys of the client interface at the call\n * sites (the exported singletons are typed below), so the forward is sound.\n */\nfunction makeServiceProxy<K extends keyof RuntimeServices>(key: K): RuntimeServices[K] {\n const handler: ProxyHandler<RuntimeServices[K]> = {\n get(_target, prop, receiver) {\n const client = __getRuntime()[key];\n const value = Reflect.get(client as object, prop, receiver) as unknown;\n // Bind methods to their owning client so `this` stays correct when the\n // author destructures or calls `Database.query(...)`.\n return typeof value === \"function\" ? value.bind(client) : value;\n },\n };\n // The Proxy target is irrelevant (all access goes through `get`); the cast\n // names the surface type the singleton presents to authors.\n return new Proxy({} as RuntimeServices[K], handler);\n}\n\n/**\n * Build the `.tables` accessor for the top-level `Database`. Each\n * `tables.<name>` access returns a small object that forwards the five CRUD ops\n * to the underlying client using `name` as the string table identifier. The\n * shapes are typed against the generated `palbase-env.d.ts` (`EnvTables`); at\n * runtime they are plain string-keyed calls, so no schema value is needed here.\n *\n * Returns `EnvTables` — TS cannot infer the mapped type through the Proxy, so\n * a single structural narrowing names the surface (the proxy returns a\n * correctly-shaped accessor for whatever string member is read).\n */\n/** T018 (C-8): similar/recommend'in string-keyed yüzü. DBOps'a (endpoint.ts)\n * BİLEREK eklenmedi — search-param imza üçlüsü (engine/db + typed-db +\n * endpoint) büyümesin: proxy dispatch runtime'da engine ops'una zaten ulaşır,\n * derleme güvenliğini typed yüzey (EnvTypedTable) verir. */\ninterface RecoOps {\n similar(table: string, id: string, params?: Record<string, unknown>): Promise<Record<string, unknown>[]>;\n recommend(table: string, params: Record<string, unknown>): Promise<Record<string, unknown>[]>;\n}\n\nfunction makeTablesAccessor(ops: () => DBOps & RecoOps): EnvTables {\n const tablesProxy = new Proxy(\n {},\n {\n get(_t, prop: string | symbol) {\n if (typeof prop !== \"string\") return undefined;\n const name = prop;\n return {\n insert: (data: Record<string, unknown>) => ops().insert(name, data),\n update: (id: string, data: Record<string, unknown>) => ops().update(name, id, data),\n delete: (id: string) => ops().delete(name, id),\n findById: (id: string) => ops().findById(name, id),\n findMany: (query?: Record<string, unknown>) => ops().findMany(name, query),\n search: (params?: Record<string, unknown>) => ops().search(name, params),\n similar: (id: string, params?: Record<string, unknown>) => ops().similar(name, id, params),\n recommend: (params: Record<string, unknown>) => ops().recommend(name, params),\n supersede: (id: string, row: Record<string, unknown>) => ops().supersede(name, id, row),\n };\n },\n },\n );\n return tablesProxy as EnvTables;\n}\n\n/** The raw string-keyed `DBClient` for the current request scope. */\nconst rawDatabase: DBClient = makeServiceProxy(\"Database\");\n\n/**\n * Wrap a raw `DBClient` into the typed `{ ...ops, tables, transaction }`\n * surface. The five string ops forward straight through; `tables` is the\n * env-typed accessor; `transaction` yields typed tables. Reused for both the\n * default (RLS-enforced) `Database` and the `asService()` sibling — each is\n * fed its own raw client (the default proxy vs `rawDatabase.asService()`).\n *\n * The `satisfies` pins the op surface so a missing/renamed op is a compile\n * error; the assembled object carries `tables`/`transaction` alongside.\n */\nfunction makeTypedSurface(raw: Omit<DBClient, \"asService\">): EnvServiceDatabase {\n // Proxy dispatch her üyeyi taşır; RecoOps tipi DBClient'a eklenmediğinden\n // (yukarıdaki karar) similar/recommend erişimi bu daraltmadan geçer.\n const reco = raw as Omit<DBClient, \"asService\"> & RecoOps;\n const ops = {\n query: (sql: string, params?: unknown[]) => raw.query(sql, params),\n insert: (table: string, data: Record<string, unknown>) => raw.insert(table, data),\n update: (table: string, id: string, data: Record<string, unknown>) =>\n raw.update(table, id, data),\n delete: (table: string, id: string) => raw.delete(table, id),\n findById: (table: string, id: string) => raw.findById(table, id),\n findMany: (table: string, query?: Record<string, unknown>) => raw.findMany(table, query),\n search: (table: string, params?: Record<string, unknown>) => raw.search(table, params),\n similar: (table: string, id: string, params?: Record<string, unknown>) =>\n reco.similar(table, id, params),\n recommend: (table: string, params: Record<string, unknown>) => reco.recommend(table, params),\n supersede: (table: string, id: string, row: Record<string, unknown>) =>\n raw.supersede(table, id, row),\n } satisfies DBOps & RecoOps;\n return Object.assign(ops, {\n tables: makeTablesAccessor(() => reco),\n transaction<T>(\n fn: (tx: TxPlan) => T extends Promise<unknown> ? never : T,\n ): Promise<Materialized<T>> {\n // A FRESH builder per call: it holds this transaction's ops and its error\n // slot table, and the runtime serves concurrent requests on one event loop.\n // A shared builder would splice one request's writes into another's plan.\n const builder = new TxPlanBuilder();\n return runTxPlan(raw, makeTxTablesAccessor(builder), builder, fn) as Promise<\n Materialized<T>\n >;\n },\n });\n}\n\n/**\n * The transaction twin of {@link makeTablesAccessor}: `tables.<name>` yields the\n * table's PLAN operations, recorded into `builder` instead of sent one by one.\n *\n * Same Proxy shape and same reason for the single narrowing — TS cannot infer a\n * mapped type through a Proxy, so the cast names what the trap returns.\n */\nfunction makeTxTablesAccessor(builder: TxPlanBuilder): TxTables {\n const tablesProxy = new Proxy(\n {},\n {\n get(_t, prop: string | symbol) {\n if (typeof prop !== \"string\") return undefined;\n return builder.table(prop);\n },\n },\n );\n return tablesProxy as TxTables;\n}\n\n/**\n * The project's own Postgres (pgx, schema `env_<envId>`).\n *\n * Typed by default: `Database.tables.<name>.insert({...})` is typed against\n * the project's generated `palbase-env.d.ts` with NO import and NO generic.\n * The raw string ops (`query`/`insert`/`update`/`delete`/`findById`/`findMany`)\n * are also available for dynamic table names and read-only SQL.\n *\n * RLS is enforced by default (the runtime runs each op as `authenticated` with\n * the verified user's claims). To bypass RLS, call `Database.asService()` —\n * explicit and greppable — which runs as the `service_role` (BYPASSRLS).\n *\n * @example\n * import { Database } from \"@palbase/backend\";\n *\n * const todo = await Database.tables.todos.insert({ title: req.input.title });\n * todo.id; // string ✓\n * const rows = await Database.query(\"SELECT id FROM todos WHERE done = $1\", [false]);\n * const all = await Database.asService().tables.todos.findMany({}); // RLS bypass\n */\nexport const Database: EnvTypedDatabase = Object.assign(makeTypedSurface(rawDatabase), {\n /**\n * Lazily resolve the runtime's service-role sibling on each call. We do NOT\n * cache it: `rawDatabase.asService()` reads the CURRENT request scope through\n * the runtime proxy, and the per-request runtime injects a service client\n * bound to that request's identity headers — caching would leak one request's\n * sibling into another concurrent request.\n */\n asService(): EnvServiceDatabase {\n return makeTypedSurface(rawDatabase.asService());\n },\n});\n\n/** Firestore-like document client (PalDocs). */\nexport const Documents: PalbaseDocsClient = makeServiceProxy(\"Documents\");\n\n/**\n * `buckets.<name>` — the storage twin of `Database.tables.<name>`, and the same\n * mechanism: `config/storage.ts` generates a `Buckets` augmentation into\n * `palbase-env.d.ts`, so a bucket name is a property with no import and no\n * generic, and a typo is a compile error.\n *\n * The intermediate `.buckets` is there for the reason `.tables` is: bucket\n * names must not share a namespace with the client's own methods, or the day\n * somebody declares a bucket called `bucket` the surface breaks.\n */\nexport type EnvBuckets = {\n [K in keyof Buckets]: TypedBucketClient<Buckets[K] extends BucketTypes ? Buckets[K] : BucketTypes>;\n};\n\n/** One bucket, with its declared variant names in the type. */\nexport interface TypedBucketClient<B extends BucketTypes> extends Omit<PalbaseBucketClient, \"getPublicUrl\"> {\n /** The public URL for the object, or for one of THIS bucket's declared\n * renditions. A name the bucket does not declare is a compile error. */\n getPublicUrl(path: string, options?: { variant?: B[\"variants\"] }): string;\n}\n\nfunction makeBucketsAccessor(storage: () => PalbaseStorageClient): EnvBuckets {\n return new Proxy(\n {},\n {\n get(_t, prop: string | symbol) {\n if (typeof prop !== \"string\") return undefined;\n return storage().bucket(prop);\n },\n },\n ) as EnvBuckets;\n}\n\nconst rawStorage: PalbaseStorageClient = makeServiceProxy(\"Storage\");\n\n/**\n * Object storage: buckets, objects, renditions and signed URLs.\n *\n * `Storage.buckets.posts.upload(...)` is the typed path. `Storage.bucket(name)`\n * remains for a name computed at runtime — rare, and it gives up the typing,\n * which is the honest trade rather than a second blessed way to do it.\n */\nexport const Storage: PalbaseStorageClient & { buckets: EnvBuckets } = Object.assign(\n {\n // FORWARDED explicitly, not assigned onto the service proxy.\n //\n // `Object.assign(rawStorage, {buckets})` writes onto the proxy's TARGET, and\n // the proxy's only trap is `get`, which forwards every read to the module\n // client — so the property landed somewhere nothing reads and\n // `Storage.buckets.docs` was `undefined` in a deployed handler. It\n // typechecked, every test passed, and the live call answered\n // \"TypeError: undefined is not an object\". `Database` never had the bug\n // because it builds a plain surface the same way this now does.\n bucket: (name: string) => rawStorage.bucket(name),\n },\n { buckets: makeBucketsAccessor(() => rawStorage) },\n);\n\n/** JSON-typed cache (get/set/incr/getOrSet). */\nexport const Cache: CacheClient = makeServiceProxy(\"Cache\");\n\n\n/**\n * This tenant's secrets — API keys, provider credentials, signing material the\n * BACKEND owns.\n *\n * `await Secrets.get(\"STRIPE_KEY\")` in a handler. There is no `.env` to read\n * and no file to mount: a secret is written through the vault's authenticated\n * API (or the CLI/MCP that calls it), and this is how the deployed code reads\n * it back. A name this tenant has not set answers null — as does every name\n * the STACK holds, because no route returns a platform secret's value at all.\n */\nexport const Secrets: SecretsService = makeServiceProxy(\"Secrets\");\n\n/** Structured logger. */\nexport const Log: Logger = makeServiceProxy(\"Log\");\n\n/** Push / email / SMS / in-app notifications. */\nexport const Notifications: PalbaseNotificationsClient = makeServiceProxy(\"Notifications\");\n\n\n/**\n * The raw runtime Flags client for the current request scope. Carries the\n * default-surface reads + `setOverride` AND the runtime's `asService()` sibling\n * (the br-pod's `buildFlagsClient` returns both). The default `Flags` singleton\n * below forwards reads + `setOverride` through here; `Flags.asService()`\n * forwards to this client's own `asService()`.\n */\nconst rawFlags: PalbaseFlagsClient = makeServiceProxy(\"Flags\");\n\n/**\n * Feature flags.\n *\n * Mirrors the `Database` / `Database.asService()` model. The default surface is\n * RLS-equivalent for flags: reads resolve against the CURRENT request user and\n * `Flags.setOverride(key, value)` writes an override for that same signed-in\n * user (no userId argument, no admin power). Cross-user admin writes\n * (`setOverrideForUser`, …) live behind `Flags.asService()` — explicit and\n * greppable, just like `Database.asService()`.\n *\n * @example\n * import { Flags } from \"@palbase/backend\";\n *\n * if (await Flags.isEnabled(\"new_checkout\")) { ... } // current user\n * await Flags.setOverride(\"new_checkout\", true); // current user\n * await Flags.asService().setOverrideForUser(\"u_9\", \"x\", true); // cross-user\n */\nexport const Flags: PalbaseFlagsClient = Object.assign(\n {\n isEnabled(\n flagName: PalbaseFlagKey,\n context?: PalbaseFlagContext,\n ): Promise<PalbaseResult<boolean>> {\n return rawFlags.isEnabled(flagName, context);\n },\n getVariant(\n flagName: PalbaseFlagKey,\n context?: PalbaseFlagContext,\n ): Promise<PalbaseResult<PalbaseFlagVariant>> {\n return rawFlags.getVariant(flagName, context);\n },\n getAll(context?: PalbaseFlagContext): Promise<PalbaseResult<PalbaseFlag[]>> {\n return rawFlags.getAll(context);\n },\n /**\n * Resolve a flag's value, with an optional fallback.\n *\n * FORWARDED as of 2026-08-15. This surface is written out by hand, method\n * by method, and `get` was missing from it — so the client implemented it,\n * thirty assertions covered it, and `Flags.get(\"x\")` was `undefined` in a\n * deployed handler. Exactly the shape of the `Storage.buckets` defect found\n * the same day: a hand-maintained forwarding list is a list somebody has to\n * remember to update.\n */\n get(\n flagName: PalbaseFlagKey,\n defaultOrContext?: PalbaseFlagValue | PalbaseFlagContext,\n maybeContext?: PalbaseFlagContext,\n ): Promise<PalbaseResult<PalbaseFlagValue>> {\n return rawFlags.get(flagName, defaultOrContext, maybeContext);\n },\n setOverride(\n key: PalbaseFlagKey,\n value: PalbaseFlagValue,\n ): Promise<PalbaseResult<PalbaseSetOverrideResult>> {\n return rawFlags.setOverride(key, value);\n },\n },\n {\n /**\n * Lazily resolve the runtime's cross-user sibling on each call. We do NOT\n * cache it: `rawFlags.asService()` reads the CURRENT request scope through\n * the runtime proxy, so caching would leak one request's sibling into\n * another concurrent request. Mirrors `Database.asService()`.\n */\n asService(): PalbaseFlagsServiceClient {\n return rawFlags.asService();\n },\n },\n);\n\n/**\n * The Realtime broadcast singleton for the current request scope. Backend-side\n * Realtime is BROADCAST-ONLY (a stateless handler can push but not subscribe —\n * `subscribe()` lives on the client SDK's `pb.realtime`). Fire-and-forget:\n * `broadcast` resolves once accepted (or with an `error`), never blocking the\n * handler on subscribers.\n *\n * @example\n * import { Realtime } from \"@palbase/backend\";\n *\n * await Realtime.broadcast(\"room:42\", \"message\", { text, from: user.id });\n */\nexport const Realtime: PalbaseRealtimeClient = makeServiceProxy(\"Realtime\");\n"],"mappings":"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","names":[]}