@palbase/backend 17.3.0 → 18.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 (73) hide show
  1. package/dist/bin/palbase-backend.cjs +1848 -0
  2. package/dist/bin/palbase-backend.cjs.map +1 -0
  3. package/dist/bin/palbase-backend.d.cts +1 -0
  4. package/dist/bin/palbase-backend.d.ts +1 -0
  5. package/dist/bin/palbase-backend.js +168 -0
  6. package/dist/bin/palbase-backend.js.map +1 -0
  7. package/dist/chunk-7D4SUZUM.js +38 -0
  8. package/dist/chunk-7D4SUZUM.js.map +1 -0
  9. package/dist/chunk-N32VDWKH.js +172 -0
  10. package/dist/chunk-N32VDWKH.js.map +1 -0
  11. package/dist/chunk-POYAFBLF.js +189 -0
  12. package/dist/chunk-POYAFBLF.js.map +1 -0
  13. package/dist/chunk-QMVK4X3V.js +200 -0
  14. package/dist/chunk-QMVK4X3V.js.map +1 -0
  15. package/dist/chunk-SSGAMC26.js +342 -0
  16. package/dist/chunk-SSGAMC26.js.map +1 -0
  17. package/dist/chunk-VYH4U7ZQ.js +1138 -0
  18. package/dist/chunk-VYH4U7ZQ.js.map +1 -0
  19. package/dist/{chunk-IBIME7N2.js → chunk-W5ODXPY3.js} +2 -321
  20. package/dist/chunk-W5ODXPY3.js.map +1 -0
  21. package/dist/chunk-YL4C5NRY.js +90 -0
  22. package/dist/chunk-YL4C5NRY.js.map +1 -0
  23. package/dist/db/env.cjs.map +1 -1
  24. package/dist/db/env.d.cts +21 -1
  25. package/dist/db/env.d.ts +21 -1
  26. package/dist/db/index.cjs +15 -0
  27. package/dist/db/index.cjs.map +1 -1
  28. package/dist/db/index.d.cts +2 -1
  29. package/dist/db/index.d.ts +2 -1
  30. package/dist/db/index.js +9 -6
  31. package/dist/{index-J1tnAnFj.d.ts → endpoint-B0LpZixz.d.cts} +124 -667
  32. package/dist/{index-D6mRh4yq.d.cts → endpoint-B0LpZixz.d.ts} +124 -667
  33. package/dist/engine/index.cjs +1797 -0
  34. package/dist/engine/index.cjs.map +1 -0
  35. package/dist/engine/index.d.cts +7 -0
  36. package/dist/engine/index.d.ts +7 -0
  37. package/dist/engine/index.js +43 -0
  38. package/dist/engine/index.js.map +1 -0
  39. package/dist/index-B46CGNvx.d.cts +839 -0
  40. package/dist/index-BGSCWlUa.d.cts +674 -0
  41. package/dist/index-DZDUMth5.d.ts +839 -0
  42. package/dist/index-g-EzitI-.d.ts +674 -0
  43. package/dist/index.cjs +1046 -11
  44. package/dist/index.cjs.map +1 -1
  45. package/dist/index.d.cts +290 -532
  46. package/dist/index.d.ts +290 -532
  47. package/dist/index.js +999 -509
  48. package/dist/index.js.map +1 -1
  49. package/dist/openapi/index.cjs +6464 -0
  50. package/dist/openapi/index.cjs.map +1 -0
  51. package/dist/openapi/index.d.cts +170 -0
  52. package/dist/openapi/index.d.ts +170 -0
  53. package/dist/openapi/index.js +6248 -0
  54. package/dist/openapi/index.js.map +1 -0
  55. package/dist/registry-3BLYv4si.d.ts +338 -0
  56. package/dist/registry-Cw0YEYCg.d.cts +338 -0
  57. package/dist/test/index.js +2 -0
  58. package/dist/test/index.js.map +1 -1
  59. package/docs/database.md +16 -3
  60. package/docs/llms-full.txt +16 -3
  61. package/package.json +43 -13
  62. package/stager/package.json +4 -0
  63. package/stager/return_types.js +338 -0
  64. package/stager/stage.js +78 -0
  65. package/stager/throw_analysis.js +726 -0
  66. package/template/AGENTS.md +261 -0
  67. package/template/config/secrets.ts +24 -0
  68. package/template/controllers/health.controller.ts +30 -0
  69. package/template/db/schema.ts +35 -0
  70. package/template/package.json +18 -0
  71. package/template/tsconfig.json +30 -0
  72. package/LICENSE +0 -21
  73. package/dist/chunk-IBIME7N2.js.map +0 -1
