@palbase/backend 25.1.0 → 27.1.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 (104) hide show
  1. package/dist/bin/palbase-backend.cjs +2432 -1039
  2. package/dist/bin/palbase-backend.cjs.map +1 -1
  3. package/dist/bin/palbase-backend.js +87 -51
  4. package/dist/bin/palbase-backend.js.map +1 -1
  5. package/dist/chunk-BQN723PL.js +930 -0
  6. package/dist/chunk-BQN723PL.js.map +1 -0
  7. package/dist/chunk-CGNN2PUH.js +213 -0
  8. package/dist/chunk-CGNN2PUH.js.map +1 -0
  9. package/dist/{chunk-VDF2T4AS.js → chunk-EB3TUX5J.js} +1228 -591
  10. package/dist/chunk-EB3TUX5J.js.map +1 -0
  11. package/dist/chunk-JVZQCC77.js +728 -0
  12. package/dist/chunk-JVZQCC77.js.map +1 -0
  13. package/dist/chunk-OZKSM3JW.js +370 -0
  14. package/dist/chunk-OZKSM3JW.js.map +1 -0
  15. package/dist/{chunk-YOY5DFQS.js → chunk-TWX6JTGJ.js} +76 -34
  16. package/dist/{chunk-YOY5DFQS.js.map → chunk-TWX6JTGJ.js.map} +1 -1
  17. package/dist/{chunk-35PNTIRN.js → chunk-VVMJEVQP.js} +63 -162
  18. package/dist/chunk-VVMJEVQP.js.map +1 -0
  19. package/dist/{chunk-7D4SUZUM.js → chunk-VXPNPVAG.js} +3 -1
  20. package/dist/chunk-XABHGMUT.js +885 -0
  21. package/dist/chunk-XABHGMUT.js.map +1 -0
  22. package/dist/db/env.cjs.map +1 -1
  23. package/dist/db/env.d.cts +2 -2
  24. package/dist/db/env.d.ts +2 -2
  25. package/dist/db/index.cjs +780 -344
  26. package/dist/db/index.cjs.map +1 -1
  27. package/dist/db/index.d.cts +2 -2
  28. package/dist/db/index.d.ts +2 -2
  29. package/dist/db/index.js +7 -4
  30. package/dist/engine/index.cjs +2366 -1002
  31. package/dist/engine/index.cjs.map +1 -1
  32. package/dist/engine/index.d.cts +6 -6
  33. package/dist/engine/index.d.ts +6 -6
  34. package/dist/engine/index.js +7 -6
  35. package/dist/{index-CUomTA3e.d.ts → index-CAKOgAlP.d.ts} +171 -296
  36. package/dist/index-CgE4sVhg.d.cts +4864 -0
  37. package/dist/{index-ClpDeSos.d.cts → index-H-0qv5d4.d.cts} +171 -296
  38. package/dist/index-V7QRh1wg.d.ts +4864 -0
  39. package/dist/index.cjs +2720 -1169
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/index.d.cts +165 -19
  42. package/dist/index.d.ts +165 -19
  43. package/dist/index.js +738 -477
  44. package/dist/index.js.map +1 -1
  45. package/dist/module-Dl1KFVtc.d.cts +54 -0
  46. package/dist/module-Dl1KFVtc.d.ts +54 -0
  47. package/dist/openapi/index.cjs +1330 -484
  48. package/dist/openapi/index.cjs.map +1 -1
  49. package/dist/openapi/index.d.cts +4 -2
  50. package/dist/openapi/index.d.ts +4 -2
  51. package/dist/openapi/index.js +1264 -474
  52. package/dist/openapi/index.js.map +1 -1
  53. package/dist/{registry-dZZ5JKYg.d.ts → registry-4EI8aaFs.d.ts} +1 -1
  54. package/dist/{registry-CC0WBQq6.d.cts → registry-DHsPDY0_.d.cts} +1 -1
  55. package/dist/stack.cjs.map +1 -1
  56. package/dist/test/index.cjs +732 -141
  57. package/dist/test/index.cjs.map +1 -1
  58. package/dist/test/index.d.cts +30 -4
  59. package/dist/test/index.d.ts +30 -4
  60. package/dist/test/index.js +490 -124
  61. package/dist/test/index.js.map +1 -1
  62. package/docs/README.md +33 -18
  63. package/docs/auth.md +1 -1
  64. package/docs/background.md +2 -2
  65. package/docs/database.md +255 -50
  66. package/docs/endpoints.md +3 -4
  67. package/docs/events.md +3 -3
  68. package/docs/getting-started.md +1 -1
  69. package/docs/llms-full.txt +435 -117
  70. package/docs/migrations.md +2 -2
  71. package/docs/schema.md +19 -10
  72. package/docs/services.md +116 -26
  73. package/package.json +8 -4
  74. package/stager/generics.js +205 -0
  75. package/stager/stage.js +39 -3
  76. package/template/AGENTS.md +110 -72
  77. package/template/db/public.ts +1 -1
  78. package/template/{controllers → modules/health}/health.controller.ts +1 -1
  79. package/template/modules/health/health.module.ts +24 -0
  80. package/template/modules/notes/note.service.test.ts +49 -0
  81. package/template/modules/notes/note.service.ts +108 -0
  82. package/template/{controllers → modules/notes}/notes.controller.ts +17 -11
  83. package/template/modules/notes/notes.module.ts +37 -0
  84. package/template/package.json +5 -3
  85. package/template/scripts/test.sh +33 -0
  86. package/template/tsconfig.json +29 -30
  87. package/dist/chunk-35PNTIRN.js.map +0 -1
  88. package/dist/chunk-CJSKYY76.js +0 -627
  89. package/dist/chunk-CJSKYY76.js.map +0 -1
  90. package/dist/chunk-CRQKCRGF.js +0 -276
  91. package/dist/chunk-CRQKCRGF.js.map +0 -1
  92. package/dist/chunk-G4R6BTLV.js +0 -662
  93. package/dist/chunk-G4R6BTLV.js.map +0 -1
  94. package/dist/chunk-VDF2T4AS.js.map +0 -1
  95. package/dist/chunk-XABBC7JP.js +0 -55
  96. package/dist/chunk-XABBC7JP.js.map +0 -1
  97. package/dist/endpoint-CTEHhb7A.d.ts +0 -2386
  98. package/dist/endpoint-DYHMo6cC.d.cts +0 -2386
  99. package/dist/index-CW21M9Z3.d.ts +0 -1222
  100. package/dist/index-CmBK76nx.d.cts +0 -1222
  101. package/template/services/note.service.test.ts +0 -45
  102. package/template/services/note.service.ts +0 -76
  103. /package/dist/{chunk-7D4SUZUM.js.map → chunk-VXPNPVAG.js.map} +0 -0
  104. /package/template/{models/notes → modules/notes/dto}/create.ts +0 -0
