@palbase/backend 10.3.0 → 11.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 +459 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +14 -2
- 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/database.md +106 -16
- package/docs/llms-full.txt +112 -20
- package/docs/schema.md +6 -4
- package/package.json +1 -1
- package/dist/chunk-2PCNZBNZ.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -66,6 +66,8 @@ __export(src_exports, {
|
|
|
66
66
|
TEST_USERS_CONFIG_KIND: () => TEST_USERS_CONFIG_KIND,
|
|
67
67
|
TooManyRequests: () => TooManyRequests,
|
|
68
68
|
TraceId: () => TraceId,
|
|
69
|
+
TxPlanError: () => TxPlanError,
|
|
70
|
+
TxRefError: () => TxRefError,
|
|
69
71
|
Unauthorized: () => Unauthorized,
|
|
70
72
|
Upload: () => Upload,
|
|
71
73
|
UploadedObject: () => UploadedObject,
|
|
@@ -82,6 +84,7 @@ __export(src_exports, {
|
|
|
82
84
|
boolean: () => boolean,
|
|
83
85
|
bucket: () => bucket,
|
|
84
86
|
buildProvider: () => buildProvider,
|
|
87
|
+
dec: () => dec,
|
|
85
88
|
defineEgress: () => defineEgress,
|
|
86
89
|
defineError: () => defineError,
|
|
87
90
|
defineFlags: () => defineFlags,
|
|
@@ -99,12 +102,14 @@ __export(src_exports, {
|
|
|
99
102
|
flag: () => flag,
|
|
100
103
|
getErrorRegistry: () => getErrorRegistry,
|
|
101
104
|
getRoutes: () => getRoutes,
|
|
105
|
+
inc: () => inc,
|
|
102
106
|
integer: () => integer,
|
|
103
107
|
isPalbaseExtension: () => isPalbaseExtension,
|
|
104
108
|
jsonb: () => jsonb,
|
|
105
109
|
makeEnvDts: () => makeEnvDts,
|
|
106
110
|
makePurchasesDts: () => makePurchasesDts,
|
|
107
111
|
makeTypedDB: () => makeTypedDB,
|
|
112
|
+
now: () => now,
|
|
108
113
|
numeric: () => numeric,
|
|
109
114
|
parseFileSizeLimit: () => parseFileSizeLimit,
|
|
110
115
|
policy: () => policy,
|
|
@@ -124,6 +129,416 @@ module.exports = __toCommonJS(src_exports);
|
|
|
124
129
|
|
|
125
130
|
// src/runtime.ts
|
|
126
131
|
var import_node_async_hooks = require("async_hooks");
|
|
132
|
+
|
|
133
|
+
// src/db/tx-plan.ts
|
|
134
|
+
var TxRefError = class extends Error {
|
|
135
|
+
constructor(message) {
|
|
136
|
+
super(message);
|
|
137
|
+
this.name = "TxRefError";
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
var TxPlanError = class extends Error {
|
|
141
|
+
constructor(message) {
|
|
142
|
+
super(message);
|
|
143
|
+
this.name = "TxPlanError";
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
var EXPR = /* @__PURE__ */ Symbol.for("palbase.tx.expr");
|
|
147
|
+
var REF = /* @__PURE__ */ Symbol.for("palbase.tx.ref");
|
|
148
|
+
var ROW = /* @__PURE__ */ Symbol.for("palbase.tx.row");
|
|
149
|
+
var ROWS = /* @__PURE__ */ Symbol.for("palbase.tx.rows");
|
|
150
|
+
var TRAPPED_PROPS = [
|
|
151
|
+
"then",
|
|
152
|
+
"valueOf",
|
|
153
|
+
"toString",
|
|
154
|
+
"toJSON",
|
|
155
|
+
Symbol.toPrimitive
|
|
156
|
+
];
|
|
157
|
+
function trap(prop, what, hint) {
|
|
158
|
+
const name = typeof prop === "symbol" ? prop.description ?? String(prop) : prop;
|
|
159
|
+
throw new TxRefError(
|
|
160
|
+
`${what} was used as a value (via \`${name}\`). Nothing in a transaction callback has run yet, so there is no value to read. ${hint}`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
function now() {
|
|
164
|
+
return makeExpr({ fn: "now" });
|
|
165
|
+
}
|
|
166
|
+
function inc(by) {
|
|
167
|
+
assertFiniteNumber(by, "inc");
|
|
168
|
+
return makeExpr({ fn: "inc", by });
|
|
169
|
+
}
|
|
170
|
+
function dec(by) {
|
|
171
|
+
assertFiniteNumber(by, "dec");
|
|
172
|
+
return makeExpr({ fn: "dec", by });
|
|
173
|
+
}
|
|
174
|
+
function assertFiniteNumber(by, fn) {
|
|
175
|
+
if (typeof by !== "number" || !Number.isFinite(by)) {
|
|
176
|
+
throw new TxPlanError(`${fn}() needs a finite number, got ${String(by)}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function makeExpr(expr) {
|
|
180
|
+
return new Proxy(
|
|
181
|
+
{ [EXPR]: expr },
|
|
182
|
+
{
|
|
183
|
+
get(target, prop) {
|
|
184
|
+
if (prop === EXPR) return target[EXPR];
|
|
185
|
+
if (TRAPPED_PROPS.includes(prop)) {
|
|
186
|
+
trap(prop, "A plan expression", "Write it into an operation instead.");
|
|
187
|
+
}
|
|
188
|
+
return void 0;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
function makeRef(op, field) {
|
|
194
|
+
const target = { [REF]: { op, field } };
|
|
195
|
+
return new Proxy(target, {
|
|
196
|
+
get(t, prop) {
|
|
197
|
+
if (prop === REF) return t[REF];
|
|
198
|
+
if (TRAPPED_PROPS.includes(prop)) {
|
|
199
|
+
trap(
|
|
200
|
+
prop,
|
|
201
|
+
`\`${field}\` of a row this transaction has not written yet`,
|
|
202
|
+
"Pass it to another operation in the same plan, or return it from the callback and read it after `transaction()` resolves."
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
return void 0;
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
function makeRowHandle(op) {
|
|
210
|
+
const target = { [ROW]: op };
|
|
211
|
+
return new Proxy(target, {
|
|
212
|
+
get(t, prop) {
|
|
213
|
+
if (prop === ROW) return t[ROW];
|
|
214
|
+
if (TRAPPED_PROPS.includes(prop)) {
|
|
215
|
+
trap(
|
|
216
|
+
prop,
|
|
217
|
+
"A row this transaction has not written yet",
|
|
218
|
+
"Read one of its columns to reference it, or return the row from the callback and read it after `transaction()` resolves."
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
if (typeof prop === "symbol") return void 0;
|
|
222
|
+
return makeRef(op, prop);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
function refDescriptor(v) {
|
|
227
|
+
if (typeof v !== "object" || v === null) return null;
|
|
228
|
+
const d = v[REF];
|
|
229
|
+
return isRefDescriptor(d) ? d : null;
|
|
230
|
+
}
|
|
231
|
+
function isRefDescriptor(d) {
|
|
232
|
+
return typeof d === "object" && d !== null && typeof d.op === "number" && typeof d.field === "string";
|
|
233
|
+
}
|
|
234
|
+
function rowOpIndex(v) {
|
|
235
|
+
if (typeof v !== "object" || v === null) return null;
|
|
236
|
+
const op = v[ROW];
|
|
237
|
+
return typeof op === "number" ? op : null;
|
|
238
|
+
}
|
|
239
|
+
function exprOf(v) {
|
|
240
|
+
if (typeof v !== "object" || v === null) return null;
|
|
241
|
+
const e = v[EXPR];
|
|
242
|
+
return typeof e === "object" && e !== null ? e : null;
|
|
243
|
+
}
|
|
244
|
+
function isRowsHandle(v) {
|
|
245
|
+
return typeof v === "object" && v !== null && v[ROWS] !== void 0;
|
|
246
|
+
}
|
|
247
|
+
function encodeValue(value, column, allowColumnExpr) {
|
|
248
|
+
const ref = refDescriptor(value);
|
|
249
|
+
if (ref) return { $ref: { op: ref.op, field: ref.field } };
|
|
250
|
+
const expr = exprOf(value);
|
|
251
|
+
if (expr) {
|
|
252
|
+
if (expr.fn !== "now" && !allowColumnExpr) {
|
|
253
|
+
throw new TxPlanError(
|
|
254
|
+
`\`${column}\`: ${expr.fn}() reads the column's current value, so it is only valid in updateWhere(where, set).`
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
return { $expr: expr };
|
|
258
|
+
}
|
|
259
|
+
if (rowOpIndex(value) !== null) {
|
|
260
|
+
throw new TxPlanError(
|
|
261
|
+
`\`${column}\`: a row handle is not a value. Read the column you meant (e.g. \`row.id\`).`
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
if (isRowsHandle(value)) {
|
|
265
|
+
throw new TxPlanError(
|
|
266
|
+
`\`${column}\`: an operation result is not a value. Declare an expectation first (\`.expectOne(err)\`) and read a column from the row.`
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
assertNoNestedHandles(value, column);
|
|
270
|
+
return value;
|
|
271
|
+
}
|
|
272
|
+
function assertNoNestedHandles(value, column) {
|
|
273
|
+
if (typeof value !== "object" || value === null) return;
|
|
274
|
+
if (value instanceof Date) return;
|
|
275
|
+
if (refDescriptor(value) || exprOf(value) || rowOpIndex(value) !== null || isRowsHandle(value)) {
|
|
276
|
+
throw new TxPlanError(
|
|
277
|
+
`\`${column}\`: a plan handle is nested inside a value. The server would store it as literal JSON, not resolve it. Put the reference directly in the column.`
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
if (Array.isArray(value)) {
|
|
281
|
+
for (const item of value) assertNoNestedHandles(item, column);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
for (const item of Object.values(value)) {
|
|
285
|
+
assertNoNestedHandles(item, column);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
function encodeMap(map, allowColumnExpr) {
|
|
289
|
+
const out = {};
|
|
290
|
+
for (const key of Object.keys(map).sort()) {
|
|
291
|
+
const value = map[key];
|
|
292
|
+
if (value === void 0) continue;
|
|
293
|
+
out[key] = encodeValue(value, key, allowColumnExpr);
|
|
294
|
+
}
|
|
295
|
+
return out;
|
|
296
|
+
}
|
|
297
|
+
var SKIPPED_OP = -1;
|
|
298
|
+
var TxRowsImpl = class {
|
|
299
|
+
constructor(builder, opIndex, what) {
|
|
300
|
+
this.builder = builder;
|
|
301
|
+
this.opIndex = opIndex;
|
|
302
|
+
this.what = what;
|
|
303
|
+
}
|
|
304
|
+
builder;
|
|
305
|
+
opIndex;
|
|
306
|
+
what;
|
|
307
|
+
// Present so `isRowsHandle` recognises the object; never read for its value.
|
|
308
|
+
[ROWS] = true;
|
|
309
|
+
guarded = false;
|
|
310
|
+
// The type-level `await` guard made real: TS rejects `await rows` at compile
|
|
311
|
+
// time, and reaching this means someone called `.then(...)` by hand.
|
|
312
|
+
then() {
|
|
313
|
+
throw new TxRefError(
|
|
314
|
+
`${this.what} cannot be awaited: a transaction callback builds a plan, it does not run statements. Remove the \`await\`.`
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
expectOne(error) {
|
|
318
|
+
this.declareGuard("one", 1, error);
|
|
319
|
+
if (this.opIndex === SKIPPED_OP) throw error;
|
|
320
|
+
return makeRowHandle(this.opIndex);
|
|
321
|
+
}
|
|
322
|
+
expectNone(error) {
|
|
323
|
+
this.declareGuard("none", 0, error);
|
|
324
|
+
}
|
|
325
|
+
expectAtLeast(n, error) {
|
|
326
|
+
assertGuardCount(n, "expectAtLeast");
|
|
327
|
+
this.declareGuard("atLeast", n, error);
|
|
328
|
+
if (this.opIndex === SKIPPED_OP && n > 0) throw error;
|
|
329
|
+
}
|
|
330
|
+
expectAtMost(n, error) {
|
|
331
|
+
assertGuardCount(n, "expectAtMost");
|
|
332
|
+
this.declareGuard("atMost", n, error);
|
|
333
|
+
}
|
|
334
|
+
declareGuard(kind, n, error) {
|
|
335
|
+
if (!(error instanceof Error)) {
|
|
336
|
+
throw new TxPlanError(
|
|
337
|
+
`${this.what}: an expectation needs the Error to throw when it does not hold (e.g. \`.expect\u2026(new Conflict("already accepted"))\`).`
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
if (this.guarded) {
|
|
341
|
+
throw new TxPlanError(
|
|
342
|
+
`${this.what} already has an expectation. One operation carries one expectation; declare the second one on its own operation.`
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
this.guarded = true;
|
|
346
|
+
if (this.opIndex === SKIPPED_OP) return;
|
|
347
|
+
this.builder.attachGuard(this.opIndex, kind, n, error);
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
function assertGuardCount(n, fn) {
|
|
351
|
+
if (!Number.isInteger(n) || n < 0) {
|
|
352
|
+
throw new TxPlanError(`${fn}(n) needs a non-negative integer, got ${String(n)}`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
var MAX_OPS = 1e3;
|
|
356
|
+
var MAX_ROWS = 5e3;
|
|
357
|
+
var TxPlanBuilder = class {
|
|
358
|
+
ops = [];
|
|
359
|
+
/** Errors handed to expectations, indexed by the `slot` the server echoes. */
|
|
360
|
+
slots = [];
|
|
361
|
+
/** The table surface handed to the callback. Untyped here; the public
|
|
362
|
+
* `transaction()` signatures put the schema types on top. */
|
|
363
|
+
table(name) {
|
|
364
|
+
return {
|
|
365
|
+
insert: (values) => {
|
|
366
|
+
const encoded = encodeMap(values, false);
|
|
367
|
+
if (Object.keys(encoded).length === 0) {
|
|
368
|
+
throw new TxPlanError(`${name}.insert() needs at least one column`);
|
|
369
|
+
}
|
|
370
|
+
return this.push({ op: "insert", table: name, values: encoded }, `${name}.insert()`);
|
|
371
|
+
},
|
|
372
|
+
insertMany: (rows) => {
|
|
373
|
+
if (rows.length === 0) {
|
|
374
|
+
return new TxRowsImpl(this, SKIPPED_OP, `${name}.insertMany()`);
|
|
375
|
+
}
|
|
376
|
+
if (rows.length > MAX_ROWS) {
|
|
377
|
+
throw new TxPlanError(
|
|
378
|
+
`${name}.insertMany() has ${rows.length} rows; the limit is ${MAX_ROWS}. Split the write across requests.`
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
const encoded = rows.map((row) => encodeMap(row, false));
|
|
382
|
+
assertUniformRows(encoded, name);
|
|
383
|
+
return this.push({ op: "insertMany", table: name, rows: encoded }, `${name}.insertMany()`);
|
|
384
|
+
},
|
|
385
|
+
updateWhere: (where, set) => {
|
|
386
|
+
const encodedWhere = encodeMap(where, false);
|
|
387
|
+
const encodedSet = encodeMap(set, true);
|
|
388
|
+
if (Object.keys(encodedWhere).length === 0) {
|
|
389
|
+
throw new TxPlanError(
|
|
390
|
+
`${name}.updateWhere() needs a filter. An update with no filter rewrites the whole table.`
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
if (Object.keys(encodedSet).length === 0) {
|
|
394
|
+
throw new TxPlanError(`${name}.updateWhere() needs at least one column to set`);
|
|
395
|
+
}
|
|
396
|
+
return this.push(
|
|
397
|
+
{ op: "update", table: name, set: encodedSet, where: encodedWhere },
|
|
398
|
+
`${name}.updateWhere()`
|
|
399
|
+
);
|
|
400
|
+
},
|
|
401
|
+
deleteWhere: (where) => {
|
|
402
|
+
const encodedWhere = encodeMap(where, false);
|
|
403
|
+
if (Object.keys(encodedWhere).length === 0) {
|
|
404
|
+
throw new TxPlanError(
|
|
405
|
+
`${name}.deleteWhere() needs a filter. A delete with no filter empties the table.`
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
return this.push(
|
|
409
|
+
{ op: "delete", table: name, where: encodedWhere },
|
|
410
|
+
`${name}.deleteWhere()`
|
|
411
|
+
);
|
|
412
|
+
},
|
|
413
|
+
select: (where, options) => {
|
|
414
|
+
const op = { op: "select", table: name };
|
|
415
|
+
const encodedWhere = encodeMap(where ?? {}, false);
|
|
416
|
+
if (Object.keys(encodedWhere).length > 0) op.where = encodedWhere;
|
|
417
|
+
if (options?.limit !== void 0) {
|
|
418
|
+
if (!Number.isInteger(options.limit) || options.limit < 0) {
|
|
419
|
+
throw new TxPlanError(
|
|
420
|
+
`${name}.select(): limit needs a non-negative integer, got ${String(options.limit)}`
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
op.limit = options.limit;
|
|
424
|
+
}
|
|
425
|
+
if (options?.lock !== void 0) op.lock = options.lock;
|
|
426
|
+
return this.push(op, `${name}.select()`);
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
push(op, what) {
|
|
431
|
+
if (this.ops.length >= MAX_OPS) {
|
|
432
|
+
throw new TxPlanError(
|
|
433
|
+
`this transaction has ${MAX_OPS} operations, which is the limit. Use insertMany() for bulk writes, or split the work across requests.`
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
const index = this.ops.length;
|
|
437
|
+
this.ops.push(op);
|
|
438
|
+
return new TxRowsImpl(this, index, what);
|
|
439
|
+
}
|
|
440
|
+
/** Attach an expectation to an op and record its error in the slot table. */
|
|
441
|
+
attachGuard(opIndex, kind, n, error) {
|
|
442
|
+
const op = this.ops[opIndex];
|
|
443
|
+
if (!op) throw new TxPlanError(`internal: expectation on unknown operation ${opIndex}`);
|
|
444
|
+
const slot = this.slots.length;
|
|
445
|
+
this.slots.push(error);
|
|
446
|
+
op.guard = { kind, n, slot };
|
|
447
|
+
}
|
|
448
|
+
/** The serialisable plan. Empty when the callback described no writes. */
|
|
449
|
+
body() {
|
|
450
|
+
return { ops: this.ops };
|
|
451
|
+
}
|
|
452
|
+
/** The error the server's `slot` selects, or `null` when it names one this
|
|
453
|
+
* plan never declared (a server/client disagreement, not a tenant error). */
|
|
454
|
+
errorForSlot(slot) {
|
|
455
|
+
return this.slots[slot] ?? null;
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
function assertUniformRows(rows, table) {
|
|
459
|
+
const first = rows[0];
|
|
460
|
+
if (!first) return;
|
|
461
|
+
const want = Object.keys(first);
|
|
462
|
+
const wantKey = want.join(",");
|
|
463
|
+
for (let i = 1; i < rows.length; i++) {
|
|
464
|
+
const got = Object.keys(rows[i]);
|
|
465
|
+
if (got.join(",") !== wantKey) {
|
|
466
|
+
throw new TxPlanError(
|
|
467
|
+
`${table}.insertMany(): every row must set the same columns. Row 0 sets [${want.join(", ")}] but row ${i} sets [${got.join(", ")}]. (A property set to \`undefined\` counts as absent \u2014 use \`null\`.)`
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
function materializeResult(value, results) {
|
|
473
|
+
const ref = refDescriptor(value);
|
|
474
|
+
if (ref) {
|
|
475
|
+
const row = rowOf(results, ref.op, `\`${ref.field}\``);
|
|
476
|
+
if (!(ref.field in row)) {
|
|
477
|
+
throw new TxPlanError(
|
|
478
|
+
`the transaction's operation ${ref.op} returned no column \`${ref.field}\`.`
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
return row[ref.field];
|
|
482
|
+
}
|
|
483
|
+
const rowOp = rowOpIndex(value);
|
|
484
|
+
if (rowOp !== null) return rowOf(results, rowOp, "a row");
|
|
485
|
+
if (isRowsHandle(value)) {
|
|
486
|
+
throw new TxPlanError(
|
|
487
|
+
"an operation result cannot be returned from a transaction callback: its row count is not known until the plan runs. Declare an expectation (`.expectOne(err)`) and return the row, or a column of it."
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
if (Array.isArray(value)) return value.map((item) => materializeResult(item, results));
|
|
491
|
+
if (isPlainObject(value)) {
|
|
492
|
+
const out = {};
|
|
493
|
+
for (const [key, item] of Object.entries(value)) out[key] = materializeResult(item, results);
|
|
494
|
+
return out;
|
|
495
|
+
}
|
|
496
|
+
return value;
|
|
497
|
+
}
|
|
498
|
+
function rowOf(results, opIndex, what) {
|
|
499
|
+
const result = results[opIndex];
|
|
500
|
+
if (!result) {
|
|
501
|
+
throw new TxPlanError(
|
|
502
|
+
`the transaction returned no result for operation ${opIndex}, so ${what} cannot be read.`
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
const row = result.rows[0];
|
|
506
|
+
if (!row) {
|
|
507
|
+
throw new TxPlanError(
|
|
508
|
+
`the transaction's operation ${opIndex} returned no row, so ${what} cannot be read.`
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
return row;
|
|
512
|
+
}
|
|
513
|
+
function isPlainObject(value) {
|
|
514
|
+
if (typeof value !== "object" || value === null) return false;
|
|
515
|
+
const proto = Object.getPrototypeOf(value);
|
|
516
|
+
return proto === Object.prototype || proto === null;
|
|
517
|
+
}
|
|
518
|
+
async function runTxPlan(transport, tables, builder, fn) {
|
|
519
|
+
const returned = fn({ tables });
|
|
520
|
+
const body = builder.body();
|
|
521
|
+
if (body.ops.length === 0) {
|
|
522
|
+
return materializeResult(returned, []);
|
|
523
|
+
}
|
|
524
|
+
let response;
|
|
525
|
+
try {
|
|
526
|
+
response = await transport.txPlan(body);
|
|
527
|
+
} catch (err) {
|
|
528
|
+
throw translateRejection(err, builder);
|
|
529
|
+
}
|
|
530
|
+
return materializeResult(returned, response.results);
|
|
531
|
+
}
|
|
532
|
+
function translateRejection(err, builder) {
|
|
533
|
+
if (typeof err !== "object" || err === null) return err;
|
|
534
|
+
const rejection = err;
|
|
535
|
+
if (rejection.error_code !== "tx_guard_failed" || typeof rejection.slot !== "number") {
|
|
536
|
+
return err;
|
|
537
|
+
}
|
|
538
|
+
return builder.errorForSlot(rejection.slot) ?? err;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// src/runtime.ts
|
|
127
542
|
var __requestALS = new import_node_async_hooks.AsyncLocalStorage();
|
|
128
543
|
var runtime = null;
|
|
129
544
|
function __setRuntime(services) {
|
|
@@ -184,10 +599,23 @@ function makeTypedSurface(raw2) {
|
|
|
184
599
|
return Object.assign(ops, {
|
|
185
600
|
tables: makeTablesAccessor(() => raw2),
|
|
186
601
|
transaction(fn) {
|
|
187
|
-
|
|
602
|
+
const builder = new TxPlanBuilder();
|
|
603
|
+
return runTxPlan(raw2, makeTxTablesAccessor(builder), builder, fn);
|
|
188
604
|
}
|
|
189
605
|
});
|
|
190
606
|
}
|
|
607
|
+
function makeTxTablesAccessor(builder) {
|
|
608
|
+
const tablesProxy = new Proxy(
|
|
609
|
+
{},
|
|
610
|
+
{
|
|
611
|
+
get(_t, prop) {
|
|
612
|
+
if (typeof prop !== "string") return void 0;
|
|
613
|
+
return builder.table(prop);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
);
|
|
617
|
+
return tablesProxy;
|
|
618
|
+
}
|
|
191
619
|
var Database = Object.assign(makeTypedSurface(rawDatabase), {
|
|
192
620
|
/**
|
|
193
621
|
* Lazily resolve the runtime's service-role sibling on each call. We do NOT
|
|
@@ -782,19 +1210,29 @@ function makeTypedTable(name, raw2) {
|
|
|
782
1210
|
};
|
|
783
1211
|
}
|
|
784
1212
|
function makeTypedDB(schema, raw2) {
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
tables[key] = makeTypedTable(tableDef.name, client);
|
|
791
|
-
}
|
|
1213
|
+
const tables = {};
|
|
1214
|
+
for (const key of Object.keys(schema.tables)) {
|
|
1215
|
+
const tableDef = schema.tables[key];
|
|
1216
|
+
if (tableDef !== void 0) {
|
|
1217
|
+
tables[key] = makeTypedTable(tableDef.name, raw2);
|
|
792
1218
|
}
|
|
793
|
-
return tables;
|
|
794
1219
|
}
|
|
795
1220
|
const result = {
|
|
796
|
-
tables
|
|
797
|
-
transaction
|
|
1221
|
+
tables,
|
|
1222
|
+
transaction(fn) {
|
|
1223
|
+
const builder = new TxPlanBuilder();
|
|
1224
|
+
const planTables = {};
|
|
1225
|
+
for (const key of Object.keys(schema.tables)) {
|
|
1226
|
+
const tableDef = schema.tables[key];
|
|
1227
|
+
if (tableDef !== void 0) planTables[key] = builder.table(tableDef.name);
|
|
1228
|
+
}
|
|
1229
|
+
return runTxPlan(
|
|
1230
|
+
raw2,
|
|
1231
|
+
planTables,
|
|
1232
|
+
builder,
|
|
1233
|
+
fn
|
|
1234
|
+
);
|
|
1235
|
+
}
|
|
798
1236
|
};
|
|
799
1237
|
return result;
|
|
800
1238
|
}
|
|
@@ -1133,12 +1571,12 @@ var E164_RE = /^\+[1-9]\d{1,14}$/;
|
|
|
1133
1571
|
var OTP_LENGTH = 6;
|
|
1134
1572
|
var OTP_RE = /^\d{6}$/;
|
|
1135
1573
|
var MIN_PASSWORD_LENGTH = 8;
|
|
1136
|
-
function
|
|
1574
|
+
function isPlainObject2(value) {
|
|
1137
1575
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1138
1576
|
}
|
|
1139
1577
|
function normalizeSeed(seed, userName) {
|
|
1140
1578
|
if (seed === void 0) return {};
|
|
1141
|
-
if (!
|
|
1579
|
+
if (!isPlainObject2(seed)) {
|
|
1142
1580
|
throw new Error(`testUser ${JSON.stringify(userName)}: seed must be { <table>: [ {...} ] }`);
|
|
1143
1581
|
}
|
|
1144
1582
|
const out = {};
|
|
@@ -1154,7 +1592,7 @@ function normalizeSeed(seed, userName) {
|
|
|
1154
1592
|
);
|
|
1155
1593
|
}
|
|
1156
1594
|
for (const row of rows) {
|
|
1157
|
-
if (!
|
|
1595
|
+
if (!isPlainObject2(row)) {
|
|
1158
1596
|
throw new Error(
|
|
1159
1597
|
`testUser ${JSON.stringify(userName)}: every row in seed.${table} must be an object`
|
|
1160
1598
|
);
|
|
@@ -1165,7 +1603,7 @@ function normalizeSeed(seed, userName) {
|
|
|
1165
1603
|
return out;
|
|
1166
1604
|
}
|
|
1167
1605
|
function testUser(opts = {}) {
|
|
1168
|
-
if (!
|
|
1606
|
+
if (!isPlainObject2(opts)) {
|
|
1169
1607
|
throw new Error("testUser() expects { email?, password?, phone?, otp?, seed? }");
|
|
1170
1608
|
}
|
|
1171
1609
|
const hasEmail = opts.email !== void 0;
|
|
@@ -1215,7 +1653,7 @@ function testUser(opts = {}) {
|
|
|
1215
1653
|
return { email, password, phone, otp, seed: normalizeSeed(opts.seed, email ?? "<template>") };
|
|
1216
1654
|
}
|
|
1217
1655
|
function defineTestUsers(input) {
|
|
1218
|
-
if (!
|
|
1656
|
+
if (!isPlainObject2(input) || !isPlainObject2(input.users)) {
|
|
1219
1657
|
throw new Error("defineTestUsers expects { users: { <name>: testUser({...}) } }");
|
|
1220
1658
|
}
|
|
1221
1659
|
const users = {};
|
|
@@ -2031,6 +2469,8 @@ var import_zod2 = require("zod");
|
|
|
2031
2469
|
TEST_USERS_CONFIG_KIND,
|
|
2032
2470
|
TooManyRequests,
|
|
2033
2471
|
TraceId,
|
|
2472
|
+
TxPlanError,
|
|
2473
|
+
TxRefError,
|
|
2034
2474
|
Unauthorized,
|
|
2035
2475
|
Upload,
|
|
2036
2476
|
UploadedObject,
|
|
@@ -2047,6 +2487,7 @@ var import_zod2 = require("zod");
|
|
|
2047
2487
|
boolean,
|
|
2048
2488
|
bucket,
|
|
2049
2489
|
buildProvider,
|
|
2490
|
+
dec,
|
|
2050
2491
|
defineEgress,
|
|
2051
2492
|
defineError,
|
|
2052
2493
|
defineFlags,
|
|
@@ -2064,12 +2505,14 @@ var import_zod2 = require("zod");
|
|
|
2064
2505
|
flag,
|
|
2065
2506
|
getErrorRegistry,
|
|
2066
2507
|
getRoutes,
|
|
2508
|
+
inc,
|
|
2067
2509
|
integer,
|
|
2068
2510
|
isPalbaseExtension,
|
|
2069
2511
|
jsonb,
|
|
2070
2512
|
makeEnvDts,
|
|
2071
2513
|
makePurchasesDts,
|
|
2072
2514
|
makeTypedDB,
|
|
2515
|
+
now,
|
|
2073
2516
|
numeric,
|
|
2074
2517
|
parseFileSizeLimit,
|
|
2075
2518
|
policy,
|