@@ -0,0 +1,674 @@
1
+ import { Tables, TableTypes } from './db/env.js';
2
+ import { D as DBClient, b8 as TxPlanHandle, bh as TxTable, M as Materialized } from './endpoint-B0LpZixz.js';
3
+
4
+ /** On delete action for foreign key references. */
5
+ type OnDeleteAction = 'cascade' | 'set null' | 'restrict' | 'no action';
6
+ /**
7
+ * The ON DELETE actions permitted on a foreign key to the built-in auth users
8
+ * (`auth.users`). Both let a user's rows be removed (`cascade`) or detached
9
+ * (`set null`) when the account is erased; `restrict` / `no action` would BLOCK
10
+ * erasure and are therefore excluded. This is the CLIENT-SIDE mirror of the
11
+ * server's auth-FK deletion policy — the server (validateAuthUserFK) is the real
12
+ * boundary, this narrows the type so the common mistake is caught at compile time.
13
+ */
14
+ type AuthUserOnDelete = Extract<OnDeleteAction, 'cascade' | 'set null'>;
15
+ /** Column type identifiers. */
16
+ type ColumnType = 'uuid' | 'text' | 'integer' | 'bigint' | 'numeric' | 'boolean' | 'timestamp' | 'jsonb' | 'enum';
17
+ /** Base column definition shared by all column types. */
18
+ interface ColumnDef {
19
+ type: ColumnType;
20
+ nullable: boolean;
21
+ primaryKey: boolean;
22
+ defaultValue?: unknown;
23
+ defaultRandom?: boolean;
24
+ defaultNow?: boolean;
25
+ references?: {
26
+ table: string;
27
+ column: string;
28
+ };
29
+ /**
30
+ * The name this column used to have. A diff cannot tell a rename from a drop and
31
+ * an add — both leave one name gone and another present — so the intent has to be
32
+ * declared. Without it, renaming a column loses its data.
33
+ */
34
+ renamedFrom?: string;
35
+ onDeleteAction?: OnDeleteAction;
36
+ enumName?: string;
37
+ enumValues?: string[];
38
+ unique?: boolean;
39
+ }
40
+ declare const __colKind: unique symbol;
41
+ declare const __colNullable: unique symbol;
42
+ declare const __colHasDefault: unique symbol;
43
+ declare const __colEnumValues: unique symbol;
44
+ declare const __colPayload: unique symbol;
45
+ /**
46
+ * Fluent column builder with phantom type params:
47
+ * K — ColumnType literal (e.g. "text", "integer")
48
+ * N — boolean: true when nullable() has been called last (false = NOT NULL)
49
+ * D — boolean: true when a default has been set
50
+ * E — enum value union (never for non-enum columns)
51
+ * P — jsonb payload shape (unknown unless jsonb<T>() supplied one)
52
+ *
53
+ * All five params have defaults so bare `ColumnBuilder` (no args) still
54
+ * satisfies `Record<string, ColumnBuilder>` in schema.ts without modification.
55
+ *
56
+ * The five `declare readonly` brand fields carry the phantom types into the
57
+ * structural shape so that conditional types like ColValue<C> can discriminate
58
+ * on K without requiring runtime values on those fields.
59
+ */
60
+ declare class ColumnBuilder<K extends ColumnType = ColumnType, N extends boolean = boolean, D extends boolean = boolean, E = unknown, P = unknown> {
61
+ readonly [__colKind]: K;
62
+ readonly [__colNullable]: N;
63
+ readonly [__colHasDefault]: D;
64
+ readonly [__colEnumValues]: E;
65
+ readonly [__colPayload]: P;
66
+ readonly _def: ColumnDef;
67
+ constructor(type: K, existingDef?: ColumnDef);
68
+ /** Mark this column as the primary key. */
69
+ primaryKey(): ColumnBuilder<K, N, D, E, P>;
70
+ /** Mark this column as NOT NULL (default). */
71
+ notNull(): ColumnBuilder<K, false, D, E, P>;
72
+ /** Allow NULL values. */
73
+ nullable(): ColumnBuilder<K, true, D, E, P>;
74
+ /** Set a default value. */
75
+ default(value: unknown): ColumnBuilder<K, N, true, E, P>;
76
+ /** UUID: generate a random default (gen_random_uuid()). */
77
+ defaultRandom(): ColumnBuilder<K, N, true, E, P>;
78
+ /** Timestamp: default to now(). */
79
+ defaultNow(): ColumnBuilder<K, N, true, E, P>;
80
+ /** Add a foreign key reference. */
81
+ /**
82
+ * Declares that this column used to be called `previous`.
83
+ *
84
+ * A schema diff sees one name gone and another present; it cannot know whether
85
+ * you renamed a column or dropped one and added another, and the two are very
86
+ * different — the second loses every value. Saying so here turns the plan into
87
+ * `ALTER TABLE … RENAME COLUMN` instead.
88
+ *
89
+ * Once the rename has been applied the annotation is inert (the old name is no
90
+ * longer there to rename), so it can be deleted at your leisure.
91
+ */
92
+ renamedFrom(previous: string): ColumnBuilder<K, N, D, E, P>;
93
+ references(table: string, column: string): ColumnBuilder<K, N, D, E, P>;
94
+ /**
95
+ * Add a real DB-level foreign key to the built-in auth users
96
+ * (`REFERENCES auth.users(id)`), so a column like `user_id` gets true
97
+ * database cascade/integrity instead of app-layer-only. Sugar for
98
+ * `.references("auth.users", "id")`.
99
+ *
100
+ * `auth.users` lives in the SAME tenant database (palauth-owned), so this is
101
+ * a genuine cross-schema integrity constraint scoped to THIS tenant's users.
102
+ * The referenced `auth.users.id` is `text` (palauth ids are `usr_<uuid>`), so
103
+ * the referencing column must be `text()` too.
104
+ *
105
+ * ON DELETE is REQUIRED here and may only be `cascade` or `set null`: an
106
+ * account-erasure request must never be blocked by a lingering FK, so
107
+ * `restrict` / `no action` are not accepted (they don't type-check). Example:
108
+ * `text().notNull().referencesAuthUser("cascade")`, or
109
+ * `text().nullable().referencesAuthUser("set null")`. The server
110
+ * (validateAuthUserFK) enforces this — and the remaining rules the type can't
111
+ * express (referencing column is text, `set null` needs a nullable column) —
112
+ * as the real boundary; this signature is the compile-time DX mirror.
113
+ */
114
+ referencesAuthUser(onDelete: AuthUserOnDelete): ColumnBuilder<K, N, D, E, P>;
115
+ /**
116
+ * Add a real DB-level foreign key to the canonical, server-minted installation
117
+ * anchor (`REFERENCES auth.installations(id)`) — the app-scoped verified-device
118
+ * root (`ins_...`). Sugar for `.references("auth.installations", "id")`.
119
+ *
120
+ * An installation is an APP INSTALL, not a user: this FK is NOT user ownership.
121
+ * A user-owned row STILL needs its own `.referencesAuthUser(...)` FK so account
122
+ * erasure removes it — an installation reference alone does not tie a row to a
123
+ * user's deletion. Use this only for install-scoped state (device prefs, push
124
+ * routing, …), alongside a separate auth-user FK where the row is user-owned.
125
+ *
126
+ * `auth.installations` lives in the SAME tenant DB (palauth-owned); its `id` is
127
+ * `text` (`ins_<uuid>`), so the referencing column must be `text()` too. ON
128
+ * DELETE is REQUIRED and may only be `cascade` or `set null` (same allowed set
129
+ * as an auth-user FK): an installation revoke / orphan cleanup must never be
130
+ * blocked by a lingering FK. The server (validateAuthAnchorFK) is the real
131
+ * boundary; this signature is the compile-time DX mirror.
132
+ */
133
+ referencesInstallation(onDelete: AuthUserOnDelete): ColumnBuilder<K, N, D, E, P>;
134
+ /** Set the ON DELETE action for a foreign key reference. */
135
+ onDelete(action: OnDeleteAction): ColumnBuilder<K, N, D, E, P>;
136
+ /** Add a single-column UNIQUE constraint. */
137
+ unique(): ColumnBuilder<K, N, D, E, P>;
138
+ }
139
+ /**
140
+ * Extracts the TypeScript value type for a column, respecting nullability.
141
+ * - "uuid" | "text" | "timestamp" | "bigint" | "numeric" → string (or string | null when N = true)
142
+ * Note: bigint/numeric surface as string — JS number loses precision past 2^53,
143
+ * and pgx/PostgREST serialize int8/numeric as strings. App code uses
144
+ * BigInt(row.amount) for bigint, or a decimal lib for numeric.
145
+ * - "integer" → number
146
+ * - "boolean" → boolean
147
+ * - "jsonb" → P (the dev-supplied payload shape from jsonb<T>(), else unknown)
148
+ * - "enum" → E (the union of literal values)
149
+ */
150
+ type ColValue<C> = C extends ColumnBuilder<'uuid' | 'text' | 'timestamp' | 'bigint' | 'numeric', infer N, infer _D, infer _E, infer _P> ? N extends true ? string | null : string : C extends ColumnBuilder<'integer', infer N, infer _D, infer _E, infer _P> ? N extends true ? number | null : number : C extends ColumnBuilder<'boolean', infer N, infer _D, infer _E, infer _P> ? N extends true ? boolean | null : boolean : C extends ColumnBuilder<'jsonb', infer N, infer _D, infer _E, infer P> ? N extends true ? P | null : P : C extends ColumnBuilder<'enum', infer N, infer _D, infer E, infer _P> ? N extends true ? E | null : E : never;
151
+ /**
152
+ * True when a column is optional on INSERT:
153
+ * - nullable columns (N = true) — the DB allows NULL so the field may be omitted
154
+ * - columns with a default (D = true) — the DB fills in the value when absent
155
+ */
156
+ type ColIsOptionalOnInsert<C> = C extends ColumnBuilder<infer _K, true, infer _D, infer _E> ? true : C extends ColumnBuilder<infer _K, infer _N, true, infer _E> ? true : false;
157
+ /** Create a UUID column. */
158
+ declare function uuid(): ColumnBuilder<'uuid', false, false, never>;
159
+ /** Create a TEXT column. */
160
+ declare function text(): ColumnBuilder<'text', false, false, never>;
161
+ /** Create an INTEGER column. Emits int4 (max ~2.1B). */
162
+ declare function integer(): ColumnBuilder<'integer', false, false, never>;
163
+ /**
164
+ * Create a BIGINT column (Postgres int8, max ~9.2×10^18).
165
+ * Surfaces as `string` in row/insert types — JS number loses precision past 2^53
166
+ * and pgx/PostgREST serialize int8 as a JSON string. Use BigInt(row.column) in app code.
167
+ */
168
+ declare function bigint(): ColumnBuilder<'bigint', false, false, never>;
169
+ /**
170
+ * Create a NUMERIC column (Postgres `numeric`/`decimal`, arbitrary precision).
171
+ * For exact fractional values (money with cents as a decimal, rates, weights)
172
+ * where int4/int8 don't fit. Surfaces as `string` in row/insert types — JS
173
+ * number can't hold arbitrary-precision decimals without rounding, and
174
+ * pgx/PostgREST serialize numeric as a JSON string. Parse with a decimal lib
175
+ * (or BigInt for scaled integers) in app code.
176
+ */
177
+ declare function numeric(): ColumnBuilder<'numeric', false, false, never>;
178
+ /** Create a BOOLEAN column. */
179
+ declare function boolean(): ColumnBuilder<'boolean', false, false, never>;
180
+ /** Create a TIMESTAMP column. */
181
+ declare function timestamp(): ColumnBuilder<'timestamp', false, false, never>;
182
+ /**
183
+ * Create a JSONB column. Pass a payload type to make the generated row/insert
184
+ * type concrete instead of `unknown`:
185
+ *
186
+ * tags: jsonb<string[]>() // row.tags: string[]
187
+ * meta: jsonb<{ tier: string }>() // row.meta: { tier: string }
188
+ * raw: jsonb() // row.raw: unknown (back-compat)
189
+ *
190
+ * The runtime accepts a plain JS object/array directly (no JSON.stringify); the
191
+ * generic only refines the TYPE the env codegen emits.
192
+ */
193
+ declare function jsonb<T = unknown>(): ColumnBuilder<'jsonb', false, false, never, T>;
194
+ /**
195
+ * Create an ENUM column.
196
+ * @param name The PostgreSQL enum type name (used in DDL).
197
+ * @param values A readonly tuple of valid string values — kept `const` so the
198
+ * union `V[number]` is as narrow as possible.
199
+ */
200
+ declare function enumType<const V extends readonly string[]>(name: string, values: V): ColumnBuilder<'enum', false, false, V[number]>;
201
+
202
+ /**
203
+ * policy.ts — the RLS policy authoring DSL.
204
+ *
205
+ * `policy(name)` returns a fluent builder that mirrors the `ColumnBuilder`
206
+ * style in columns.ts: each chainable method mutates the underlying
207
+ * definition and returns the builder so calls compose. The terminal value is
208
+ * a plain {@link PolicyDef} — the exact JSON shape the runtime's
209
+ * `schema_extract.js` reads off the bundled module and the Go side parses into
210
+ * `PolicyJSON` (CONTRACT-POLICY).
211
+ *
212
+ * @example
213
+ * import { policy } from "@palbase/backend";
214
+ *
215
+ * policy("owner_select")
216
+ * .for("select")
217
+ * .to("authenticated")
218
+ * .using("owner = (select auth.uid())");
219
+ */
220
+ /** The SQL command a policy applies to. `"all"` covers SELECT/INSERT/UPDATE/DELETE. */
221
+ type PolicyCommand = "all" | "select" | "insert" | "update" | "delete";
222
+ /** Whether a policy is permissive (OR-combined, the default) or restrictive
223
+ * (AND-combined). Mirrors Postgres `CREATE POLICY ... AS PERMISSIVE|RESTRICTIVE`. */
224
+ type PolicyMode = "permissive" | "restrictive";
225
+ /**
226
+ * The compiled, serializable policy definition — the EXACT shape consumed by
227
+ * `schema_extract.js` → Go `PolicyJSON` (CONTRACT-POLICY).
228
+ *
229
+ * - `roles`: the DB roles this policy applies to (`TO` clause). An empty array
230
+ * means the policy applies to PUBLIC (all roles) — the Postgres default.
231
+ * - `using`: the `USING (...)` row-visibility expression, or `null` when none.
232
+ * - `withCheck`: the `WITH CHECK (...)` write-validation expression, or `null`.
233
+ * - `permissive`: `true` for `AS PERMISSIVE` (default), `false` for restrictive.
234
+ */
235
+ interface PolicyDef {
236
+ name: string;
237
+ command: PolicyCommand;
238
+ roles: string[];
239
+ using: string | null;
240
+ withCheck: string | null;
241
+ permissive: boolean;
242
+ }
243
+ /**
244
+ * Fluent RLS policy builder.
245
+ *
246
+ * Defaults (documented, applied at construction):
247
+ * - `command`: `"all"` — applies to every SQL command unless `.for(...)` narrows it.
248
+ * - `roles`: `["authenticated"]` — the common case is "rule applies to signed-in
249
+ * users". Call `.to(...)` to override; pass `.to()` with no roles (or never
250
+ * call it after a reset) to target PUBLIC.
251
+ * - `using` / `withCheck`: `null` — no row filter / write check until set.
252
+ * - `permissive`: `true` — `AS PERMISSIVE` (policies OR together).
253
+ *
254
+ * Each method mutates `_def` in place and returns `this`, so the chain is a
255
+ * single builder instance (no per-call allocation, like a tagged-template
256
+ * compile target). The terminal `PolicyDef` is read directly off `_def` by
257
+ * `schema_extract.js`.
258
+ */
259
+ declare class PolicyBuilder {
260
+ readonly _def: PolicyDef;
261
+ constructor(name: string);
262
+ /** Restrict the policy to a single SQL command (default `"all"`). */
263
+ for(command: PolicyCommand): this;
264
+ /**
265
+ * Set the DB roles the policy applies to (the `TO` clause), replacing any
266
+ * previously-set roles. Call with no arguments to target PUBLIC (all roles).
267
+ *
268
+ * @example
269
+ * policy("p").to("authenticated")
270
+ * policy("p").to("authenticated", "service_role")
271
+ * policy("p").to() // PUBLIC
272
+ */
273
+ to(...roles: string[]): this;
274
+ /** Set the `USING (...)` row-visibility expression (raw SQL). */
275
+ using(sqlExpr: string): this;
276
+ /** Set the `WITH CHECK (...)` write-validation expression (raw SQL). */
277
+ withCheck(sqlExpr: string): this;
278
+ /** Set the policy mode: `"permissive"` (default, OR-combined) or
279
+ * `"restrictive"` (AND-combined). */
280
+ as(mode: PolicyMode): this;
281
+ }
282
+ /**
283
+ * Start authoring an RLS policy. Returns a {@link PolicyBuilder}; the resulting
284
+ * `PolicyBuilder` is accepted directly in a table's `policies: [...]` array
285
+ * (its `_def` is read at schema-extract time).
286
+ *
287
+ * @param name The policy name. Palbase reconciliation keys policies by
288
+ * `(table, name)`, so names must be unique per table.
289
+ */
290
+ declare function policy(name: string): PolicyBuilder;
291
+
292
+ /**
293
+ * Postgres extensions a Palbase project can enable from its schema.
294
+ *
295
+ * Extensions are config-as-code: declare them in `defineSchema({ extensions })`
296
+ * and the deploy installs them (CREATE EXTENSION … SCHEMA extensions) using the
297
+ * deploy path's privileged connection. They are NOT toggled live from Studio —
298
+ * CREATE EXTENSION requires a superuser role that only the deploy path holds.
299
+ *
300
+ * The list is an allowlist (a string-literal union) so editors autocomplete the
301
+ * supported names and a typo fails typecheck. It is intentionally extensible:
302
+ * add a name here (+ confirm the base image ships it) to support more.
303
+ */
304
+ declare const PALBASE_EXTENSIONS: readonly ["vector", "pg_trgm", "unaccent", "citext", "postgis", "cube", "earthdistance", "hstore", "ltree", "btree_gist", "pg_cron", "pgcrypto", "uuid-ossp"];
305
+ /** A Postgres extension supported by Palbase (allowlist union). */
306
+ type PalbaseExtension = (typeof PALBASE_EXTENSIONS)[number];
307
+ /**
308
+ * Extensions that depend on another extension. The deploy installs
309
+ * dependencies first; declaring `earthdistance` without `cube` still works
310
+ * because the deploy resolves the order, but listing both is clearer.
311
+ */
312
+ declare const EXTENSION_DEPENDENCIES: Partial<Record<PalbaseExtension, PalbaseExtension[]>>;
313
+ /** Runtime guard: is `name` a supported Palbase extension? */
314
+ declare function isPalbaseExtension(name: string): name is PalbaseExtension;
315
+
316
+ /**
317
+ * A named raw-SQL DDL object declared in db/schema.ts for anything the typed DSL
318
+ * cannot express (EXCLUDE, CHECK, partial/expression indexes, triggers, views).
319
+ * The deploy emits `up` verbatim on the privileged DDL connection — same trust
320
+ * posture as policy().using(). Tracked by NAME (not by diffing the body), so a
321
+ * changed body needs a new name or an explicit drop+add.
322
+ */
323
+ interface RawConstraintDef {
324
+ name: string;
325
+ up: string;
326
+ down?: string;
327
+ }
328
+ declare function raw(name: string, up: string, opts?: {
329
+ down?: string;
330
+ }): RawConstraintDef;
331
+
332
+ /**
333
+ * A map of column builders keyed by column name — the value you write under
334
+ * the `columns` key of `defineSchema({ tables: { <name>: { columns } } })`.
335
+ *
336
+ * The default `Record<string, ColumnBuilder>` keeps bare references compiling
337
+ * without a type argument.
338
+ */
339
+ type ColumnMap = Record<string, ColumnBuilder>;
340
+ /**
341
+ * The author-facing value written under each table key:
342
+ * `{ columns, rls?, policies? }`.
343
+ *
344
+ * - `columns`: the column map (required).
345
+ * - `rls`: enable + FORCE row-level security on this table. **Defaults to
346
+ * `true`**, and is forced on when `policies` is non-empty. A table with RLS
347
+ * and no policies is deny-all, which is the starting state: nothing reads it
348
+ * until a policy says who may. Set `rls: false` only for a genuinely public
349
+ * table — it is an explicit opt-out that a reviewer can grep for, not
350
+ * something you get by forgetting.
351
+ * - `policies`: the RLS policies for this table, authored with `policy(name)`.
352
+ * Each entry may be a {@link PolicyBuilder} (the normal `policy(...)` chain)
353
+ * or a raw {@link PolicyDef} object.
354
+ *
355
+ * The `C` type parameter preserves the precise per-column phantom types so the
356
+ * typed `Database.tables.*` surface keeps inferring insert/row shapes.
357
+ */
358
+ interface TableInput<C extends ColumnMap = ColumnMap> {
359
+ columns: C;
360
+ rls?: boolean;
361
+ policies?: (PolicyBuilder | PolicyDef)[];
362
+ /** Composite/named primary key (ordered column names). Omit for single-column inline .primaryKey(). */
363
+ primaryKey?: string[];
364
+ /** Named multi-column UNIQUE constraints. */
365
+ unique?: {
366
+ name: string;
367
+ columns: string[];
368
+ }[];
369
+ /** Named raw-SQL DDL objects (EXCLUDE, triggers, views) that the typed DSL cannot express. */
370
+ raw?: RawConstraintDef[];
371
+ /**
372
+ * Named first-class CHECK constraints. Diffed by NAME with a BODY compare:
373
+ * a changed `expr` (after pg normalization) recreates the constraint
374
+ * (DROP + ADD). `expr` is trusted SQL emitted verbatim (like policy USING),
375
+ * `name` is identifier-validated.
376
+ */
377
+ checks?: {
378
+ name: string;
379
+ expr: string;
380
+ }[];
381
+ /**
382
+ * Plain (non-unique) btree indexes over an ordered column list, emitted as
383
+ * standalone `CREATE INDEX [IF NOT EXISTS] name ON table (col1, col2)`
384
+ * statements (NOT a table clause — a separate migration statement category).
385
+ * Structural compare by NAME (no expression normalization). `name` and each
386
+ * column are identifier-validated by the Go differ.
387
+ *
388
+ * Scope: columns-only plain btree. Partial (`where`) and expression indexes
389
+ * are a deliberate follow-up — modelling them needs the same raw-SQL
390
+ * normalization round-trip CHECK uses (Task 10), so they are NOT in this
391
+ * type yet to avoid a half-working partial-index path.
392
+ */
393
+ indexes?: {
394
+ name: string;
395
+ columns: string[];
396
+ }[];
397
+ }
398
+ /**
399
+ * A table definition — the runtime value the Go runtime's `schema_extract.js`
400
+ * reads. It keys tables by `tableDef.name`, reads `tableDef.columns` for the
401
+ * column DDL, and `tableDef.rls` + `tableDef.policies` for RLS.
402
+ *
403
+ * `defineSchema` derives `name` from the object key, so authors never repeat
404
+ * the table name. `rls`/`policies` are always present after normalization
405
+ * (defaulted to `true`/`[]`).
406
+ *
407
+ * The `C` type parameter preserves the precise per-column phantom types so that
408
+ * downstream mapped types (InsertShape, RowShape) can discriminate on them.
409
+ */
410
+ interface TableDef<C extends ColumnMap = ColumnMap> {
411
+ name: string;
412
+ columns: C;
413
+ rls: boolean;
414
+ policies: PolicyDef[];
415
+ primaryKey?: string[];
416
+ unique?: {
417
+ name: string;
418
+ columns: string[];
419
+ }[];
420
+ /** Named raw-SQL DDL objects emitted verbatim on deploy. Tracked by name. */
421
+ raw?: RawConstraintDef[];
422
+ /** Named first-class CHECK constraints. Diffed by name + (normalized) body. */
423
+ checks?: {
424
+ name: string;
425
+ expr: string;
426
+ }[];
427
+ /** Plain btree indexes (columns-only), emitted as standalone CREATE INDEX. Diffed by name. */
428
+ indexes?: {
429
+ name: string;
430
+ columns: string[];
431
+ }[];
432
+ }
433
+ /**
434
+ * A schema definition containing multiple tables, keyed by table name.
435
+ *
436
+ * The `T` type parameter preserves the exact `TableDef<...>` type for each
437
+ * table so that `SchemaDef["tables"]["rooms"]` resolves to the precise
438
+ * `TableDef<{ id: ColumnBuilder<'uuid', false, true, never>; ... }>`.
439
+ */
440
+ interface SchemaDef<T extends Record<string, TableDef> = Record<string, TableDef>> {
441
+ tables: T;
442
+ /** Postgres extensions to install on deploy. Normalized to `[]` when absent. */
443
+ extensions: PalbaseExtension[];
444
+ }
445
+ /** The author-facing input to `defineSchema` — a `tables` map whose keys are
446
+ * the table names and whose values are `{ columns, rls?, policies? }`, plus an
447
+ * optional `extensions` allowlist. */
448
+ interface SchemaInput<T extends Record<string, TableInput> = Record<string, TableInput>> {
449
+ tables: T;
450
+ /**
451
+ * Postgres extensions to enable for this project, e.g. `["vector"]`.
452
+ * Config-as-code: installed by the deploy (CREATE EXTENSION … SCHEMA
453
+ * extensions) with the privileged deploy connection. The type is an
454
+ * allowlist union, so unsupported names fail typecheck.
455
+ */
456
+ extensions?: PalbaseExtension[];
457
+ }
458
+ /** Map the author's `{ tables: { <name>: { columns } } }` input to the
459
+ * `{ tables: { <name>: TableDef<columns> } }` runtime/type shape, threading the
460
+ * per-table column map `T[K]["columns"]` so column-level inference survives. */
461
+ type TablesFromInput<T extends Record<string, TableInput>> = {
462
+ [K in keyof T]: TableDef<T[K]["columns"]>;
463
+ };
464
+ /**
465
+ * Define a schema. The table NAME comes from the object key. Each table value
466
+ * is `{ columns, rls?, policies? }`:
467
+ *
468
+ * export default defineSchema({
469
+ * tables: {
470
+ * todos: {
471
+ * columns: {
472
+ * id: uuid().primaryKey().defaultRandom(),
473
+ * owner: text().notNull(),
474
+ * title: text().notNull(),
475
+ * },
476
+ * rls: true,
477
+ * policies: [
478
+ * policy("owner_all").for("all").to("authenticated")
479
+ * .using("owner = (select auth.uid())")
480
+ * .withCheck("owner = (select auth.uid())"),
481
+ * ],
482
+ * },
483
+ * },
484
+ * });
485
+ *
486
+ * The returned value is
487
+ * `{ tables: { todos: { name, columns, rls, policies } } }` — the exact shape
488
+ * the runtime schema extractor parses. Per-column phantom types are preserved
489
+ * so `Database.tables.todos.insert({...})` stays typed.
490
+ *
491
+ * RLS normalization: `rls` defaults to **`true`**, `policies` to `[]`. A table
492
+ * that declares neither is therefore deny-all — nothing reads it until a policy
493
+ * says who may, which is the safe starting point rather than a bug. Declare
494
+ * `rls: false` for a genuinely public table; that is an explicit, greppable
495
+ * statement of intent instead of an omission. When `policies` is non-empty,
496
+ * `rls` is forced on (ENABLE + FORCE) regardless of the declared flag — a table
497
+ * with policies must have RLS enabled or the policies would be inert.
498
+ */
499
+ declare function defineSchema<T extends Record<string, TableInput>>(input: SchemaInput<T>): SchemaDef<TablesFromInput<T>>;
500
+
501
+ /**
502
+ * typed-db.ts — Task 2: TypedDB schema-derived insert/row shapes.
503
+ *
504
+ * Derives INSERT and full-row TypeScript types from a `defineSchema()` result
505
+ * and wraps the untyped runtime `DBClient` with a typed facade.
506
+ *
507
+ * No value-any. No `as unknown as X`. The two narrow `as` casts in
508
+ * `makeTypedTable` are safe because:
509
+ * - `data as Record<string, unknown>`: InsertShape<T> maps string keys to
510
+ * typed values; all value types are subsets of `unknown`, so the cast is
511
+ * structurally sound.
512
+ * - `result as RowShape<T>`: The runtime DBClient returns `Record<string,
513
+ * unknown>` which is the erased form of the typed row; we're narrowing back
514
+ * to the precise shape that the schema declared.
515
+ * Both casts are narrowing only (not widening) and correctness is guaranteed
516
+ * by the schema the caller provides.
517
+ */
518
+
519
+ /** Keys of C whose columns are required on INSERT (not nullable, no default). */
520
+ type RequiredKeys<C> = {
521
+ [K in keyof C]: ColIsOptionalOnInsert<C[K]> extends true ? never : K;
522
+ }[keyof C];
523
+ /** Keys of C whose columns are optional on INSERT (nullable or has a default). */
524
+ type OptionalKeys<C> = {
525
+ [K in keyof C]: ColIsOptionalOnInsert<C[K]> extends true ? K : never;
526
+ }[keyof C];
527
+ /**
528
+ * The TypeScript type for an INSERT payload for table `T`.
529
+ * - Required: columns that are NOT NULL and have no DB-level default.
530
+ * - Optional: columns that are nullable or carry a default.
531
+ *
532
+ * When all columns are optional, `RequiredKeys<C>` resolves to `never` and
533
+ * the first part becomes `{}`, which is a neutral element for `&`.
534
+ */
535
+ type InsertShape<T extends TableDef> = {
536
+ [K in RequiredKeys<T["columns"]>]: ColValue<T["columns"][K]>;
537
+ } & {
538
+ [K in OptionalKeys<T["columns"]>]?: ColValue<T["columns"][K]>;
539
+ };
540
+ /**
541
+ * The TypeScript type for a full row returned by the DB for table `T`.
542
+ * Every column is present; nullable columns resolve to `T | null`.
543
+ */
544
+ type RowShape<T extends TableDef> = {
545
+ [K in keyof T["columns"]]: ColValue<T["columns"][K]>;
546
+ };
547
+ /** A typed table accessor that mirrors the runtime DBClient surface. */
548
+ interface TypedTable<T extends TableDef> {
549
+ insert(data: InsertShape<T>): Promise<RowShape<T>>;
550
+ /** Update the row by id; resolves to the updated row, or `null` if no row
551
+ * matched (absent or RLS-hidden) — an idempotent outcome, mirroring
552
+ * `findById`. The runtime returns a null row rather than throwing. */
553
+ update(id: string, data: Partial<InsertShape<T>>): Promise<RowShape<T> | null>;
554
+ delete(id: string): Promise<void>;
555
+ findById(id: string): Promise<RowShape<T> | null>;
556
+ findMany(query?: Partial<RowShape<T>>): Promise<RowShape<T>[]>;
557
+ }
558
+ /** A typed DB facade covering all tables declared in schema `S`. */
559
+ interface TypedDB<S extends SchemaDef> {
560
+ tables: {
561
+ [K in keyof S["tables"]]: TypedTable<S["tables"][K]>;
562
+ };
563
+ /** Run a transaction plan. See {@link EnvTypedDatabase.transaction}. */
564
+ transaction<T>(fn: (tx: TypedTx<S>) => T extends Promise<unknown> ? never : T): Promise<Materialized<T>>;
565
+ }
566
+ /** The plan-building handle a `TypedDB<S>` transaction callback receives: the
567
+ * schema's tables, expressed as plan operations rather than awaited calls. */
568
+ type TypedTx<S extends SchemaDef> = TxPlanHandle<{
569
+ [K in keyof S["tables"]]: TxTable<RowShape<S["tables"][K]>, InsertShape<S["tables"][K]>>;
570
+ }>;
571
+ /**
572
+ * Wraps a raw `DBClient` with the type-safe `TypedDB<S>` facade derived from
573
+ * the provided schema. No behavior change for the direct ops — all calls
574
+ * delegate to `raw` with the table name as a plain string.
575
+ *
576
+ * `transaction` does NOT delegate to a per-op client: the callback describes a
577
+ * plan against a fresh {@link TxPlanBuilder}, and the whole plan travels in one
578
+ * `raw.txPlan` call. The schema is used only for its table NAMES; the values
579
+ * are typed by `S` at compile time and are plain strings at run time.
580
+ *
581
+ * The `as` casts are single structural narrowings from a dynamically-built
582
+ * object to the precise mapped type (TS cannot infer the mapped-type result
583
+ * through `Object.keys` iteration) — see the module-level doc comment.
584
+ */
585
+ declare function makeTypedDB<S extends SchemaDef>(schema: S, raw: DBClient): TypedDB<S>;
586
+ /** A typed table accessor derived from one env `Tables` entry's flat shapes. */
587
+ interface EnvTypedTable<T extends TableTypes> {
588
+ insert(data: T["insert"]): Promise<T["row"]>;
589
+ /** Update the row by id; resolves to the updated row, or `null` if no row
590
+ * matched (absent or RLS-hidden) — an idempotent outcome, mirroring
591
+ * `findById`. The runtime returns a null row rather than throwing. */
592
+ update(id: string, data: Partial<T["insert"]>): Promise<T["row"] | null>;
593
+ delete(id: string): Promise<void>;
594
+ findById(id: string): Promise<T["row"] | null>;
595
+ findMany(query?: Partial<T["row"]>): Promise<T["row"][]>;
596
+ }
597
+ /** The `tables` map exposed on `Database`/`tx`, keyed by the env `Tables`
598
+ * interface. When no schema is declared `Tables` is empty, so `tables` is an
599
+ * empty object — accessing `.tables.foo` is then a compile error (no member). */
600
+ type EnvTables = {
601
+ [K in keyof Tables]: EnvTypedTable<Tables[K]>;
602
+ };
603
+ /** The project's tables as PLAN operations, keyed by the env `Tables`
604
+ * interface. The transaction twin of {@link EnvTables}. */
605
+ type TxTables = {
606
+ [K in keyof Tables]: TxTable<Tables[K]["row"], Tables[K]["insert"]>;
607
+ };
608
+ /**
609
+ * The handle a `Database.transaction(…)` callback receives.
610
+ *
611
+ * Tables only — no `query`, no `findById`, no `asService`. A read whose value
612
+ * the plan does not write belongs outside the transaction, where it costs one
613
+ * round trip and is an ordinary value you can branch on.
614
+ */
615
+ type TxPlan = TxPlanHandle<TxTables>;
616
+ /**
617
+ * The RLS-bypass sibling returned by `Database.asService()`. Same typed surface
618
+ * as {@link EnvTypedDatabase} — `tables`, the raw string ops, and a typed
619
+ * `transaction` — but it does NOT re-expose `asService` (no double-bypass).
620
+ * Every op it performs runs as the `service_role` (BYPASSRLS).
621
+ */
622
+ interface EnvServiceDatabase extends Omit<DBClient, "txPlan" | "asService"> {
623
+ tables: EnvTables;
624
+ transaction<T>(fn: (tx: TxPlan) => T extends Promise<unknown> ? never : T): Promise<Materialized<T>>;
625
+ }
626
+ /**
627
+ * The typed-by-default Database surface: the raw string-keyed `DBClient` ops
628
+ * PLUS a `tables` map typed against the project's generated `palbase-env.d.ts`,
629
+ * a `transaction` that runs a whole plan in one request, and `asService()` for
630
+ * the explicit RLS-bypass sibling.
631
+ *
632
+ * The low-level `txPlan` op is deliberately NOT re-exposed here: `transaction`
633
+ * is the surface, and a hand-built plan would bypass the ref/guard machinery
634
+ * that makes one safe to write.
635
+ */
636
+ interface EnvTypedDatabase extends Omit<DBClient, "txPlan" | "asService"> {
637
+ tables: EnvTables;
638
+ /**
639
+ * Run a transaction. The callback DESCRIBES the operations; the whole
640
+ * description travels in one request and the broker runs it inside a single
641
+ * transaction — committing when it finishes, rolling back on any failure.
642
+ *
643
+ * The callback is SYNCHRONOUS: nothing has run when it returns, so there is
644
+ * nothing to await. `async` on it and `await` inside it are compile errors.
645
+ * Values a later operation needs are {@link Ref}s, written straight into the
646
+ * next operation; values the CALLER needs are returned and substituted before
647
+ * this promise resolves.
648
+ *
649
+ * @example
650
+ * const { statementId } = await Database.transaction((tx) => {
651
+ * const st = tx.tables.statements
652
+ * .insert({ household_id: hid, file_sha256: sha, status: "reviewing" })
653
+ * .expectOne(new Internal("statement insert failed"));
654
+ *
655
+ * tx.tables.statement_lines.insertMany(
656
+ * lines.map((l) => ({ statement_id: st.id, category: resolveCategory(l) })),
657
+ * );
658
+ *
659
+ * return { statementId: st.id };
660
+ * });
661
+ */
662
+ transaction<T>(fn: (tx: TxPlan) => T extends Promise<unknown> ? never : T): Promise<Materialized<T>>;
663
+ /**
664
+ * Return a sibling that bypasses RLS by running as the `service_role`. Use
665
+ * sparingly and explicitly — the default `Database.*` path is RLS-enforced.
666
+ *
667
+ * @example
668
+ * const all = await Database.asService().tables.todos.findMany({});
669
+ * const rows = await Database.asService().query("SELECT * FROM todos");
670
+ */
671
+ asService(): EnvServiceDatabase;
672
+ }
673
+
674
+ export { jsonb as A, makeTypedDB as B, ColumnBuilder as C, numeric as D, type EnvTypedDatabase as E, policy as F, raw as G, text as H, type InsertShape as I, timestamp as J, uuid as K, type OnDeleteAction as O, PALBASE_EXTENSIONS as P, type RawConstraintDef as R, type SchemaDef as S, type TableDef as T, type ColumnDef as a, type ColumnMap as b, type ColumnType as c, EXTENSION_DEPENDENCIES as d, type EnvServiceDatabase as e, type EnvTables as f, type EnvTypedTable as g, type PalbaseExtension as h, PolicyBuilder as i, type PolicyCommand as j, type PolicyDef as k, type PolicyMode as l, type RowShape as m, type SchemaInput as n, type TableInput as o, type TxPlan as p, type TxTables as q, type TypedDB as r, type TypedTable as s, type TypedTx as t, bigint as u, boolean as v, defineSchema as w, enumType as x, integer as y, isPalbaseExtension as z };