@palbase/backend 10.3.0 → 12.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/chunk-7LAXRLPG.js +418 -0
- package/dist/chunk-7LAXRLPG.js.map +1 -0
- package/dist/{chunk-4WOQWFUP.js → chunk-LUV36KQU.js} +26 -11
- package/dist/{chunk-4WOQWFUP.js.map → chunk-LUV36KQU.js.map} +1 -1
- package/dist/{chunk-2PCNZBNZ.js → chunk-XATG7BRC.js} +20 -2
- package/dist/chunk-XATG7BRC.js.map +1 -0
- package/dist/db/index.cjs +438 -10
- 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 +13 -1
- package/dist/{endpoint-92kVepng.d.cts → endpoint-Ck4hER_7.d.cts} +343 -11
- package/dist/{endpoint-92kVepng.d.ts → endpoint-Ck4hER_7.d.ts} +343 -11
- package/dist/{index-BaHs33jr.d.cts → index-BJAf1uPC.d.cts} +64 -34
- package/dist/{index-DfryIw9U.d.ts → index-l7DhBDtn.d.ts} +64 -34
- package/dist/index.cjs +589 -124
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +60 -162
- package/dist/index.d.ts +60 -162
- package/dist/index.js +139 -108
- package/dist/index.js.map +1 -1
- package/dist/test/index.cjs +545 -12
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.d.cts +1 -1
- package/dist/test/index.d.ts +1 -1
- package/dist/test/index.js +153 -12
- package/dist/test/index.js.map +1 -1
- package/docs/background.md +8 -9
- package/docs/database.md +106 -16
- package/docs/events.md +20 -17
- package/docs/llms-full.txt +141 -47
- package/docs/schema.md +6 -4
- package/package.json +1 -1
- package/dist/chunk-2PCNZBNZ.js.map +0 -1
|
@@ -67,6 +67,333 @@ type MiddlewareHandler = (ctx: MiddlewareContext, next: () => Promise<void>) =>
|
|
|
67
67
|
*/
|
|
68
68
|
declare function defineMiddleware(fn: MiddlewareHandler): MiddlewareHandler;
|
|
69
69
|
|
|
70
|
+
/**
|
|
71
|
+
* tx-plan.ts — `Database.transaction()` as a PLAN, not a pinned session.
|
|
72
|
+
*
|
|
73
|
+
* A transaction used to be a conversation: BEGIN, then one network round trip
|
|
74
|
+
* per operation, then COMMIT. Each of those round trips cost ~4 ms and, because
|
|
75
|
+
* the pooler runs in transaction mode, an open transaction pinned a Postgres
|
|
76
|
+
* backend for the whole conversation. A 121-operation statement upload pinned
|
|
77
|
+
* one backend for ~490 ms.
|
|
78
|
+
*
|
|
79
|
+
* So the callback no longer TALKS to the database. It DESCRIBES what should
|
|
80
|
+
* happen; the description is serialised and sent once; the broker runs the whole
|
|
81
|
+
* thing inside one transaction and answers once. Committing on return and
|
|
82
|
+
* rolling back on throw is unchanged — that is the only property tenant code
|
|
83
|
+
* actually asked for.
|
|
84
|
+
*
|
|
85
|
+
* The consequences, stated plainly, because they are the whole design:
|
|
86
|
+
*
|
|
87
|
+
* - The callback is SYNCHRONOUS. There is nothing to await: no statement has
|
|
88
|
+
* run yet when it returns. `async` on the callback and `await` inside it are
|
|
89
|
+
* both compile errors (see {@link TxPlan} and {@link NotAwaitable}).
|
|
90
|
+
* - `insert()` does not hand back a row, it hands back {@link TxRows}. Reading
|
|
91
|
+
* a field requires `.expectOne(err)` first, which makes "what if the row
|
|
92
|
+
* isn't there" a question you cannot route around: it is the argument.
|
|
93
|
+
* - A field read from a row is a {@link Ref} — a PROMISE OF A VALUE THE SERVER
|
|
94
|
+
* WILL PRODUCE, not the value. It can be written into a later operation and
|
|
95
|
+
* it can be returned from the callback (it is substituted for the real value
|
|
96
|
+
* before `transaction()` resolves). It cannot be branched on. See the
|
|
97
|
+
* "Truthiness" note below — this is the sharp edge of the whole design.
|
|
98
|
+
* - Control flow that needs a real value must move OUT of the callback: read
|
|
99
|
+
* before the transaction, or express the condition as a guard
|
|
100
|
+
* (`updateWhere({ id, accepted_at: null }, …).expectOne(new Conflict(…))`)
|
|
101
|
+
* which the server evaluates and which rolls the whole plan back.
|
|
102
|
+
*
|
|
103
|
+
* # Truthiness — the hole this file CANNOT close
|
|
104
|
+
*
|
|
105
|
+
* JavaScript does not let a Proxy trap truthiness. `if (ref)` takes the true
|
|
106
|
+
* branch, always, for every Ref, and no `get` handler ever runs. `tsc` is silent
|
|
107
|
+
* because a Ref is a perfectly good object. So:
|
|
108
|
+
*
|
|
109
|
+
* const pot = tx.tables.pots.select({ id }, { limit: 1 }).expectOne(e);
|
|
110
|
+
* if (!pot.balance) { … } // ← ALWAYS false. Silently wrong data.
|
|
111
|
+
*
|
|
112
|
+
* What this file does close: coercion (`Symbol.toPrimitive`/`valueOf`/
|
|
113
|
+
* `toString`), awaiting (`then` is a callable member with a non-thenable
|
|
114
|
+
* signature, which is a *compile* error), serialisation (`toJSON`), and nesting
|
|
115
|
+
* a Ref inside a literal value where the server would store it as data. What it
|
|
116
|
+
* cannot close is a bare truthiness test. The real defence is the build-time
|
|
117
|
+
* static analysis (`tx_analysis.js`, phase P4); until that ships, this hole is
|
|
118
|
+
* open and this comment is the only warning.
|
|
119
|
+
*
|
|
120
|
+
* # Wire contract
|
|
121
|
+
*
|
|
122
|
+
* The JSON this file emits is consumed by
|
|
123
|
+
* `modules/backend/internal/management/tx_program.go`. That decoder rejects
|
|
124
|
+
* unknown fields at every level, so an op carries EXACTLY the fields its kind
|
|
125
|
+
* takes. Everything here that looks like a needless restriction is one of the
|
|
126
|
+
* server's rules made visible early:
|
|
127
|
+
*
|
|
128
|
+
* - `$ref` only points BACKWARDS, and only at an op statically known to yield
|
|
129
|
+
* at most one row (insert, or a `one`/`atMost 1` guard, or `select limit 1`).
|
|
130
|
+
* `.expectOne()` is what this file uses to satisfy that, always.
|
|
131
|
+
* - `$expr` is a closed set: `inc`/`dec` (update only — they read the column's
|
|
132
|
+
* current value) and `now()`.
|
|
133
|
+
* - `update`/`delete` require a `where`; `insert` refuses one.
|
|
134
|
+
* - `insertMany` rows must all set the same columns.
|
|
135
|
+
* - ≤1000 ops, ≤5000 rows per insertMany, ≤8 MiB of JSON.
|
|
136
|
+
*
|
|
137
|
+
* Column keys are emitted SORTED, so the same callback always produces byte-
|
|
138
|
+
* identical JSON. That is what lets the Go decoder be locked to golden files
|
|
139
|
+
* this SDK emits (`testdata/tx_plan_golden/`).
|
|
140
|
+
*/
|
|
141
|
+
/**
|
|
142
|
+
* A plan handle was used as if it were a value: awaited, coerced to a string or
|
|
143
|
+
* number, serialised, or nested inside another value.
|
|
144
|
+
*
|
|
145
|
+
* Thrown while the callback is still BUILDING the plan, so nothing has been sent
|
|
146
|
+
* and nothing has been written.
|
|
147
|
+
*/
|
|
148
|
+
declare class TxRefError extends Error {
|
|
149
|
+
constructor(message: string);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* The plan the callback described cannot be sent: it breaks a rule the server
|
|
153
|
+
* would reject, and rejecting it here names the line that wrote it instead of
|
|
154
|
+
* returning a 400 about an op index.
|
|
155
|
+
*/
|
|
156
|
+
declare class TxPlanError extends Error {
|
|
157
|
+
constructor(message: string);
|
|
158
|
+
}
|
|
159
|
+
/** A backwards reference to an earlier op's single-row result. */
|
|
160
|
+
interface TxWireRef {
|
|
161
|
+
$ref: {
|
|
162
|
+
op: number;
|
|
163
|
+
field: string;
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/** A call from the server's closed function set. */
|
|
167
|
+
interface TxWireExpr {
|
|
168
|
+
$expr: {
|
|
169
|
+
fn: "inc" | "dec";
|
|
170
|
+
by: number;
|
|
171
|
+
} | {
|
|
172
|
+
fn: "now";
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
/** One value in a `values`/`set`/`where` map: a literal, a `$ref` or a `$expr`. */
|
|
176
|
+
type TxWireValue = TxWireRef | TxWireExpr | unknown;
|
|
177
|
+
/** The tenant's declared expectation about an op's row count. `slot` indexes the
|
|
178
|
+
* client-side error table — the error OBJECT never travels. */
|
|
179
|
+
interface TxWireGuard {
|
|
180
|
+
kind: "one" | "none" | "atLeast" | "atMost";
|
|
181
|
+
n: number;
|
|
182
|
+
slot: number;
|
|
183
|
+
}
|
|
184
|
+
/** One operation in the wire plan. Fields are omitted, never null: the decoder
|
|
185
|
+
* rejects a field that does not belong to the op's kind. */
|
|
186
|
+
interface TxWireOp {
|
|
187
|
+
op: "insert" | "insertMany" | "update" | "delete" | "select";
|
|
188
|
+
table: string;
|
|
189
|
+
values?: Record<string, TxWireValue>;
|
|
190
|
+
rows?: Record<string, TxWireValue>[];
|
|
191
|
+
set?: Record<string, TxWireValue>;
|
|
192
|
+
where?: Record<string, TxWireValue>;
|
|
193
|
+
limit?: number;
|
|
194
|
+
lock?: "update";
|
|
195
|
+
guard?: TxWireGuard;
|
|
196
|
+
}
|
|
197
|
+
/** The request body of `POST /internal-api/db/tx`. */
|
|
198
|
+
interface TxPlanBody {
|
|
199
|
+
ops: TxWireOp[];
|
|
200
|
+
}
|
|
201
|
+
/** One op's outcome, positionally matched to the plan's ops. */
|
|
202
|
+
interface TxPlanOpResult {
|
|
203
|
+
rows: Record<string, unknown>[];
|
|
204
|
+
rows_affected: number;
|
|
205
|
+
}
|
|
206
|
+
/** The response body of `POST /internal-api/db/tx`. */
|
|
207
|
+
interface TxPlanResponse {
|
|
208
|
+
results: TxPlanOpResult[];
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* The fields the runtime must copy from the broker's error envelope onto the
|
|
212
|
+
* rejection it throws out of {@link DBClient.txPlan}.
|
|
213
|
+
*
|
|
214
|
+
* `slot` is the whole point: on a guard failure the server answers with the
|
|
215
|
+
* INDEX of the expectation that did not hold, never with an error message of its
|
|
216
|
+
* own, and this SDK maps that index back to the `Error` the callback handed to
|
|
217
|
+
* `.expectOne(…)`. Without `slot` a guard failure degrades to a generic 409.
|
|
218
|
+
*/
|
|
219
|
+
interface TxPlanRejection {
|
|
220
|
+
status?: number;
|
|
221
|
+
/** `tx_plan_invalid` | `tx_guard_failed` | `tx_ref_unresolved` | a pg class. */
|
|
222
|
+
error_code?: string;
|
|
223
|
+
/** Present only for `tx_guard_failed`: the client-side error table index. */
|
|
224
|
+
slot?: number;
|
|
225
|
+
/** Present on a database error: which op failed. */
|
|
226
|
+
op?: number;
|
|
227
|
+
}
|
|
228
|
+
declare const refBrand: unique symbol;
|
|
229
|
+
declare const rowBrand: unique symbol;
|
|
230
|
+
declare const rowsBrand: unique symbol;
|
|
231
|
+
/**
|
|
232
|
+
* Makes a handle a compile error to `await`.
|
|
233
|
+
*
|
|
234
|
+
* `then` is declared as a CALLABLE member whose signature is not `PromiseLike`,
|
|
235
|
+
* which is precisely the shape TypeScript rejects: `await handle` is TS1320 and
|
|
236
|
+
* `async () => handle` is TS1058. A non-callable `then` would not do it — the
|
|
237
|
+
* compiler simply ignores those.
|
|
238
|
+
*/
|
|
239
|
+
interface NotAwaitable {
|
|
240
|
+
/** Not a promise. Nothing here has run yet; there is nothing to await. */
|
|
241
|
+
then(doNotAwaitAPlanHandle: "a transaction plan is built synchronously"): never;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* A value the SERVER will produce, standing in for a column of a row this plan
|
|
245
|
+
* writes or reads.
|
|
246
|
+
*
|
|
247
|
+
* Legal uses: write it into a later operation's `values`/`set`/`where`, or
|
|
248
|
+
* return it from the callback (it is replaced by the real value before
|
|
249
|
+
* `transaction()` resolves).
|
|
250
|
+
*
|
|
251
|
+
* Illegal, and caught: `await`, `String(ref)`, `` `${ref}` ``, `ref + 1`,
|
|
252
|
+
* `JSON.stringify(ref)`, burying it inside a jsonb object.
|
|
253
|
+
*
|
|
254
|
+
* Illegal, and NOT caught: `if (ref)`. See the truthiness note at the top.
|
|
255
|
+
*/
|
|
256
|
+
interface Ref<T> extends NotAwaitable {
|
|
257
|
+
readonly [refBrand]: T;
|
|
258
|
+
}
|
|
259
|
+
/** The brand carried by a single-row handle, and the seam `Materialized` reads
|
|
260
|
+
* to turn `return st` into the whole row. */
|
|
261
|
+
interface TxRowHandle<Row> extends NotAwaitable {
|
|
262
|
+
readonly [rowBrand]: Row;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* A row this plan is known to produce exactly one of. Every property is a
|
|
266
|
+
* {@link Ref}; returning the handle itself yields the whole row.
|
|
267
|
+
*
|
|
268
|
+
* Only `.expectOne(err)` produces one — which is the design: a row you can read
|
|
269
|
+
* fields from is a row whose absence you have already answered for.
|
|
270
|
+
*/
|
|
271
|
+
type TxRow<Row> = {
|
|
272
|
+
readonly [K in keyof Row]: Ref<Row[K]>;
|
|
273
|
+
} & TxRowHandle<Row>;
|
|
274
|
+
/**
|
|
275
|
+
* The result of one operation, before any expectation is declared about it.
|
|
276
|
+
*
|
|
277
|
+
* Deliberately not a row and not a list: an operation's row count is not known
|
|
278
|
+
* until the server runs it, so the only thing that can be said about it here is
|
|
279
|
+
* an EXPECTATION. Declaring one is also the only way to get a readable row.
|
|
280
|
+
*
|
|
281
|
+
* At most one expectation per operation — the wire carries one guard per op, and
|
|
282
|
+
* a second call throws rather than silently dropping the first.
|
|
283
|
+
*/
|
|
284
|
+
interface TxRows<Row> extends NotAwaitable {
|
|
285
|
+
readonly [rowsBrand]: Row;
|
|
286
|
+
/**
|
|
287
|
+
* Require exactly one row, and read it. On any other count the server rolls
|
|
288
|
+
* the whole transaction back and this `error` is thrown to the caller.
|
|
289
|
+
*
|
|
290
|
+
* This is the only way to reach a row's fields, and the only shape a `$ref`
|
|
291
|
+
* may point at.
|
|
292
|
+
*/
|
|
293
|
+
expectOne(error: Error): TxRow<Row>;
|
|
294
|
+
/** Require zero rows (e.g. "this membership must not already exist"). */
|
|
295
|
+
expectNone(error: Error): void;
|
|
296
|
+
/** Require at least `n` rows. */
|
|
297
|
+
expectAtLeast(n: number, error: Error): void;
|
|
298
|
+
/** Require at most `n` rows. */
|
|
299
|
+
expectAtMost(n: number, error: Error): void;
|
|
300
|
+
}
|
|
301
|
+
/** `now()` — the server's clock, usable wherever a value is. */
|
|
302
|
+
interface TxNow extends NotAwaitable {
|
|
303
|
+
readonly $expr: {
|
|
304
|
+
fn: "now";
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
/** `inc(n)` / `dec(n)` — read the column's CURRENT value and write it back
|
|
308
|
+
* changed. Only meaningful in an update's `set`, which is where the types allow
|
|
309
|
+
* it and where the server allows it. */
|
|
310
|
+
interface TxColumnExpr extends NotAwaitable {
|
|
311
|
+
readonly $expr: {
|
|
312
|
+
fn: "inc" | "dec";
|
|
313
|
+
by: number;
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Resolve a callback's return type against what actually comes back: every
|
|
318
|
+
* {@link Ref} becomes its value, every {@link TxRow} becomes its row, and
|
|
319
|
+
* anything else keeps its shape.
|
|
320
|
+
*
|
|
321
|
+
* A {@link TxRows} resolves to an explanatory string type rather than a row
|
|
322
|
+
* list: it has no single answer to give, and saying so in the type is louder
|
|
323
|
+
* than a runtime throw.
|
|
324
|
+
*/
|
|
325
|
+
type Materialized<T> = T extends Ref<infer U> ? U : T extends TxRowHandle<infer R> ? R : T extends TxRows<unknown> ? "a TxRows cannot leave the transaction callback — read a row with .expectOne(err) first" : T extends Date ? T : T extends object ? {
|
|
326
|
+
[K in keyof T]: Materialized<T[K]>;
|
|
327
|
+
} : T;
|
|
328
|
+
/** A value written by an INSERT: a literal, an earlier row's field, or `now()`.
|
|
329
|
+
* `inc`/`dec` are absent on purpose — they read a current value, and an inserted
|
|
330
|
+
* row has none. */
|
|
331
|
+
type TxInsertValue<V> = V | Ref<V> | TxNow;
|
|
332
|
+
/** A value written by an UPDATE's `set`: everything an insert takes, plus the
|
|
333
|
+
* read-modify-write expressions. */
|
|
334
|
+
type TxSetValue<V> = V | Ref<V> | TxNow | TxColumnExpr;
|
|
335
|
+
/** An insert payload: the table's insert shape, with refs and `now()` allowed. */
|
|
336
|
+
type TxInsertShape<Insert> = {
|
|
337
|
+
[K in keyof Insert]: TxInsertValue<Insert[K]>;
|
|
338
|
+
};
|
|
339
|
+
/** An update's `set`: any subset of the insert shape, with expressions allowed. */
|
|
340
|
+
type TxSetShape<Insert> = {
|
|
341
|
+
[K in keyof Insert]?: TxSetValue<Insert[K]>;
|
|
342
|
+
};
|
|
343
|
+
/**
|
|
344
|
+
* A filter. Every entry is an equality test and they are AND-ed; a `null`
|
|
345
|
+
* becomes `IS NULL`, which is what makes `{ accepted_at: null }` a usable
|
|
346
|
+
* "not yet accepted" guard rather than a clause that matches nothing.
|
|
347
|
+
*/
|
|
348
|
+
type TxWhere<Row> = {
|
|
349
|
+
[K in keyof Row]?: Row[K] | Ref<Row[K]>;
|
|
350
|
+
};
|
|
351
|
+
/** Options for a plan `select`. */
|
|
352
|
+
interface TxSelectOptions {
|
|
353
|
+
/** Cap the rows read. */
|
|
354
|
+
limit?: number;
|
|
355
|
+
/** Take a real `FOR UPDATE` row lock for the rest of the transaction. */
|
|
356
|
+
lock?: "update";
|
|
357
|
+
}
|
|
358
|
+
/** One table, as the plan sees it. */
|
|
359
|
+
interface TxTable<Row, Insert> {
|
|
360
|
+
/** Insert one row. Returns a handle — call `.expectOne(err)` to read fields. */
|
|
361
|
+
insert(values: TxInsertShape<Insert>): TxRows<Row>;
|
|
362
|
+
/**
|
|
363
|
+
* Insert many rows in ONE statement. Every row must set the same columns
|
|
364
|
+
* (a row that omits one would silently take the column's default).
|
|
365
|
+
*
|
|
366
|
+
* An empty list writes nothing and sends nothing.
|
|
367
|
+
*/
|
|
368
|
+
insertMany(rows: readonly TxInsertShape<Insert>[]): TxRows<Row>;
|
|
369
|
+
/**
|
|
370
|
+
* Update every row matching `where`. The filter comes first because it is the
|
|
371
|
+
* dangerous half: an update whose `where` you got wrong rewrites rows you
|
|
372
|
+
* never looked at. The server refuses an update with no `where` at all.
|
|
373
|
+
*/
|
|
374
|
+
updateWhere(where: TxWhere<Row>, set: TxSetShape<Insert>): TxRows<Row>;
|
|
375
|
+
/** Delete every row matching `where`. The server refuses an unfiltered delete. */
|
|
376
|
+
deleteWhere(where: TxWhere<Row>): TxRows<Row>;
|
|
377
|
+
/** Read rows, optionally locking them for the rest of the transaction. */
|
|
378
|
+
select(where?: TxWhere<Row>, options?: TxSelectOptions): TxRows<Row>;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* The handle a transaction callback receives.
|
|
382
|
+
*
|
|
383
|
+
* It carries tables and nothing else: no `query`, no `findById`, no `asService`.
|
|
384
|
+
* A read whose value the plan does not write belongs OUTSIDE the transaction,
|
|
385
|
+
* where it costs one round trip and can be branched on like an ordinary value.
|
|
386
|
+
*/
|
|
387
|
+
interface TxPlanHandle<TTables> {
|
|
388
|
+
tables: TTables;
|
|
389
|
+
}
|
|
390
|
+
/** The server's `now()`. */
|
|
391
|
+
declare function now(): TxNow;
|
|
392
|
+
/** Add `by` to the column's current value. Only valid in an update's `set`. */
|
|
393
|
+
declare function inc(by: number): TxColumnExpr;
|
|
394
|
+
/** Subtract `by` from the column's current value. Only valid in an update's `set`. */
|
|
395
|
+
declare function dec(by: number): TxColumnExpr;
|
|
396
|
+
|
|
70
397
|
/** HTTP error with structured error response format.
|
|
71
398
|
*
|
|
72
399
|
* The base class for the throwable error classes (`PalError`, `Conflict`,
|
|
@@ -1397,24 +1724,29 @@ interface DBOps {
|
|
|
1397
1724
|
findById(table: string, id: string): Promise<Record<string, unknown> | null>;
|
|
1398
1725
|
findMany(table: string, query?: Record<string, unknown>): Promise<Record<string, unknown>[]>;
|
|
1399
1726
|
}
|
|
1400
|
-
/** The transaction-scoped client passed to `db.transaction(fn)` — the raw DB
|
|
1401
|
-
* ops only. No nested transaction and no `asService` (the DB role is fixed once
|
|
1402
|
-
* when the transaction begins; see `Database.asService().transaction(...)`). */
|
|
1403
|
-
type TxClient = DBOps;
|
|
1404
1727
|
/** Database client interface injected into endpoint context. */
|
|
1405
1728
|
interface DBClient extends DBOps {
|
|
1406
1729
|
/**
|
|
1407
|
-
* Run
|
|
1408
|
-
*
|
|
1409
|
-
*
|
|
1730
|
+
* Run a whole transaction in ONE request (`POST /internal-api/db/tx`).
|
|
1731
|
+
*
|
|
1732
|
+
* The low-level seam behind `Database.transaction(plan)`: the SDK builds the
|
|
1733
|
+
* plan, this sends it, and the broker executes every operation inside a single
|
|
1734
|
+
* transaction that commits or rolls back before the response is written.
|
|
1735
|
+
* Nothing pins a Postgres backend across round trips, because there is only
|
|
1736
|
+
* one round trip.
|
|
1737
|
+
*
|
|
1738
|
+
* On failure the runtime must reject with an error carrying the broker's
|
|
1739
|
+
* envelope fields — see {@link TxPlanRejection}. `slot` in particular is what
|
|
1740
|
+
* turns a `tx_guard_failed` back into the `Error` the tenant handed to
|
|
1741
|
+
* `.expectOne(…)`; without it a declared expectation degrades to a generic 409.
|
|
1410
1742
|
*/
|
|
1411
|
-
|
|
1743
|
+
txPlan(plan: TxPlanBody): Promise<TxPlanResponse>;
|
|
1412
1744
|
/**
|
|
1413
1745
|
* Return a sibling DB client that bypasses Row-Level Security by running as
|
|
1414
1746
|
* the `service_role` (BYPASSRLS). Use sparingly and explicitly — the default
|
|
1415
1747
|
* `Database.*` path is RLS-enforced. The returned client exposes the same op
|
|
1416
|
-
* surface (`query`/`insert`/.../`
|
|
1417
|
-
*
|
|
1748
|
+
* surface (`query`/`insert`/.../`txPlan`) but never re-exposes `asService`
|
|
1749
|
+
* (no double-bypass).
|
|
1418
1750
|
*/
|
|
1419
1751
|
asService(): Omit<DBClient, "asService">;
|
|
1420
1752
|
}
|
|
@@ -1652,4 +1984,4 @@ type Middleware = (ctx: MiddlewareContext, next: () => Promise<void>) => Promise
|
|
|
1652
1984
|
*/
|
|
1653
1985
|
type AuthSpec = boolean | Partial<AuthConfig>;
|
|
1654
1986
|
|
|
1655
|
-
export { type
|
|
1987
|
+
export { type PalbaseDeviceInfo as $, type AuthSpec as A, BadRequest as B, type CacheClient as C, type DBClient as D, type ErrorDef as E, type FileContext as F, type PalbaseAuthClient as G, HttpError as H, type PalbaseBatchOverrideOperation as I, type PalbaseBatchSetOverridesResult as J, type PalbaseBindDeviceParams as K, type Logger as L, type Materialized as M, NotFound as N, type PalbaseBucketClient as O, type PBRequest as P, type QueueClient as Q, type RateLimitConfig as R, type PalbaseClearAllOverridesResult as S, type PalbaseClearOverrideResult as T, type User as U, type PalbaseCohortQueryInput as V, type PalbaseCohortResult as W, type PalbaseCollectionRef as X, type PalbaseCountQueryInput as Y, type PalbaseCountResult as Z, type PalbaseCreateLinkParams as _, type PalbaseModuleClients as a, type PalbaseUsersResult as a$, type PalbaseDeviceTokenView as a0, type PalbaseDocumentRef as a1, type PalbaseDocumentSnapshot as a2, type PalbaseEmailClient as a3, type PalbaseEmailSendParams as a4, type PalbaseEmailSendResponse as a5, type PalbaseEventNamesResult as a6, type PalbaseEventsQueryInput as a7, type PalbaseEventsResult as a8, type PalbaseFileObject as a9, type PalbaseMultiChannelResponse as aA, type PalbaseOverviewResult as aB, type PalbasePreferences as aC, type PalbasePreferencesClient as aD, type PalbasePublicUrlResponse as aE, type PalbasePushClient as aF, type PalbasePushSendParams as aG, type PalbasePushSendResponse as aH, type PalbaseQrCodeOptions as aI, type PalbaseQuerySnapshot as aJ, type PalbaseRegisterDeviceParams as aK, type PalbaseResult as aL, type PalbaseRetentionQueryInput as aM, type PalbaseRetentionResult as aN, type PalbaseSession as aO, type PalbaseSetOverrideResult as aP, type PalbaseSetOverridesResult as aQ, type PalbaseSignedUrlResponse as aR, type PalbaseSmsClient as aS, type PalbaseSmsSendParams as aT, type PalbaseSmsSendResponse as aU, type PalbaseTransformOptions as aV, type PalbaseUpdateLinkParams as aW, type PalbaseUploadOptions as aX, type PalbaseUser as aY, type PalbaseUserDetailResult as aZ, type PalbaseUsersQueryInput as a_, type PalbaseFlag as aa, type PalbaseFlagContext as ab, type PalbaseFlagSource as ac, type PalbaseFlagValue as ad, type PalbaseFlagVariant as ae, type PalbaseFlagsServiceClient as af, type PalbaseFunctionsClient as ag, type PalbaseFunnelQueryInput as ah, type PalbaseFunnelResult as ai, type PalbaseIdentifyTraits as aj, type PalbaseInboxClient as ak, type PalbaseInboxListOptions as al, type PalbaseInboxListResult as am, type PalbaseInboxMessage as an, type PalbaseInboxSendParams as ao, type PalbaseInboxSendResponse as ap, type PalbaseInitialLink as aq, type PalbaseInvokeOptions as ar, type PalbaseLink as as, type PalbaseLinkAnalytics as at, type PalbaseLinkDetails as au, type PalbaseLinksClient as av, type PalbaseListLinksOptions as aw, type PalbaseListLinksResult as ax, type PalbaseListOptions as ay, type PalbaseMatchParams as az, type PalbaseDocsClient as b, type PalbaseVerifyRequestSignatureParams as b0, type PalbaseWhereOperator as b1, type Ref as b2, TooManyRequests as b3, type TxColumnExpr as b4, type TxInsertShape as b5, type TxInsertValue as b6, type TxNow as b7, type TxPlanBody as b8, TxPlanError as b9, type TxPlanHandle as ba, type TxPlanOpResult as bb, type TxPlanRejection as bc, type TxPlanResponse as bd, TxRefError as be, type TxRow as bf, type TxRows as bg, type TxSelectOptions as bh, type TxSetShape as bi, type TxSetValue as bj, type TxTable as bk, type TxWhere as bl, type TxWireExpr as bm, type TxWireGuard as bn, type TxWireOp as bo, type TxWireRef as bp, type TxWireValue as bq, Unauthorized as br, type VerifiedDevice as bs, dec as bt, defineMiddleware as bu, inc as bv, now as bw, type PalbaseFlagsClient as c, type PalbaseNotificationsClient as d, type PalbaseRealtimeClient as e, type PalbaseStorageClient as f, type AuthConfig as g, type ClientInfo as h, Conflict as i, type DBOps as j, type ErrorMap as k, type ErrorThrowers as l, Forbidden as m, type HttpMethod as n, type Middleware as o, type MiddlewareContext as p, type MiddlewareHandler as q, PalError as r, type PalbaseAnalyticsClient as s, type PalbaseAnalyticsManagementNamespace as t, type PalbaseAnalyticsProperties as u, type PalbaseAnalyticsQueryNamespace as v, type PalbaseAttestAndroidParams as w, type PalbaseAttestAndroidResult as x, type PalbaseAttestiOSParams as y, type PalbaseAttestiOSResult as z };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Tables, TableTypes } from './db/env.cjs';
|
|
2
|
-
import { D as DBClient } from './endpoint-
|
|
2
|
+
import { D as DBClient, ba as TxPlanHandle, bk as TxTable, M as Materialized } from './endpoint-Ck4hER_7.cjs';
|
|
3
3
|
|
|
4
4
|
/** On delete action for foreign key references. */
|
|
5
5
|
type OnDeleteAction = 'cascade' | 'set null' | 'restrict' | 'no action';
|
|
@@ -536,29 +536,27 @@ interface TypedDB<S extends SchemaDef> {
|
|
|
536
536
|
tables: {
|
|
537
537
|
[K in keyof S["tables"]]: TypedTable<S["tables"][K]>;
|
|
538
538
|
};
|
|
539
|
-
transaction
|
|
540
|
-
|
|
541
|
-
/** Transaction-scoped typed facade: same typed tables, no nested transaction. */
|
|
542
|
-
interface TypedTx<S extends SchemaDef> {
|
|
543
|
-
tables: {
|
|
544
|
-
[K in keyof S["tables"]]: TypedTable<S["tables"][K]>;
|
|
545
|
-
};
|
|
539
|
+
/** Run a transaction plan. See {@link EnvTypedDatabase.transaction}. */
|
|
540
|
+
transaction<T>(fn: (tx: TypedTx<S>) => T extends Promise<unknown> ? never : T): Promise<Materialized<T>>;
|
|
546
541
|
}
|
|
542
|
+
/** The plan-building handle a `TypedDB<S>` transaction callback receives: the
|
|
543
|
+
* schema's tables, expressed as plan operations rather than awaited calls. */
|
|
544
|
+
type TypedTx<S extends SchemaDef> = TxPlanHandle<{
|
|
545
|
+
[K in keyof S["tables"]]: TxTable<RowShape<S["tables"][K]>, InsertShape<S["tables"][K]>>;
|
|
546
|
+
}>;
|
|
547
547
|
/**
|
|
548
548
|
* Wraps a raw `DBClient` with the type-safe `TypedDB<S>` facade derived from
|
|
549
|
-
* the provided schema. No behavior change
|
|
550
|
-
* the table name as a plain string.
|
|
549
|
+
* the provided schema. No behavior change for the direct ops — all calls
|
|
550
|
+
* delegate to `raw` with the table name as a plain string.
|
|
551
551
|
*
|
|
552
|
-
* `
|
|
553
|
-
*
|
|
554
|
-
*
|
|
555
|
-
*
|
|
556
|
-
* the runtime yields so the callback sees the same typed `.tables` API.
|
|
552
|
+
* `transaction` does NOT delegate to a per-op client: the callback describes a
|
|
553
|
+
* plan against a fresh {@link TxPlanBuilder}, and the whole plan travels in one
|
|
554
|
+
* `raw.txPlan` call. The schema is used only for its table NAMES; the values
|
|
555
|
+
* are typed by `S` at compile time and are plain strings at run time.
|
|
557
556
|
*
|
|
558
|
-
* `
|
|
559
|
-
*
|
|
560
|
-
*
|
|
561
|
-
* cannot infer through `Object.keys` iteration) — see module-level doc comment.
|
|
557
|
+
* The `as` casts are single structural narrowings from a dynamically-built
|
|
558
|
+
* object to the precise mapped type (TS cannot infer the mapped-type result
|
|
559
|
+
* through `Object.keys` iteration) — see the module-level doc comment.
|
|
562
560
|
*/
|
|
563
561
|
declare function makeTypedDB<S extends SchemaDef>(schema: S, raw: DBClient): TypedDB<S>;
|
|
564
562
|
/** A typed table accessor derived from one env `Tables` entry's flat shapes. */
|
|
@@ -578,34 +576,66 @@ interface EnvTypedTable<T extends TableTypes> {
|
|
|
578
576
|
type EnvTables = {
|
|
579
577
|
[K in keyof Tables]: EnvTypedTable<Tables[K]>;
|
|
580
578
|
};
|
|
581
|
-
/**
|
|
582
|
-
*
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
}
|
|
579
|
+
/** The project's tables as PLAN operations, keyed by the env `Tables`
|
|
580
|
+
* interface. The transaction twin of {@link EnvTables}. */
|
|
581
|
+
type TxTables = {
|
|
582
|
+
[K in keyof Tables]: TxTable<Tables[K]["row"], Tables[K]["insert"]>;
|
|
583
|
+
};
|
|
584
|
+
/**
|
|
585
|
+
* The handle a `Database.transaction(…)` callback receives.
|
|
586
|
+
*
|
|
587
|
+
* Tables only — no `query`, no `findById`, no `asService`. A read whose value
|
|
588
|
+
* the plan does not write belongs outside the transaction, where it costs one
|
|
589
|
+
* round trip and is an ordinary value you can branch on.
|
|
590
|
+
*/
|
|
591
|
+
type TxPlan = TxPlanHandle<TxTables>;
|
|
586
592
|
/**
|
|
587
593
|
* The RLS-bypass sibling returned by `Database.asService()`. Same typed surface
|
|
588
594
|
* as {@link EnvTypedDatabase} — `tables`, the raw string ops, and a typed
|
|
589
595
|
* `transaction` — but it does NOT re-expose `asService` (no double-bypass).
|
|
590
596
|
* Every op it performs runs as the `service_role` (BYPASSRLS).
|
|
591
597
|
*/
|
|
592
|
-
interface EnvServiceDatabase extends Omit<DBClient, "
|
|
598
|
+
interface EnvServiceDatabase extends Omit<DBClient, "txPlan" | "asService"> {
|
|
593
599
|
tables: EnvTables;
|
|
594
|
-
transaction<T>(fn: (tx:
|
|
600
|
+
transaction<T>(fn: (tx: TxPlan) => T extends Promise<unknown> ? never : T): Promise<Materialized<T>>;
|
|
595
601
|
}
|
|
596
602
|
/**
|
|
597
603
|
* The typed-by-default Database surface: the raw string-keyed `DBClient` ops
|
|
598
604
|
* PLUS a `tables` map typed against the project's generated `palbase-env.d.ts`,
|
|
599
|
-
* a `transaction`
|
|
600
|
-
*
|
|
605
|
+
* a `transaction` that runs a whole plan in one request, and `asService()` for
|
|
606
|
+
* the explicit RLS-bypass sibling.
|
|
601
607
|
*
|
|
602
|
-
* `
|
|
603
|
-
*
|
|
604
|
-
*
|
|
608
|
+
* The low-level `txPlan` op is deliberately NOT re-exposed here: `transaction`
|
|
609
|
+
* is the surface, and a hand-built plan would bypass the ref/guard machinery
|
|
610
|
+
* that makes one safe to write.
|
|
605
611
|
*/
|
|
606
|
-
interface EnvTypedDatabase extends Omit<DBClient, "
|
|
612
|
+
interface EnvTypedDatabase extends Omit<DBClient, "txPlan" | "asService"> {
|
|
607
613
|
tables: EnvTables;
|
|
608
|
-
|
|
614
|
+
/**
|
|
615
|
+
* Run a transaction. The callback DESCRIBES the operations; the whole
|
|
616
|
+
* description travels in one request and the broker runs it inside a single
|
|
617
|
+
* transaction — committing when it finishes, rolling back on any failure.
|
|
618
|
+
*
|
|
619
|
+
* The callback is SYNCHRONOUS: nothing has run when it returns, so there is
|
|
620
|
+
* nothing to await. `async` on it and `await` inside it are compile errors.
|
|
621
|
+
* Values a later operation needs are {@link Ref}s, written straight into the
|
|
622
|
+
* next operation; values the CALLER needs are returned and substituted before
|
|
623
|
+
* this promise resolves.
|
|
624
|
+
*
|
|
625
|
+
* @example
|
|
626
|
+
* const { statementId } = await Database.transaction((tx) => {
|
|
627
|
+
* const st = tx.tables.statements
|
|
628
|
+
* .insert({ household_id: hid, file_sha256: sha, status: "reviewing" })
|
|
629
|
+
* .expectOne(new Internal("statement insert failed"));
|
|
630
|
+
*
|
|
631
|
+
* tx.tables.statement_lines.insertMany(
|
|
632
|
+
* lines.map((l) => ({ statement_id: st.id, category: resolveCategory(l) })),
|
|
633
|
+
* );
|
|
634
|
+
*
|
|
635
|
+
* return { statementId: st.id };
|
|
636
|
+
* });
|
|
637
|
+
*/
|
|
638
|
+
transaction<T>(fn: (tx: TxPlan) => T extends Promise<unknown> ? never : T): Promise<Materialized<T>>;
|
|
609
639
|
/**
|
|
610
640
|
* Return a sibling that bypasses RLS by running as the `service_role`. Use
|
|
611
641
|
* sparingly and explicitly — the default `Database.*` path is RLS-enforced.
|
|
@@ -617,4 +647,4 @@ interface EnvTypedDatabase extends Omit<DBClient, "transaction" | "asService"> {
|
|
|
617
647
|
asService(): EnvServiceDatabase;
|
|
618
648
|
}
|
|
619
649
|
|
|
620
|
-
export {
|
|
650
|
+
export { jsonb as A, makeTypedDB as B, ColumnBuilder as C, numeric as D, type EnvTypedDatabase as E, policy as F, raw as G, text as H, type InsertShape as I, timestamp as J, uuid as K, type OnDeleteAction as O, PALBASE_EXTENSIONS as P, type RawConstraintDef as R, type SchemaDef as S, type TableDef as T, type ColumnDef as a, type ColumnMap as b, type ColumnType as c, EXTENSION_DEPENDENCIES as d, type EnvServiceDatabase as e, type EnvTables as f, type EnvTypedTable as g, type PalbaseExtension as h, PolicyBuilder as i, type PolicyCommand as j, type PolicyDef as k, type PolicyMode as l, type RowShape as m, type SchemaInput as n, type TableInput as o, type TxPlan as p, type TxTables as q, type TypedDB as r, type TypedTable as s, type TypedTx as t, bigint as u, boolean as v, defineSchema as w, enumType as x, integer as y, isPalbaseExtension as z };
|