@palbase/backend 23.0.0 → 24.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/palbase-backend.cjs +695 -61
- package/dist/bin/palbase-backend.cjs.map +1 -1
- package/dist/bin/palbase-backend.js +4 -5
- package/dist/bin/palbase-backend.js.map +1 -1
- package/dist/{chunk-FSGSB42K.js → chunk-7Z6MGMXQ.js} +64 -4
- package/dist/chunk-7Z6MGMXQ.js.map +1 -0
- package/dist/{chunk-OMRTHM4X.js → chunk-H3JAISUY.js} +136 -1
- package/dist/chunk-H3JAISUY.js.map +1 -0
- package/dist/{chunk-REZU6UKT.js → chunk-NXDH6VQJ.js} +549 -42
- package/dist/chunk-NXDH6VQJ.js.map +1 -0
- package/dist/{chunk-W5ODXPY3.js → chunk-P2Q27SGP.js} +32 -3
- package/dist/chunk-P2Q27SGP.js.map +1 -0
- package/dist/{chunk-ZC6Q2BRD.js → chunk-T5IOSOE5.js} +7 -2
- package/dist/chunk-T5IOSOE5.js.map +1 -0
- package/dist/{chunk-HAF67F2H.js → chunk-ZUGY7RGS.js} +86 -3
- package/dist/chunk-ZUGY7RGS.js.map +1 -0
- package/dist/db/index.cjs +115 -3
- package/dist/db/index.cjs.map +1 -1
- package/dist/db/index.d.cts +2 -2
- package/dist/db/index.d.ts +2 -2
- package/dist/db/index.js +2 -2
- package/dist/{endpoint-BavvbW4P.d.ts → endpoint-0_DGBajf.d.ts} +168 -9
- package/dist/{endpoint-i8TTCohk.d.cts → endpoint-CcQ1a36a.d.cts} +168 -9
- package/dist/engine/index.cjs +684 -48
- package/dist/engine/index.cjs.map +1 -1
- package/dist/engine/index.d.cts +4 -4
- package/dist/engine/index.d.ts +4 -4
- package/dist/engine/index.js +4 -4
- package/dist/{index-B3jmmItD.d.ts → index-CJiJU9ux.d.ts} +209 -36
- package/dist/{index-Bmvx1EvJ.d.cts → index-D-4-PNuQ.d.cts} +209 -36
- package/dist/{index-B7YBEG5w.d.ts → index-D17r-MKb.d.ts} +177 -7
- package/dist/{index-E7OscPJT.d.cts → index-DRFxf07H.d.cts} +177 -7
- package/dist/index.cjs +269 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +54 -12
- package/dist/index.d.ts +54 -12
- package/dist/index.js +54 -11
- package/dist/index.js.map +1 -1
- package/dist/openapi/index.cjs +45 -5
- package/dist/openapi/index.cjs.map +1 -1
- package/dist/openapi/index.d.cts +9 -4
- package/dist/openapi/index.d.ts +9 -4
- package/dist/openapi/index.js +35 -11
- package/dist/openapi/index.js.map +1 -1
- package/dist/{registry-C3H2uPeZ.d.cts → registry-1X-skBNu.d.cts} +101 -7
- package/dist/{registry-DY3d9l1k.d.ts → registry-CEod_5sz.d.ts} +101 -7
- package/dist/test/index.cjs +509 -9
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.d.cts +35 -3
- package/dist/test/index.d.ts +35 -3
- package/dist/test/index.js +507 -8
- package/dist/test/index.js.map +1 -1
- package/docs/README.md +4 -4
- package/docs/database.md +115 -11
- package/docs/getting-started.md +5 -4
- package/docs/llms-full.txt +385 -89
- package/docs/migrations.md +81 -59
- package/docs/schema.md +82 -2
- package/docs/services.md +98 -9
- package/package.json +3 -2
- package/stager/return_types.js +23 -0
- package/template/AGENTS.md +121 -41
- package/template/controllers/notes.controller.ts +64 -0
- package/template/package.json +1 -1
- package/template/services/note.service.ts +74 -0
- package/template/tsconfig.json +11 -1
- package/dist/chunk-FSGSB42K.js.map +0 -1
- package/dist/chunk-HAF67F2H.js.map +0 -1
- package/dist/chunk-OMRTHM4X.js.map +0 -1
- package/dist/chunk-REZU6UKT.js.map +0 -1
- package/dist/chunk-W5ODXPY3.js.map +0 -1
- package/dist/chunk-Y5HXVUMP.js +0 -90
- package/dist/chunk-Y5HXVUMP.js.map +0 -1
- package/dist/chunk-ZC6Q2BRD.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Tables, TableTypes } from './db/env.cjs';
|
|
2
|
-
import { D as DBClient, b8 as TxPlanHandle, bh as TxTable, M as Materialized } from './endpoint-
|
|
2
|
+
import { D as DBClient, b8 as TxPlanHandle, bh as TxTable, M as Materialized } from './endpoint-CcQ1a36a.cjs';
|
|
3
3
|
|
|
4
4
|
/** On delete action for foreign key references. */
|
|
5
5
|
type OnDeleteAction = 'cascade' | 'set null' | 'restrict' | 'no action';
|
|
@@ -36,15 +36,47 @@ interface ColumnDef {
|
|
|
36
36
|
enumName?: string;
|
|
37
37
|
enumValues?: string[];
|
|
38
38
|
unique?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* The value is written by the DATABASE — a trigger, a rule, an identity — not by
|
|
41
|
+
* the author and not by a DEFAULT this schema declares. It makes the column
|
|
42
|
+
* optional on INSERT without putting a DEFAULT in the DDL.
|
|
43
|
+
*
|
|
44
|
+
* Before this existed the only way to keep a trigger-filled column off the
|
|
45
|
+
* INSERT type was to give it a fake `default()`: a value the schema claimed to
|
|
46
|
+
* write and the trigger immediately overwrote. That made the schema lie about
|
|
47
|
+
* its own data.
|
|
48
|
+
*/
|
|
49
|
+
dbAssigned?: boolean;
|
|
39
50
|
/** vector(n): the declared dimension count — part of the TYPE (typmod), read
|
|
40
51
|
* by the wire serializer and the deploy's auto-index (FR-001). */
|
|
41
52
|
dimensions?: number;
|
|
53
|
+
/**
|
|
54
|
+
* How the stored value is projected in and out of this process (FR-009).
|
|
55
|
+
*
|
|
56
|
+
* NOT part of the DDL: the column's Postgres type is unchanged and this pair
|
|
57
|
+
* is never serialized into a migration. It exists so the row surface can hand
|
|
58
|
+
* back the type the application actually works with.
|
|
59
|
+
*/
|
|
60
|
+
transform?: ColumnTransform;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The read/write pair a column may declare (FR-009).
|
|
64
|
+
*
|
|
65
|
+
* `fromDb` takes whatever the driver produced for this column and returns the
|
|
66
|
+
* value the application sees; `toDb` is its inverse on the way out. Kept
|
|
67
|
+
* deliberately unexported — a column declares one inline, nobody needs to name
|
|
68
|
+
* the shape.
|
|
69
|
+
*/
|
|
70
|
+
interface ColumnTransform<T = unknown> {
|
|
71
|
+
fromDb: (value: unknown) => T;
|
|
72
|
+
toDb: (value: T) => unknown;
|
|
42
73
|
}
|
|
43
74
|
declare const __colKind: unique symbol;
|
|
44
75
|
declare const __colNullable: unique symbol;
|
|
45
76
|
declare const __colHasDefault: unique symbol;
|
|
46
77
|
declare const __colEnumValues: unique symbol;
|
|
47
78
|
declare const __colPayload: unique symbol;
|
|
79
|
+
declare const __colTransform: unique symbol;
|
|
48
80
|
/**
|
|
49
81
|
* Fluent column builder with phantom type params:
|
|
50
82
|
* K — ColumnType literal (e.g. "text", "integer")
|
|
@@ -52,34 +84,50 @@ declare const __colPayload: unique symbol;
|
|
|
52
84
|
* D — boolean: true when a default has been set
|
|
53
85
|
* E — enum value union (never for non-enum columns)
|
|
54
86
|
* P — jsonb payload shape (unknown unless jsonb<T>() supplied one)
|
|
87
|
+
* T — transform target type (`never` when the column declares no transform;
|
|
88
|
+
* `never` is the sentinel because it is the only type that survives
|
|
89
|
+
* `[T] extends [never]` and never collides with a real target type)
|
|
55
90
|
*
|
|
56
|
-
* All
|
|
91
|
+
* All six params have defaults so bare `ColumnBuilder` (no args) still
|
|
57
92
|
* satisfies `Record<string, ColumnBuilder>` in schema.ts without modification.
|
|
58
93
|
*
|
|
59
|
-
* The
|
|
94
|
+
* The six `declare readonly` brand fields carry the phantom types into the
|
|
60
95
|
* structural shape so that conditional types like ColValue<C> can discriminate
|
|
61
96
|
* on K without requiring runtime values on those fields.
|
|
62
97
|
*/
|
|
63
|
-
declare class ColumnBuilder<K extends ColumnType = ColumnType, N extends boolean = boolean, D extends boolean = boolean, E = unknown, P = unknown> {
|
|
98
|
+
declare class ColumnBuilder<K extends ColumnType = ColumnType, N extends boolean = boolean, D extends boolean = boolean, E = unknown, P = unknown, T = never> {
|
|
64
99
|
readonly [__colKind]: K;
|
|
65
100
|
readonly [__colNullable]: N;
|
|
66
101
|
readonly [__colHasDefault]: D;
|
|
67
102
|
readonly [__colEnumValues]: E;
|
|
68
103
|
readonly [__colPayload]: P;
|
|
104
|
+
readonly [__colTransform]: T;
|
|
69
105
|
readonly _def: ColumnDef;
|
|
70
106
|
constructor(type: K, existingDef?: ColumnDef);
|
|
71
107
|
/** Mark this column as the primary key. */
|
|
72
|
-
primaryKey(): ColumnBuilder<K, N, D, E, P>;
|
|
108
|
+
primaryKey(): ColumnBuilder<K, N, D, E, P, T>;
|
|
73
109
|
/** Mark this column as NOT NULL (default). */
|
|
74
|
-
notNull(): ColumnBuilder<K, false, D, E, P>;
|
|
110
|
+
notNull(): ColumnBuilder<K, false, D, E, P, T>;
|
|
75
111
|
/** Allow NULL values. */
|
|
76
|
-
nullable(): ColumnBuilder<K, true, D, E, P>;
|
|
112
|
+
nullable(): ColumnBuilder<K, true, D, E, P, T>;
|
|
77
113
|
/** Set a default value. */
|
|
78
|
-
default(value: unknown): ColumnBuilder<K, N, true, E, P>;
|
|
114
|
+
default(value: unknown): ColumnBuilder<K, N, true, E, P, T>;
|
|
79
115
|
/** UUID: generate a random default (gen_random_uuid()). */
|
|
80
|
-
defaultRandom(): ColumnBuilder<K, N, true, E, P>;
|
|
116
|
+
defaultRandom(): ColumnBuilder<K, N, true, E, P, T>;
|
|
81
117
|
/** Timestamp: default to now(). */
|
|
82
|
-
defaultNow(): ColumnBuilder<K, N, true, E, P>;
|
|
118
|
+
defaultNow(): ColumnBuilder<K, N, true, E, P, T>;
|
|
119
|
+
/**
|
|
120
|
+
* The DATABASE assigns this column's value — a trigger, a rule, an identity.
|
|
121
|
+
*
|
|
122
|
+
* The column becomes optional on INSERT (the author has nothing to send) while
|
|
123
|
+
* the DDL stays free of a DEFAULT this schema would not honour. It is NOT
|
|
124
|
+
* `default()`: that declares a value the schema promises to write.
|
|
125
|
+
*
|
|
126
|
+
* Naming: deliberately not `generated()`. Postgres has GENERATED columns and
|
|
127
|
+
* they are a different thing; borrowing the word would send a reader — or a
|
|
128
|
+
* model writing a schema — to the wrong feature.
|
|
129
|
+
*/
|
|
130
|
+
dbAssigned(): ColumnBuilder<K, N, true, E, P, T>;
|
|
83
131
|
/** Add a foreign key reference. */
|
|
84
132
|
/**
|
|
85
133
|
* Declares that this column used to be called `previous`.
|
|
@@ -92,8 +140,8 @@ declare class ColumnBuilder<K extends ColumnType = ColumnType, N extends boolean
|
|
|
92
140
|
* Once the rename has been applied the annotation is inert (the old name is no
|
|
93
141
|
* longer there to rename), so it can be deleted at your leisure.
|
|
94
142
|
*/
|
|
95
|
-
renamedFrom(previous: string): ColumnBuilder<K, N, D, E, P>;
|
|
96
|
-
references(table: string, column: string): ColumnBuilder<K, N, D, E, P>;
|
|
143
|
+
renamedFrom(previous: string): ColumnBuilder<K, N, D, E, P, T>;
|
|
144
|
+
references(table: string, column: string): ColumnBuilder<K, N, D, E, P, T>;
|
|
97
145
|
/**
|
|
98
146
|
* Add a real DB-level foreign key to the built-in auth users
|
|
99
147
|
* (`REFERENCES auth.users(id)`), so a column like `user_id` gets true
|
|
@@ -114,7 +162,7 @@ declare class ColumnBuilder<K extends ColumnType = ColumnType, N extends boolean
|
|
|
114
162
|
* express (referencing column is text, `set null` needs a nullable column) —
|
|
115
163
|
* as the real boundary; this signature is the compile-time DX mirror.
|
|
116
164
|
*/
|
|
117
|
-
referencesAuthUser(onDelete: AuthUserOnDelete): ColumnBuilder<K, N, D, E, P>;
|
|
165
|
+
referencesAuthUser(onDelete: AuthUserOnDelete): ColumnBuilder<K, N, D, E, P, T>;
|
|
118
166
|
/**
|
|
119
167
|
* Add a real DB-level foreign key to the canonical, server-minted installation
|
|
120
168
|
* anchor (`REFERENCES auth.installations(id)`) — the app-scoped verified-device
|
|
@@ -133,11 +181,30 @@ declare class ColumnBuilder<K extends ColumnType = ColumnType, N extends boolean
|
|
|
133
181
|
* blocked by a lingering FK. The server (validateAuthAnchorFK) is the real
|
|
134
182
|
* boundary; this signature is the compile-time DX mirror.
|
|
135
183
|
*/
|
|
136
|
-
referencesInstallation(onDelete: AuthUserOnDelete): ColumnBuilder<K, N, D, E, P>;
|
|
184
|
+
referencesInstallation(onDelete: AuthUserOnDelete): ColumnBuilder<K, N, D, E, P, T>;
|
|
137
185
|
/** Set the ON DELETE action for a foreign key reference. */
|
|
138
|
-
onDelete(action: OnDeleteAction): ColumnBuilder<K, N, D, E, P>;
|
|
186
|
+
onDelete(action: OnDeleteAction): ColumnBuilder<K, N, D, E, P, T>;
|
|
139
187
|
/** Add a single-column UNIQUE constraint. */
|
|
140
|
-
unique(): ColumnBuilder<K, N, D, E, P>;
|
|
188
|
+
unique(): ColumnBuilder<K, N, D, E, P, T>;
|
|
189
|
+
/**
|
|
190
|
+
* Declare how this column's value is projected in and out of the process.
|
|
191
|
+
*
|
|
192
|
+
* The DDL does not move: `numeric` stays `numeric`, and the driver still hands
|
|
193
|
+
* back what Postgres sent. What changes is the type the row surface exposes —
|
|
194
|
+
* it becomes `Target`:
|
|
195
|
+
*
|
|
196
|
+
* amount: numeric().transform<number>({ fromDb: Number, toDb: String })
|
|
197
|
+
*
|
|
198
|
+
* `numeric` surfacing as `string` is CORRECT (a JS number cannot hold
|
|
199
|
+
* arbitrary precision), and that is exactly why this exists: application code
|
|
200
|
+
* that does arithmetic on the column otherwise rewrites the same
|
|
201
|
+
* `Number(row.amount)` / `String(x)` pair in every controller that touches it,
|
|
202
|
+
* and each rewrite is a place the two directions can drift apart.
|
|
203
|
+
*
|
|
204
|
+
* A transform is a PROJECTION, never a constraint: it lives only in this
|
|
205
|
+
* process, so it can neither validate nor migrate what is stored.
|
|
206
|
+
*/
|
|
207
|
+
transform<Target>(fns: ColumnTransform<Target>): ColumnBuilder<K, N, D, E, P, Target>;
|
|
141
208
|
}
|
|
142
209
|
/**
|
|
143
210
|
* Extracts the TypeScript value type for a column, respecting nullability.
|
|
@@ -149,14 +216,22 @@ declare class ColumnBuilder<K extends ColumnType = ColumnType, N extends boolean
|
|
|
149
216
|
* - "boolean" → boolean
|
|
150
217
|
* - "jsonb" → P (the dev-supplied payload shape from jsonb<T>(), else unknown)
|
|
151
218
|
* - "enum" → E (the union of literal values)
|
|
219
|
+
*
|
|
220
|
+
* A declared `.transform<T>()` OVERRIDES the table above: the column then
|
|
221
|
+
* surfaces as T (or T | null when nullable), because that is the value the
|
|
222
|
+
* application is handed. Nullability is still the column's, not the
|
|
223
|
+
* transform's — `fromDb` is not called for a NULL.
|
|
152
224
|
*/
|
|
153
|
-
type ColValue<C> = C extends ColumnBuilder<
|
|
225
|
+
type ColValue<C> = C extends ColumnBuilder<ColumnType, infer N, boolean, unknown, unknown, infer T> ? [T] extends [never] ? ColStoredValue<C> : N extends true ? T | null : T : never;
|
|
226
|
+
/** The value as the DATABASE hands it over — the branch table above, before any
|
|
227
|
+
* transform. This is what a column's `fromDb` receives. */
|
|
228
|
+
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;
|
|
154
229
|
/**
|
|
155
230
|
* True when a column is optional on INSERT:
|
|
156
231
|
* - nullable columns (N = true) — the DB allows NULL so the field may be omitted
|
|
157
232
|
* - columns with a default (D = true) — the DB fills in the value when absent
|
|
158
233
|
*/
|
|
159
|
-
type ColIsOptionalOnInsert<C> = C extends ColumnBuilder<
|
|
234
|
+
type ColIsOptionalOnInsert<C> = C extends ColumnBuilder<ColumnType, true, boolean, unknown, unknown, unknown> ? true : C extends ColumnBuilder<ColumnType, boolean, true, unknown, unknown, unknown> ? true : false;
|
|
160
235
|
/** Create a UUID column. */
|
|
161
236
|
declare function uuid(): ColumnBuilder<'uuid', false, false, never>;
|
|
162
237
|
/** Create a TEXT column. */
|
|
@@ -247,22 +322,6 @@ interface PolicyDef {
|
|
|
247
322
|
withCheck: string | null;
|
|
248
323
|
permissive: boolean;
|
|
249
324
|
}
|
|
250
|
-
/**
|
|
251
|
-
* Fluent RLS policy builder.
|
|
252
|
-
*
|
|
253
|
-
* Defaults (documented, applied at construction):
|
|
254
|
-
* - `command`: `"all"` — applies to every SQL command unless `.for(...)` narrows it.
|
|
255
|
-
* - `roles`: `["authenticated"]` — the common case is "rule applies to signed-in
|
|
256
|
-
* users". Call `.to(...)` to override; pass `.to()` with no roles (or never
|
|
257
|
-
* call it after a reset) to target PUBLIC.
|
|
258
|
-
* - `using` / `withCheck`: `null` — no row filter / write check until set.
|
|
259
|
-
* - `permissive`: `true` — `AS PERMISSIVE` (policies OR together).
|
|
260
|
-
*
|
|
261
|
-
* Each method mutates `_def` in place and returns `this`, so the chain is a
|
|
262
|
-
* single builder instance (no per-call allocation, like a tagged-template
|
|
263
|
-
* compile target). The terminal `PolicyDef` is read directly off `_def` by
|
|
264
|
-
* `schema_extract.js`.
|
|
265
|
-
*/
|
|
266
325
|
declare class PolicyBuilder {
|
|
267
326
|
readonly _def: PolicyDef;
|
|
268
327
|
constructor(name: string);
|
|
@@ -280,6 +339,34 @@ declare class PolicyBuilder {
|
|
|
280
339
|
to(...roles: string[]): this;
|
|
281
340
|
/** Set the `USING (...)` row-visibility expression (raw SQL). */
|
|
282
341
|
using(sqlExpr: string): this;
|
|
342
|
+
/**
|
|
343
|
+
* "Rows of THIS table whose owner the caller is a member of" — the membership
|
|
344
|
+
* pattern, written so it cannot recurse.
|
|
345
|
+
*
|
|
346
|
+
* THE TRAP IT EXISTS FOR. Written by hand, membership policies point at each
|
|
347
|
+
* other: `channels` is visible to members, so its policy reads
|
|
348
|
+
* `channel_members`; `channel_members` is visible to members, so its policy
|
|
349
|
+
* reads `channels`. Postgres refuses the pair at query time with `infinite
|
|
350
|
+
* recursion detected in policy for relation ...`, and the error names the
|
|
351
|
+
* relation but not the cycle. The way out is asymmetry — the MEMBERSHIP table
|
|
352
|
+
* is protected by `user_id = auth.uid()` and nothing else, and every other
|
|
353
|
+
* table subqueries INTO it. That shape was in the platform's own schema and
|
|
354
|
+
* written down nowhere; a customer recovered it by reading that schema.
|
|
355
|
+
*
|
|
356
|
+
* `(select auth.uid())` rather than a bare call: the scalar subquery is
|
|
357
|
+
* evaluated ONCE per statement instead of per row.
|
|
358
|
+
*
|
|
359
|
+
* @example
|
|
360
|
+
* // channels: visible to members. The membership table gets the simple one.
|
|
361
|
+
* policy("member_read").for("select").to("authenticated")
|
|
362
|
+
* .memberOf("channel_members", "channel_id")
|
|
363
|
+
* // → id IN (SELECT "channel_id" FROM "channel_members"
|
|
364
|
+
* // WHERE "user_id" = (select auth.uid()))
|
|
365
|
+
*/
|
|
366
|
+
memberOf(membershipTable: string, foreignKey: string, options?: {
|
|
367
|
+
column?: string;
|
|
368
|
+
userColumn?: string;
|
|
369
|
+
}): this;
|
|
283
370
|
/** Set the `WITH CHECK (...)` write-validation expression (raw SQL). */
|
|
284
371
|
withCheck(sqlExpr: string): this;
|
|
285
372
|
/** Set the policy mode: `"permissive"` (default, OR-combined) or
|
|
@@ -647,13 +734,24 @@ type RowShape<T extends TableDef> = {
|
|
|
647
734
|
/** A typed table accessor that mirrors the runtime DBClient surface. */
|
|
648
735
|
interface TypedTable<T extends TableDef> {
|
|
649
736
|
insert(data: InsertShape<T>): Promise<RowShape<T>>;
|
|
737
|
+
upsert(data: InsertShape<T>, opts: {
|
|
738
|
+
onConflict: readonly string[];
|
|
739
|
+
}): Promise<RowShape<T>>;
|
|
650
740
|
/** Update the row by id; resolves to the updated row, or `null` if no row
|
|
651
741
|
* matched (absent or RLS-hidden) — an idempotent outcome, mirroring
|
|
652
742
|
* `findById`. The runtime returns a null row rather than throwing. */
|
|
653
743
|
update(id: string, data: Partial<InsertShape<T>>): Promise<RowShape<T> | null>;
|
|
654
744
|
delete(id: string): Promise<void>;
|
|
655
745
|
findById(id: string): Promise<RowShape<T> | null>;
|
|
656
|
-
|
|
746
|
+
/** Rows matching the filter. Operators, ordering and paging are the ENGINE's
|
|
747
|
+
* surface — this declaration is what makes them callable. */
|
|
748
|
+
findMany(query?: WhereFilter<RowShape<T>>, opts?: FindManyOpts<RowShape<T>>): Promise<RowShape<T>[]>;
|
|
749
|
+
/** Update every matching row in one statement; an empty filter is refused. */
|
|
750
|
+
updateMany(where: WhereFilter<RowShape<T>>, set: Partial<InsertShape<T>>): Promise<RowShape<T>[]>;
|
|
751
|
+
/** Delete every matching row; resolves to how many. Empty filter refused. */
|
|
752
|
+
deleteMany(where: WhereFilter<RowShape<T>>): Promise<number>;
|
|
753
|
+
/** How many rows match. An empty filter is legitimate: counting is a read. */
|
|
754
|
+
count(where?: WhereFilter<RowShape<T>>): Promise<number>;
|
|
657
755
|
}
|
|
658
756
|
/** A typed DB facade covering all tables declared in schema `S`. */
|
|
659
757
|
interface TypedDB<S extends SchemaDef> {
|
|
@@ -692,6 +790,30 @@ type WhereOp<V> = V | {
|
|
|
692
790
|
neq?: V;
|
|
693
791
|
in?: V[];
|
|
694
792
|
};
|
|
793
|
+
/**
|
|
794
|
+
* A filter over a row: every field optional, each one a plain value (equality)
|
|
795
|
+
* or an operator object. THE filter language — `findMany`, `updateMany`,
|
|
796
|
+
* `deleteMany` and `count` all take this one, because two spellings of a filter
|
|
797
|
+
* is how the two come to disagree.
|
|
798
|
+
*/
|
|
799
|
+
type WhereFilter<Row> = {
|
|
800
|
+
[K in keyof Row]?: WhereOp<Row[K]>;
|
|
801
|
+
};
|
|
802
|
+
/**
|
|
803
|
+
* Ordering and paging for a read.
|
|
804
|
+
*
|
|
805
|
+
* `column` is `keyof Row`, not `string`: a mistyped column name is a compile
|
|
806
|
+
* error here rather than a runtime rejection three layers down. `offset`
|
|
807
|
+
* without `limit` is refused by the engine — a page with no size is not a page.
|
|
808
|
+
*/
|
|
809
|
+
type FindManyOpts<Row> = {
|
|
810
|
+
orderBy?: {
|
|
811
|
+
column: Extract<keyof Row, string>;
|
|
812
|
+
direction?: "asc" | "desc";
|
|
813
|
+
};
|
|
814
|
+
limit?: number;
|
|
815
|
+
offset?: number;
|
|
816
|
+
};
|
|
695
817
|
/** search() parametreleri, satır tipiyle koşullanmış (FR-013). `offset` BİLEREK yok (UD-013). */
|
|
696
818
|
interface SearchParamsTyped<T extends TableTypes> {
|
|
697
819
|
/** Metin sorgusu: FTS kolunu besler; embed beyanlıysa sorgu vektörü de bundan üretilir. */
|
|
@@ -755,13 +877,31 @@ type RecommendParamsTyped<T extends TableTypes> = SimilarParamsTyped<T> & {
|
|
|
755
877
|
/** Temel tablo erişimcisi — search'süz beş op. */
|
|
756
878
|
interface EnvTypedTableBase<T extends TableTypes> {
|
|
757
879
|
insert(data: T["insert"]): Promise<T["row"]>;
|
|
880
|
+
/**
|
|
881
|
+
* Insert the row, or update it when it collides on `onConflict`.
|
|
882
|
+
*
|
|
883
|
+
* The conflict columns must carry a unique constraint or index — that is what
|
|
884
|
+
* Postgres matches on — and they are excluded from the update, since they are
|
|
885
|
+
* what matched.
|
|
886
|
+
*/
|
|
887
|
+
upsert(data: T["insert"], opts: {
|
|
888
|
+
onConflict: readonly Extract<keyof T["row"], string>[];
|
|
889
|
+
}): Promise<T["row"]>;
|
|
758
890
|
/** Update the row by id; resolves to the updated row, or `null` if no row
|
|
759
891
|
* matched (absent or RLS-hidden) — an idempotent outcome, mirroring
|
|
760
892
|
* `findById`. The runtime returns a null row rather than throwing. */
|
|
761
893
|
update(id: string, data: Partial<T["insert"]>): Promise<T["row"] | null>;
|
|
762
894
|
delete(id: string): Promise<void>;
|
|
763
895
|
findById(id: string): Promise<T["row"] | null>;
|
|
764
|
-
|
|
896
|
+
/** Rows matching the filter. See {@link WhereFilter} / {@link FindManyOpts} —
|
|
897
|
+
* this declaration is what makes the engine's operators callable. */
|
|
898
|
+
findMany(query?: WhereFilter<T["row"]>, opts?: FindManyOpts<T["row"]>): Promise<T["row"][]>;
|
|
899
|
+
/** Update every matching row in one statement; an empty filter is refused. */
|
|
900
|
+
updateMany(where: WhereFilter<T["row"]>, set: Partial<T["insert"]>): Promise<T["row"][]>;
|
|
901
|
+
/** Delete every matching row; resolves to how many. Empty filter refused. */
|
|
902
|
+
deleteMany(where: WhereFilter<T["row"]>): Promise<number>;
|
|
903
|
+
/** How many rows match. An empty filter is legitimate: counting is a read. */
|
|
904
|
+
count(where?: WhereFilter<T["row"]>): Promise<number>;
|
|
765
905
|
/** Validity'li tabloda satırın yeni versiyonu (FR-029, C-9): eski satır
|
|
766
906
|
* kapanır (valid_to/superseded_by), yenisi TEK savepoint'te eklenir; dönüş
|
|
767
907
|
* yeni satır. Validity beyanı olmayan tabloda adlandırılmış çalışma-zamanı
|
|
@@ -784,6 +924,18 @@ type EnvTypedTable<T extends TableTypes> = EnvTypedTableBase<T> & (T extends {
|
|
|
784
924
|
similar(id: string, params?: SimilarParamsTyped<T>): Promise<Array<T["row"] & {
|
|
785
925
|
_score: number;
|
|
786
926
|
}>>;
|
|
927
|
+
/** D-021: sayaçlar bağımsız dönüşle — search'ün dizi-üstü _facets'i
|
|
928
|
+
* JSON.stringify'da kaybolur; ciddi sözleşme budur. */
|
|
929
|
+
facets(params: {
|
|
930
|
+
facets: Array<keyof T["row"] & string>;
|
|
931
|
+
where?: Partial<T["row"]>;
|
|
932
|
+
validity?: "all" | {
|
|
933
|
+
asOf: string;
|
|
934
|
+
};
|
|
935
|
+
}): Promise<Record<string, {
|
|
936
|
+
value: string | null;
|
|
937
|
+
count: number;
|
|
938
|
+
}[]>>;
|
|
787
939
|
/** positive/negative beğenilerden öneri (FR-023): hedef vektör DB-içi
|
|
788
940
|
* avg CTE'leriyle; kaynak id'ler sonuçta yoktur. */
|
|
789
941
|
recommend(params: RecommendParamsTyped<T>): Promise<Array<T["row"] & {
|
|
@@ -856,6 +1008,27 @@ interface EnvTypedDatabase extends Omit<DBClient, "txPlan" | "asService"> {
|
|
|
856
1008
|
* });
|
|
857
1009
|
*/
|
|
858
1010
|
transaction<T>(fn: (tx: TxPlan) => T extends Promise<unknown> ? never : T): Promise<Materialized<T>>;
|
|
1011
|
+
/**
|
|
1012
|
+
* Run `fn` inside a SAVEPOINT, so a write that fails in it does not poison the
|
|
1013
|
+
* rest of the request.
|
|
1014
|
+
*
|
|
1015
|
+
* A request is ONE Postgres transaction: a failed statement aborts it and
|
|
1016
|
+
* every later one answers `current transaction is aborted`. That is why
|
|
1017
|
+
* "insert, catch the unique violation, update instead" cannot be written
|
|
1018
|
+
* directly — and why {@link EnvTypedTableBase.upsert} exists for the common
|
|
1019
|
+
* case. Reach for `attempt` when the recovery is not an upsert.
|
|
1020
|
+
*
|
|
1021
|
+
* The handle is a parameter, not the ambient `Database`: only what `tx` writes
|
|
1022
|
+
* is inside the boundary, so a concurrent branch of the same request cannot be
|
|
1023
|
+
* rolled back by someone else's failure.
|
|
1024
|
+
*
|
|
1025
|
+
* @example
|
|
1026
|
+
* const claimed = await Database.attempt(async (tx) => {
|
|
1027
|
+
* await tx.insert("seats", { row: 4, seat: 12, user_id: user.id });
|
|
1028
|
+
* return true;
|
|
1029
|
+
* }).catch(() => false);
|
|
1030
|
+
*/
|
|
1031
|
+
attempt<T>(fn: (tx: Omit<DBClient, "attempt" | "txPlan" | "asService">) => Promise<T>): Promise<T>;
|
|
859
1032
|
/**
|
|
860
1033
|
* Return a sibling that bypasses RLS by running as the `service_role`. Use
|
|
861
1034
|
* sparingly and explicitly — the default `Database.*` path is RLS-enforced.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Buckets, BucketTypes } from './stack.js';
|
|
2
2
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
3
|
-
import { C as CacheClient, P as PalbaseDocsClient, a as PalbaseFlagsClient, L as Logger, b as PalbaseNotificationsClient, c as PalbaseRealtimeClient, D as DBClient, S as SecretsService, d as PalbaseStorageClient, e as PalbaseBucketClient, T as TxPlanBody,
|
|
4
|
-
import { E as EnvTypedDatabase } from './index-
|
|
5
|
-
import { R as RouteMeta } from './registry-
|
|
3
|
+
import { C as CacheClient, P as PalbaseDocsClient, a as PalbaseFlagsClient, L as Logger, b as PalbaseNotificationsClient, c as PalbaseRealtimeClient, D as DBClient, S as SecretsService, d as PalbaseStorageClient, e as PalbaseBucketClient, f as DBOps, T as TxPlanBody, g as TxPlanResponse, A as AuthSpec } from './endpoint-0_DGBajf.js';
|
|
4
|
+
import { E as EnvTypedDatabase } from './index-CJiJU9ux.js';
|
|
5
|
+
import { R as RouteMeta } from './registry-CEod_5sz.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* runtime.ts — request-scoped service singletons.
|
|
@@ -112,6 +112,72 @@ declare function __runWithRuntime<T>(services: RuntimeServices, fn: () => T): T;
|
|
|
112
112
|
* process-global fallback (dev-server / tests). NOT part of the public
|
|
113
113
|
* author-facing API — used by the runtime and the singleton Proxies. */
|
|
114
114
|
declare function __getRuntime(): RuntimeServices;
|
|
115
|
+
/** A lifecycle hook. Sync or async; the runtime awaits what it returns. */
|
|
116
|
+
type LifecycleHook = () => void | Promise<void>;
|
|
117
|
+
/** Runs one release's shutdown hooks. Handed back by {@link __runStartHooks}
|
|
118
|
+
* and called by the engine's `app.shutdown()`. Idempotent. */
|
|
119
|
+
type ShutdownRunner = () => Promise<void>;
|
|
120
|
+
/**
|
|
121
|
+
* Run `hook` ONCE while the application comes up, before it serves anything.
|
|
122
|
+
*
|
|
123
|
+
* Call it at MODULE SCOPE in a file the application imports — the same rule
|
|
124
|
+
* `defineDefaultAuth` and `@Controller` follow, and for the same reason: the
|
|
125
|
+
* declaration is claimed when the app boots, which is after module loading and
|
|
126
|
+
* before the first request. `name` is not decoration: a hook that throws is
|
|
127
|
+
* reported by that name and the boot is REFUSED, so it is what tells an
|
|
128
|
+
* operator which resource did not come up.
|
|
129
|
+
*
|
|
130
|
+
* There is no request scope yet, so the `Database`/`Secrets`/… singletons are
|
|
131
|
+
* NOT available inside a start hook. A secret is read from `process.env` here
|
|
132
|
+
* (the runtime mirrors the vault into it at boot).
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* // resources/graph.ts
|
|
136
|
+
* import neo4j from "neo4j-driver";
|
|
137
|
+
* import { onStart, onShutdown } from "@palbase/backend";
|
|
138
|
+
*
|
|
139
|
+
* export let graph: Driver;
|
|
140
|
+
* onStart("graph", () => {
|
|
141
|
+
* graph = neo4j.driver(process.env.NEO4J_URL!, neo4j.auth.basic("neo4j", process.env.NEO4J_PASSWORD!));
|
|
142
|
+
* });
|
|
143
|
+
* onShutdown("graph", () => graph.close());
|
|
144
|
+
*/
|
|
145
|
+
declare function onStart(name: string, hook: LifecycleHook): void;
|
|
146
|
+
/**
|
|
147
|
+
* Run `hook` while the application shuts down — the place a pool opened in
|
|
148
|
+
* {@link onStart} is closed.
|
|
149
|
+
*
|
|
150
|
+
* Shutdown is BEST-EFFORT by design: a hook that throws is reported by name and
|
|
151
|
+
* the rest still run. A drain that abandoned the remaining hooks on the first
|
|
152
|
+
* failure would leak exactly what this exists to release, and the process is
|
|
153
|
+
* leaving anyway.
|
|
154
|
+
*
|
|
155
|
+
* Hooks run in REVERSE declaration order, so a resource is released before what
|
|
156
|
+
* it was built on.
|
|
157
|
+
*/
|
|
158
|
+
declare function onShutdown(name: string, hook: LifecycleHook): void;
|
|
159
|
+
/**
|
|
160
|
+
* CLAIM what has been declared, run the start hooks, and hand back the runner
|
|
161
|
+
* for this release's shutdown hooks. Called by the engine's `createApp`; the
|
|
162
|
+
* `App.shutdown()` it builds calls what comes back. NOT part of the public
|
|
163
|
+
* author-facing API.
|
|
164
|
+
*
|
|
165
|
+
* IT CLAIMS RATHER THAN READS, which is what makes it correct in this runtime:
|
|
166
|
+
* a candidate release is loaded BESIDE the live one in one process
|
|
167
|
+
* (`v2/runtime/src/registry-scope.ts`), and both bundles append to the one
|
|
168
|
+
* shared slot above. If each app read the whole list, the live app's shutdown
|
|
169
|
+
* would close the candidate's pool and the candidate's would close the live
|
|
170
|
+
* app's. Taking the declarations leaves each app holding exactly its own.
|
|
171
|
+
*
|
|
172
|
+
* A start hook that throws REFUSES THE BOOT — with the hook's name in the
|
|
173
|
+
* message — after releasing whatever the earlier hooks already opened. Serving
|
|
174
|
+
* from a half-initialised app is the silence this whole surface replaces, and a
|
|
175
|
+
* boot that dies holding an open pool is the leak it replaces.
|
|
176
|
+
*/
|
|
177
|
+
declare function __runStartHooks(): Promise<ShutdownRunner>;
|
|
178
|
+
/** Drop every declaration. For tests, which declare repeatedly in one process.
|
|
179
|
+
* NOT part of the public author-facing API. */
|
|
180
|
+
declare function __resetLifecycleHooks(): void;
|
|
115
181
|
/**
|
|
116
182
|
* The project's own Postgres (pgx, schema `env_<envId>`).
|
|
117
183
|
*
|
|
@@ -361,14 +427,79 @@ declare function createLazyTransaction(sql: SqlDriver, role: string, claimsJson:
|
|
|
361
427
|
type LazyTransaction = ReturnType<typeof createLazyTransaction>;
|
|
362
428
|
/** Either a live driver transaction or the lazy holder above. */
|
|
363
429
|
type TxLike = SqlTx | LazyTransaction;
|
|
430
|
+
/** What `findMany` accepts beside its filter: an ordering, a row ceiling and a
|
|
431
|
+
* page offset. All three used to require dropping to raw SQL, and the docs said
|
|
432
|
+
* so — which is how a tenant's controllers filled up with hand-written SELECTs. */
|
|
433
|
+
interface FindManyOptions {
|
|
434
|
+
orderBy?: {
|
|
435
|
+
column: string;
|
|
436
|
+
direction?: "asc" | "desc";
|
|
437
|
+
};
|
|
438
|
+
limit?: number;
|
|
439
|
+
/** Rows to skip before the page starts. Only meaningful with `limit`, and
|
|
440
|
+
* refused without it — see `offsetClause`. */
|
|
441
|
+
offset?: number;
|
|
442
|
+
}
|
|
364
443
|
/** The six string-keyed operations, plus an interactive `transaction`. */
|
|
365
444
|
declare function createOps(tx: TxLike): {
|
|
366
445
|
query(sql: string, params?: unknown[]): Promise<Row[]>;
|
|
367
446
|
insert(table: string, data: Row): Promise<Row>;
|
|
447
|
+
/**
|
|
448
|
+
* INSERT the row, or UPDATE it when it collides on `onConflict`.
|
|
449
|
+
*
|
|
450
|
+
* WHY IT IS AN OPERATION rather than a recipe. "Try the insert, catch the
|
|
451
|
+
* unique violation, update instead" does not work here: a request runs in ONE
|
|
452
|
+
* Postgres transaction, so the failed insert aborts it and every later
|
|
453
|
+
* statement answers `current transaction is aborted`. A tenant measured that
|
|
454
|
+
* as 7 of 8 concurrent requests returning 500, gave up on upsert, and had a
|
|
455
|
+
* trigger create the row instead — a workaround that needs a new trigger for
|
|
456
|
+
* every table with a unique row.
|
|
457
|
+
*
|
|
458
|
+
* The conflict columns are excluded from the SET list: they are what MATCHED,
|
|
459
|
+
* so writing them back is at best a no-op and at worst a surprise.
|
|
460
|
+
*/
|
|
461
|
+
upsert(table: string, data: Row, opts: {
|
|
462
|
+
onConflict: readonly string[];
|
|
463
|
+
}): Promise<Row>;
|
|
368
464
|
update(table: string, id: string, data: Row): Promise<Row | null>;
|
|
369
465
|
delete(table: string, id: string): Promise<void>;
|
|
466
|
+
/**
|
|
467
|
+
* Update every row the filter matches, in ONE statement.
|
|
468
|
+
*
|
|
469
|
+
* The capability was already here and only reachable from inside a
|
|
470
|
+
* transaction plan (`tx.tables.x.updateWhere`, since 11.0.0). Outside it the
|
|
471
|
+
* only door was `update(id, …)`, so "mark every unapproved row in this
|
|
472
|
+
* household" was an N+1 loop or hand-written SQL — and hand-written SQL is
|
|
473
|
+
* where the typed surface and RLS both stop helping.
|
|
474
|
+
*
|
|
475
|
+
* The filter language is `findMany`'s, compiled by the same `compileWhere`:
|
|
476
|
+
* one filter language, or the two spellings drift.
|
|
477
|
+
*
|
|
478
|
+
* AN EMPTY FILTER IS REFUSED. `UPDATE … WHERE true` is a whole-table write,
|
|
479
|
+
* and the shape that produces it by accident — a filter object built from
|
|
480
|
+
* request input that happened to come back empty — is exactly the shape that
|
|
481
|
+
* should not silently succeed. Callers who mean every row say so with a
|
|
482
|
+
* predicate that is true for every row.
|
|
483
|
+
*/
|
|
484
|
+
updateMany(table: string, where: Row, set: Row): Promise<Row[]>;
|
|
485
|
+
/**
|
|
486
|
+
* Delete every row the filter matches, in ONE statement; resolves to how
|
|
487
|
+
* many went. Same filter language, same empty-filter refusal as
|
|
488
|
+
* {@link updateMany} — and here the accident is worse.
|
|
489
|
+
*/
|
|
490
|
+
deleteMany(table: string, where: Row): Promise<number>;
|
|
491
|
+
/**
|
|
492
|
+
* How many rows match — the half of pagination `limit`/`offset` cannot
|
|
493
|
+
* supply. Without it a page count is either a guess or "fetch everything and
|
|
494
|
+
* read .length", and the second one is the scale risk this surface exists to
|
|
495
|
+
* remove.
|
|
496
|
+
*
|
|
497
|
+
* An empty filter is legitimate HERE: counting a whole table is a read, and
|
|
498
|
+
* reads do not destroy anything.
|
|
499
|
+
*/
|
|
500
|
+
count(table: string, where?: Row): Promise<number>;
|
|
370
501
|
findById(table: string, id: string): Promise<Row | null>;
|
|
371
|
-
findMany(table: string, query?: Row): Promise<Row[]>;
|
|
502
|
+
findMany(table: string, query?: Row, opts?: FindManyOptions): Promise<Row[]>;
|
|
372
503
|
/**
|
|
373
504
|
* Tek-SQL hibrit arama (FR-014): iki kol CTE + FULL OUTER JOIN + RRF
|
|
374
505
|
* (1/(50+rank), CLAIM-N4). Operatör şema-nitelikli (C-10, M-1); GUC
|
|
@@ -417,6 +548,21 @@ declare function createOps(tx: TxLike): {
|
|
|
417
548
|
* tek SQL'de "id yok" ile "0 komşu" ayrılamazdı; +1 küçük turla id-yokluğu
|
|
418
549
|
* adlandırılmış hataya çevrilir. Sonuç şekli search ile aynı (FR-015).
|
|
419
550
|
*/
|
|
551
|
+
/** D-021 (FR-027 DX): sayaçlar BAĞIMSIZ op'la — search'ün dizi-üstü
|
|
552
|
+
* `_facets` özelliği JSON.stringify'da kaybolur (dizi özelliği), tenant
|
|
553
|
+
* yanıtına koyunca sessizce yok olurdu. Ayrı dönüş ciddi bir sözleşmedir;
|
|
554
|
+
* search'teki alan geriye-uyum için DURUR. where + validity default'u
|
|
555
|
+
* sayaçlara da uygulanır (sayaç, kullanıcının gördüğü kümeyi anlatır). */
|
|
556
|
+
facets(table: string, params: {
|
|
557
|
+
facets: string[];
|
|
558
|
+
where?: Record<string, unknown>;
|
|
559
|
+
validity?: "all" | {
|
|
560
|
+
asOf: string;
|
|
561
|
+
};
|
|
562
|
+
}): Promise<Record<string, {
|
|
563
|
+
value: string | null;
|
|
564
|
+
count: number;
|
|
565
|
+
}[]>>;
|
|
420
566
|
similar(table: string, id: string, opts?: {
|
|
421
567
|
where?: Record<string, unknown>;
|
|
422
568
|
limit?: number;
|
|
@@ -476,6 +622,24 @@ declare function createOps(tx: TxLike): {
|
|
|
476
622
|
supersede(table: string, id: string, row: Row): Promise<Row>;
|
|
477
623
|
/** A real SAVEPOINT inside the request's transaction. */
|
|
478
624
|
transaction<T>(cb: (t: unknown) => Promise<T>): Promise<T>;
|
|
625
|
+
/**
|
|
626
|
+
* Run `fn` against a handle bound to a SAVEPOINT, so a failure inside it
|
|
627
|
+
* rolls back only what that handle wrote and the request can keep writing.
|
|
628
|
+
*
|
|
629
|
+
* WHY THE HANDLE IS AN ARGUMENT. The obvious shape — `attempt(async () => {
|
|
630
|
+
* ... Database.insert(...) ... })`, with no parameter — would have to point
|
|
631
|
+
* the ambient `Database` at the savepoint for the duration, and a request is
|
|
632
|
+
* concurrent with itself: `Promise.all([Database.insert(a),
|
|
633
|
+
* Database.attempt(...)])` would put `a` inside the savepoint and roll it
|
|
634
|
+
* back with it. Silent data loss, and the same interleaving this file already
|
|
635
|
+
* refuses for `asService()`. Passing the handle makes the boundary something
|
|
636
|
+
* you can see in the code that crosses it.
|
|
637
|
+
*
|
|
638
|
+
* Postgres, not us: the savepoint is released on success and rolled back on
|
|
639
|
+
* failure by the driver, so an aborted statement inside `fn` does not poison
|
|
640
|
+
* the surrounding transaction.
|
|
641
|
+
*/
|
|
642
|
+
attempt<T>(fn: (tx: DBOps) => Promise<T>): Promise<T>;
|
|
479
643
|
/**
|
|
480
644
|
* Execute a whole transaction plan — what `Database.transaction(fn)` builds.
|
|
481
645
|
*
|
|
@@ -587,8 +751,11 @@ interface RouteEntry {
|
|
|
587
751
|
instance: Record<string, (...args: unknown[]) => unknown>;
|
|
588
752
|
/** `GET /todos/{id}` — stable, human-readable, used as the rate-limit key. */
|
|
589
753
|
id: string;
|
|
590
|
-
/** The
|
|
591
|
-
|
|
754
|
+
/** The auth spec that applies when the route itself declares none —
|
|
755
|
+
* controller default ?? application default ?? `true`, resolved once at boot
|
|
756
|
+
* by `resolveEffectiveAuth`. `engine/index.ts` reconciles the route's own
|
|
757
|
+
* spec against it per request. */
|
|
758
|
+
controllerAuth: AuthSpec;
|
|
592
759
|
}
|
|
593
760
|
/**
|
|
594
761
|
* Build the table from controller classes.
|
|
@@ -596,6 +763,9 @@ interface RouteEntry {
|
|
|
596
763
|
* @throws when a class carries no routes — a controller that collected zero
|
|
597
764
|
* endpoints is the silent failure this whole runtime is built to refuse, and
|
|
598
765
|
* it must be loud at boot rather than a 404 in production.
|
|
766
|
+
* @throws when a class declares constructor parameters — the same class of
|
|
767
|
+
* silence one level down (FR-010): nothing here has an argument to pass, so
|
|
768
|
+
* the field would simply be `undefined` in production.
|
|
599
769
|
*/
|
|
600
770
|
declare function buildRouteTable(controllers: readonly unknown[]): RouteEntry[];
|
|
601
771
|
interface RouteMatch {
|
|
@@ -879,4 +1049,4 @@ interface App {
|
|
|
879
1049
|
*/
|
|
880
1050
|
declare function createApp(opts: CreateAppOptions): Promise<App>;
|
|
881
1051
|
|
|
882
|
-
export { type App as A, BootRefused as B, Cache as C, Database as D, type EgressPolicy as E, Flags as F,
|
|
1052
|
+
export { type App as A, BootRefused as B, Cache as C, Database as D, type EgressPolicy as E, Flags as F, createOps as G, createRequestDatabase as H, effectiveAuth as I, hostAllowed as J, installEgressFence as K, type LifecycleHook as L, type ModuleClients as M, Notifications as N, loadConfig as O, makeMemoryCache as P, matchRoute as Q, Realtime as R, Secrets as S, quoteIdent as T, scrubSecrets as U, withTables as V, __getRuntime as _, Documents as a, Log as b, type RequestStore as c, type RuntimeServices as d, type ShutdownRunner as e, Storage as f, __requestALS as g, __resetLifecycleHooks as h, __runStartHooks as i, __runWithRuntime as j, __setRuntime as k, onStart as l, AuthVerifier as m, type CreateAppOptions as n, onShutdown as o, type EngineConfig as p, RateLimiter as q, type RequestDatabase as r, type RouteEntry as s, type RuntimeHooks as t, type ScrubResult as u, type SqlDriver as v, type SqlTx as w, buildRouteTable as x, createApp as y, createLazyTransaction as z };
|