@@ -1,1222 +0,0 @@
1
- import { Tables, TableTypes, Schemas } from './db/env.js';
2
- import { D as DBClient, b8 as TxPlanHandle, bh as TxTable, M as Materialized } from './endpoint-CTEHhb7A.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' | 'vector';
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
- /** Pending FK target, resolved by `defineSchema` once every binding exists. */
30
- referencesThunk?: () => ColumnBuilder;
31
- /** FK onto THIS table — no thunk needed, the target is the declaring table. */
32
- selfRefColumn?: string;
33
- /** This column OWNS the row: erasure, RLS and the `owner` relation read it. */
34
- owns?: true;
35
- /** Explicit name for the FORWARD relation (child → parent), when the one
36
- * derived from the column would be ambiguous or unclear. */
37
- refAs?: string;
38
- /**
39
- * Explicit name for the REVERSE relation (parent → children).
40
- *
41
- * Separate from {@link refAs} because the two directions are different facts:
42
- * the forward name describes the parent this row points at (`author`), the
43
- * reverse one describes the rows hanging off the parent (`posts`). One option
44
- * naming both made an ordinary schema undeclarable — `posts.author_id` and
45
- * `comments.author_id` both named `{ as: "author" }` collided on `users`, and
46
- * the refusal asked for the `{ as }` they had both already written.
47
- */
48
- reverseAs?: string;
49
- /** The table this column belongs to; set by `defineTable`. */
50
- ownerTable?: {
51
- name: string;
52
- columns: Record<string, ColumnBuilder>;
53
- };
54
- /**
55
- * The name this column used to have. A diff cannot tell a rename from a drop and
56
- * an add — both leave one name gone and another present — so the intent has to be
57
- * declared. Without it, renaming a column loses its data.
58
- */
59
- renamedFrom?: string;
60
- /**
61
- * This release's PROMISE that it does not reference this column — neither
62
- * reads it nor writes it, and never names it in a filter, a sort or a SET.
63
- *
64
- * The contraction gate reads it and nothing else does: dropping a column is
65
- * refused while the RUNNING release's declaration still lacks this mark, so
66
- * removing a column is two deploys — mark it, ship, then drop it.
67
- *
68
- * The word is `ignored` and not `deprecated` deliberately: RFC 9745 defines
69
- * deprecation as changing NO behaviour, and this changes what a deploy will
70
- * accept. Rails calls the same thing `ignored_columns`.
71
- */
72
- ignored?: boolean;
73
- onDeleteAction?: OnDeleteAction;
74
- enumName?: string;
75
- enumValues?: string[];
76
- unique?: boolean;
77
- /**
78
- * The value is written by the DATABASE — a trigger, a rule, an identity — not by
79
- * the author and not by a DEFAULT this schema declares. It makes the column
80
- * optional on INSERT without putting a DEFAULT in the DDL.
81
- *
82
- * Before this existed the only way to keep a trigger-filled column off the
83
- * INSERT type was to give it a fake `default()`: a value the schema claimed to
84
- * write and the trigger immediately overwrote. That made the schema lie about
85
- * its own data.
86
- */
87
- dbAssigned?: boolean;
88
- /** vector(n): the declared dimension count — part of the TYPE (typmod), read
89
- * by the wire serializer and the deploy's auto-index (FR-001). */
90
- dimensions?: number;
91
- /**
92
- * How the stored value is projected in and out of this process (FR-009).
93
- *
94
- * NOT part of the DDL: the column's Postgres type is unchanged and this pair
95
- * is never serialized into a migration. It exists so the row surface can hand
96
- * back the type the application actually works with.
97
- */
98
- transform?: ColumnTransform;
99
- }
100
- /**
101
- * The read/write pair a column may declare (FR-009).
102
- *
103
- * `fromDb` takes whatever the driver produced for this column and returns the
104
- * value the application sees; `toDb` is its inverse on the way out. Kept
105
- * deliberately unexported — a column declares one inline, nobody needs to name
106
- * the shape.
107
- */
108
- interface ColumnTransform<T = unknown> {
109
- fromDb: (value: unknown) => T;
110
- toDb: (value: T) => unknown;
111
- }
112
- declare const __colKind: unique symbol;
113
- declare const __colNullable: unique symbol;
114
- declare const __colHasDefault: unique symbol;
115
- declare const __colEnumValues: unique symbol;
116
- declare const __colPayload: unique symbol;
117
- declare const __colTransform: unique symbol;
118
- /**
119
- * Fluent column builder with phantom type params:
120
- * K — ColumnType literal (e.g. "text", "integer")
121
- * N — boolean: true when nullable() has been called last (false = NOT NULL)
122
- * D — boolean: true when a default has been set
123
- * E — enum value union (never for non-enum columns)
124
- * P — jsonb payload shape (unknown unless jsonb<T>() supplied one)
125
- * T — transform target type (`never` when the column declares no transform;
126
- * `never` is the sentinel because it is the only type that survives
127
- * `[T] extends [never]` and never collides with a real target type)
128
- *
129
- * All six params have defaults so bare `ColumnBuilder` (no args) still
130
- * satisfies `Record<string, ColumnBuilder>` in schema.ts without modification.
131
- *
132
- * The six `declare readonly` brand fields carry the phantom types into the
133
- * structural shape so that conditional types like ColValue<C> can discriminate
134
- * on K without requiring runtime values on those fields.
135
- */
136
- declare class ColumnBuilder<K extends ColumnType = ColumnType, N extends boolean = boolean, D extends boolean = boolean, E = unknown, P = unknown, T = unknown> {
137
- readonly [__colKind]: K;
138
- readonly [__colNullable]: N;
139
- readonly [__colHasDefault]: D;
140
- readonly [__colEnumValues]: E;
141
- readonly [__colPayload]: P;
142
- readonly [__colTransform]: T;
143
- readonly _def: ColumnDef;
144
- constructor(type: K, existingDef?: ColumnDef);
145
- /** Mark this column as the primary key. */
146
- primaryKey(): ColumnBuilder<K, N, D, E, P, T>;
147
- /** Mark this column as NOT NULL (default). */
148
- notNull(): ColumnBuilder<K, false, D, E, P, T>;
149
- /** Allow NULL values. */
150
- nullable(): ColumnBuilder<K, true, D, E, P, T>;
151
- /** Set a default value. */
152
- default(value: unknown): ColumnBuilder<K, N, true, E, P, T>;
153
- /** UUID: generate a random default (gen_random_uuid()). */
154
- defaultRandom(): ColumnBuilder<K, N, true, E, P, T>;
155
- /** Timestamp: default to now(). */
156
- defaultNow(): ColumnBuilder<K, N, true, E, P, T>;
157
- /**
158
- * The DATABASE assigns this column's value — a trigger, a rule, an identity.
159
- *
160
- * The column becomes optional on INSERT (the author has nothing to send) while
161
- * the DDL stays free of a DEFAULT this schema would not honour. It is NOT
162
- * `default()`: that declares a value the schema promises to write.
163
- *
164
- * Naming: deliberately not `generated()`. Postgres has GENERATED columns and
165
- * they are a different thing; borrowing the word would send a reader — or a
166
- * model writing a schema — to the wrong feature.
167
- */
168
- dbAssigned(): ColumnBuilder<K, N, true, E, P, T>;
169
- /** Add a foreign key reference. */
170
- /**
171
- * Declares that this column used to be called `previous`.
172
- *
173
- * A schema diff sees one name gone and another present; it cannot know whether
174
- * you renamed a column or dropped one and added another, and the two are very
175
- * different — the second loses every value. Saying so here turns the plan into
176
- * `ALTER TABLE … RENAME COLUMN` instead.
177
- *
178
- * Once the rename has been applied the annotation is inert (the old name is no
179
- * longer there to rename), so it can be deleted at your leisure.
180
- */
181
- renamedFrom(previous: string): ColumnBuilder<K, N, D, E, P, T>;
182
- /**
183
- * See {@link ColumnDef.ignored}.
184
- *
185
- * COPIES the def rather than mutating it. The constructor takes an existing
186
- * def BY REFERENCE, so every builder derived from another shares one object —
187
- * `const a = slug.unique()` leaves `a._def === slug._def`. An in-place
188
- * `ignored = true` therefore marks every column sharing that def, including
189
- * one another table actively reads, and the gate would let THAT column be
190
- * dropped. Measured before this copy existed.
191
- *
192
- * The aliasing is older than this method and other fields leak through it too.
193
- * The reason this one cannot wait: every other leak produces a VISIBLE schema
194
- * difference — the plan shows it, the DDL shows it. This one is invisible by
195
- * design (no DDL, no diff, no plan line), so its only effect is to disarm a
196
- * safety gate in silence.
197
- */
198
- ignored(): ColumnBuilder<K, N, D, E, P, T>;
199
- /**
200
- * Foreign key onto another table's column.
201
- *
202
- * The target is a THUNK, not a direct reference. In a cycle (`x → y`, `y → x`)
203
- * the second table does not exist yet when the first is built; a direct
204
- * reference makes TypeScript chase its own tail (TS7022 — measured, and making
205
- * the return type independent of the target does NOT help). The thunk is
206
- * invoked in `defineSchema`, where every binding exists and every table
207
- * already knows its name.
208
- *
209
- * In a cycle, ONE side needs an explicit return type:
210
- * `references((): AnyColumn => y.id)`. One side is enough — measured.
211
- * For a self-reference use `selfReferences(column)`: no thunk, no annotation.
212
- *
213
- * `as` names the FORWARD relation (`author_id` → `author` by default);
214
- * `reverseAs` names the REVERSE one on the parent, whose default is this
215
- * table's own name (`users.posts`). Two foreign keys from one table onto one
216
- * parent therefore need a `reverseAs` on at least one of them — the reverse
217
- * names would otherwise both be this table's name.
218
- */
219
- references(target: () => AnyColumn, opts?: {
220
- as?: string;
221
- reverseAs?: string;
222
- onDelete?: OnDeleteAction;
223
- }): ColumnBuilder<K, N, D, E, P, T>;
224
- /**
225
- * Foreign key onto THIS table (`parent_id → id`) — category trees, comment
226
- * replies, org charts.
227
- *
228
- * No thunk and no type annotation: the target table is the one being declared,
229
- * so there is nothing to defer and nothing for TypeScript to chase in a circle.
230
- * Drizzle forces an explicit `(): AnyPgColumn =>` here because its reference
231
- * always goes through a callback; measured, we do not need one.
232
- */
233
- selfReferences(column: string, opts?: {
234
- as?: string;
235
- onDelete?: OnDeleteAction;
236
- }): ColumnBuilder<K, N, D, E, P, T>;
237
- /** Set the ON DELETE action for a foreign key reference. */
238
- onDelete(action: OnDeleteAction): ColumnBuilder<K, N, D, E, P, T>;
239
- /** Add a single-column UNIQUE constraint. */
240
- unique(): ColumnBuilder<K, N, D, E, P, T>;
241
- /**
242
- * Declare how this column's value is projected in and out of the process.
243
- *
244
- * The DDL does not move: `numeric` stays `numeric`, and the driver still hands
245
- * back what Postgres sent. What changes is the type the row surface exposes —
246
- * it becomes `Target`:
247
- *
248
- * amount: numeric().transform<number>({ fromDb: Number, toDb: String })
249
- *
250
- * `numeric` surfacing as `string` is CORRECT (a JS number cannot hold
251
- * arbitrary precision), and that is exactly why this exists: application code
252
- * that does arithmetic on the column otherwise rewrites the same
253
- * `Number(row.amount)` / `String(x)` pair in every controller that touches it,
254
- * and each rewrite is a place the two directions can drift apart.
255
- *
256
- * A transform is a PROJECTION, never a constraint: it lives only in this
257
- * process, so it can neither validate nor migrate what is stored.
258
- */
259
- transform<Target>(fns: ColumnTransform<Target>): ColumnBuilder<K, N, D, E, P, Target>;
260
- }
261
- /**
262
- * Extracts the TypeScript value type for a column, respecting nullability.
263
- * - "uuid" | "text" | "timestamp" | "bigint" | "numeric" → string (or string | null when N = true)
264
- * Note: bigint/numeric surface as string — JS number loses precision past 2^53,
265
- * and pgx/PostgREST serialize int8/numeric as strings. App code uses
266
- * BigInt(row.amount) for bigint, or a decimal lib for numeric.
267
- * - "integer" → number
268
- * - "boolean" → boolean
269
- * - "jsonb" → P (the dev-supplied payload shape from jsonb<T>(), else unknown)
270
- * - "enum" → E (the union of literal values)
271
- *
272
- * A declared `.transform<T>()` OVERRIDES the table above: the column then
273
- * surfaces as T (or T | null when nullable), because that is the value the
274
- * application is handed. Nullability is still the column's, not the
275
- * transform's — `fromDb` is not called for a NULL.
276
- */
277
- type ColValue<C> = C extends ColumnBuilder<ColumnType, infer N, boolean, unknown, unknown, infer T> ? [unknown] extends [T] ? ColStoredValue<C> : N extends true ? T | null : T : never;
278
- /** The value as the DATABASE hands it over — the branch table above, before any
279
- * transform. This is what a column's `fromDb` receives. */
280
- type ColStoredValue<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<'vector', infer N, infer _D, infer _E, infer _P> ? N extends true ? number[] | null : number[] : C extends ColumnBuilder<'enum', infer N, infer _D, infer E, infer _P> ? N extends true ? E | null : E : never;
281
- /**
282
- * True when a column is optional on INSERT:
283
- * - nullable columns (N = true) — the DB allows NULL so the field may be omitted
284
- * - columns with a default (D = true) — the DB fills in the value when absent
285
- */
286
- type ColIsOptionalOnInsert<C> = C extends ColumnBuilder<ColumnType, true, boolean, unknown, unknown, unknown> ? true : C extends ColumnBuilder<ColumnType, boolean, true, unknown, unknown, unknown> ? true : false;
287
- /** Create a UUID column. */
288
- declare function uuid(): ColumnBuilder<'uuid', false, false, never>;
289
- /** Create a TEXT column. */
290
- declare function text(): ColumnBuilder<'text', false, false, never>;
291
- /** Create an INTEGER column. Emits int4 (max ~2.1B). */
292
- declare function integer(): ColumnBuilder<'integer', false, false, never>;
293
- /**
294
- * Create a BIGINT column (Postgres int8, max ~9.2×10^18).
295
- * Surfaces as `string` in row/insert types — JS number loses precision past 2^53
296
- * and pgx/PostgREST serialize int8 as a JSON string. Use BigInt(row.column) in app code.
297
- */
298
- declare function bigint(): ColumnBuilder<'bigint', false, false, never>;
299
- /**
300
- * Create a NUMERIC column (Postgres `numeric`/`decimal`, arbitrary precision).
301
- * For exact fractional values (money with cents as a decimal, rates, weights)
302
- * where int4/int8 don't fit. Surfaces as `string` in row/insert types — JS
303
- * number can't hold arbitrary-precision decimals without rounding, and
304
- * pgx/PostgREST serialize numeric as a JSON string. Parse with a decimal lib
305
- * (or BigInt for scaled integers) in app code.
306
- */
307
- declare function numeric(): ColumnBuilder<'numeric', false, false, never>;
308
- /** Create a BOOLEAN column. */
309
- declare function boolean(): ColumnBuilder<'boolean', false, false, never>;
310
- /** Create a TIMESTAMP column. */
311
- declare function timestamp(): ColumnBuilder<'timestamp', false, false, never>;
312
- /**
313
- * Create a JSONB column. Pass a payload type to make the generated row/insert
314
- * type concrete instead of `unknown`:
315
- *
316
- * tags: jsonb<string[]>() // row.tags: string[]
317
- * meta: jsonb<{ tier: string }>() // row.meta: { tier: string }
318
- * raw: jsonb() // row.raw: unknown (back-compat)
319
- *
320
- * The runtime accepts a plain JS object/array directly (no JSON.stringify); the
321
- * generic only refines the TYPE the env codegen emits.
322
- */
323
- declare function jsonb<T = unknown>(): ColumnBuilder<'jsonb', false, false, never, T>;
324
- /**
325
- * Create an ENUM column.
326
- * @param name The PostgreSQL enum type name (used in DDL).
327
- * @param values A readonly tuple of valid string values — kept `const` so the
328
- * union `V[number]` is as narrow as possible.
329
- */
330
- declare function enumType<const V extends readonly string[]>(name: string, values: V): ColumnBuilder<'enum', false, false, V[number]>;
331
- /** vector(n) — pgvector kolonu. n TİPİN parçasıdır (typmod) ve [1, 2000] —
332
- * 2000 = pgvector'ün HNSW-indekslenebilir tavanı; auto-index bu beyanla bağlı
333
- * (spec FR-001, D-3). */
334
- declare function vector(dimensions: number): ColumnBuilder<'vector', false, false, unknown, number[]>;
335
- /**
336
- * Any column, whatever its type parameters.
337
- *
338
- * Exported so a cycle can be broken from ONE side:
339
- * `references((): AnyColumn => y.id)`.
340
- */
341
- type AnyColumn = ColumnBuilder;
342
- /**
343
- * The column that OWNS this row: a `text` FK onto `auth.users(id)`, NOT NULL,
344
- * ON DELETE CASCADE.
345
- *
346
- * Ownership drives account erasure, so cascade is the only correct action and
347
- * takes no argument. The referencing column must be `text` (palauth ids are
348
- * `usr_<uuid>`) and NOT NULL — both are implied here rather than left to the
349
- * caller, so three rules the type could not express before become UNWRITABLE.
350
- *
351
- * At most ONE per table. The old shape let several columns reference
352
- * `auth.users` and picked the FIRST IN DECLARATION ORDER as the owner — moving a
353
- * `created_by` above a `user_id` silently changed which rows an account deletion
354
- * took with it. A second one is now rejected at push.
355
- *
356
- * For a column that merely POINTS at a user without owning the row
357
- * (`created_by`, `edited_by`), use `userRef({ onDelete })`.
358
- */
359
- declare function ownedByUser(): ColumnBuilder<'text', false, false, never>;
360
- /**
361
- * A plain FK onto `auth.users(id)` that does NOT own the row.
362
- *
363
- * `created_by` / `edited_by`: deleting that user must not delete the row. ON
364
- * DELETE is required and limited to `cascade | set null` so an erasure request
365
- * is never blocked by a lingering FK; `set null` needs a nullable column.
366
- */
367
- declare function userRef(opts: {
368
- onDelete: AuthUserOnDelete;
369
- as?: string;
370
- }): ColumnBuilder<'text', boolean, false, never>;
371
- /**
372
- * A plain FK onto `auth.installations(id)` — the app-scoped verified-device
373
- * anchor.
374
- *
375
- * An installation is an APP INSTALL, not a user: this is NOT ownership. A
376
- * user-owned row still needs its own `ownedByUser()` so account erasure removes
377
- * it; an installation reference alone does not tie a row to a user's deletion.
378
- */
379
- declare function installationRef(opts: {
380
- onDelete: AuthUserOnDelete;
381
- as?: string;
382
- }): ColumnBuilder<'text', boolean, false, never>;
383
-
384
- /**
385
- * policy.ts — the RLS policy authoring DSL.
386
- *
387
- * `policy(name)` returns a fluent builder that mirrors the `ColumnBuilder`
388
- * style in columns.ts: each chainable method mutates the underlying
389
- * definition and returns the builder so calls compose. The terminal value is
390
- * a plain {@link PolicyDef} — the exact JSON shape the runtime's
391
- * `schema_extract.js` reads off the bundled module and the Go side parses into
392
- * `PolicyJSON` (CONTRACT-POLICY).
393
- *
394
- * @example
395
- * import { policy } from "@palbase/backend";
396
- *
397
- * policy("owner_select")
398
- * .for("select")
399
- * .to("authenticated")
400
- * .using("owner = (select auth.uid())");
401
- */
402
- /** The SQL command a policy applies to. `"all"` covers SELECT/INSERT/UPDATE/DELETE. */
403
- type PolicyCommand = "all" | "select" | "insert" | "update" | "delete";
404
- /** Whether a policy is permissive (OR-combined, the default) or restrictive
405
- * (AND-combined). Mirrors Postgres `CREATE POLICY ... AS PERMISSIVE|RESTRICTIVE`. */
406
- type PolicyMode = "permissive" | "restrictive";
407
- /**
408
- * The compiled, serializable policy definition — the EXACT shape consumed by
409
- * `schema_extract.js` → Go `PolicyJSON` (CONTRACT-POLICY).
410
- *
411
- * - `roles`: the DB roles this policy applies to (`TO` clause). An empty array
412
- * means the policy applies to PUBLIC (all roles) — the Postgres default.
413
- * - `using`: the `USING (...)` row-visibility expression, or `null` when none.
414
- * - `withCheck`: the `WITH CHECK (...)` write-validation expression, or `null`.
415
- * - `permissive`: `true` for `AS PERMISSIVE` (default), `false` for restrictive.
416
- */
417
- interface PolicyDef {
418
- name: string;
419
- command: PolicyCommand;
420
- roles: string[];
421
- using: string | null;
422
- withCheck: string | null;
423
- permissive: boolean;
424
- }
425
- declare class PolicyBuilder {
426
- readonly _def: PolicyDef;
427
- constructor(name: string);
428
- /** Restrict the policy to a single SQL command (default `"all"`). */
429
- for(command: PolicyCommand): this;
430
- /**
431
- * Set the DB roles the policy applies to (the `TO` clause), replacing any
432
- * previously-set roles. Call with no arguments to target PUBLIC (all roles).
433
- *
434
- * @example
435
- * policy("p").to("authenticated")
436
- * policy("p").to("authenticated", "service_role")
437
- * policy("p").to() // PUBLIC
438
- */
439
- to(...roles: string[]): this;
440
- /** Set the `USING (...)` row-visibility expression (raw SQL). */
441
- using(sqlExpr: string): this;
442
- /**
443
- * "Rows of THIS table whose owner the caller is a member of" — the membership
444
- * pattern, written so it cannot recurse.
445
- *
446
- * THE TRAP IT EXISTS FOR. Written by hand, membership policies point at each
447
- * other: `channels` is visible to members, so its policy reads
448
- * `channel_members`; `channel_members` is visible to members, so its policy
449
- * reads `channels`. Postgres refuses the pair at query time with `infinite
450
- * recursion detected in policy for relation ...`, and the error names the
451
- * relation but not the cycle. The way out is asymmetry — the MEMBERSHIP table
452
- * is protected by `user_id = auth.uid()` and nothing else, and every other
453
- * table subqueries INTO it. That shape was in the platform's own schema and
454
- * written down nowhere; a customer recovered it by reading that schema.
455
- *
456
- * `(select auth.uid())` rather than a bare call: the scalar subquery is
457
- * evaluated ONCE per statement instead of per row.
458
- *
459
- * @example
460
- * // channels: visible to members. The membership table gets the simple one.
461
- * policy("member_read").for("select").to("authenticated")
462
- * .memberOf("channel_members", "channel_id")
463
- * // → id IN (SELECT "channel_id" FROM "channel_members"
464
- * // WHERE "user_id" = (select auth.uid()))
465
- */
466
- memberOf(membershipTable: string, foreignKey: string, options?: {
467
- column?: string;
468
- userColumn?: string;
469
- }): this;
470
- /** Set the `WITH CHECK (...)` write-validation expression (raw SQL). */
471
- withCheck(sqlExpr: string): this;
472
- /** Set the policy mode: `"permissive"` (default, OR-combined) or
473
- * `"restrictive"` (AND-combined). */
474
- as(mode: PolicyMode): this;
475
- }
476
- /**
477
- * Start authoring an RLS policy. Returns a {@link PolicyBuilder}; the resulting
478
- * `PolicyBuilder` is accepted directly in a table's `policies: [...]` array
479
- * (its `_def` is read at schema-extract time).
480
- *
481
- * @param name The policy name. Palbase reconciliation keys policies by
482
- * `(table, name)`, so names must be unique per table.
483
- */
484
- declare function policy(name: string): PolicyBuilder;
485
-
486
- /**
487
- * Postgres extensions a Palbase project can enable from its schema.
488
- *
489
- * Extensions are config-as-code: declare them in `defineSchema({ extensions })`
490
- * and the deploy installs them (CREATE EXTENSION … SCHEMA extensions) using the
491
- * deploy path's privileged connection. They are NOT toggled live from Studio —
492
- * CREATE EXTENSION requires a superuser role that only the deploy path holds.
493
- *
494
- * The list is an allowlist (a string-literal union) so editors autocomplete the
495
- * supported names and a typo fails typecheck. It is intentionally extensible:
496
- * add a name here (+ confirm the base image ships it) to support more.
497
- */
498
- declare const PALBASE_EXTENSIONS: readonly ["vector", "pg_trgm", "unaccent", "citext", "cube", "earthdistance", "hstore", "ltree", "btree_gist", "pgcrypto", "uuid-ossp"];
499
- /** A Postgres extension supported by Palbase (allowlist union). */
500
- type PalbaseExtension = (typeof PALBASE_EXTENSIONS)[number];
501
- /**
502
- * Extensions that depend on another extension. The deploy installs
503
- * dependencies first; declaring `earthdistance` without `cube` still works
504
- * because the deploy resolves the order, but listing both is clearer.
505
- */
506
- declare const EXTENSION_DEPENDENCIES: Partial<Record<PalbaseExtension, PalbaseExtension[]>>;
507
- /** Runtime guard: is `name` a supported Palbase extension? */
508
- declare function isPalbaseExtension(name: string): name is PalbaseExtension;
509
-
510
- /**
511
- * A named raw-SQL DDL object declared in a schema file (`db/public.ts`,
512
- * `db/billing.ts`, …) for anything the typed DSL
513
- * cannot express (EXCLUDE, CHECK, partial/expression indexes, triggers, views).
514
- * The deploy emits `up` verbatim on the privileged DDL connection — same trust
515
- * posture as policy().using(). Tracked by NAME (not by diffing the body), so a
516
- * changed body needs a new name or an explicit drop+add.
517
- */
518
- interface RawConstraintDef {
519
- name: string;
520
- up: string;
521
- down?: string;
522
- }
523
- declare function raw(name: string, up: string, opts?: {
524
- down?: string;
525
- }): RawConstraintDef;
526
-
527
- /** Embedding sağlayıcı DESKRIPTORU — canlı istemci değil, düz veri: şemayla
528
- * birlikte serileşir, çağrıyı Go worker (yazma) ve engine (sorgu) yapar.
529
- * Adlandırma Vercel AI SDK'nın aynasıdır (openai.embedding("...")) ama paket
530
- * bağımlılığı bilinçli olarak YOKTUR (spec C-3, UD-016). v1 sağlayıcı: openai (D-10). */
531
- interface EmbeddingModelRef {
532
- provider: "openai";
533
- model: string;
534
- dimensions?: number;
535
- apiKeyName?: string;
536
- baseURL?: string;
537
- }
538
- /** Chat/damıtma modeli DESKRIPTORU (C-11, D-019) — memory beyanının extract'i.
539
- * Embedding gibi düz veridir; çağrıyı worker yapar, anahtar vault'taki
540
- * OPENAI_API_KEY'dir (D-017: aynı sağlayıcı, yeni dış sistem yok). */
541
- interface ChatModelRef {
542
- provider: "openai";
543
- model: string;
544
- }
545
- declare const openai: {
546
- embedding(model: string, opts?: {
547
- dimensions?: number;
548
- apiKeyName?: string;
549
- baseURL?: string;
550
- }): EmbeddingModelRef;
551
- chat(model: string): ChatModelRef;
552
- };
553
-
554
- /**
555
- * A map of column builders keyed by column name — the value you write under
556
- * the `columns` key of `defineTable("<name>", { columns })`.
557
- *
558
- * The default `Record<string, ColumnBuilder>` keeps bare references compiling
559
- * without a type argument.
560
- */
561
- type ColumnMap = Record<string, ColumnBuilder>;
562
- /**
563
- * The author-facing value written under each table key:
564
- * `{ columns, rls?, policies? }`.
565
- *
566
- * - `columns`: the column map (required).
567
- * - `rls`: enable + FORCE row-level security on this table. **Defaults to
568
- * `true`**, and is forced on when `policies` is non-empty. A table with RLS
569
- * and no policies is deny-all, which is the starting state: nothing reads it
570
- * until a policy says who may. Set `rls: false` only for a genuinely public
571
- * table — it is an explicit opt-out that a reviewer can grep for, not
572
- * something you get by forgetting.
573
- * - `policies`: the RLS policies for this table, authored with `policy(name)`.
574
- * Each entry may be a {@link PolicyBuilder} (the normal `policy(...)` chain)
575
- * or a raw {@link PolicyDef} object.
576
- *
577
- * The `C` type parameter preserves the precise per-column phantom types so the
578
- * typed `Database.tables.*` surface keeps inferring insert/row shapes.
579
- */
580
- /** Arama metriği — tek kelime; opclass ve operatör bundan türetilir, asla yüzeye çıkmaz (D-5/D-6). */
581
- type SearchMetric = "cosine" | "euclidean" | "inner_product";
582
- /** Bir vektör arama kolu. `model` varsa auto-embed: platform yazma+sorgu embedding'ini üstlenir (D-9). */
583
- interface VectorSearchDecl {
584
- /** Hedef vector kolonu; tabloda TEK vector kolonu varsa atlanabilir (Go apply çözer, FR-010). */
585
- column?: string;
586
- /** Varsa auto-embed. Deskriptor düz veridir — şemayla serileşir (C-3). */
587
- model?: EmbeddingModelRef;
588
- /** model varsa ZORUNLU: embed kaynak kolonları = trigger'ın UPDATE OF listesi (C-2). */
589
- from?: string[];
590
- metric?: SearchMetric;
591
- /** Kaynak metin değişince ara-dönem davranışı (yalnız auto-embed'de):
592
- * "null" (vars.) — embedding anında NULL'lanır; satır yeni vektör yazılana
593
- * dek anlamsal aramada aday değildir. Bayat eşleşme asla servis edilmez.
594
- * "keep" — eski vektör aramada kalır, platform yenisini yazınca sessizce
595
- * değişir. Görünürlük penceresi sıfır; bedeli saniyeler süren bayat
596
- * eşleşme riski (Confluence-tipi sync yükleri için). */
597
- staleness?: "null" | "keep";
598
- }
599
- /** Tablonun arama beyanı — İKİ biçim (D-007, tek yüzey):
600
- *
601
- * YENİ (önerilen): `{ from, model, ... }` — `from` kolonları hem FTS'e hem
602
- * embed'e girer. Tabloda vector kolonu declare edilmişse SATIR-modu; yoksa
603
- * CHUNK-modu otomatiktir (D-010): vektörler türev `__palbase_chunks`
604
- * tablosunda yaşar, içerik otomatik bölünür. `text: false` FTS'i kapatır,
605
- * `text: [..]` FTS kolonlarını from'dan ayırır. `chunks` yalnız ince ayar.
606
- *
607
- * ESKİ: `text: string[]` + `vector: {...}` — aynen çalışır, wire çıktısı
608
- * bayt-aynı kalır (NFR-B1). İki biçim KARIŞTIRILAMAZ. */
609
- interface SearchDecl {
610
- text?: string[] | boolean;
611
- vector?: VectorSearchDecl | VectorSearchDecl[];
612
- /** Yeni biçim: arama kaynağı kolonlar (FTS + embed). Varlığı yeni biçimi seçer. */
613
- from?: string[];
614
- /** Yeni biçim: auto-embed modeli (zorunlu — BYO için eski biçimi kullanın). */
615
- model?: EmbeddingModelRef;
616
- metric?: SearchMetric;
617
- staleness?: "null" | "keep";
618
- /** Chunk-modu ince ayarı (yalnız vector kolonsuz tabloda anlamlı). */
619
- chunks?: {
620
- size?: number;
621
- overlap?: number;
622
- };
623
- /** Sorgu-yeniden-yazımı: tek yönlü eş anlamlı haritası (FR-026). */
624
- synonyms?: Record<string, string[]>;
625
- /** Geçerlilik kolonları türetilir; arama varsayılan yalnız günceli tarar (FR-029). */
626
- validity?: boolean;
627
- }
628
- /** Hafıza beyanı (FR-032, D-019): kaynak tablonun yazımlarından platform
629
- * fact damıtır ve `into` tablosuna yazar. Hedef NORMAL declared tablodur —
630
- * kendi search/unique/validity beyanlarıyla. Okuma = Database.search(into).
631
- * subject default "owner": fact'in kime ait olduğu kolonu (iki tabloda da). */
632
- interface MemoryDecl {
633
- from: string[];
634
- into: string;
635
- extract: ChatModelRef;
636
- subject?: string;
637
- }
638
- interface TableInput<C extends ColumnMap = ColumnMap> {
639
- columns: C;
640
- rls?: boolean;
641
- policies?: (PolicyBuilder | PolicyDef)[];
642
- /** Composite/named primary key (ordered column names). Omit for single-column inline .primaryKey(). */
643
- primaryKey?: string[];
644
- /** Named multi-column UNIQUE constraints. */
645
- unique?: {
646
- name: string;
647
- columns: string[];
648
- }[];
649
- /** Named raw-SQL DDL objects (EXCLUDE, triggers, views) that the typed DSL cannot express. */
650
- raw?: RawConstraintDef[];
651
- /**
652
- * Named first-class CHECK constraints. Diffed by NAME with a BODY compare:
653
- * a changed `expr` (after pg normalization) recreates the constraint
654
- * (DROP + ADD). `expr` is trusted SQL emitted verbatim (like policy USING),
655
- * `name` is identifier-validated.
656
- */
657
- checks?: {
658
- name: string;
659
- expr: string;
660
- }[];
661
- /**
662
- * Plain (non-unique) btree indexes over an ordered column list, emitted as
663
- * standalone `CREATE INDEX [IF NOT EXISTS] name ON table (col1, col2)`
664
- * statements (NOT a table clause — a separate migration statement category).
665
- * Structural compare by NAME (no expression normalization). `name` and each
666
- * column are identifier-validated by the Go differ.
667
- *
668
- * Scope: columns-only plain btree. Partial (`where`) and expression indexes
669
- * are a deliberate follow-up — modelling them needs the same raw-SQL
670
- * normalization round-trip CHECK uses (Task 10), so they are NOT in this
671
- * type yet to avoid a half-working partial-index path.
672
- */
673
- indexes?: {
674
- name: string;
675
- columns: string[];
676
- }[];
677
- /** Arama beyanı — bkz. SearchDecl. */
678
- search?: SearchDecl;
679
- /** Hafıza beyanı — bkz. MemoryDecl (FR-032). */
680
- memory?: MemoryDecl;
681
- }
682
- /**
683
- * A table definition — the runtime value the Go runtime's `schema_extract.js`
684
- * reads. It keys tables by `tableDef.name`, reads `tableDef.columns` for the
685
- * column DDL, and `tableDef.rls` + `tableDef.policies` for RLS.
686
- *
687
- * `defineSchema` derives `name` from the object key, so authors never repeat
688
- * the table name. `rls`/`policies` are always present after normalization
689
- * (defaulted to `true`/`[]`).
690
- *
691
- * The `C` type parameter preserves the precise per-column phantom types so that
692
- * downstream mapped types (InsertShape, RowShape) can discriminate on them.
693
- */
694
- interface TableDef<C extends ColumnMap = ColumnMap> {
695
- name: string;
696
- columns: C;
697
- rls: boolean;
698
- policies: PolicyDef[];
699
- primaryKey?: string[];
700
- unique?: {
701
- name: string;
702
- columns: string[];
703
- }[];
704
- /** Named raw-SQL DDL objects emitted verbatim on deploy. Tracked by name. */
705
- raw?: RawConstraintDef[];
706
- /** Named first-class CHECK constraints. Diffed by name + (normalized) body. */
707
- checks?: {
708
- name: string;
709
- expr: string;
710
- }[];
711
- /** Plain btree indexes (columns-only), emitted as standalone CREATE INDEX. Diffed by name. */
712
- indexes?: {
713
- name: string;
714
- columns: string[];
715
- }[];
716
- /** Arama beyanı, doğrulanmış ve taşınmış hali. */
717
- search?: SearchDecl;
718
- /** Hafıza beyanı, doğrulanmış ve taşınmış hali (FR-032). */
719
- memory?: MemoryDecl;
720
- }
721
- /**
722
- * A schema definition containing multiple tables, keyed by table name.
723
- *
724
- * The `T` type parameter preserves the exact `TableDef<...>` type for each
725
- * table so that `SchemaDef["tables"]["rooms"]` resolves to the precise
726
- * `TableDef<{ id: ColumnBuilder<'uuid', false, true, never>; ... }>`.
727
- */
728
- interface SchemaDef<T extends Record<string, TableDef> = Record<string, TableDef>> {
729
- /** The schema's own name — `public`, `billing`, … Declared, never derived. */
730
- name: string;
731
- /** Reachable over HTTP? False unless the author opts in. */
732
- exposed: boolean;
733
- tables: T;
734
- /** Postgres extensions to install on deploy. Normalized to `[]` when absent. */
735
- extensions: PalbaseExtension[];
736
- }
737
- /** Map the author's `{ tables: { <name>: { columns } } }` input to the
738
- * `{ tables: { <name>: TableDef<columns> } }` runtime/type shape, threading the
739
- * per-table column map `T[K]["columns"]` so column-level inference survives. */
740
- type TablesByName<T extends readonly TableHandle[]> = {
741
- [K in T[number] as K[typeof TABLE_META]["name"]]: K[typeof TABLE_META];
742
- };
743
- /**
744
- * How a schema file reads. One file per schema — `db/public.ts`, `db/billing.ts`
745
- * — and the schema says its own name:
746
- *
747
- * const todos = defineTable("todos", {
748
- * columns: {
749
- * id: uuid().primaryKey().defaultRandom(),
750
- * owner: text().notNull(),
751
- * title: text().notNull(),
752
- * },
753
- * rls: true,
754
- * policies: [
755
- * policy("owner_all").for("all").to("authenticated")
756
- * .using("owner = (select auth.uid())")
757
- * .withCheck("owner = (select auth.uid())"),
758
- * ],
759
- * });
760
- *
761
- * export default defineSchema("public", { tables: [todos] });
762
- *
763
- * The dictionary form `defineSchema({ tables: { todos: {…} } })` is RETIRED and
764
- * refused by name: a table whose name comes from a key does not know what it is
765
- * called while it is being built, so it cannot resolve `references(() =>
766
- * other.id)`, and a schema that does not say its own name cannot tell one
767
- * `invoices` from another schema's `invoices`.
768
- *
769
- * The returned value is `{ name, tables: { todos: { name, columns, rls,
770
- * policies } }, extensions, exposed }` — the exact shape the runtime schema
771
- * extractor parses. Per-column phantom types are preserved so
772
- * `Database.tables.todos.insert({...})` stays typed.
773
- *
774
- * RLS normalization: `rls` defaults to **`true`**, `policies` to `[]`. A table
775
- * that declares neither is therefore deny-all — nothing reads it until a policy
776
- * says who may, which is the safe starting point rather than a bug. Declare
777
- * `rls: false` for a genuinely public table; that is an explicit, greppable
778
- * statement of intent instead of an omission. When `policies` is non-empty,
779
- * `rls` is forced on (ENABLE + FORCE) regardless of the declared flag — a table
780
- * with policies must have RLS enabled or the policies would be inert.
781
- */
782
- /**
783
- * Where a table's metadata lives.
784
- *
785
- * The table VALUE is its columns, so `lists.id` is the column — which means a
786
- * column called `name` would overwrite the table's own name if metadata sat in
787
- * a plain field. Measured, not imagined: the existing `users` / `rooms` tests
788
- * (both declare a `name` column) produced an `[object Object]` table key.
789
- * `columns`, `rls`, `search`, `memory`, `unique` and `indexes` are all plausible
790
- * column names too. A column cannot collide with a symbol.
791
- */
792
- declare const TABLE_META: unique symbol;
793
- /** A value produced by `defineTable`: the columns, plus metadata behind the symbol. */
794
- type TableHandle<C extends ColumnMap = ColumnMap, N extends string = string> = C & {
795
- readonly [TABLE_META]: TableDef<C> & {
796
- name: N;
797
- };
798
- };
799
- /**
800
- * Declare one table as a standalone value that KNOWS ITS OWN NAME.
801
- *
802
- * The name lives here, not in a dictionary key. A table whose name comes from a
803
- * key cannot resolve `references(() => other.id)` when it is built — it does not
804
- * know what to call itself — which forces a two-phase resolution with an
805
- * empty-name intermediate state. One name source, no empty identity.
806
- *
807
- * The returned object spreads its own columns, so a sibling can point at it:
808
- * `references(() => lists.id)`.
809
- */
810
- declare function defineTable<const N extends string, C extends ColumnMap>(name: N, input: TableInput<C>): TableHandle<C, N>;
811
- /**
812
- * Collect declared tables into one schema.
813
- *
814
- * Takes an ARRAY, not a dictionary: the tables already carry their names, and a
815
- * second name source is a second thing that can disagree.
816
- */
817
- declare function defineSchema<const T extends readonly TableHandle[]>(name: string, input: {
818
- tables: T;
819
- exposed?: boolean;
820
- extensions?: PalbaseExtension[];
821
- }): SchemaDef<TablesByName<T>>;
822
-
823
- /**
824
- * typed-db.ts — Task 2: TypedDB schema-derived insert/row shapes.
825
- *
826
- * Derives INSERT and full-row TypeScript types from a `defineSchema()` result
827
- * and wraps the untyped runtime `DBClient` with a typed facade.
828
- *
829
- * No value-any. No `as unknown as X`. The two narrow `as` casts in
830
- * `makeTypedTable` are safe because:
831
- * - `data as Record<string, unknown>`: InsertShape<T> maps string keys to
832
- * typed values; all value types are subsets of `unknown`, so the cast is
833
- * structurally sound.
834
- * - `result as RowShape<T>`: The runtime DBClient returns `Record<string,
835
- * unknown>` which is the erased form of the typed row; we're narrowing back
836
- * to the precise shape that the schema declared.
837
- * Both casts are narrowing only (not widening) and correctness is guaranteed
838
- * by the schema the caller provides.
839
- */
840
-
841
- /** Keys of C whose columns are required on INSERT (not nullable, no default). */
842
- type RequiredKeys<C> = {
843
- [K in keyof C]: ColIsOptionalOnInsert<C[K]> extends true ? never : K;
844
- }[keyof C];
845
- /** Keys of C whose columns are optional on INSERT (nullable or has a default). */
846
- type OptionalKeys<C> = {
847
- [K in keyof C]: ColIsOptionalOnInsert<C[K]> extends true ? K : never;
848
- }[keyof C];
849
- /**
850
- * The TypeScript type for an INSERT payload for table `T`.
851
- * - Required: columns that are NOT NULL and have no DB-level default.
852
- * - Optional: columns that are nullable or carry a default.
853
- *
854
- * When all columns are optional, `RequiredKeys<C>` resolves to `never` and
855
- * the first part becomes `{}`, which is a neutral element for `&`.
856
- */
857
- type InsertShape<T extends TableDef> = {
858
- [K in RequiredKeys<T["columns"]>]: ColValue<T["columns"][K]>;
859
- } & {
860
- [K in OptionalKeys<T["columns"]>]?: ColValue<T["columns"][K]>;
861
- };
862
- /**
863
- * The TypeScript type for a full row returned by the DB for table `T`.
864
- * Every column is present; nullable columns resolve to `T | null`.
865
- */
866
- type RowShape<T extends TableDef> = {
867
- [K in keyof T["columns"]]: ColValue<T["columns"][K]>;
868
- };
869
- /** A typed table accessor that mirrors the runtime DBClient surface. */
870
- interface TypedTable<T extends TableDef> {
871
- insert(data: InsertShape<T>): Promise<RowShape<T>>;
872
- upsert(data: InsertShape<T>, opts: {
873
- onConflict: readonly string[];
874
- }): Promise<RowShape<T>>;
875
- /** Update the row by id; resolves to the updated row, or `null` if no row
876
- * matched (absent or RLS-hidden) — an idempotent outcome, mirroring
877
- * `findById`. The runtime returns a null row rather than throwing. */
878
- update(id: string, data: Partial<InsertShape<T>>): Promise<RowShape<T> | null>;
879
- delete(id: string): Promise<void>;
880
- findById(id: string): Promise<RowShape<T> | null>;
881
- /** Rows matching the filter. Operators, ordering and paging are the ENGINE's
882
- * surface — this declaration is what makes them callable. */
883
- findMany(query?: WhereFilter<RowShape<T>>, opts?: FindManyOpts<RowShape<T>>): Promise<RowShape<T>[]>;
884
- /** Update every matching row in one statement; an empty filter is refused. */
885
- updateMany(where: WhereFilter<RowShape<T>>, set: Partial<InsertShape<T>>): Promise<RowShape<T>[]>;
886
- /** Delete every matching row; resolves to how many. Empty filter refused. */
887
- deleteMany(where: WhereFilter<RowShape<T>>): Promise<number>;
888
- /** How many rows match. An empty filter is legitimate: counting is a read. */
889
- count(where?: WhereFilter<RowShape<T>>): Promise<number>;
890
- }
891
- /** A typed DB facade covering all tables declared in schema `S`. */
892
- interface TypedDB<S extends SchemaDef> {
893
- tables: {
894
- [K in keyof S["tables"]]: TypedTable<S["tables"][K]>;
895
- };
896
- /** Run a transaction plan. See {@link EnvTypedDatabase.transaction}. */
897
- transaction<T>(fn: (tx: TypedTx<S>) => T extends Promise<unknown> ? never : T): Promise<Materialized<T>>;
898
- }
899
- /** The plan-building handle a `TypedDB<S>` transaction callback receives: the
900
- * schema's tables, expressed as plan operations rather than awaited calls. */
901
- type TypedTx<S extends SchemaDef> = TxPlanHandle<{
902
- [K in keyof S["tables"]]: TxTable<RowShape<S["tables"][K]>, InsertShape<S["tables"][K]>>;
903
- }>;
904
- /**
905
- * Wraps a raw `DBClient` with the type-safe `TypedDB<S>` facade derived from
906
- * the provided schema. No behavior change for the direct ops — all calls
907
- * delegate to `raw` with the table name as a plain string.
908
- *
909
- * `transaction` does NOT delegate to a per-op client: the callback describes a
910
- * plan against a fresh {@link TxPlanBuilder}, and the whole plan travels in one
911
- * `raw.txPlan` call. The schema is used only for its table NAMES; the values
912
- * are typed by `S` at compile time and are plain strings at run time.
913
- *
914
- * The `as` casts are single structural narrowings from a dynamically-built
915
- * object to the precise mapped type (TS cannot infer the mapped-type result
916
- * through `Object.keys` iteration) — see the module-level doc comment.
917
- */
918
- declare function makeTypedDB<S extends SchemaDef>(schema: S, raw: DBClient): TypedDB<S>;
919
- /** Bir where değeri: düz eşitlik YA DA operatör nesnesi (FR-016). */
920
- type WhereOp<V> = V | {
921
- gt?: V;
922
- gte?: V;
923
- lt?: V;
924
- lte?: V;
925
- neq?: V;
926
- in?: V[];
927
- };
928
- /**
929
- * A filter over a row: every field optional, each one a plain value (equality)
930
- * or an operator object. THE filter language — `findMany`, `updateMany`,
931
- * `deleteMany` and `count` all take this one, because two spellings of a filter
932
- * is how the two come to disagree.
933
- */
934
- type WhereFilter<Row> = {
935
- [K in keyof Row]?: WhereOp<Row[K]>;
936
- };
937
- /**
938
- * Ordering and paging for a read.
939
- *
940
- * `column` is `keyof Row`, not `string`: a mistyped column name is a compile
941
- * error here rather than a runtime rejection three layers down. `offset`
942
- * without `limit` is refused by the engine — a page with no size is not a page.
943
- */
944
- type FindManyOpts<Row> = {
945
- orderBy?: {
946
- column: Extract<keyof Row, string>;
947
- direction?: "asc" | "desc";
948
- };
949
- limit?: number;
950
- offset?: number;
951
- };
952
- /** search() parametreleri, satır tipiyle koşullanmış (FR-013). `offset` BİLEREK yok (UD-013). */
953
- interface SearchParamsTyped<T extends TableTypes> {
954
- /** Metin sorgusu: FTS kolunu besler; embed beyanlıysa sorgu vektörü de bundan üretilir. */
955
- query?: string;
956
- /** Hazır sorgu vektörü — verilirse embed çağrısı olmaz (FR-025). */
957
- vector?: number[];
958
- where?: {
959
- [K in keyof T["row"]]?: WhereOp<T["row"][K]>;
960
- };
961
- /** default 20, tavan 100 (engine uygular). */
962
- limit?: number;
963
- /** Birden çok vektör kolonunda hedef seçimi (model geçişi, FR-013/using). */
964
- using?: string;
965
- mode?: "hybrid" | "text" | "vector";
966
- /** Nihai (RRF-sonrası) skor alt eşiği — süzme LIMIT'ten önce uygulanır (FR-001). */
967
- minScore?: number;
968
- /** Chunk-modunda satır başına en iyi blok sayısı (1..10, vars. 3; FR-015). */
969
- blocksPerRow?: number;
970
- /** Tazelik çürümesi: nihai skor RRF-sonrası exp(-ln(2)*yaş/halfLife) ile çarpılır;
971
- * field bir timestamp kolonu, halfLife "90s" | "15m" | "12h" | "30d" biçiminde (FR-004). */
972
- recency?: {
973
- field: Extract<keyof T["row"], string>;
974
- halfLife: string;
975
- };
976
- /** Filtrelenmiş küme üzerinde kolon başına top-20 değer sayacı — dönüş
977
- * dizisinin `_facets` özelliği (FR-027). */
978
- facets?: Extract<keyof T["row"], string>[];
979
- /** Satır-modunda FTS eşleşme vurgusu: sonuç satırına `_highlight` ekler;
980
- * chunk-modda no-op — bloklar zaten eşleşen kesittir (FR-025). */
981
- highlight?: boolean;
982
- /** Validity'li tabloda zaman penceresi: varsayılan yalnız güncel versiyon;
983
- * "all" tüm versiyonlar; {asOf} o anda geçerli olan (FR-029). */
984
- validity?: "all" | {
985
- asOf: string;
986
- };
987
- /** Alan-boost (FR-030): skor * (1 + w·x/(1+x)) — sayısal kolonla sınırlı
988
- * çarpan, dış servissiz; bileşim RRF → boost → recency → minScore. */
989
- boost?: {
990
- field: Extract<keyof T["row"], string>;
991
- weight: number;
992
- };
993
- }
994
- /** search() dönüş dizisinin sorgu-düzeyi ekleri (FR-027): `_facets` dizinin
995
- * ÖZELLİĞİDİR, satırlara kopyalanmaz (JSON'a satır başına şişme olmasın). */
996
- type SearchFacets = Record<string, {
997
- value: string | null;
998
- count: number;
999
- }[]>;
1000
- /** similar()/recommend() taşıyıcı opsiyonları (T018, FR-022): search'ün
1001
- * paramlarından query/vector/mode düşer — hedef vektörü metodun kendisi
1002
- * DB'den kurar; facets/highlight de düşer (T020) — engine bu ikisini
1003
- * similar/recommend'e geçirmez, tip vaadi gerçekle aynı kalır. */
1004
- type SimilarParamsTyped<T extends TableTypes> = Omit<SearchParamsTyped<T>, "query" | "vector" | "mode" | "facets" | "highlight">;
1005
- /** recommend() parametreleri (T018, FR-023). */
1006
- type RecommendParamsTyped<T extends TableTypes> = SimilarParamsTyped<T> & {
1007
- /** Kaynak beğeniler — hedef vektör bunların DB-içi avg'ı; boş olamaz. */
1008
- positive: string[];
1009
- /** İtilen örnekler — hedef pos.v + (pos.v - neg.v) ile yönlenir. */
1010
- negative?: string[];
1011
- };
1012
- /** Temel tablo erişimcisi — search'süz beş op. */
1013
- interface EnvTypedTableBase<T extends TableTypes> {
1014
- insert(data: T["insert"]): Promise<T["row"]>;
1015
- /**
1016
- * Insert the row, or update it when it collides on `onConflict`.
1017
- *
1018
- * The conflict columns must carry a unique constraint or index — that is what
1019
- * Postgres matches on — and they are excluded from the update, since they are
1020
- * what matched.
1021
- */
1022
- upsert(data: T["insert"], opts: {
1023
- onConflict: readonly Extract<keyof T["row"], string>[];
1024
- }): Promise<T["row"]>;
1025
- /** Update the row by id; resolves to the updated row, or `null` if no row
1026
- * matched (absent or RLS-hidden) — an idempotent outcome, mirroring
1027
- * `findById`. The runtime returns a null row rather than throwing. */
1028
- update(id: string, data: Partial<T["insert"]>): Promise<T["row"] | null>;
1029
- delete(id: string): Promise<void>;
1030
- findById(id: string): Promise<T["row"] | null>;
1031
- /** Rows matching the filter. See {@link WhereFilter} / {@link FindManyOpts} —
1032
- * this declaration is what makes the engine's operators callable. */
1033
- findMany(query?: WhereFilter<T["row"]>, opts?: FindManyOpts<T["row"]>): Promise<T["row"][]>;
1034
- /** Update every matching row in one statement; an empty filter is refused. */
1035
- updateMany(where: WhereFilter<T["row"]>, set: Partial<T["insert"]>): Promise<T["row"][]>;
1036
- /** Delete every matching row; resolves to how many. Empty filter refused. */
1037
- deleteMany(where: WhereFilter<T["row"]>): Promise<number>;
1038
- /** How many rows match. An empty filter is legitimate: counting is a read. */
1039
- count(where?: WhereFilter<T["row"]>): Promise<number>;
1040
- /** Validity'li tabloda satırın yeni versiyonu (FR-029, C-9): eski satır
1041
- * kapanır (valid_to/superseded_by), yenisi TEK savepoint'te eklenir; dönüş
1042
- * yeni satır. Validity beyanı olmayan tabloda adlandırılmış çalışma-zamanı
1043
- * hatası — tip düzeyinde ayrım env `Tables` bayrağı taşımadığından yapılamaz. */
1044
- supersede(id: string, row: T["insert"]): Promise<T["row"]>;
1045
- }
1046
- /** Tablo erişimcisi: env girdisi `searchable: true` taşıyorsa (vector kolonu ya da
1047
- * search beyanı — env-gen üretir) `search()` üyesi VARDIR; yoksa üye hiç yoktur ve
1048
- * çağrı derleme hatasıdır (FR-013). Yapısal koşul TableTypes'ı genişletmeden çalışır. */
1049
- type EnvTypedTable<T extends TableTypes> = EnvTypedTableBase<T> & (T extends {
1050
- searchable: true;
1051
- } ? {
1052
- search(params: SearchParamsTyped<T>): Promise<Array<T["row"] & {
1053
- _score: number;
1054
- }> & {
1055
- _facets?: SearchFacets;
1056
- }>;
1057
- /** "Bu satıra benzeyenler" (FR-022): hedef vektör DB'den okunur,
1058
- * kaynak satır sonuçta yoktur; id yoksa adlandırılmış hata. */
1059
- similar(id: string, params?: SimilarParamsTyped<T>): Promise<Array<T["row"] & {
1060
- _score: number;
1061
- }>>;
1062
- /** D-021: sayaçlar bağımsız dönüşle — search'ün dizi-üstü _facets'i
1063
- * JSON.stringify'da kaybolur; ciddi sözleşme budur. */
1064
- facets(params: {
1065
- facets: Array<keyof T["row"] & string>;
1066
- where?: Partial<T["row"]>;
1067
- validity?: "all" | {
1068
- asOf: string;
1069
- };
1070
- }): Promise<Record<string, {
1071
- value: string | null;
1072
- count: number;
1073
- }[]>>;
1074
- /** positive/negative beğenilerden öneri (FR-023): hedef vektör DB-içi
1075
- * avg CTE'leriyle; kaynak id'ler sonuçta yoktur. */
1076
- recommend(params: RecommendParamsTyped<T>): Promise<Array<T["row"] & {
1077
- _score: number;
1078
- }>>;
1079
- } : Record<never, never>);
1080
- /** The `tables` map exposed on `Database`/`tx`, keyed by the env `Tables`
1081
- * interface. When no schema is declared `Tables` is empty, so `tables` is an
1082
- * empty object — accessing `.tables.foo` is then a compile error (no member). */
1083
- type EnvTables = {
1084
- [K in keyof Tables]: EnvTypedTable<Tables[K]>;
1085
- };
1086
- /**
1087
- * The project's NON-public schemas, keyed by schema name, each exposing its own
1088
- * `tables` map — the shape `Database.schema("billing")` returns.
1089
- *
1090
- * The intermediate `tables` is there for the reason {@link EnvTables} sits under
1091
- * `.tables`: a schema's table names must not share a namespace with anything the
1092
- * accessor itself might grow.
1093
- *
1094
- * Empty by default. The generated `palbase-env.d.ts` augments `Schemas` with one
1095
- * member per declared schema other than `public`, so a project that declares
1096
- * none has `keyof Schemas = never` and every `schema(...)` call is a compile
1097
- * error rather than a runtime surprise.
1098
- */
1099
- type EnvSchemas = {
1100
- [S in keyof Schemas]: {
1101
- tables: {
1102
- [T in keyof Schemas[S]]: EnvTypedTable<Extract<Schemas[S][T], TableTypes>>;
1103
- };
1104
- };
1105
- };
1106
- /** The project's tables as PLAN operations, keyed by the env `Tables`
1107
- * interface. The transaction twin of {@link EnvTables}. */
1108
- type TxTables = {
1109
- [K in keyof Tables]: TxTable<Tables[K]["row"], Tables[K]["insert"]>;
1110
- };
1111
- /**
1112
- * The handle a `Database.transaction(…)` callback receives.
1113
- *
1114
- * Tables only — no `query`, no `findById`, no `asService`. A read whose value
1115
- * the plan does not write belongs outside the transaction, where it costs one
1116
- * round trip and is an ordinary value you can branch on.
1117
- */
1118
- type TxPlan = TxPlanHandle<TxTables>;
1119
- /**
1120
- * The RLS-bypass sibling returned by `Database.asService()`. Same typed surface
1121
- * as {@link EnvTypedDatabase} — `tables`, the raw string ops, and a typed
1122
- * `transaction` — but it does NOT re-expose `asService` (no double-bypass).
1123
- * Every op it performs runs as the `service_role` (BYPASSRLS).
1124
- */
1125
- interface EnvServiceDatabase extends Omit<DBClient, "txPlan" | "asService"> {
1126
- tables: EnvTables;
1127
- /** See {@link EnvTypedDatabase.schema}. The bypass sibling reaches every
1128
- * schema the default surface does — a `billing` table is no less reachable
1129
- * because the caller asked to run as the service role. */
1130
- schema<S extends keyof Schemas>(name: S): EnvSchemas[S];
1131
- transaction<T>(fn: (tx: TxPlan) => T extends Promise<unknown> ? never : T): Promise<Materialized<T>>;
1132
- }
1133
- /**
1134
- * The typed-by-default Database surface: the raw string-keyed `DBClient` ops
1135
- * PLUS a `tables` map typed against the project's generated `palbase-env.d.ts`,
1136
- * a `transaction` that runs a whole plan in one request, and `asService()` for
1137
- * the explicit RLS-bypass sibling.
1138
- *
1139
- * The low-level `txPlan` op is deliberately NOT re-exposed here: `transaction`
1140
- * is the surface, and a hand-built plan would bypass the ref/guard machinery
1141
- * that makes one safe to write.
1142
- */
1143
- interface EnvTypedDatabase extends Omit<DBClient, "txPlan" | "asService"> {
1144
- tables: EnvTables;
1145
- /**
1146
- * The tables of a schema other than `public`.
1147
- *
1148
- * `Database.tables.*` is the PUBLIC schema and only the public schema. Two
1149
- * schemas may declare the same table name — `public.invoices` and
1150
- * `billing.invoices` are different tables — so one flat namespace would make
1151
- * `Database.tables.invoices` resolve by declaration order. It does not: the
1152
- * bare name is always public's, and everything else is asked for by schema.
1153
- *
1154
- * The name is `keyof Schemas`, so a schema the project never declared is a
1155
- * compile error. On the wire the table travels schema-qualified
1156
- * (`billing.invoices`); `public` stays bare.
1157
- *
1158
- * Declaring a schema does not publish it — reachability over HTTP is the
1159
- * schema's own `exposed` flag, checked by the broker.
1160
- *
1161
- * @example
1162
- * const inv = await Database.schema("billing").tables.invoices.findById(id);
1163
- */
1164
- schema<S extends keyof Schemas>(name: S): EnvSchemas[S];
1165
- /**
1166
- * Run a transaction. The callback DESCRIBES the operations; the whole
1167
- * description travels in one request and the broker runs it inside a single
1168
- * transaction — committing when it finishes, rolling back on any failure.
1169
- *
1170
- * The callback is SYNCHRONOUS: nothing has run when it returns, so there is
1171
- * nothing to await. `async` on it and `await` inside it are compile errors.
1172
- * Values a later operation needs are {@link Ref}s, written straight into the
1173
- * next operation; values the CALLER needs are returned and substituted before
1174
- * this promise resolves.
1175
- *
1176
- * @example
1177
- * const { statementId } = await Database.transaction((tx) => {
1178
- * const st = tx.tables.statements
1179
- * .insert({ household_id: hid, file_sha256: sha, status: "reviewing" })
1180
- * .expectOne(new Internal("statement insert failed"));
1181
- *
1182
- * tx.tables.statement_lines.insertMany(
1183
- * lines.map((l) => ({ statement_id: st.id, category: resolveCategory(l) })),
1184
- * );
1185
- *
1186
- * return { statementId: st.id };
1187
- * });
1188
- */
1189
- transaction<T>(fn: (tx: TxPlan) => T extends Promise<unknown> ? never : T): Promise<Materialized<T>>;
1190
- /**
1191
- * Run `fn` inside a SAVEPOINT, so a write that fails in it does not poison the
1192
- * rest of the request.
1193
- *
1194
- * A request is ONE Postgres transaction: a failed statement aborts it and
1195
- * every later one answers `current transaction is aborted`. That is why
1196
- * "insert, catch the unique violation, update instead" cannot be written
1197
- * directly — and why {@link EnvTypedTableBase.upsert} exists for the common
1198
- * case. Reach for `attempt` when the recovery is not an upsert.
1199
- *
1200
- * The handle is a parameter, not the ambient `Database`: only what `tx` writes
1201
- * is inside the boundary, so a concurrent branch of the same request cannot be
1202
- * rolled back by someone else's failure.
1203
- *
1204
- * @example
1205
- * const claimed = await Database.attempt(async (tx) => {
1206
- * await tx.insert("seats", { row: 4, seat: 12, user_id: user.id });
1207
- * return true;
1208
- * }).catch(() => false);
1209
- */
1210
- attempt<T>(fn: (tx: Omit<DBClient, "attempt" | "txPlan" | "asService">) => Promise<T>): Promise<T>;
1211
- /**
1212
- * Return a sibling that bypasses RLS by running as the `service_role`. Use
1213
- * sparingly and explicitly — the default `Database.*` path is RLS-enforced.
1214
- *
1215
- * @example
1216
- * const all = await Database.asService().tables.todos.findMany({});
1217
- * const rows = await Database.asService().query("SELECT * FROM todos");
1218
- */
1219
- asService(): EnvServiceDatabase;
1220
- }
1221
-
1222
- export { type AnyColumn as A, defineTable as B, ColumnBuilder as C, enumType as D, type EnvTypedDatabase as E, installationRef as F, integer as G, isPalbaseExtension as H, type InsertShape as I, jsonb as J, makeTypedDB as K, numeric as L, openai as M, ownedByUser as N, type OnDeleteAction as O, PALBASE_EXTENSIONS as P, policy as Q, type RawConstraintDef as R, type SchemaDef as S, TABLE_META as T, raw as U, text as V, timestamp as W, userRef as X, uuid as Y, vector as Z, type ColumnDef as a, type ColumnMap as b, type ColumnType as c, EXTENSION_DEPENDENCIES as d, type EmbeddingModelRef as e, type EnvSchemas as f, type EnvServiceDatabase as g, type EnvTables as h, type EnvTypedTable as i, type PalbaseExtension as j, PolicyBuilder as k, type PolicyCommand as l, type PolicyDef as m, type PolicyMode as n, type RowShape as o, type TableDef as p, type TableHandle as q, type TableInput as r, type TxPlan as s, type TxTables as t, type TypedDB as u, type TypedTable as v, type TypedTx as w, bigint as x, boolean as y, defineSchema as z };