@palbase/backend 33.0.2 → 34.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 +2020 -350
- package/dist/bin/palbase-backend.cjs.map +1 -1
- package/dist/bin/palbase-backend.js +9 -7
- package/dist/bin/palbase-backend.js.map +1 -1
- package/dist/{chunk-SI4KGEM3.js → chunk-FOWA7PF4.js} +97 -96
- package/dist/chunk-FOWA7PF4.js.map +1 -0
- package/dist/chunk-GHNC2UHS.js +20 -0
- package/dist/chunk-GHNC2UHS.js.map +1 -0
- package/dist/{chunk-AAT5G7KY.js → chunk-JJW3L62T.js} +2 -2
- package/dist/chunk-JJW3L62T.js.map +1 -0
- package/dist/{chunk-TVCCR6SO.js → chunk-LOGL2ZHD.js} +58 -9
- package/dist/chunk-LOGL2ZHD.js.map +1 -0
- package/dist/{chunk-C6COAB3E.js → chunk-MNGZE2MM.js} +1651 -186
- package/dist/chunk-MNGZE2MM.js.map +1 -0
- package/dist/{chunk-XOX6RFPZ.js → chunk-PRH45NZU.js} +46 -3
- package/dist/chunk-PRH45NZU.js.map +1 -0
- package/dist/{chunk-26GH54D2.js → chunk-RZLY4BOY.js} +5 -3
- package/dist/{chunk-26GH54D2.js.map → chunk-RZLY4BOY.js.map} +1 -1
- package/dist/{chunk-YIQ4RS4F.js → chunk-SEM36BWY.js} +283 -5
- package/dist/chunk-SEM36BWY.js.map +1 -0
- package/dist/{chunk-KATPXCJ5.js → chunk-UMESJJRS.js} +19 -40
- package/dist/chunk-UMESJJRS.js.map +1 -0
- package/dist/chunk-VXPNPVAG.js +40 -0
- package/dist/chunk-VXPNPVAG.js.map +1 -0
- package/dist/{chunk-WWUG2QXF.js → chunk-YX7UA7VC.js} +53 -4
- package/dist/chunk-YX7UA7VC.js.map +1 -0
- package/dist/db/env.cjs.map +1 -1
- package/dist/db/env.d.cts +2 -0
- package/dist/db/env.d.ts +2 -0
- package/dist/db/index.cjs +261 -2
- package/dist/db/index.cjs.map +1 -1
- package/dist/db/index.d.cts +46 -3
- package/dist/db/index.d.ts +46 -3
- package/dist/db/index.js +14 -5
- package/dist/{index-DMZG3kpo.d.ts → endpoint-DpNUx-ON.d.ts} +315 -57
- package/dist/{index-DB_nW-AV.d.cts → endpoint-kYgEHrrV.d.cts} +315 -57
- package/dist/engine/index.cjs +2026 -348
- package/dist/engine/index.cjs.map +1 -1
- package/dist/engine/index.d.cts +5 -5
- package/dist/engine/index.d.ts +5 -5
- package/dist/engine/index.js +17 -7
- package/dist/{index-D-3duy8Y.d.ts → index-JrHqNsjQ.d.ts} +108 -50
- package/dist/{index-DLveQoOf.d.cts → index-pnDsEfxJ.d.cts} +108 -50
- package/dist/index.cjs +616 -254
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -6
- package/dist/index.d.ts +7 -6
- package/dist/index.js +25 -15
- package/dist/index.js.map +1 -1
- package/dist/openapi/index.cjs +7 -0
- package/dist/openapi/index.cjs.map +1 -1
- package/dist/openapi/index.d.cts +3 -3
- package/dist/openapi/index.d.ts +3 -3
- package/dist/openapi/index.js +4 -3
- package/dist/openapi/index.js.map +1 -1
- package/dist/{registry-DSTThhKf.d.cts → registry-BcOd8Zhf.d.cts} +3 -1
- package/dist/{registry-JjF5lcj4.d.ts → registry-BoX5iNyY.d.ts} +3 -1
- package/dist/test/index.cjs +103 -66
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.d.cts +3 -3
- package/dist/test/index.d.ts +3 -3
- package/dist/test/index.js +33 -7
- package/dist/test/index.js.map +1 -1
- package/docs/README.md +1 -1
- package/docs/database.md +308 -0
- package/docs/examples/check-query-contracts.ts +5 -0
- package/docs/examples/database-dx.contract.json +65 -0
- package/docs/examples/database-dx.ts +63 -0
- package/docs/examples/money-transfer.ts +73 -0
- package/docs/llms-full.txt +309 -1
- package/package.json +1 -2
- package/template/package.json +1 -1
- package/dist/chunk-AAT5G7KY.js.map +0 -1
- package/dist/chunk-C6COAB3E.js.map +0 -1
- package/dist/chunk-KATPXCJ5.js.map +0 -1
- package/dist/chunk-SI4KGEM3.js.map +0 -1
- package/dist/chunk-TVCCR6SO.js.map +0 -1
- package/dist/chunk-WWUG2QXF.js.map +0 -1
- package/dist/chunk-XOX6RFPZ.js.map +0 -1
- package/dist/chunk-YIQ4RS4F.js.map +0 -1
- package/stack-images.json +0 -24
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DeclarationRefused,
|
|
3
3
|
assertNoOrphanEntryPoints,
|
|
4
|
+
assertUniqueLookup,
|
|
4
5
|
buildContainer,
|
|
5
|
-
buildRelations
|
|
6
|
-
|
|
6
|
+
buildRelations,
|
|
7
|
+
uniqueKeysOf
|
|
8
|
+
} from "./chunk-YX7UA7VC.js";
|
|
9
|
+
import {
|
|
10
|
+
validateInsertManyOptions
|
|
11
|
+
} from "./chunk-GHNC2UHS.js";
|
|
7
12
|
import {
|
|
8
13
|
__requestALS,
|
|
9
14
|
__runStartHooks,
|
|
10
15
|
__runWithRuntime
|
|
11
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-LOGL2ZHD.js";
|
|
12
17
|
import {
|
|
13
|
-
CODECS
|
|
14
|
-
|
|
18
|
+
CODECS,
|
|
19
|
+
assertReadContract
|
|
20
|
+
} from "./chunk-PRH45NZU.js";
|
|
15
21
|
import {
|
|
16
22
|
assertNoExpressionHandles,
|
|
17
23
|
assertUsableFilter,
|
|
@@ -24,21 +30,31 @@ import {
|
|
|
24
30
|
isSqlFragment,
|
|
25
31
|
looksLikeUnbrandedPlanRef,
|
|
26
32
|
looksLikeUnbrandedRef,
|
|
27
|
-
qualifiedTableKey
|
|
28
|
-
|
|
33
|
+
qualifiedTableKey,
|
|
34
|
+
retryBudget,
|
|
35
|
+
validateAtomicOptions,
|
|
36
|
+
validateWorkload
|
|
37
|
+
} from "./chunk-FOWA7PF4.js";
|
|
29
38
|
import {
|
|
30
39
|
getRoutes,
|
|
31
40
|
resolveEffectiveAuth
|
|
32
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-JJW3L62T.js";
|
|
33
42
|
import {
|
|
43
|
+
BadRequest,
|
|
34
44
|
DeadlockDetected,
|
|
45
|
+
HttpError,
|
|
46
|
+
SQLSTATE,
|
|
35
47
|
SerializationFailure,
|
|
48
|
+
TooManyRequests,
|
|
36
49
|
UniqueViolation,
|
|
37
|
-
__name,
|
|
38
50
|
isEngineRaised,
|
|
39
51
|
isHttpError,
|
|
52
|
+
isRetryable,
|
|
40
53
|
markEngineRaised
|
|
41
|
-
} from "./chunk-
|
|
54
|
+
} from "./chunk-UMESJJRS.js";
|
|
55
|
+
import {
|
|
56
|
+
__name
|
|
57
|
+
} from "./chunk-VXPNPVAG.js";
|
|
42
58
|
|
|
43
59
|
// src/engine/config.ts
|
|
44
60
|
var BootRefused = class extends Error {
|
|
@@ -90,6 +106,8 @@ ${detail}`);
|
|
|
90
106
|
uploadSecret: env.PALBASE_UPLOAD_SECRET ?? "",
|
|
91
107
|
anonKey: env.PALBASE_ANON_KEY ?? "",
|
|
92
108
|
serviceRoleKey: env.PALBASE_SERVICE_ROLE_KEY ?? "",
|
|
109
|
+
cursorSecret: env.PALBASE_CURSOR_SECRET,
|
|
110
|
+
enableCommands: env.PALBASE_DB_COMMANDS === "true",
|
|
93
111
|
realtimeSecret: env.REALTIME_INGESTION_SECRET ?? "",
|
|
94
112
|
port,
|
|
95
113
|
dbRole: env.DB_ROLE ?? "backend_authenticated",
|
|
@@ -98,7 +116,14 @@ ${detail}`);
|
|
|
98
116
|
// (`roleBackendServiceRole = "backend_service_role"`, NOLOGIN BYPASSRLS),
|
|
99
117
|
// and the live database agrees (pg_roles.rolbypassrls = true).
|
|
100
118
|
dbServiceRole: env.DB_SERVICE_ROLE ?? "backend_service_role",
|
|
101
|
-
poolMax
|
|
119
|
+
poolMax,
|
|
120
|
+
...env.PALBASE_DB_DIAGNOSTICS === "true" ? {
|
|
121
|
+
databaseDiagnostics: {
|
|
122
|
+
onComplete: /* @__PURE__ */ __name((diagnostics) => {
|
|
123
|
+
console.log("palbase.database", JSON.stringify(diagnostics));
|
|
124
|
+
}, "onComplete")
|
|
125
|
+
}
|
|
126
|
+
} : {}
|
|
102
127
|
};
|
|
103
128
|
}
|
|
104
129
|
__name(loadConfig, "loadConfig");
|
|
@@ -196,14 +221,14 @@ var AuthVerifier = class {
|
|
|
196
221
|
return null;
|
|
197
222
|
}
|
|
198
223
|
if (header.alg !== "ES256" || !header.kid) return null;
|
|
199
|
-
const
|
|
200
|
-
if (!
|
|
224
|
+
const key2 = await this.key(header.kid);
|
|
225
|
+
if (!key2) return null;
|
|
201
226
|
let ok = false;
|
|
202
227
|
try {
|
|
203
228
|
ok = await crypto.subtle.verify({
|
|
204
229
|
name: "ECDSA",
|
|
205
230
|
hash: "SHA-256"
|
|
206
|
-
},
|
|
231
|
+
}, key2, b64urlToBytes(sig), new TextEncoder().encode(`${h}.${p}`));
|
|
207
232
|
} catch {
|
|
208
233
|
return null;
|
|
209
234
|
}
|
|
@@ -269,12 +294,12 @@ var RateLimiter = class {
|
|
|
269
294
|
* wait (never 0 — a caller told to wait 0 comes straight back to the same
|
|
270
295
|
* refusal).
|
|
271
296
|
*/
|
|
272
|
-
check(rule,
|
|
297
|
+
check(rule, key2, now) {
|
|
273
298
|
if (!rule || !(rule.max > 0) || !(rule.window > 0)) return null;
|
|
274
|
-
const bucket = this.buckets.get(
|
|
299
|
+
const bucket = this.buckets.get(key2);
|
|
275
300
|
if (!bucket || now >= bucket.resetAt) {
|
|
276
301
|
if (this.buckets.size >= this.maxKeys) this.evict(now);
|
|
277
|
-
this.buckets.set(
|
|
302
|
+
this.buckets.set(key2, {
|
|
278
303
|
count: 1,
|
|
279
304
|
resetAt: now + rule.window * 1e3
|
|
280
305
|
});
|
|
@@ -317,11 +342,11 @@ function makeMemoryCache(opts = {}) {
|
|
|
317
342
|
const now = opts.now ?? (() => Date.now());
|
|
318
343
|
const store = /* @__PURE__ */ new Map();
|
|
319
344
|
const inflight = /* @__PURE__ */ new Map();
|
|
320
|
-
const live = /* @__PURE__ */ __name((
|
|
321
|
-
const e = store.get(
|
|
345
|
+
const live = /* @__PURE__ */ __name((key2) => {
|
|
346
|
+
const e = store.get(key2);
|
|
322
347
|
if (!e) return void 0;
|
|
323
348
|
if (e.expiresAt !== 0 && e.expiresAt <= now()) {
|
|
324
|
-
store.delete(
|
|
349
|
+
store.delete(key2);
|
|
325
350
|
return void 0;
|
|
326
351
|
}
|
|
327
352
|
return e;
|
|
@@ -344,61 +369,742 @@ function makeMemoryCache(opts = {}) {
|
|
|
344
369
|
if (victim) store.delete(victim[0]);
|
|
345
370
|
}
|
|
346
371
|
}, "evict");
|
|
347
|
-
const set = /* @__PURE__ */ __name(async (
|
|
348
|
-
if (store.size >= maxEntries && !store.has(
|
|
349
|
-
store.set(
|
|
372
|
+
const set = /* @__PURE__ */ __name(async (key2, value, ttl) => {
|
|
373
|
+
if (store.size >= maxEntries && !store.has(key2)) evict();
|
|
374
|
+
store.set(key2, {
|
|
350
375
|
value,
|
|
351
376
|
expiresAt: ttl && ttl > 0 ? now() + ttl * 1e3 : 0
|
|
352
377
|
});
|
|
353
378
|
}, "set");
|
|
354
379
|
return {
|
|
355
|
-
async get(
|
|
356
|
-
const e = live(
|
|
380
|
+
async get(key2) {
|
|
381
|
+
const e = live(key2);
|
|
357
382
|
return e ? e.value : null;
|
|
358
383
|
},
|
|
359
384
|
set,
|
|
360
|
-
async del(
|
|
361
|
-
store.delete(
|
|
385
|
+
async del(key2) {
|
|
386
|
+
store.delete(key2);
|
|
362
387
|
},
|
|
363
|
-
async incr(
|
|
364
|
-
const e = live(
|
|
388
|
+
async incr(key2) {
|
|
389
|
+
const e = live(key2);
|
|
365
390
|
const next = (typeof e?.value === "number" ? e.value : 0) + 1;
|
|
366
|
-
store.set(
|
|
391
|
+
store.set(key2, {
|
|
367
392
|
value: next,
|
|
368
393
|
expiresAt: e?.expiresAt ?? 0
|
|
369
394
|
});
|
|
370
395
|
return next;
|
|
371
396
|
},
|
|
372
|
-
async getOrSet(
|
|
373
|
-
const hit = live(
|
|
397
|
+
async getOrSet(key2, ttl, fn) {
|
|
398
|
+
const hit = live(key2);
|
|
374
399
|
if (hit) return hit.value;
|
|
375
|
-
const running = inflight.get(
|
|
400
|
+
const running = inflight.get(key2);
|
|
376
401
|
if (running) return running;
|
|
377
402
|
const fill = (async () => {
|
|
378
403
|
try {
|
|
379
404
|
const value = await fn();
|
|
380
|
-
await set(
|
|
405
|
+
await set(key2, value, ttl);
|
|
381
406
|
return value;
|
|
382
407
|
} finally {
|
|
383
|
-
inflight.delete(
|
|
408
|
+
inflight.delete(key2);
|
|
384
409
|
}
|
|
385
410
|
})();
|
|
386
|
-
inflight.set(
|
|
411
|
+
inflight.set(key2, fill);
|
|
387
412
|
return fill;
|
|
388
413
|
}
|
|
389
414
|
};
|
|
390
415
|
}
|
|
391
416
|
__name(makeMemoryCache, "makeMemoryCache");
|
|
392
417
|
|
|
418
|
+
// src/engine/db.ts
|
|
419
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
420
|
+
|
|
421
|
+
// src/engine/schema-cache.ts
|
|
422
|
+
function memoTable(cache, schema, table, compute) {
|
|
423
|
+
if (!schema.tables || !Object.hasOwn(schema.tables, table)) return compute();
|
|
424
|
+
let tables = cache.get(schema);
|
|
425
|
+
if (!tables) {
|
|
426
|
+
tables = /* @__PURE__ */ new Map();
|
|
427
|
+
cache.set(schema, tables);
|
|
428
|
+
}
|
|
429
|
+
if (!tables.has(table)) tables.set(table, compute());
|
|
430
|
+
return tables.get(table);
|
|
431
|
+
}
|
|
432
|
+
__name(memoTable, "memoTable");
|
|
433
|
+
function snapshotDeclaration(value, seen = /* @__PURE__ */ new WeakMap()) {
|
|
434
|
+
if (value === null || typeof value !== "object") return value;
|
|
435
|
+
const previous = seen.get(value);
|
|
436
|
+
if (previous) return previous;
|
|
437
|
+
if (value instanceof Date) return new Date(value.getTime());
|
|
438
|
+
const result = Array.isArray(value) ? [] : Object.create(Object.getPrototypeOf(value));
|
|
439
|
+
seen.set(value, result);
|
|
440
|
+
for (const key2 of Reflect.ownKeys(value)) {
|
|
441
|
+
if (Array.isArray(value) && key2 === "length") continue;
|
|
442
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key2);
|
|
443
|
+
if ("value" in descriptor) descriptor.value = snapshotDeclaration(descriptor.value, seen);
|
|
444
|
+
Object.defineProperty(result, key2, descriptor);
|
|
445
|
+
}
|
|
446
|
+
return Object.freeze(result);
|
|
447
|
+
}
|
|
448
|
+
__name(snapshotDeclaration, "snapshotDeclaration");
|
|
449
|
+
|
|
450
|
+
// src/engine/cursor.ts
|
|
451
|
+
import { createCipheriv, createDecipheriv, createHash, randomBytes } from "crypto";
|
|
452
|
+
var key = null;
|
|
453
|
+
var invalidPage = /* @__PURE__ */ __name((message, field = "page") => new BadRequest({
|
|
454
|
+
fields: [
|
|
455
|
+
{
|
|
456
|
+
field,
|
|
457
|
+
message
|
|
458
|
+
}
|
|
459
|
+
]
|
|
460
|
+
}, message), "invalidPage");
|
|
461
|
+
function setCursorKey(secret) {
|
|
462
|
+
key = secret ? createHash("sha256").update("palbase:cursor:v1\0").update(secret).digest() : null;
|
|
463
|
+
}
|
|
464
|
+
__name(setCursorKey, "setCursorKey");
|
|
465
|
+
function cursorKey() {
|
|
466
|
+
if (!key) throw new Error("Database.page needs PALBASE_CURSOR_SECRET or PALBASE_SERVICE_ROLE_KEY shared by all replicas");
|
|
467
|
+
return key;
|
|
468
|
+
}
|
|
469
|
+
__name(cursorKey, "cursorKey");
|
|
470
|
+
function fingerprint(value) {
|
|
471
|
+
const stable = /* @__PURE__ */ __name((v) => {
|
|
472
|
+
if (typeof v === "bigint") return {
|
|
473
|
+
$bigint: v.toString()
|
|
474
|
+
};
|
|
475
|
+
if (v instanceof Date) return {
|
|
476
|
+
$date: v.toISOString()
|
|
477
|
+
};
|
|
478
|
+
if (Array.isArray(v)) return v.map(stable);
|
|
479
|
+
if (v && typeof v === "object") {
|
|
480
|
+
return Object.fromEntries(Object.keys(v).sort().map((k) => [
|
|
481
|
+
k,
|
|
482
|
+
stable(v[k])
|
|
483
|
+
]));
|
|
484
|
+
}
|
|
485
|
+
return v;
|
|
486
|
+
}, "stable");
|
|
487
|
+
return createHash("sha256").update(JSON.stringify(stable(value))).digest("base64url");
|
|
488
|
+
}
|
|
489
|
+
__name(fingerprint, "fingerprint");
|
|
490
|
+
function cursorIdentity(role, claimsJson) {
|
|
491
|
+
const claims = JSON.parse(claimsJson);
|
|
492
|
+
const { iat, exp, nbf, jti, ...authorization } = claims;
|
|
493
|
+
return fingerprint({
|
|
494
|
+
role,
|
|
495
|
+
authorization
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
__name(cursorIdentity, "cursorIdentity");
|
|
499
|
+
function encodeCursor(secret, scope, values) {
|
|
500
|
+
const nonce = randomBytes(12);
|
|
501
|
+
const cipher = createCipheriv("aes-256-gcm", secret, nonce);
|
|
502
|
+
cipher.setAAD(Buffer.from(scope));
|
|
503
|
+
const body = Buffer.concat([
|
|
504
|
+
cipher.update(JSON.stringify(values), "utf8"),
|
|
505
|
+
cipher.final()
|
|
506
|
+
]);
|
|
507
|
+
const token = `p1.${Buffer.concat([
|
|
508
|
+
nonce,
|
|
509
|
+
cipher.getAuthTag(),
|
|
510
|
+
body
|
|
511
|
+
]).toString("base64url")}`;
|
|
512
|
+
if (token.length > 16384) throw invalidPage("page cursor values are too large; choose shorter ordering columns");
|
|
513
|
+
return token;
|
|
514
|
+
}
|
|
515
|
+
__name(encodeCursor, "encodeCursor");
|
|
516
|
+
function decodeCursor(secret, scope, token, count) {
|
|
517
|
+
try {
|
|
518
|
+
if (typeof token !== "string" || token.length > 16384 || !/^p1\.[\w-]+$/.test(token)) throw new Error();
|
|
519
|
+
const data = Buffer.from(token.slice(3), "base64url");
|
|
520
|
+
if (data.length < 30 || data.toString("base64url") !== token.slice(3)) throw new Error();
|
|
521
|
+
const cipher = createDecipheriv("aes-256-gcm", secret, data.subarray(0, 12));
|
|
522
|
+
cipher.setAAD(Buffer.from(scope));
|
|
523
|
+
cipher.setAuthTag(data.subarray(12, 28));
|
|
524
|
+
const values = JSON.parse(Buffer.concat([
|
|
525
|
+
cipher.update(data.subarray(28)),
|
|
526
|
+
cipher.final()
|
|
527
|
+
]).toString("utf8"));
|
|
528
|
+
if (!Array.isArray(values) || values.length !== count || values.some((v) => v !== null && typeof v !== "string")) throw new Error();
|
|
529
|
+
return values;
|
|
530
|
+
} catch {
|
|
531
|
+
throw invalidPage("Invalid cursor for this query or identity; restart from the first page", "cursor");
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
__name(decodeCursor, "decodeCursor");
|
|
535
|
+
function pageOrder(columns, primaryKey, opts) {
|
|
536
|
+
const raw = opts;
|
|
537
|
+
if ("limit" in raw || "offset" in raw) throw invalidPage("page uses first/after or last/before, not limit/offset");
|
|
538
|
+
const backward = opts.last !== void 0 || opts.before !== void 0;
|
|
539
|
+
if (backward && (opts.first !== void 0 || opts.after !== void 0)) throw invalidPage("Use first/after or last/before in one page request");
|
|
540
|
+
const size = (backward ? opts.last : opts.first) ?? 50;
|
|
541
|
+
if (!Number.isInteger(size) || size < 1 || size > 1e3) throw invalidPage("page size must be an integer between 1 and 1000");
|
|
542
|
+
if (primaryKey.length === 0) throw new Error("page requires a declared primary key for a unique, stable ordering");
|
|
543
|
+
const input = opts.orderBy === void 0 ? [] : Array.isArray(opts.orderBy) ? opts.orderBy : [
|
|
544
|
+
opts.orderBy
|
|
545
|
+
];
|
|
546
|
+
if (input.length > 16) throw invalidPage("page supports at most 16 ordering columns");
|
|
547
|
+
const seen = /* @__PURE__ */ new Set();
|
|
548
|
+
const order = [];
|
|
549
|
+
const add = /* @__PURE__ */ __name((o) => {
|
|
550
|
+
const def = columns[o?.column];
|
|
551
|
+
if (!Object.hasOwn(columns, o?.column) || !def) throw invalidPage(`page order column ${o?.column} is not declared`);
|
|
552
|
+
if (!(/* @__PURE__ */ new Set([
|
|
553
|
+
"uuid",
|
|
554
|
+
"text",
|
|
555
|
+
"integer",
|
|
556
|
+
"bigint",
|
|
557
|
+
"boolean",
|
|
558
|
+
"timestamp",
|
|
559
|
+
"numeric",
|
|
560
|
+
"enum"
|
|
561
|
+
])).has(def.type ?? "")) {
|
|
562
|
+
throw invalidPage(`page cannot order ${o.column}: ${def.type ?? "unknown"} is not a supported scalar column`);
|
|
563
|
+
}
|
|
564
|
+
if (seen.has(o.column)) throw invalidPage(`page order column ${o.column} is repeated`);
|
|
565
|
+
if (o.direction !== void 0 && o.direction !== "asc" && o.direction !== "desc") throw invalidPage("page direction must be asc or desc");
|
|
566
|
+
if (o.nulls !== void 0 && o.nulls !== "first" && o.nulls !== "last") throw invalidPage("page nulls must be first or last");
|
|
567
|
+
const direction = o.direction ?? "asc";
|
|
568
|
+
seen.add(o.column);
|
|
569
|
+
order.push({
|
|
570
|
+
column: o.column,
|
|
571
|
+
direction,
|
|
572
|
+
nulls: o.nulls ?? (direction === "asc" ? "last" : "first"),
|
|
573
|
+
nullable: def.nullable !== false
|
|
574
|
+
});
|
|
575
|
+
}, "add");
|
|
576
|
+
for (const o of input) add(o);
|
|
577
|
+
for (const column of primaryKey) if (!seen.has(column)) add({
|
|
578
|
+
column,
|
|
579
|
+
direction: order.at(-1)?.direction ?? "asc"
|
|
580
|
+
});
|
|
581
|
+
return {
|
|
582
|
+
order,
|
|
583
|
+
size,
|
|
584
|
+
backward,
|
|
585
|
+
token: backward ? opts.before : opts.after
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
__name(pageOrder, "pageOrder");
|
|
589
|
+
function pageBoundary(order, values, backward, bind, column, inclusive = false) {
|
|
590
|
+
const refs = values.map((v) => bind(v));
|
|
591
|
+
if (order.every((o) => !o.nullable && o.direction === order[0].direction) && values.every((v) => v !== null)) {
|
|
592
|
+
const greater = order[0].direction === "asc" !== backward;
|
|
593
|
+
return `(${order.map((o) => column(o.column)).join(", ")}) ${greater ? ">" : "<"}${inclusive ? "=" : ""} (${refs.join(", ")})`;
|
|
594
|
+
}
|
|
595
|
+
const parts = [];
|
|
596
|
+
const prefix = [];
|
|
597
|
+
for (let i = 0; i < order.length; i++) {
|
|
598
|
+
const o = order[i], c = column(o.column), r = refs[i];
|
|
599
|
+
const nullsLast = o.nulls === "last" !== backward;
|
|
600
|
+
const cmp = o.direction === "asc" !== backward ? ">" : "<";
|
|
601
|
+
const tail = values[i] === null ? nullsLast ? "false" : `${c} IS NOT NULL` : `(${c} ${cmp} ${r}${nullsLast ? ` OR ${c} IS NULL` : ""})`;
|
|
602
|
+
parts.push(`(${[
|
|
603
|
+
...prefix,
|
|
604
|
+
tail
|
|
605
|
+
].join(" AND ")})`);
|
|
606
|
+
prefix.push(`${c} IS NOT DISTINCT FROM ${r}`);
|
|
607
|
+
}
|
|
608
|
+
if (inclusive) parts.push(`(${prefix.join(" AND ")})`);
|
|
609
|
+
return `(${parts.join(" OR ")})`;
|
|
610
|
+
}
|
|
611
|
+
__name(pageBoundary, "pageBoundary");
|
|
612
|
+
|
|
613
|
+
// src/engine/commands.ts
|
|
614
|
+
var installed = /* @__PURE__ */ new WeakSet();
|
|
615
|
+
var hasCommandExecutor = /* @__PURE__ */ __name((sql) => installed.has(sql), "hasCommandExecutor");
|
|
616
|
+
var COMMAND_EXECUTOR_DDL = `
|
|
617
|
+
CREATE SCHEMA IF NOT EXISTS palbase_runtime;
|
|
618
|
+
CREATE OR REPLACE FUNCTION palbase_runtime.command_v1(steps jsonb) RETURNS jsonb
|
|
619
|
+
LANGUAGE plpgsql VOLATILE SECURITY INVOKER SET search_path = pg_catalog, public AS $pb_command$
|
|
620
|
+
DECLARE
|
|
621
|
+
results jsonb := '[]'::jsonb;
|
|
622
|
+
step jsonb; chunk jsonb; binding jsonb; source jsonb; rows jsonb;
|
|
623
|
+
statement text; rendered text; detail text; guard jsonb;
|
|
624
|
+
index integer := 0; affected integer; passed boolean;
|
|
625
|
+
BEGIN
|
|
626
|
+
FOR step IN SELECT value FROM jsonb_array_elements(steps) LOOP
|
|
627
|
+
IF step->>'empty' = 'true' THEN rows := '[]'::jsonb;
|
|
628
|
+
ELSE
|
|
629
|
+
statement := '';
|
|
630
|
+
FOR chunk IN SELECT value FROM jsonb_array_elements(step->'chunks') LOOP
|
|
631
|
+
IF jsonb_typeof(chunk) = 'string' THEN statement := statement || (chunk #>> '{}');
|
|
632
|
+
ELSE
|
|
633
|
+
binding := step->'bindings'->((chunk->>'bind')::integer);
|
|
634
|
+
IF binding ? 'ref' THEN
|
|
635
|
+
source := results->((binding->'ref'->>'op')::integer)->'rows'->0;
|
|
636
|
+
IF source IS NULL OR NOT source ? (binding->'ref'->>'field') THEN
|
|
637
|
+
RAISE EXCEPTION 'Command reference has no source row/column; use expectOne before reading it'
|
|
638
|
+
USING ERRCODE = 'PBC02', DETAIL = jsonb_build_object('error_code','tx_ref_unresolved','op',index)::text;
|
|
639
|
+
END IF;
|
|
640
|
+
source := source->(binding->'ref'->>'field');
|
|
641
|
+
ELSE source := binding->'value'; END IF;
|
|
642
|
+
rendered := CASE WHEN source IS NULL OR source = 'null'::jsonb THEN NULL
|
|
643
|
+
WHEN binding->>'json' = 'true' THEN source::text
|
|
644
|
+
WHEN jsonb_typeof(source) = 'string' THEN source #>> '{}' ELSE source::text END;
|
|
645
|
+
-- PostgreSQL quotes the value itself. Values reach this function as
|
|
646
|
+
-- one bound JSON parameter, never as application-generated SQL text.
|
|
647
|
+
statement := statement || quote_nullable(rendered);
|
|
648
|
+
END IF;
|
|
649
|
+
END LOOP;
|
|
650
|
+
EXECUTE statement INTO rows;
|
|
651
|
+
END IF;
|
|
652
|
+
affected := jsonb_array_length(rows);
|
|
653
|
+
guard := step->'guard';
|
|
654
|
+
IF guard IS NOT NULL AND guard <> 'null'::jsonb THEN
|
|
655
|
+
passed := CASE guard->>'kind'
|
|
656
|
+
WHEN 'one' THEN affected = 1 WHEN 'none' THEN affected = 0
|
|
657
|
+
WHEN 'atLeast' THEN affected >= (guard->>'n')::integer
|
|
658
|
+
WHEN 'atMost' THEN affected <= (guard->>'n')::integer ELSE false END;
|
|
659
|
+
IF NOT passed THEN
|
|
660
|
+
RAISE EXCEPTION 'Command expectation failed'
|
|
661
|
+
USING ERRCODE = 'PBC01', DETAIL = jsonb_build_object('error_code','tx_guard_failed','slot',guard->'slot','op',index)::text;
|
|
662
|
+
END IF;
|
|
663
|
+
END IF;
|
|
664
|
+
results := results || jsonb_build_array(jsonb_build_object('rows',rows,'rows_affected',affected));
|
|
665
|
+
index := index + 1;
|
|
666
|
+
END LOOP;
|
|
667
|
+
RETURN jsonb_build_object('results',results);
|
|
668
|
+
EXCEPTION WHEN SQLSTATE 'PBC01' OR SQLSTATE 'PBC02' THEN
|
|
669
|
+
GET STACKED DIAGNOSTICS detail = PG_EXCEPTION_DETAIL;
|
|
670
|
+
RETURN jsonb_build_object('error',detail::jsonb);
|
|
671
|
+
END
|
|
672
|
+
$pb_command$;
|
|
673
|
+
REVOKE ALL ON FUNCTION palbase_runtime.command_v1(jsonb) FROM PUBLIC;
|
|
674
|
+
CREATE OR REPLACE FUNCTION palbase_runtime.command_rows_v1(rows jsonb, guard jsonb) RETURNS jsonb
|
|
675
|
+
LANGUAGE plpgsql VOLATILE SECURITY INVOKER SET search_path = pg_catalog, public AS $pb_rows$
|
|
676
|
+
DECLARE affected integer := jsonb_array_length(rows); passed boolean;
|
|
677
|
+
BEGIN
|
|
678
|
+
IF guard IS NOT NULL AND guard <> 'null'::jsonb THEN
|
|
679
|
+
passed := CASE guard->>'kind'
|
|
680
|
+
WHEN 'one' THEN affected = 1 WHEN 'none' THEN affected = 0
|
|
681
|
+
WHEN 'atLeast' THEN affected >= (guard->>'n')::integer
|
|
682
|
+
WHEN 'atMost' THEN affected <= (guard->>'n')::integer ELSE false END;
|
|
683
|
+
IF NOT passed THEN
|
|
684
|
+
RAISE EXCEPTION 'Command expectation failed'
|
|
685
|
+
USING ERRCODE = 'PBC01', DETAIL = jsonb_build_object('error_code','tx_guard_failed','slot',guard->'slot')::text;
|
|
686
|
+
END IF;
|
|
687
|
+
END IF;
|
|
688
|
+
RETURN rows;
|
|
689
|
+
END
|
|
690
|
+
$pb_rows$;
|
|
691
|
+
REVOKE ALL ON FUNCTION palbase_runtime.command_rows_v1(jsonb,jsonb) FROM PUBLIC;
|
|
692
|
+
`;
|
|
693
|
+
async function installCommandExecutor(sql, roles) {
|
|
694
|
+
const quote = /* @__PURE__ */ __name((s) => `"${s.replace(/"/g, '""')}"`, "quote");
|
|
695
|
+
if (roles.length === 0) throw new Error("Command executor needs explicit runtime roles");
|
|
696
|
+
await sql.begin(async (tx) => {
|
|
697
|
+
await tx.unsafe("SELECT pg_advisory_xact_lock(196754721, 1)");
|
|
698
|
+
await tx.unsafe(COMMAND_EXECUTOR_DDL);
|
|
699
|
+
const list = [
|
|
700
|
+
...new Set(roles)
|
|
701
|
+
].map(quote).join(", ");
|
|
702
|
+
await tx.unsafe(`GRANT USAGE ON SCHEMA palbase_runtime TO ${list}`);
|
|
703
|
+
await tx.unsafe(`GRANT EXECUTE ON FUNCTION palbase_runtime.command_v1(jsonb) TO ${list}`);
|
|
704
|
+
await tx.unsafe(`GRANT EXECUTE ON FUNCTION palbase_runtime.command_rows_v1(jsonb,jsonb) TO ${list}`);
|
|
705
|
+
});
|
|
706
|
+
installed.add(sql);
|
|
707
|
+
}
|
|
708
|
+
__name(installCommandExecutor, "installCommandExecutor");
|
|
709
|
+
function parameterChunks(sql) {
|
|
710
|
+
const chunks = [];
|
|
711
|
+
let start = 0, quote = null;
|
|
712
|
+
for (let i = 0; i < sql.length; i++) {
|
|
713
|
+
const c = sql[i];
|
|
714
|
+
if (quote) {
|
|
715
|
+
if (c === quote) {
|
|
716
|
+
if (sql[i + 1] === quote) i++;
|
|
717
|
+
else quote = null;
|
|
718
|
+
}
|
|
719
|
+
continue;
|
|
720
|
+
}
|
|
721
|
+
if (c === "'" || c === '"') {
|
|
722
|
+
quote = c;
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
if (c !== "$") continue;
|
|
726
|
+
const parameter = /^\$(\d+)\b/.exec(sql.slice(i));
|
|
727
|
+
if (!parameter) continue;
|
|
728
|
+
chunks.push(sql.slice(start, i), {
|
|
729
|
+
bind: Number(parameter[1]) - 1
|
|
730
|
+
});
|
|
731
|
+
i += parameter[0].length - 1;
|
|
732
|
+
start = i + 1;
|
|
733
|
+
}
|
|
734
|
+
chunks.push(sql.slice(start));
|
|
735
|
+
return chunks;
|
|
736
|
+
}
|
|
737
|
+
__name(parameterChunks, "parameterChunks");
|
|
738
|
+
|
|
739
|
+
// src/engine/admission.ts
|
|
740
|
+
var WorkloadAdmission = class {
|
|
741
|
+
static {
|
|
742
|
+
__name(this, "WorkloadAdmission");
|
|
743
|
+
}
|
|
744
|
+
definition;
|
|
745
|
+
pending = [];
|
|
746
|
+
active = 0;
|
|
747
|
+
activeKeys = /* @__PURE__ */ new Set();
|
|
748
|
+
tenants = /* @__PURE__ */ new Map();
|
|
749
|
+
draining = false;
|
|
750
|
+
constructor(definition) {
|
|
751
|
+
this.definition = definition;
|
|
752
|
+
validateWorkload(definition);
|
|
753
|
+
}
|
|
754
|
+
get snapshot() {
|
|
755
|
+
return {
|
|
756
|
+
active: this.active,
|
|
757
|
+
queued: this.pending.length,
|
|
758
|
+
tenants: this.tenants.size,
|
|
759
|
+
keys: this.activeKeys.size
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
acquire(tenant, keys, signal) {
|
|
763
|
+
if (signal?.aborted) return Promise.reject(signal.reason ?? new Error("Database admission cancelled"));
|
|
764
|
+
const normalized = [
|
|
765
|
+
...new Set(keys)
|
|
766
|
+
].sort();
|
|
767
|
+
if (normalized.length > 64 || normalized.some((k) => typeof k !== "string" || k.length === 0 || k.length > 256)) return Promise.reject(new Error("conflictKeys must contain at most 64 nonempty strings of at most 256 characters"));
|
|
768
|
+
const scoped = normalized;
|
|
769
|
+
const globalFull = this.pending.length >= this.definition.queueLimit;
|
|
770
|
+
const tenantFull = this.pending.filter((w) => w.tenant === tenant).length >= this.definition.tenantQueueLimit;
|
|
771
|
+
const canStart = this.active < this.definition.concurrency && (this.tenants.get(tenant) ?? 0) < this.definition.tenantConcurrency && scoped.every((k) => !this.activeKeys.has(k) && !this.pending.some((w) => w.keys.includes(k)));
|
|
772
|
+
if (!canStart && (globalFull || tenantFull)) {
|
|
773
|
+
return Promise.reject(new TooManyRequests({
|
|
774
|
+
retryAfter: 1
|
|
775
|
+
}, `Database workload ${this.definition.name}: ${tenantFull ? "tenant" : "global"} queue is full`));
|
|
776
|
+
}
|
|
777
|
+
return new Promise((grant, reject) => {
|
|
778
|
+
const waiter = {
|
|
779
|
+
tenant,
|
|
780
|
+
keys: scoped,
|
|
781
|
+
entered: performance.now(),
|
|
782
|
+
grant,
|
|
783
|
+
reject,
|
|
784
|
+
cleanup: /* @__PURE__ */ __name(() => {
|
|
785
|
+
}, "cleanup")
|
|
786
|
+
};
|
|
787
|
+
const remove = /* @__PURE__ */ __name((reason) => {
|
|
788
|
+
const index = this.pending.indexOf(waiter);
|
|
789
|
+
if (index === -1) return;
|
|
790
|
+
this.pending.splice(index, 1);
|
|
791
|
+
waiter.cleanup();
|
|
792
|
+
reject(reason);
|
|
793
|
+
this.drain();
|
|
794
|
+
}, "remove");
|
|
795
|
+
const abort = /* @__PURE__ */ __name(() => remove(signal?.reason ?? new Error("Database admission cancelled")), "abort");
|
|
796
|
+
const timer = setTimeout(() => remove(new TooManyRequests({
|
|
797
|
+
retryAfter: 1
|
|
798
|
+
}, `Database workload ${this.definition.name}: queue deadline exceeded`)), this.definition.queueTimeoutMs);
|
|
799
|
+
waiter.cleanup = () => {
|
|
800
|
+
clearTimeout(timer);
|
|
801
|
+
signal?.removeEventListener("abort", abort);
|
|
802
|
+
};
|
|
803
|
+
signal?.addEventListener("abort", abort, {
|
|
804
|
+
once: true
|
|
805
|
+
});
|
|
806
|
+
this.pending.push(waiter);
|
|
807
|
+
if (signal?.aborted) abort();
|
|
808
|
+
else this.drain();
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
drain() {
|
|
812
|
+
if (this.draining) return;
|
|
813
|
+
this.draining = true;
|
|
814
|
+
try {
|
|
815
|
+
const blockedKeys = /* @__PURE__ */ new Set();
|
|
816
|
+
for (let index = 0; index < this.pending.length && this.active < this.definition.concurrency; ) {
|
|
817
|
+
const waiter = this.pending[index];
|
|
818
|
+
if ((this.tenants.get(waiter.tenant) ?? 0) >= this.definition.tenantConcurrency || waiter.keys.some((key2) => this.activeKeys.has(key2) || blockedKeys.has(key2))) {
|
|
819
|
+
for (const key2 of waiter.keys) blockedKeys.add(key2);
|
|
820
|
+
index++;
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
this.pending.splice(index, 1);
|
|
824
|
+
waiter.cleanup();
|
|
825
|
+
this.active++;
|
|
826
|
+
this.tenants.set(waiter.tenant, (this.tenants.get(waiter.tenant) ?? 0) + 1);
|
|
827
|
+
for (const key2 of waiter.keys) this.activeKeys.add(key2);
|
|
828
|
+
let released = false;
|
|
829
|
+
waiter.grant({
|
|
830
|
+
queueMs: performance.now() - waiter.entered,
|
|
831
|
+
release: /* @__PURE__ */ __name(() => {
|
|
832
|
+
if (released) return;
|
|
833
|
+
released = true;
|
|
834
|
+
this.active--;
|
|
835
|
+
const left = this.tenants.get(waiter.tenant) - 1;
|
|
836
|
+
if (left === 0) this.tenants.delete(waiter.tenant);
|
|
837
|
+
else this.tenants.set(waiter.tenant, left);
|
|
838
|
+
for (const key2 of waiter.keys) this.activeKeys.delete(key2);
|
|
839
|
+
this.drain();
|
|
840
|
+
}, "release")
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
} finally {
|
|
844
|
+
this.draining = false;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
};
|
|
848
|
+
var registries = /* @__PURE__ */ new WeakMap();
|
|
849
|
+
function workloadAdmission(owner, definition) {
|
|
850
|
+
validateWorkload(definition);
|
|
851
|
+
let registry = registries.get(owner);
|
|
852
|
+
if (!registry) {
|
|
853
|
+
registry = /* @__PURE__ */ new Map();
|
|
854
|
+
registries.set(owner, registry);
|
|
855
|
+
}
|
|
856
|
+
const existing = registry.get(definition.name);
|
|
857
|
+
if (existing) {
|
|
858
|
+
const d = existing.definition;
|
|
859
|
+
if (Object.keys(d).some((k) => d[k] !== definition[k])) throw new Error(`Workload ${definition.name} was already registered with different limits`);
|
|
860
|
+
return existing;
|
|
861
|
+
}
|
|
862
|
+
if (registry.size >= 64) throw new Error("At most 64 named database workloads may be registered per engine; declare and reuse them at module scope");
|
|
863
|
+
const admission = new WorkloadAdmission(Object.freeze({
|
|
864
|
+
...definition
|
|
865
|
+
}));
|
|
866
|
+
registry.set(definition.name, admission);
|
|
867
|
+
return admission;
|
|
868
|
+
}
|
|
869
|
+
__name(workloadAdmission, "workloadAdmission");
|
|
870
|
+
function workloadTenant(definition, claimsJson) {
|
|
871
|
+
const claims = JSON.parse(claimsJson);
|
|
872
|
+
const value = claims[definition.tenantClaim] ?? claims.sub;
|
|
873
|
+
return typeof value === "string" && value.length > 0 ? value : "__anonymous__";
|
|
874
|
+
}
|
|
875
|
+
__name(workloadTenant, "workloadTenant");
|
|
876
|
+
|
|
877
|
+
// src/engine/transaction-control.ts
|
|
878
|
+
var TransactionControl = class {
|
|
879
|
+
static {
|
|
880
|
+
__name(this, "TransactionControl");
|
|
881
|
+
}
|
|
882
|
+
controller = new AbortController();
|
|
883
|
+
active = /* @__PURE__ */ new Set();
|
|
884
|
+
cancelBackend;
|
|
885
|
+
cancellation = Promise.resolve();
|
|
886
|
+
timer;
|
|
887
|
+
unlink;
|
|
888
|
+
rejection;
|
|
889
|
+
deadline;
|
|
890
|
+
get signal() {
|
|
891
|
+
return this.controller.signal;
|
|
892
|
+
}
|
|
893
|
+
constructor(timeoutMs, external) {
|
|
894
|
+
this.deadline = timeoutMs === void 0 ? void 0 : performance.now() + timeoutMs;
|
|
895
|
+
this.rejection = new Promise((_, reject) => {
|
|
896
|
+
this.signal.addEventListener("abort", () => {
|
|
897
|
+
reject(this.signal.reason);
|
|
898
|
+
if (this.active.size && this.cancelBackend) {
|
|
899
|
+
this.cancellation = this.cancelBackend().catch(() => void 0);
|
|
900
|
+
} else {
|
|
901
|
+
for (const query of this.active) {
|
|
902
|
+
try {
|
|
903
|
+
query.cancel?.();
|
|
904
|
+
} catch {
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
}, {
|
|
909
|
+
once: true
|
|
910
|
+
});
|
|
911
|
+
});
|
|
912
|
+
void this.rejection.catch(() => void 0);
|
|
913
|
+
const abort = /* @__PURE__ */ __name(() => this.controller.abort(external?.reason ?? new DOMException("Database transaction cancelled", "AbortError")), "abort");
|
|
914
|
+
external?.addEventListener("abort", abort, {
|
|
915
|
+
once: true
|
|
916
|
+
});
|
|
917
|
+
this.unlink = () => external?.removeEventListener("abort", abort);
|
|
918
|
+
if (external?.aborted) abort();
|
|
919
|
+
if (timeoutMs !== void 0) this.timer = setTimeout(() => this.controller.abort(new HttpError(503, "database_deadline_exceeded", "Database transaction exceeded its deadline")), timeoutMs);
|
|
920
|
+
}
|
|
921
|
+
check() {
|
|
922
|
+
if (this.deadline !== void 0 && performance.now() >= this.deadline && !this.signal.aborted) {
|
|
923
|
+
this.controller.abort(new HttpError(503, "database_deadline_exceeded", "Database transaction exceeded its deadline"));
|
|
924
|
+
}
|
|
925
|
+
this.signal.throwIfAborted();
|
|
926
|
+
}
|
|
927
|
+
remainingMs() {
|
|
928
|
+
this.check();
|
|
929
|
+
return this.deadline === void 0 ? void 0 : Math.max(1, Math.ceil(this.deadline - performance.now()));
|
|
930
|
+
}
|
|
931
|
+
race(work) {
|
|
932
|
+
return Promise.race([
|
|
933
|
+
work,
|
|
934
|
+
this.rejection
|
|
935
|
+
]);
|
|
936
|
+
}
|
|
937
|
+
bindBackend(cancel) {
|
|
938
|
+
this.cancelBackend = cancel;
|
|
939
|
+
}
|
|
940
|
+
/** Keep the application's connection parked until the cancellation request
|
|
941
|
+
* and all statements settle; a late cancel must not hit its next borrower. */
|
|
942
|
+
async drainCancellation() {
|
|
943
|
+
await this.cancellation;
|
|
944
|
+
await Promise.allSettled([
|
|
945
|
+
...this.active
|
|
946
|
+
]);
|
|
947
|
+
}
|
|
948
|
+
async watch(query) {
|
|
949
|
+
this.active.add(query);
|
|
950
|
+
try {
|
|
951
|
+
if (this.signal.aborted) query.cancel?.();
|
|
952
|
+
return await query;
|
|
953
|
+
} finally {
|
|
954
|
+
this.active.delete(query);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
dispose() {
|
|
958
|
+
if (this.timer !== void 0) clearTimeout(this.timer);
|
|
959
|
+
this.unlink();
|
|
960
|
+
}
|
|
961
|
+
};
|
|
962
|
+
|
|
963
|
+
// src/engine/diagnostics.ts
|
|
964
|
+
import { createHash as createHash2 } from "crypto";
|
|
965
|
+
var DatabaseTrace = class {
|
|
966
|
+
static {
|
|
967
|
+
__name(this, "DatabaseTrace");
|
|
968
|
+
}
|
|
969
|
+
options;
|
|
970
|
+
start = performance.now();
|
|
971
|
+
ended;
|
|
972
|
+
failure;
|
|
973
|
+
state;
|
|
974
|
+
constructor(options) {
|
|
975
|
+
this.options = options;
|
|
976
|
+
for (const key2 of [
|
|
977
|
+
"maxQueries",
|
|
978
|
+
"maxRows",
|
|
979
|
+
"maxBytes"
|
|
980
|
+
]) {
|
|
981
|
+
const value = options[key2];
|
|
982
|
+
if (value !== void 0 && (!Number.isSafeInteger(value) || value < 0)) throw new Error(`database ${key2} must be a nonnegative safe integer`);
|
|
983
|
+
}
|
|
984
|
+
this.state = {
|
|
985
|
+
endpoint: options.endpoint,
|
|
986
|
+
requestId: options.requestId,
|
|
987
|
+
outcome: "active",
|
|
988
|
+
elapsedMs: 0,
|
|
989
|
+
queries: 0,
|
|
990
|
+
rows: 0,
|
|
991
|
+
bytes: 0,
|
|
992
|
+
queueMs: 0,
|
|
993
|
+
poolMs: 0,
|
|
994
|
+
setupMs: 0,
|
|
995
|
+
sqlMs: 0,
|
|
996
|
+
commitMs: 0,
|
|
997
|
+
rollbackMs: 0,
|
|
998
|
+
commits: 0,
|
|
999
|
+
commitFailures: 0,
|
|
1000
|
+
rollbackRequests: 0,
|
|
1001
|
+
retries: 0,
|
|
1002
|
+
statements: []
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
check() {
|
|
1006
|
+
if (this.failure) throw this.failure;
|
|
1007
|
+
}
|
|
1008
|
+
time(key2, started) {
|
|
1009
|
+
this.state[key2] += performance.now() - started;
|
|
1010
|
+
}
|
|
1011
|
+
retry() {
|
|
1012
|
+
this.state.retries++;
|
|
1013
|
+
}
|
|
1014
|
+
committed() {
|
|
1015
|
+
this.state.commits++;
|
|
1016
|
+
}
|
|
1017
|
+
commitFailed() {
|
|
1018
|
+
this.state.commitFailures++;
|
|
1019
|
+
}
|
|
1020
|
+
failed() {
|
|
1021
|
+
this.state.outcome = "failed";
|
|
1022
|
+
}
|
|
1023
|
+
rolledBack() {
|
|
1024
|
+
this.state.rollbackRequests++;
|
|
1025
|
+
}
|
|
1026
|
+
refuse(kind) {
|
|
1027
|
+
this.failure ??= new HttpError(500, "database_budget_exceeded", `Database ${kind} budget exceeded${this.options.endpoint ? ` in ${this.options.endpoint}` : ""}`);
|
|
1028
|
+
throw this.failure;
|
|
1029
|
+
}
|
|
1030
|
+
emit(callback, event) {
|
|
1031
|
+
try {
|
|
1032
|
+
const result = callback?.(event);
|
|
1033
|
+
if (result) void Promise.resolve(result).catch(() => void 0);
|
|
1034
|
+
} catch {
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
async query(sql, name, run) {
|
|
1038
|
+
this.check();
|
|
1039
|
+
if (this.options.maxQueries !== void 0 && this.state.queries >= this.options.maxQueries) this.refuse("query count");
|
|
1040
|
+
const ordinal = ++this.state.queries;
|
|
1041
|
+
const event = {
|
|
1042
|
+
ordinal,
|
|
1043
|
+
fingerprint: createHash2("sha256").update(sql).digest("hex").slice(0, 24),
|
|
1044
|
+
...name === void 0 ? {} : {
|
|
1045
|
+
name: name.slice(0, 128)
|
|
1046
|
+
},
|
|
1047
|
+
...this.options.includeSql ? {
|
|
1048
|
+
sql: sql.slice(0, 4096)
|
|
1049
|
+
} : {},
|
|
1050
|
+
durationMs: 0,
|
|
1051
|
+
rows: 0,
|
|
1052
|
+
bytes: 0
|
|
1053
|
+
};
|
|
1054
|
+
if (this.options.captureSource) event.source = new Error().stack?.split("\n").slice(1, 9).join("\n").slice(0, 2500);
|
|
1055
|
+
const started = performance.now();
|
|
1056
|
+
let measured = false;
|
|
1057
|
+
try {
|
|
1058
|
+
const result = await run();
|
|
1059
|
+
event.durationMs = performance.now() - started;
|
|
1060
|
+
measured = true;
|
|
1061
|
+
if (Array.isArray(result)) {
|
|
1062
|
+
event.rows = result.length;
|
|
1063
|
+
event.bytes = Buffer.byteLength(JSON.stringify(result, (_, value) => typeof value === "bigint" ? value.toString() : value));
|
|
1064
|
+
}
|
|
1065
|
+
this.state.rows += event.rows;
|
|
1066
|
+
this.state.bytes += event.bytes;
|
|
1067
|
+
if (this.options.maxRows !== void 0 && this.state.rows > this.options.maxRows) this.refuse("returned rows");
|
|
1068
|
+
if (this.options.maxBytes !== void 0 && this.state.bytes > this.options.maxBytes) this.refuse("result bytes");
|
|
1069
|
+
return result;
|
|
1070
|
+
} catch (error) {
|
|
1071
|
+
const e = error;
|
|
1072
|
+
event.errorCode = String(e?.sqlstate ?? e?.errno ?? e?.code ?? "query_failed").slice(0, 80);
|
|
1073
|
+
throw error;
|
|
1074
|
+
} finally {
|
|
1075
|
+
if (!measured) event.durationMs = performance.now() - started;
|
|
1076
|
+
this.state.sqlMs += event.durationMs;
|
|
1077
|
+
const frozen = Object.freeze(event);
|
|
1078
|
+
if (this.state.statements.length < 200) this.state.statements.push(frozen);
|
|
1079
|
+
this.emit(this.options.onQuery, frozen);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
get snapshot() {
|
|
1083
|
+
return Object.freeze({
|
|
1084
|
+
...this.state,
|
|
1085
|
+
elapsedMs: (this.ended ?? performance.now()) - this.start,
|
|
1086
|
+
statements: Object.freeze([
|
|
1087
|
+
...this.state.statements
|
|
1088
|
+
].sort((a, b) => a.ordinal - b.ordinal))
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
complete(outcome) {
|
|
1092
|
+
if (this.ended !== void 0) return;
|
|
1093
|
+
this.ended = performance.now();
|
|
1094
|
+
this.state.outcome = outcome;
|
|
1095
|
+
this.emit(this.options.onComplete, this.snapshot);
|
|
1096
|
+
}
|
|
1097
|
+
};
|
|
1098
|
+
|
|
393
1099
|
// src/engine/db.ts
|
|
394
1100
|
function quoteIdent(name) {
|
|
395
1101
|
return `"${name.replace(/"/g, '""')}"`;
|
|
396
1102
|
}
|
|
397
1103
|
__name(quoteIdent, "quoteIdent");
|
|
398
|
-
function quoteTable(
|
|
399
|
-
const dot =
|
|
400
|
-
if (dot === -1) return quoteIdent(
|
|
401
|
-
return `${quoteIdent(
|
|
1104
|
+
function quoteTable(key2) {
|
|
1105
|
+
const dot = key2.indexOf(".");
|
|
1106
|
+
if (dot === -1) return quoteIdent(key2);
|
|
1107
|
+
return `${quoteIdent(key2.slice(0, dot))}.${quoteIdent(key2.slice(dot + 1))}`;
|
|
402
1108
|
}
|
|
403
1109
|
__name(quoteTable, "quoteTable");
|
|
404
1110
|
function encodePgArray(value) {
|
|
@@ -411,9 +1117,10 @@ function encodePgArray(value) {
|
|
|
411
1117
|
}
|
|
412
1118
|
__name(encodePgArray, "encodePgArray");
|
|
413
1119
|
var BIND_SQL = "select set_config('role',$1,true), set_config('search_path','public',true), set_config('request.jwt.claims',$2,true)";
|
|
1120
|
+
var atomicScope = new AsyncLocalStorage();
|
|
414
1121
|
function createLazyTransaction(sql, role, claimsJson, options = {}) {
|
|
415
1122
|
const { lockTimeout } = options;
|
|
416
|
-
|
|
1123
|
+
let bindSql = lockTimeout ? `${BIND_SQL}, set_config('lock_timeout',$3,true)` : BIND_SQL;
|
|
417
1124
|
const bindParams = lockTimeout ? [
|
|
418
1125
|
role,
|
|
419
1126
|
claimsJson,
|
|
@@ -422,25 +1129,118 @@ function createLazyTransaction(sql, role, claimsJson, options = {}) {
|
|
|
422
1129
|
role,
|
|
423
1130
|
claimsJson
|
|
424
1131
|
];
|
|
1132
|
+
if (options.statementTimeout !== void 0) {
|
|
1133
|
+
bindParams.push(options.statementTimeout);
|
|
1134
|
+
bindSql += `, set_config('statement_timeout',$${bindParams.length},true)`;
|
|
1135
|
+
}
|
|
425
1136
|
let opening = null;
|
|
426
1137
|
let release = null;
|
|
427
1138
|
let fail = null;
|
|
428
1139
|
let settled = null;
|
|
429
|
-
|
|
1140
|
+
let closed = false;
|
|
1141
|
+
const assertAccess = /* @__PURE__ */ __name(() => {
|
|
1142
|
+
if (closed) throw new Error("Database transaction has settled; its handle cannot be used again");
|
|
1143
|
+
options.control?.check();
|
|
1144
|
+
options.trace?.check();
|
|
1145
|
+
if (atomicScope.getStore() !== options.scope) {
|
|
1146
|
+
throw new Error("Database.$atomic cannot use a handle from another transaction or identity; use its tx argument");
|
|
1147
|
+
}
|
|
1148
|
+
}, "assertAccess");
|
|
1149
|
+
let savepointALS;
|
|
1150
|
+
let activeSavepoint;
|
|
1151
|
+
const pendingSavepoints = /* @__PURE__ */ new Set();
|
|
1152
|
+
const scoped = /* @__PURE__ */ __name((tx, checkParent = assertAccess, owner, children = /* @__PURE__ */ new Set()) => {
|
|
1153
|
+
const access = /* @__PURE__ */ __name(() => {
|
|
1154
|
+
checkParent();
|
|
1155
|
+
if (activeSavepoint !== owner || savepointALS?.getStore() !== owner) {
|
|
1156
|
+
throw new Error("Database connection belongs to another active savepoint; use its tx argument and await it before using the parent handle");
|
|
1157
|
+
}
|
|
1158
|
+
}, "access");
|
|
1159
|
+
return {
|
|
1160
|
+
async unsafe(text, params, metadata) {
|
|
1161
|
+
access();
|
|
1162
|
+
const run = /* @__PURE__ */ __name(() => {
|
|
1163
|
+
const query = tx.unsafe(text, params);
|
|
1164
|
+
return options.control ? options.control.watch(query) : query;
|
|
1165
|
+
}, "run");
|
|
1166
|
+
return options.trace ? options.trace.query(text, metadata?.name, run) : run();
|
|
1167
|
+
},
|
|
1168
|
+
async savepoint(fn) {
|
|
1169
|
+
access();
|
|
1170
|
+
const token = /* @__PURE__ */ Symbol("savepoint"), descendants = /* @__PURE__ */ new Set();
|
|
1171
|
+
savepointALS ??= new AsyncLocalStorage();
|
|
1172
|
+
activeSavepoint = token;
|
|
1173
|
+
const work = (async () => {
|
|
1174
|
+
try {
|
|
1175
|
+
return await tx.savepoint(async (sp) => {
|
|
1176
|
+
let ended = false;
|
|
1177
|
+
const check = /* @__PURE__ */ __name(() => {
|
|
1178
|
+
checkParent();
|
|
1179
|
+
if (ended) throw new Error("Database savepoint has settled; its handle cannot be used again");
|
|
1180
|
+
}, "check");
|
|
1181
|
+
try {
|
|
1182
|
+
const callback = savepointALS.run(token, () => fn(scoped(sp, check, token, descendants)));
|
|
1183
|
+
return await (options.control ? options.control.race(callback) : callback);
|
|
1184
|
+
} finally {
|
|
1185
|
+
if (descendants.size) await Promise.allSettled([
|
|
1186
|
+
...descendants
|
|
1187
|
+
]);
|
|
1188
|
+
ended = true;
|
|
1189
|
+
}
|
|
1190
|
+
});
|
|
1191
|
+
} finally {
|
|
1192
|
+
activeSavepoint = owner;
|
|
1193
|
+
}
|
|
1194
|
+
})();
|
|
1195
|
+
pendingSavepoints.add(work);
|
|
1196
|
+
children.add(work);
|
|
1197
|
+
try {
|
|
1198
|
+
return await work;
|
|
1199
|
+
} finally {
|
|
1200
|
+
pendingSavepoints.delete(work);
|
|
1201
|
+
children.delete(work);
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
};
|
|
1205
|
+
}, "scoped");
|
|
1206
|
+
const ensure = /* @__PURE__ */ __name(async () => {
|
|
1207
|
+
assertAccess();
|
|
430
1208
|
if (opening) return opening;
|
|
431
1209
|
opening = new Promise((resolveTx2, rejectTx) => {
|
|
432
1210
|
const parked = new Promise((res, rej) => {
|
|
433
1211
|
release = res;
|
|
434
1212
|
fail = rej;
|
|
435
1213
|
});
|
|
1214
|
+
void parked.catch(() => void 0);
|
|
1215
|
+
const openingAt = options.trace ? performance.now() : 0;
|
|
436
1216
|
settled = sql.begin(async (tx) => {
|
|
437
|
-
|
|
438
|
-
|
|
1217
|
+
options.trace?.time("poolMs", openingAt);
|
|
1218
|
+
const setupAt = options.trace ? performance.now() : 0;
|
|
1219
|
+
options.control?.check();
|
|
1220
|
+
if (options.isolation !== void 0 || options.readOnly !== void 0) {
|
|
1221
|
+
const isolation = options.isolation ?? "read committed";
|
|
1222
|
+
validateAtomicOptions({
|
|
1223
|
+
isolation,
|
|
1224
|
+
readOnly: options.readOnly
|
|
1225
|
+
});
|
|
1226
|
+
await tx.unsafe(`SET TRANSACTION ISOLATION LEVEL ${isolation.toUpperCase()}${options.readOnly === void 0 ? "" : options.readOnly ? " READ ONLY" : " READ WRITE"}`);
|
|
1227
|
+
}
|
|
1228
|
+
if (options.control && sql.cancelBackend) {
|
|
1229
|
+
const ticket = await tx.unsafe("SELECT pg_backend_pid() AS pid, backend_start::text AS started FROM pg_stat_activity WHERE pid=pg_backend_pid()");
|
|
1230
|
+
if (!Number.isInteger(ticket[0]?.pid) || typeof ticket[0]?.started !== "string") throw new Error("Database cancellation cannot identify its own backend");
|
|
1231
|
+
options.control.bindBackend(() => sql.cancelBackend(ticket[0].pid, ticket[0].started));
|
|
1232
|
+
options.control.check();
|
|
1233
|
+
}
|
|
1234
|
+
const binding = tx.unsafe(bindSql, bindParams);
|
|
1235
|
+
await (options.control ? options.control.watch(binding) : binding);
|
|
1236
|
+
options.trace?.time("setupMs", setupAt);
|
|
1237
|
+
resolveTx2(scoped(tx));
|
|
439
1238
|
await parked;
|
|
440
1239
|
}).catch((e) => {
|
|
441
1240
|
rejectTx(e);
|
|
442
1241
|
throw e;
|
|
443
1242
|
});
|
|
1243
|
+
void settled.catch(() => void 0);
|
|
444
1244
|
});
|
|
445
1245
|
return opening;
|
|
446
1246
|
}, "ensure");
|
|
@@ -450,14 +1250,37 @@ function createLazyTransaction(sql, role, claimsJson, options = {}) {
|
|
|
450
1250
|
return opening !== null;
|
|
451
1251
|
},
|
|
452
1252
|
async commit() {
|
|
1253
|
+
if (pendingSavepoints.size) await Promise.allSettled([
|
|
1254
|
+
...pendingSavepoints
|
|
1255
|
+
]);
|
|
1256
|
+
options.control?.check();
|
|
1257
|
+
options.trace?.check();
|
|
1258
|
+
closed = true;
|
|
453
1259
|
if (!opening) return;
|
|
1260
|
+
const started = options.trace ? performance.now() : 0;
|
|
454
1261
|
release();
|
|
455
|
-
|
|
1262
|
+
try {
|
|
1263
|
+
await settled;
|
|
1264
|
+
options.trace?.committed();
|
|
1265
|
+
} catch (error) {
|
|
1266
|
+
options.trace?.commitFailed();
|
|
1267
|
+
throw error;
|
|
1268
|
+
} finally {
|
|
1269
|
+
options.trace?.time("commitMs", started);
|
|
1270
|
+
}
|
|
456
1271
|
},
|
|
457
1272
|
async rollback(reason) {
|
|
1273
|
+
closed = true;
|
|
458
1274
|
if (!opening) return;
|
|
1275
|
+
await options.control?.drainCancellation();
|
|
1276
|
+
if (pendingSavepoints.size) await Promise.allSettled([
|
|
1277
|
+
...pendingSavepoints
|
|
1278
|
+
]);
|
|
1279
|
+
const started = options.trace ? performance.now() : 0;
|
|
459
1280
|
fail(reason);
|
|
460
1281
|
await settled?.catch(() => void 0);
|
|
1282
|
+
options.trace?.time("rollbackMs", started);
|
|
1283
|
+
options.trace?.rolledBack();
|
|
461
1284
|
}
|
|
462
1285
|
};
|
|
463
1286
|
}
|
|
@@ -472,7 +1295,7 @@ __name(asWireValue, "asWireValue");
|
|
|
472
1295
|
function asWireRow(row) {
|
|
473
1296
|
if (row === null || typeof row !== "object") return row;
|
|
474
1297
|
const out = {};
|
|
475
|
-
for (const [
|
|
1298
|
+
for (const [key2, value] of Object.entries(row)) out[key2] = asWireValue(value);
|
|
476
1299
|
return out;
|
|
477
1300
|
}
|
|
478
1301
|
__name(asWireRow, "asWireRow");
|
|
@@ -484,7 +1307,7 @@ function toVectorLiteral(v) {
|
|
|
484
1307
|
return `[${v.join(",")}]`;
|
|
485
1308
|
}
|
|
486
1309
|
__name(toVectorLiteral, "toVectorLiteral");
|
|
487
|
-
function
|
|
1310
|
+
function computeVectorColumns(schema, table) {
|
|
488
1311
|
const out = /* @__PURE__ */ new Set();
|
|
489
1312
|
const def = tableDefOf(table, schema);
|
|
490
1313
|
if (def) {
|
|
@@ -495,9 +1318,9 @@ function vectorColumnsOf(schema, table) {
|
|
|
495
1318
|
}
|
|
496
1319
|
return out;
|
|
497
1320
|
}
|
|
498
|
-
__name(
|
|
499
|
-
function tableDefOf(
|
|
500
|
-
return schema.tables?.[
|
|
1321
|
+
__name(computeVectorColumns, "computeVectorColumns");
|
|
1322
|
+
function tableDefOf(key2, schema = currentSchema) {
|
|
1323
|
+
return schema.tables?.[key2] ?? null;
|
|
501
1324
|
}
|
|
502
1325
|
__name(tableDefOf, "tableDefOf");
|
|
503
1326
|
function reviveVectors(row, vectorCols) {
|
|
@@ -510,7 +1333,7 @@ function reviveVectors(row, vectorCols) {
|
|
|
510
1333
|
return row;
|
|
511
1334
|
}
|
|
512
1335
|
__name(reviveVectors, "reviveVectors");
|
|
513
|
-
function
|
|
1336
|
+
function computeTransforms(schema, table) {
|
|
514
1337
|
const out = /* @__PURE__ */ new Map();
|
|
515
1338
|
const def = tableDefOf(table, schema);
|
|
516
1339
|
if (def) {
|
|
@@ -533,7 +1356,7 @@ function transformsOf(schema, table) {
|
|
|
533
1356
|
}
|
|
534
1357
|
return out;
|
|
535
1358
|
}
|
|
536
|
-
__name(
|
|
1359
|
+
__name(computeTransforms, "computeTransforms");
|
|
537
1360
|
function applyFromDb(row, transforms) {
|
|
538
1361
|
if (transforms.size === 0) return row;
|
|
539
1362
|
for (const [col, t] of transforms) {
|
|
@@ -550,10 +1373,23 @@ function asTableRow(table, row) {
|
|
|
550
1373
|
return applyFromDb(revived, transformsOf(currentSchema, table));
|
|
551
1374
|
}
|
|
552
1375
|
__name(asTableRow, "asTableRow");
|
|
553
|
-
function asTableRows(table, rows) {
|
|
1376
|
+
function asTableRows(table, rows, withSpec) {
|
|
554
1377
|
const vectorCols = vectorColumnsOf(currentSchema, table);
|
|
555
1378
|
const transforms = transformsOf(currentSchema, table);
|
|
556
|
-
|
|
1379
|
+
const result = rows.map((row) => applyFromDb(reviveVectors(asWireRow(row), vectorCols), transforms));
|
|
1380
|
+
if (withSpec) for (const rel of currentRelations.get(table) ?? []) {
|
|
1381
|
+
const spec = withSpec[rel.name];
|
|
1382
|
+
if (!spec) continue;
|
|
1383
|
+
const nested = typeof spec === "object" ? spec.with : void 0;
|
|
1384
|
+
for (const row of result) {
|
|
1385
|
+
const value = row[rel.name];
|
|
1386
|
+
if (Array.isArray(value)) row[rel.name] = asTableRows(rel.to, value, nested);
|
|
1387
|
+
else if (value && typeof value === "object") row[rel.name] = asTableRows(rel.to, [
|
|
1388
|
+
value
|
|
1389
|
+
], nested)[0];
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
return result;
|
|
557
1393
|
}
|
|
558
1394
|
__name(asTableRows, "asTableRows");
|
|
559
1395
|
function asBindParams(table, cols, data, caller) {
|
|
@@ -796,7 +1632,7 @@ function excludeSql(pk, ids, add) {
|
|
|
796
1632
|
return ` AND t.${quoteIdent(pk)}::text <> ALL(ARRAY[${ids.map((x) => add(x)).join(", ")}])`;
|
|
797
1633
|
}
|
|
798
1634
|
__name(excludeSql, "excludeSql");
|
|
799
|
-
function
|
|
1635
|
+
function computePrimaryKey(table) {
|
|
800
1636
|
const t = tableDefOf(table);
|
|
801
1637
|
const columns = t?.columns ?? {};
|
|
802
1638
|
const cols = Object.keys(columns);
|
|
@@ -816,7 +1652,7 @@ function primaryKeyOf(table) {
|
|
|
816
1652
|
const pks = cols.filter((c) => pkOf(columns[c]));
|
|
817
1653
|
return pks.length === 1 ? pks[0] : null;
|
|
818
1654
|
}
|
|
819
|
-
__name(
|
|
1655
|
+
__name(computePrimaryKey, "computePrimaryKey");
|
|
820
1656
|
function isSinglePkEquality(where, pk) {
|
|
821
1657
|
if (pk === null) return false;
|
|
822
1658
|
const keys = Object.keys(where);
|
|
@@ -831,12 +1667,43 @@ function assertNotAppendOnly(caller, table) {
|
|
|
831
1667
|
throw new Error(`${caller}(${table}): "${table}" append-only \u2014 sat\u0131r g\xFCncellenemez ya da silinemez. D\xFCzeltme i\xE7in TELAF\u0130 KAYDI ekleyin: insert("${table}", { \u2026 }) ile ters i\u015Fareti ta\u015F\u0131yan yeni bir sat\u0131r yaz\u0131n; ge\xE7mi\u015F oldu\u011Fu gibi kals\u0131n diye tablo b\xF6yle bildirilmi\u015F (appendOnly: true).`);
|
|
832
1668
|
}
|
|
833
1669
|
__name(assertNotAppendOnly, "assertNotAppendOnly");
|
|
834
|
-
function
|
|
1670
|
+
function computeSchemaColumns(table) {
|
|
835
1671
|
const t = tableDefOf(table);
|
|
836
1672
|
const cols = t?.columns ? Object.keys(t.columns) : [];
|
|
837
1673
|
return cols.length > 0 ? new Set(cols) : null;
|
|
838
1674
|
}
|
|
839
|
-
__name(
|
|
1675
|
+
__name(computeSchemaColumns, "computeSchemaColumns");
|
|
1676
|
+
function timestampResult(expr) {
|
|
1677
|
+
return `CASE WHEN isfinite(${expr}) AND extract(year FROM ${expr} AT TIME ZONE 'UTC') BETWEEN 1 AND 9999 THEN to_char(${expr} AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"') ELSE ${expr}::text END`;
|
|
1678
|
+
}
|
|
1679
|
+
__name(timestampResult, "timestampResult");
|
|
1680
|
+
function columnResult(table, column, alias) {
|
|
1681
|
+
const expr = `${alias ? `${quoteIdent(alias)}.` : ""}${quoteIdent(column)}`;
|
|
1682
|
+
return columnTypeOf(table, column) === "timestamp" ? timestampResult(expr) : expr;
|
|
1683
|
+
}
|
|
1684
|
+
__name(columnResult, "columnResult");
|
|
1685
|
+
function projectedColumn(table, column, alias) {
|
|
1686
|
+
const expr = columnResult(table, column, alias);
|
|
1687
|
+
return columnTypeOf(table, column) === "timestamp" ? `${expr} AS ${quoteIdent(column)}` : expr;
|
|
1688
|
+
}
|
|
1689
|
+
__name(projectedColumn, "projectedColumn");
|
|
1690
|
+
function rowProjection(table, alias, wildcard = alias ? `${alias}.*` : "*") {
|
|
1691
|
+
const cols = schemaColumns(table);
|
|
1692
|
+
if (!cols || !memoTable(timestampMemo, currentSchema, table, () => [
|
|
1693
|
+
...cols
|
|
1694
|
+
].some((c) => columnTypeOf(table, c) === "timestamp"))) return wildcard;
|
|
1695
|
+
const projections = memoTable(projectionMemo, currentSchema, table, () => /* @__PURE__ */ new Map());
|
|
1696
|
+
const key2 = alias ?? "";
|
|
1697
|
+
let sql = projections.get(key2);
|
|
1698
|
+
if (sql === void 0) {
|
|
1699
|
+
sql = [
|
|
1700
|
+
...cols
|
|
1701
|
+
].map((c) => projectedColumn(table, c, alias)).join(", ");
|
|
1702
|
+
projections.set(key2, sql);
|
|
1703
|
+
}
|
|
1704
|
+
return sql;
|
|
1705
|
+
}
|
|
1706
|
+
__name(rowProjection, "rowProjection");
|
|
840
1707
|
function colRefSql(table, known, ref, caller, alias = "t") {
|
|
841
1708
|
const name = ref.$col;
|
|
842
1709
|
const shapeOK = /^[A-Za-z_][A-Za-z0-9_]*$/.test(name);
|
|
@@ -850,13 +1717,13 @@ function selectClause(table, known, select) {
|
|
|
850
1717
|
if (select !== void 0 && select.length === 0) {
|
|
851
1718
|
throw new Error(`findMany(${table}): select bo\u015F bir liste ald\u0131 \u2014 bu "hepsini ver" DE\u011E\u0130L, "hi\xE7birini saymad\u0131m" demek. Projeksiyon istemiyorsan\u0131z anahtar\u0131 hi\xE7 koymay\u0131n; istiyorsan\u0131z en az bir kolon ad\u0131 verin.`);
|
|
852
1719
|
}
|
|
853
|
-
if (select === void 0) return "*";
|
|
1720
|
+
if (select === void 0) return rowProjection(table, "t", "*");
|
|
854
1721
|
return select.map((col) => {
|
|
855
1722
|
const shapeOK = /^[A-Za-z_][A-Za-z0-9_]*$/.test(col);
|
|
856
1723
|
if (known !== null ? !known.has(col) : !shapeOK) {
|
|
857
1724
|
throw new Error(`findMany(${table}): select kolonu "${col}" tabloda yok`);
|
|
858
1725
|
}
|
|
859
|
-
return `t.${quoteIdent(col)}`;
|
|
1726
|
+
return columnTypeOf(table, col) === "timestamp" ? projectedColumn(table, col, "t") : `t.${quoteIdent(col)}`;
|
|
860
1727
|
}).join(", ");
|
|
861
1728
|
}
|
|
862
1729
|
__name(selectClause, "selectClause");
|
|
@@ -926,7 +1793,7 @@ function compileWhereBare(table, colSet, where, add, caller = "search", alias =
|
|
|
926
1793
|
const transforms = transformsOf(currentSchema, table);
|
|
927
1794
|
const bindFor = /* @__PURE__ */ __name((col) => {
|
|
928
1795
|
const t = transforms.get(col);
|
|
929
|
-
return t?.toDb === void 0
|
|
1796
|
+
return (v) => add(t?.toDb === void 0 || v === null || v === void 0 ? v : t.toDb(v), col, table);
|
|
930
1797
|
}, "bindFor");
|
|
931
1798
|
assertUsableFilter(caller, table, where);
|
|
932
1799
|
if (isSqlFragment(where)) {
|
|
@@ -957,8 +1824,7 @@ function compileWhereBare(table, colSet, where, add, caller = "search", alias =
|
|
|
957
1824
|
const known = rels.map((r) => r.name).sort();
|
|
958
1825
|
throw new Error(`${caller}(${table}): has.${relName} diye bir ili\u015Fki yok` + (known.length > 0 ? ` (bilinenler: ${known.join(", ")}). \u0130li\u015Fkiler yabanc\u0131 anahtarlardan T\xDCRET\u0130L\u0130R; ad\u0131n\u0131 de\u011Fi\u015Ftirmek i\xE7in references(..., { as, reverseAs }).` : `. Bu tablo H\u0130\xC7 yabanc\u0131 anahtar bildirmiyor, ve ili\u015Fkiler onlardan T\xDCRET\u0130L\u0130R. Var olan bir veritaban\u0131ndan introspect edilmi\u015F \u015Femalarda bu s\u0131k olur: kolon \`text()\`/\`uuid()\` olarak durur, \`references(() => <tablo>.<kolon>)\` yoktur. Bildirdi\u011Finiz anda ili\u015Fki iki tarafta da adland\u0131r\u0131l\u0131r \u2014 ama bu CANLI veritaban\u0131na giden bir k\u0131s\u0131tt\u0131r ve mevcut yetim sat\u0131rlar onu d\xFC\u015F\xFCr\xFCr; \xF6nce onlar\u0131 say\u0131n.`));
|
|
959
1826
|
}
|
|
960
|
-
const ourCol =
|
|
961
|
-
const theirCol = rel.kind === "many" ? rel.via : primaryKeyOf(rel.to);
|
|
1827
|
+
const [ourCol, theirCol] = relationColumns(table, rel);
|
|
962
1828
|
if (ourCol === null || theirCol === null) {
|
|
963
1829
|
const missing = ourCol === null ? table : rel.to;
|
|
964
1830
|
throw new Error(`${caller}(${table}): has.${relName} kurulam\u0131yor \u2014 "${missing}" tablosunun tek kolonlu bir primary key'i yok. Korelasyon bir anahtar ister; \xE7ok kolonlu anahtarda ili\u015Fki \xFCzerinden s\xFCzmek yerine sqlFragment ile yaz\u0131n.`);
|
|
@@ -1096,8 +1962,8 @@ async function embedQuery(embed, text) {
|
|
|
1096
1962
|
if (secretReader === null) {
|
|
1097
1963
|
throw new Error(`query embed: secret reader ba\u011Flanmam\u0131\u015F \u2014 ${embed.apiKeyName} okunam\u0131yor`);
|
|
1098
1964
|
}
|
|
1099
|
-
const
|
|
1100
|
-
if (
|
|
1965
|
+
const key2 = await secretReader(embed.apiKeyName);
|
|
1966
|
+
if (key2 === null || key2 === "") {
|
|
1101
1967
|
throw new Error(`query embed: vault'ta ${embed.apiKeyName} yok (FR-021/FR-025)`);
|
|
1102
1968
|
}
|
|
1103
1969
|
const url = (embed.baseURL ?? "https://api.openai.com/v1").replace(/\/$/, "") + "/embeddings";
|
|
@@ -1108,7 +1974,7 @@ async function embedQuery(embed, text) {
|
|
|
1108
1974
|
method: "POST",
|
|
1109
1975
|
headers: {
|
|
1110
1976
|
"Content-Type": "application/json",
|
|
1111
|
-
Authorization: `Bearer ${
|
|
1977
|
+
Authorization: `Bearer ${key2}`
|
|
1112
1978
|
},
|
|
1113
1979
|
body: JSON.stringify({
|
|
1114
1980
|
model: embed.model,
|
|
@@ -1166,14 +2032,14 @@ function buildValueTuple(table, cols, data, add, caller) {
|
|
|
1166
2032
|
}).join(", ");
|
|
1167
2033
|
}
|
|
1168
2034
|
__name(buildValueTuple, "buildValueTuple");
|
|
1169
|
-
function
|
|
2035
|
+
function computeColumnType(table, col) {
|
|
1170
2036
|
const def = tableDefOf(table);
|
|
1171
2037
|
const c = def?.columns?.[col];
|
|
1172
2038
|
if (c === void 0 || c === null) return null;
|
|
1173
2039
|
const d = typeof c === "object" && "_def" in c ? c._def : c;
|
|
1174
2040
|
return d !== null && typeof d === "object" && typeof d.type === "string" ? d.type : null;
|
|
1175
2041
|
}
|
|
1176
|
-
__name(
|
|
2042
|
+
__name(computeColumnType, "computeColumnType");
|
|
1177
2043
|
var NUMERIC_PG_TYPES = /* @__PURE__ */ new Set([
|
|
1178
2044
|
"integer",
|
|
1179
2045
|
"bigint",
|
|
@@ -1183,23 +2049,44 @@ function aggregateExpr(table, fn, col, alias) {
|
|
|
1183
2049
|
const q = `t.${quoteIdent(col)}`;
|
|
1184
2050
|
const t = columnTypeOf(table, col);
|
|
1185
2051
|
if (fn === "sum" || fn === "avg") return `${fn}(${q})::text AS ${quoteIdent(alias)}`;
|
|
2052
|
+
if (t === "timestamp") return `${timestampResult(`${fn}(${q})`)} AS ${quoteIdent(alias)}`;
|
|
1186
2053
|
return t !== null && NUMERIC_PG_TYPES.has(t) && t !== "integer" ? `${fn}(${q})::text AS ${quoteIdent(alias)}` : `${fn}(${q}) AS ${quoteIdent(alias)}`;
|
|
1187
2054
|
}
|
|
1188
2055
|
__name(aggregateExpr, "aggregateExpr");
|
|
1189
|
-
function jsonObjectFor(table, cols, alias) {
|
|
2056
|
+
function jsonObjectFor(table, cols, alias, extra = []) {
|
|
1190
2057
|
const pairs = cols.map((c) => {
|
|
1191
2058
|
const t = columnTypeOf(table, c);
|
|
1192
|
-
const expr = t !== null && (t === "numeric" || t === "bigint") ? `${alias}.${quoteIdent(c)}::text` : `${alias}.${quoteIdent(c)}`;
|
|
2059
|
+
const expr = t !== null && (t === "numeric" || t === "bigint") ? `${alias}.${quoteIdent(c)}::text` : t === "timestamp" ? timestampResult(`${alias}.${quoteIdent(c)}`) : `${alias}.${quoteIdent(c)}`;
|
|
1193
2060
|
return `${quoteLiteral(c)}, ${expr}`;
|
|
1194
2061
|
});
|
|
1195
|
-
return `json_build_object(${
|
|
2062
|
+
return `json_build_object(${[
|
|
2063
|
+
...pairs,
|
|
2064
|
+
...extra
|
|
2065
|
+
].join(", ")})`;
|
|
1196
2066
|
}
|
|
1197
2067
|
__name(jsonObjectFor, "jsonObjectFor");
|
|
1198
|
-
function
|
|
2068
|
+
function relationColumns(table, relation) {
|
|
2069
|
+
const child = relation.kind === "many" ? relation.to : table;
|
|
2070
|
+
const value = tableDefOf(child)?.columns?.[relation.via];
|
|
2071
|
+
const column = value && typeof value === "object" && "_def" in value ? value._def : value;
|
|
2072
|
+
const target = column?.references?.column ?? primaryKeyOf(relation.kind === "many" ? table : relation.to);
|
|
2073
|
+
return relation.kind === "many" ? [
|
|
2074
|
+
target,
|
|
2075
|
+
relation.via
|
|
2076
|
+
] : [
|
|
2077
|
+
relation.via,
|
|
2078
|
+
target
|
|
2079
|
+
];
|
|
2080
|
+
}
|
|
2081
|
+
__name(relationColumns, "relationColumns");
|
|
2082
|
+
function withClauses(table, spec, add, caller, parentAlias = "t", depth = 0, counter = {
|
|
2083
|
+
value: 0
|
|
2084
|
+
}) {
|
|
2085
|
+
if (depth >= 3 && Object.values(spec).some(Boolean)) throw new Error(`${caller}(${table}): with supports at most three relation levels`);
|
|
1199
2086
|
const rels = currentRelations.get(table) ?? [];
|
|
1200
2087
|
const select = [];
|
|
1201
2088
|
const joins = [];
|
|
1202
|
-
|
|
2089
|
+
const jsonPairs = [];
|
|
1203
2090
|
for (const [name, opt] of Object.entries(spec)) {
|
|
1204
2091
|
if (opt === false || opt === void 0) continue;
|
|
1205
2092
|
const rel = rels.find((r) => r.name === name);
|
|
@@ -1207,13 +2094,13 @@ function withClauses(table, spec, add, caller) {
|
|
|
1207
2094
|
const known = rels.map((r) => r.name).sort();
|
|
1208
2095
|
throw new Error(`${caller}(${table}): with.${name} diye bir ili\u015Fki yok` + (known.length > 0 ? ` (bilinenler: ${known.join(", ")})` : `. Bu tablo hi\xE7 yabanc\u0131 anahtar bildirmiyor ve ili\u015Fkiler onlardan T\xDCRET\u0130L\u0130R`) + `. \u0130li\u015Fkiler references(() => <tablo>.<kolon>) ile bildirilir.`);
|
|
1209
2096
|
}
|
|
1210
|
-
const ourCol =
|
|
1211
|
-
const theirCol = rel.kind === "many" ? rel.via : primaryKeyOf(rel.to);
|
|
2097
|
+
const [ourCol, theirCol] = relationColumns(table, rel);
|
|
1212
2098
|
if (ourCol === null || theirCol === null) {
|
|
1213
2099
|
throw new Error(`${caller}(${table}): with.${name} birincil anahtar bilmeden kurulamaz \u2014 ili\u015Fkinin bir ucu tablonun PK's\u0131 ve bu tablo tek kolonlu bir PK bildirmiyor.`);
|
|
1214
2100
|
}
|
|
1215
2101
|
const o = typeof opt === "object" && opt !== null ? opt : {};
|
|
1216
|
-
const
|
|
2102
|
+
const i = ++counter.value;
|
|
2103
|
+
const childAlias = `w${i}`;
|
|
1217
2104
|
const childKnown = schemaColumns(rel.to);
|
|
1218
2105
|
const childCols = Array.isArray(o["select"]) && o["select"].length > 0 ? o["select"] : childKnown !== null ? [
|
|
1219
2106
|
...childKnown
|
|
@@ -1226,28 +2113,100 @@ function withClauses(table, spec, add, caller) {
|
|
|
1226
2113
|
throw new Error(`${caller}(${table}): with.${name}.select kolonu "${c}" "${rel.to}" tablosunda yok`);
|
|
1227
2114
|
}
|
|
1228
2115
|
}
|
|
1229
|
-
const
|
|
2116
|
+
const nestedSpec = o["with"];
|
|
2117
|
+
if (nestedSpec !== void 0 && (nestedSpec === null || typeof nestedSpec !== "object" || Array.isArray(nestedSpec))) {
|
|
2118
|
+
throw new Error(`${caller}(${table}): with.${name}.with must be a relation object`);
|
|
2119
|
+
}
|
|
2120
|
+
const nested = nestedSpec === void 0 ? {
|
|
2121
|
+
joins: [],
|
|
2122
|
+
jsonPairs: []
|
|
2123
|
+
} : withClauses(rel.to, nestedSpec, add, caller, childAlias, depth + 1, counter);
|
|
2124
|
+
const nestedJoins = nested.joins.length ? ` ${nested.joins.join(" ")}` : "";
|
|
2125
|
+
const obj = jsonObjectFor(rel.to, childCols, childAlias, nested.jsonPairs);
|
|
1230
2126
|
const innerWhere = o["where"] !== void 0 && o["where"] !== null ? compileWhereBare(rel.to, childKnown, o["where"], add, caller, childAlias, i) : "";
|
|
1231
|
-
const link = `${childAlias}.${quoteIdent(theirCol)} =
|
|
2127
|
+
const link = `${childAlias}.${quoteIdent(theirCol)} = ${parentAlias}.${quoteIdent(ourCol)}`;
|
|
1232
2128
|
const cond = innerWhere === "" ? link : `${link} AND ${innerWhere}`;
|
|
1233
|
-
const
|
|
2129
|
+
const requestedLimit = o["limit"];
|
|
2130
|
+
limitClause(requestedLimit);
|
|
2131
|
+
const relationLimit = rel.kind === "many" ? requestedLimit : Math.min(1, requestedLimit ?? 1);
|
|
2132
|
+
const inner = orderClauseFor(rel.to, childKnown, o["orderBy"], childAlias) + limitClause(relationLimit);
|
|
1234
2133
|
const outAlias = `wj${i}`;
|
|
1235
2134
|
if (rel.kind === "many") {
|
|
1236
|
-
joins.push(`LEFT JOIN LATERAL (SELECT coalesce(json_agg(x.o), '[]'::json) AS d FROM (SELECT ${obj} AS o FROM ${quoteTable(rel.to)} ${childAlias} WHERE ${cond}${inner}) x) ${quoteIdent(outAlias)} ON true`);
|
|
2135
|
+
joins.push(`LEFT JOIN LATERAL (SELECT coalesce(json_agg(x.o), '[]'::json) AS d FROM (SELECT ${obj} AS o FROM ${quoteTable(rel.to)} ${childAlias}${nestedJoins} WHERE ${cond}${inner}) x) ${quoteIdent(outAlias)} ON true`);
|
|
1237
2136
|
} else {
|
|
1238
|
-
joins.push(`LEFT JOIN LATERAL (SELECT ${obj} AS d FROM ${quoteTable(rel.to)} ${childAlias} WHERE ${cond}${inner}
|
|
2137
|
+
joins.push(`LEFT JOIN LATERAL (SELECT ${obj} AS d FROM ${quoteTable(rel.to)} ${childAlias}${nestedJoins} WHERE ${cond}${inner}) ${quoteIdent(outAlias)} ON true`);
|
|
1239
2138
|
}
|
|
1240
2139
|
select.push(`${quoteIdent(outAlias)}."d" AS ${quoteIdent(name)}`);
|
|
2140
|
+
jsonPairs.push(`${quoteLiteral(name)}, ${quoteIdent(outAlias)}."d"`);
|
|
1241
2141
|
}
|
|
1242
2142
|
return {
|
|
1243
2143
|
select,
|
|
1244
|
-
joins
|
|
2144
|
+
joins,
|
|
2145
|
+
jsonPairs
|
|
1245
2146
|
};
|
|
1246
2147
|
}
|
|
1247
2148
|
__name(withClauses, "withClauses");
|
|
1248
|
-
function
|
|
2149
|
+
function compileFindMany(table, query = {}, opts = {}) {
|
|
2150
|
+
const params = [];
|
|
2151
|
+
const add = /* @__PURE__ */ __name((v) => {
|
|
2152
|
+
params.push(v);
|
|
2153
|
+
return `$${params.length}`;
|
|
2154
|
+
}, "add");
|
|
2155
|
+
const known = schemaColumns(table);
|
|
2156
|
+
const whereSql = compileWhere(table, known, query, add, "findMany");
|
|
2157
|
+
const order = orderClause(table, known, opts.orderBy);
|
|
2158
|
+
const limit = limitClause(opts.limit);
|
|
2159
|
+
const offset = offsetClause(opts.offset, opts.limit);
|
|
2160
|
+
const cols = selectClause(table, known, opts.select);
|
|
2161
|
+
const w = opts.with !== void 0 && Object.keys(opts.with).length > 0 ? withClauses(table, opts.with, add, "findMany") : {
|
|
2162
|
+
select: [],
|
|
2163
|
+
joins: []
|
|
2164
|
+
};
|
|
2165
|
+
const selectList = w.select.length > 0 ? `${cols === "*" ? "t.*" : cols}, ${w.select.join(", ")}` : cols;
|
|
2166
|
+
const joinSql = w.joins.length > 0 ? ` ${w.joins.join(" ")}` : "";
|
|
2167
|
+
const sql = `SELECT ${selectList} FROM ${quoteTable(table)} t${joinSql} WHERE true${whereSql}${order}${limit}${offset}`;
|
|
2168
|
+
return {
|
|
2169
|
+
sql,
|
|
2170
|
+
params
|
|
2171
|
+
};
|
|
2172
|
+
}
|
|
2173
|
+
__name(compileFindMany, "compileFindMany");
|
|
2174
|
+
var uniqueKeysCache = /* @__PURE__ */ new WeakMap();
|
|
2175
|
+
var pageSchemaCache = /* @__PURE__ */ new WeakMap();
|
|
2176
|
+
function pageSchema(table) {
|
|
2177
|
+
return memoTable(pageSchemaCache, currentSchema, table, () => {
|
|
2178
|
+
const def = tableDefOf(table);
|
|
2179
|
+
if (!def?.columns) throw new Error(`page(${table}) requires an installed table declaration`);
|
|
2180
|
+
const columns = /* @__PURE__ */ Object.create(null);
|
|
2181
|
+
const inlineKeys = [];
|
|
2182
|
+
for (const [name, column] of Object.entries(def.columns)) {
|
|
2183
|
+
const c = column;
|
|
2184
|
+
const d = c._def ?? c;
|
|
2185
|
+
columns[name] = {
|
|
2186
|
+
type: d.type,
|
|
2187
|
+
nullable: d.nullable
|
|
2188
|
+
};
|
|
2189
|
+
if (d.primaryKey) inlineKeys.push(name);
|
|
2190
|
+
}
|
|
2191
|
+
const primaryKey = def.primaryKey ?? inlineKeys;
|
|
2192
|
+
for (const name of primaryKey) if (columns[name]) columns[name].nullable = false;
|
|
2193
|
+
return {
|
|
2194
|
+
columns,
|
|
2195
|
+
primaryKey,
|
|
2196
|
+
stamp: fingerprint({
|
|
2197
|
+
columns,
|
|
2198
|
+
primaryKey
|
|
2199
|
+
})
|
|
2200
|
+
};
|
|
2201
|
+
});
|
|
2202
|
+
}
|
|
2203
|
+
__name(pageSchema, "pageSchema");
|
|
2204
|
+
function createOps(tx, context = {}) {
|
|
1249
2205
|
const at = /* @__PURE__ */ __name(() => resolveTx(tx), "at");
|
|
1250
2206
|
const ops = {
|
|
2207
|
+
diagnostics() {
|
|
2208
|
+
return context.trace?.snapshot ?? null;
|
|
2209
|
+
},
|
|
1251
2210
|
// Generic dönüş, çağıranın İDDİASIDIR (FR-021). Motor her zaman satır objeleri
|
|
1252
2211
|
// üretir; `T` onlara bir ad vermekten ibarettir ve verilmezse `unknown` kalır —
|
|
1253
2212
|
// yani "bilmiyorum", ki ham SQL için doğru cevap odur.
|
|
@@ -1261,7 +2220,7 @@ function createOps(tx) {
|
|
|
1261
2220
|
const params = [];
|
|
1262
2221
|
const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
|
|
1263
2222
|
const tuple = buildValueTuple(table, cols, data, add, "insert");
|
|
1264
|
-
const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING
|
|
2223
|
+
const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING ${rowProjection(table)}`;
|
|
1265
2224
|
const rows = await (await at()).unsafe(sql, params);
|
|
1266
2225
|
const inserted = rows[0];
|
|
1267
2226
|
if (!inserted) {
|
|
@@ -1283,8 +2242,9 @@ function createOps(tx) {
|
|
|
1283
2242
|
* bir satırın fazla kolonu SESSİZCE YAZILMAZDI. Eksik kolon için `null`
|
|
1284
2243
|
* yazın ya da varsayılanı istiyorsanız o satırları ayrı çağrıda ekleyin.
|
|
1285
2244
|
*/
|
|
1286
|
-
async
|
|
1287
|
-
|
|
2245
|
+
insertMany: /* @__PURE__ */ __name(async function(table, rows, opts) {
|
|
2246
|
+
validateInsertManyOptions(opts);
|
|
2247
|
+
if (rows.length === 0) return opts?.returning === false ? 0 : [];
|
|
1288
2248
|
const first = rows[0];
|
|
1289
2249
|
const cols = Object.keys(first);
|
|
1290
2250
|
if (cols.length === 0) throw new Error(`insertMany into ${table}: no columns given`);
|
|
@@ -1299,11 +2259,15 @@ function createOps(tx) {
|
|
|
1299
2259
|
const params = [];
|
|
1300
2260
|
const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
|
|
1301
2261
|
const tuples = rows.map((r) => `(${buildValueTuple(table, cols, r, add, "insertMany")})`);
|
|
1302
|
-
const tail = opts !== void 0 && opts.onConflict.length > 0 ? ` ${onConflictTail(cols, opts.onConflict, opts.action ?? "ignore")}` : "";
|
|
1303
|
-
const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES ${tuples.join(", ")}${tail} RETURNING
|
|
2262
|
+
const tail = opts?.onConflict !== void 0 && opts.onConflict.length > 0 ? ` ${onConflictTail(cols, opts.onConflict, opts.action ?? "ignore")}` : "";
|
|
2263
|
+
const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES ${tuples.join(", ")}${tail}${opts?.returning === false ? "" : ` RETURNING ${rowProjection(table)}`}`;
|
|
1304
2264
|
const out = await (await at()).unsafe(sql, params);
|
|
2265
|
+
if (opts?.returning === false) {
|
|
2266
|
+
if (!Number.isSafeInteger(out.count) || out.count < 0) throw new Error("insertMany returning:false requires a driver with an affected row count");
|
|
2267
|
+
return out.count;
|
|
2268
|
+
}
|
|
1305
2269
|
return out.map((r) => asTableRow(table, r));
|
|
1306
|
-
},
|
|
2270
|
+
}, "insertMany"),
|
|
1307
2271
|
/**
|
|
1308
2272
|
* Bir ADI kilitle — satırı değil (FR-054).
|
|
1309
2273
|
*
|
|
@@ -1317,9 +2281,9 @@ function createOps(tx) {
|
|
|
1317
2281
|
* Transaction kapsamlısı commit/rollback'te Postgres tarafından bırakılır,
|
|
1318
2282
|
* yani unutulması imkânsız.
|
|
1319
2283
|
*/
|
|
1320
|
-
async advisoryXactLock(
|
|
2284
|
+
async advisoryXactLock(key2) {
|
|
1321
2285
|
await (await at()).unsafe("select pg_advisory_xact_lock(hashtextextended($1, 0))", [
|
|
1322
|
-
|
|
2286
|
+
key2
|
|
1323
2287
|
]);
|
|
1324
2288
|
},
|
|
1325
2289
|
/**
|
|
@@ -1393,7 +2357,7 @@ function createOps(tx) {
|
|
|
1393
2357
|
const params = [];
|
|
1394
2358
|
const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
|
|
1395
2359
|
const tuple = buildValueTuple(table, cols, data, add, "claim");
|
|
1396
|
-
const rows = await sp.unsafe(`INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING
|
|
2360
|
+
const rows = await sp.unsafe(`INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING ${rowProjection(table)}`, params);
|
|
1397
2361
|
if (!rows[0]) {
|
|
1398
2362
|
throw new Error(`claim(${table}): INSERT sat\u0131r d\xF6nd\xFCrmedi \u2014 yazma reddedildi (b\xFCy\xFCk olas\u0131l\u0131kla bir RLS policy'si).`);
|
|
1399
2363
|
}
|
|
@@ -1410,7 +2374,7 @@ function createOps(tx) {
|
|
|
1410
2374
|
return `$${params.length}`;
|
|
1411
2375
|
}, "add");
|
|
1412
2376
|
const where = keyCols.map((c) => `t.${quoteIdent(c)} = ${add(unique[c])}`).join(" AND ");
|
|
1413
|
-
const found = await (await at()).unsafe(`SELECT * FROM ${quoteTable(table)} t WHERE ${where} LIMIT 1`, params);
|
|
2377
|
+
const found = await (await at()).unsafe(`SELECT ${rowProjection(table, "t", "*")} FROM ${quoteTable(table)} t WHERE ${where} LIMIT 1`, params);
|
|
1414
2378
|
if (!found[0]) {
|
|
1415
2379
|
const named = constraintOf(e);
|
|
1416
2380
|
throw new Error(`claim(${table}): benzersizlik ihlali${named ? ` (${named})` : ""} yakaland\u0131 ama ${keyCols.join(", ")} ile sat\u0131r bulunamad\u0131. Ya ihlal BA\u015EKA bir unique k\u0131s\u0131ttan geliyor \u2014 o zaman claim() bu \xE7a\u011Fr\u0131 i\xE7in yanl\u0131\u015F ara\xE7 \u2014 ya da sat\u0131r var ve bir RLS policy'si onu bu \xE7a\u011F\u0131randan gizliyor.`);
|
|
@@ -1447,13 +2411,13 @@ function createOps(tx) {
|
|
|
1447
2411
|
const bindData = [];
|
|
1448
2412
|
const addPut = /* @__PURE__ */ __name((v) => `$${bindData.push(v)}`, "addPut");
|
|
1449
2413
|
const ph = buildValueTuple(table, cols, data, addPut, "put");
|
|
1450
|
-
const insertSql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${ph}) ON CONFLICT (${conflict.map(quoteIdent).join(", ")}) WHERE "valid_to" IS NULL DO NOTHING RETURNING
|
|
2414
|
+
const insertSql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${ph}) ON CONFLICT (${conflict.map(quoteIdent).join(", ")}) WHERE "valid_to" IS NULL DO NOTHING RETURNING ${rowProjection(table)}`;
|
|
1451
2415
|
const live = await at();
|
|
1452
2416
|
const first = await live.unsafe(insertSql, bindData);
|
|
1453
2417
|
if (first[0]) return asTableRow(table, first[0]);
|
|
1454
2418
|
const condSql = conflict.map((c, i) => `${quoteIdent(c)} = $${i + 1}`).join(" AND ");
|
|
1455
2419
|
const condBind = conflict.map((c) => data[c]);
|
|
1456
|
-
const cur = await live.unsafe(`SELECT
|
|
2420
|
+
const cur = await live.unsafe(`SELECT ${rowProjection(table)} FROM ${quoteTable(table)} WHERE ${condSql} AND "valid_to" IS NULL`, condBind);
|
|
1457
2421
|
const old = cur[0];
|
|
1458
2422
|
if (old === void 0) {
|
|
1459
2423
|
throw new Error(`upsert into ${table}: e\u015Fzamanl\u0131 supersede yar\u0131\u015F\u0131 \u2014 ge\xE7erli sat\u0131r bulunamad\u0131, iste\u011Fi yineleyin (D-020)`);
|
|
@@ -1479,7 +2443,7 @@ function createOps(tx) {
|
|
|
1479
2443
|
const conflictSet = new Set(conflict);
|
|
1480
2444
|
const assignments = cols.filter((c) => !conflictSet.has(c)).map((c) => `${quoteIdent(c)} = EXCLUDED.${quoteIdent(c)}`);
|
|
1481
2445
|
const action = assignments.length ? `DO UPDATE SET ${assignments.join(", ")}` : `DO UPDATE SET ${quoteIdent(conflict[0])} = EXCLUDED.${quoteIdent(conflict[0])}`;
|
|
1482
|
-
const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${placeholders}) ON CONFLICT (${conflict.map(quoteIdent).join(", ")}) ${action} RETURNING
|
|
2446
|
+
const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${placeholders}) ON CONFLICT (${conflict.map(quoteIdent).join(", ")}) ${action} RETURNING ${rowProjection(table)}`;
|
|
1483
2447
|
const rows = await (await at()).unsafe(sql, params);
|
|
1484
2448
|
const row = rows[0];
|
|
1485
2449
|
if (!row) {
|
|
@@ -1497,7 +2461,7 @@ function createOps(tx) {
|
|
|
1497
2461
|
return `$${params.length}`;
|
|
1498
2462
|
}, "add");
|
|
1499
2463
|
const assignments = buildSetAssignments(table, cols, data, add, "update");
|
|
1500
|
-
const sql = `UPDATE ${quoteTable(table)} SET ${assignments} WHERE id = ${add(id)} RETURNING
|
|
2464
|
+
const sql = `UPDATE ${quoteTable(table)} SET ${assignments} WHERE id = ${add(id)} RETURNING ${rowProjection(table)}`;
|
|
1501
2465
|
const rows = await (await at()).unsafe(sql, params);
|
|
1502
2466
|
return rows[0] ? asTableRow(table, rows[0]) : null;
|
|
1503
2467
|
},
|
|
@@ -1549,9 +2513,9 @@ function createOps(tx) {
|
|
|
1549
2513
|
assertHasPredicate(whereSql, "updateMany", table);
|
|
1550
2514
|
const pk = primaryKeyOf(table);
|
|
1551
2515
|
const wantRows = opts?.returning !== false;
|
|
1552
|
-
const tail = wantRows ?
|
|
2516
|
+
const tail = wantRows ? `RETURNING ${rowProjection(table, "t", "*")}` : "RETURNING 1";
|
|
1553
2517
|
const simple = isSinglePkEquality(where, pk) || pk === null;
|
|
1554
|
-
const body = simple ? `UPDATE ${quoteTable(table)} AS t SET ${assignments} WHERE true${whereSql} ${tail}` : `UPDATE ${quoteTable(table)} AS t SET ${assignments} FROM locked WHERE t.${quoteIdent(pk)} = locked.${quoteIdent(pk)} ` + (wantRows ?
|
|
2518
|
+
const body = simple ? `UPDATE ${quoteTable(table)} AS t SET ${assignments} WHERE true${whereSql} ${tail}` : `UPDATE ${quoteTable(table)} AS t SET ${assignments} FROM locked WHERE t.${quoteIdent(pk)} = locked.${quoteIdent(pk)} ` + (wantRows ? `RETURNING ${rowProjection(table, "t")}` : "RETURNING 1");
|
|
1555
2519
|
const lockedCte = simple ? "" : `WITH locked AS (SELECT t.${quoteIdent(pk)} FROM ${quoteTable(table)} t WHERE true${whereSql} ORDER BY t.${quoteIdent(pk)} FOR NO KEY UPDATE) `;
|
|
1556
2520
|
const sql = wantRows ? `${lockedCte}${body}` : simple ? `WITH upd AS (${body}) SELECT count(*)::int AS n FROM upd` : `${lockedCte.replace(/ $/, "")}, upd AS (${body}) SELECT count(*)::int AS n FROM upd`;
|
|
1557
2521
|
const out = await (await at()).unsafe(sql, params);
|
|
@@ -1636,7 +2600,7 @@ function createOps(tx) {
|
|
|
1636
2600
|
}
|
|
1637
2601
|
const groups = q.groupBy ?? [];
|
|
1638
2602
|
for (const col of groups) check(col, "groupBy");
|
|
1639
|
-
const groupSel = groups.map((c) => `t.${quoteIdent(c)} AS ${quoteIdent(`g__${c}`)}`);
|
|
2603
|
+
const groupSel = groups.map((c) => `${columnTypeOf(table, c) === "timestamp" ? columnResult(table, c, "t") : `t.${quoteIdent(c)}`} AS ${quoteIdent(`g__${c}`)}`);
|
|
1640
2604
|
const params = [];
|
|
1641
2605
|
const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
|
|
1642
2606
|
const whereSql = compileWhere(table, known, q.where ?? {}, add, "aggregate");
|
|
@@ -1670,32 +2634,114 @@ function createOps(tx) {
|
|
|
1670
2634
|
const rows = await (await at()).unsafe(sql, params);
|
|
1671
2635
|
return Number(rows[0]?.n ?? 0);
|
|
1672
2636
|
},
|
|
2637
|
+
async findUnique(table, where, opts = {}) {
|
|
2638
|
+
const keys = memoTable(uniqueKeysCache, currentSchema, table, () => uniqueKeysOf(tableDefOf(table) ?? {}));
|
|
2639
|
+
assertUniqueLookup(table, keys, where);
|
|
2640
|
+
const rows = await ops.findMany(table, where, {
|
|
2641
|
+
...opts,
|
|
2642
|
+
select: opts.select === void 0 ? void 0 : [
|
|
2643
|
+
...opts.select
|
|
2644
|
+
],
|
|
2645
|
+
limit: 2
|
|
2646
|
+
});
|
|
2647
|
+
if (rows.length > 1) throw new Error(`findUnique(${table}): deployed constraints disagree with the schema; multiple rows matched`);
|
|
2648
|
+
return rows[0] ?? null;
|
|
2649
|
+
},
|
|
1673
2650
|
async findById(table, id) {
|
|
1674
|
-
const rows = await (await at()).unsafe(`SELECT
|
|
2651
|
+
const rows = await (await at()).unsafe(`SELECT ${rowProjection(table)} FROM ${quoteTable(table)} WHERE id = $1`, [
|
|
1675
2652
|
id
|
|
1676
2653
|
]);
|
|
1677
2654
|
return rows[0] ? asTableRow(table, rows[0]) : null;
|
|
1678
2655
|
},
|
|
2656
|
+
compileQuery(query) {
|
|
2657
|
+
assertReadContract(query, tableDefOf(query.table)?.columns);
|
|
2658
|
+
return compileFindMany(query.table, query.where, query.options);
|
|
2659
|
+
},
|
|
2660
|
+
async readQuery(query) {
|
|
2661
|
+
const compiled = ops.compileQuery(query);
|
|
2662
|
+
return asTableRows(query.table, await (await at()).unsafe(compiled.sql, compiled.params, {
|
|
2663
|
+
name: query.name
|
|
2664
|
+
}));
|
|
2665
|
+
},
|
|
2666
|
+
async explainQuery(query) {
|
|
2667
|
+
const compiled = ops.compileQuery(query);
|
|
2668
|
+
return await (await at()).unsafe(`EXPLAIN (FORMAT JSON) ${compiled.sql}`, compiled.params, {
|
|
2669
|
+
name: `${query.name}.explain`
|
|
2670
|
+
});
|
|
2671
|
+
},
|
|
1679
2672
|
async findMany(table, query = {}, opts = {}) {
|
|
2673
|
+
const compiled = compileFindMany(table, query, opts);
|
|
2674
|
+
return asTableRows(table, await (await at()).unsafe(compiled.sql, compiled.params), opts.with);
|
|
2675
|
+
},
|
|
2676
|
+
async page(table, query = {}, opts = {}) {
|
|
2677
|
+
const secret = cursorKey();
|
|
2678
|
+
const schema = pageSchema(table);
|
|
2679
|
+
const { order, size, backward, token } = pageOrder(schema.columns, schema.primaryKey, opts);
|
|
1680
2680
|
const params = [];
|
|
1681
|
-
const add = /* @__PURE__ */ __name((v) => {
|
|
1682
|
-
params.push(v);
|
|
1683
|
-
return `$${params.length}`;
|
|
1684
|
-
}, "add");
|
|
2681
|
+
const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
|
|
1685
2682
|
const known = schemaColumns(table);
|
|
1686
|
-
const
|
|
1687
|
-
const
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
const
|
|
1691
|
-
|
|
2683
|
+
const where = compileWhere(table, known, query, add, "page");
|
|
2684
|
+
const cols = selectClause(table, known, opts.select === void 0 ? void 0 : [
|
|
2685
|
+
...opts.select
|
|
2686
|
+
]);
|
|
2687
|
+
const scope = fingerprint({
|
|
2688
|
+
table,
|
|
2689
|
+
schema: schema.stamp,
|
|
2690
|
+
order,
|
|
2691
|
+
where,
|
|
2692
|
+
params,
|
|
2693
|
+
select: opts.select,
|
|
2694
|
+
with: opts.with,
|
|
2695
|
+
identity: context.cursorScope ?? "direct"
|
|
2696
|
+
});
|
|
2697
|
+
const values = token === void 0 ? void 0 : decodeCursor(secret, scope, token, order.length);
|
|
2698
|
+
const column = /* @__PURE__ */ __name((name) => `t.${quoteIdent(name)}`, "column");
|
|
2699
|
+
const boundary = values ? ` AND ${pageBoundary(order, values, backward, add, column)}` : "";
|
|
2700
|
+
const opposite = values ? `EXISTS (SELECT 1 FROM ${quoteTable(table)} t WHERE true${where} AND ${pageBoundary(order, values, !backward, add, column, true)})` : "false";
|
|
2701
|
+
let prefix = "__pb_page_";
|
|
2702
|
+
const names = [
|
|
2703
|
+
...Object.keys(schema.columns),
|
|
2704
|
+
...Object.keys(opts.with ?? {})
|
|
2705
|
+
];
|
|
2706
|
+
while (names.some((n) => n.startsWith(prefix))) prefix += "_";
|
|
2707
|
+
const ordinal = `${prefix}ordinal`, other = `${prefix}opposite`;
|
|
2708
|
+
const hidden = order.map((o, i) => `${prefix}${i}`);
|
|
2709
|
+
const cursorCols = order.map((o, i) => `${columnTypeOf(table, o.column) === "timestamp" ? timestampResult(column(o.column)) : `${column(o.column)}::text`} AS ${quoteIdent(hidden[i])}`);
|
|
2710
|
+
const w = opts.with && Object.keys(opts.with).length ? withClauses(table, opts.with, add, "page") : {
|
|
1692
2711
|
select: [],
|
|
1693
2712
|
joins: []
|
|
1694
2713
|
};
|
|
1695
|
-
const
|
|
1696
|
-
const
|
|
1697
|
-
const
|
|
1698
|
-
|
|
2714
|
+
const nativeOrder = order.map((o) => `${column(o.column)} ${o.direction === "asc" !== backward ? "ASC" : "DESC"} NULLS ${o.nulls === "first" !== backward ? "FIRST" : "LAST"}`).join(", ");
|
|
2715
|
+
const visible = cols === "*" ? "t.*" : cols;
|
|
2716
|
+
const inner = `SELECT ${[
|
|
2717
|
+
visible,
|
|
2718
|
+
...w.select,
|
|
2719
|
+
...cursorCols,
|
|
2720
|
+
`row_number() OVER (ORDER BY ${nativeOrder}) AS ${quoteIdent(ordinal)}`
|
|
2721
|
+
].join(", ")} FROM ${quoteTable(table)} t ${w.joins.join(" ")} WHERE true${where}${boundary} ORDER BY ${nativeOrder} LIMIT ${size + 1}`;
|
|
2722
|
+
const sql = `SELECT p.*, b.${quoteIdent(other)} FROM (SELECT ${opposite} AS ${quoteIdent(other)}) b LEFT JOIN LATERAL (${inner}) p ON true ORDER BY p.${quoteIdent(ordinal)}`;
|
|
2723
|
+
const result = await (await at()).unsafe(sql, params);
|
|
2724
|
+
const hasOpposite = result[0]?.[other] === true;
|
|
2725
|
+
let rows = result.filter((row) => row[ordinal] !== null && row[ordinal] !== void 0);
|
|
2726
|
+
const hasMore = rows.length > size;
|
|
2727
|
+
rows = rows.slice(0, size);
|
|
2728
|
+
if (backward) rows.reverse();
|
|
2729
|
+
const cursor = /* @__PURE__ */ __name((row) => row ? encodeCursor(secret, scope, hidden.map((name) => row[name] === null ? null : String(row[name]))) : null, "cursor");
|
|
2730
|
+
const pageInfo = {
|
|
2731
|
+
startCursor: cursor(rows[0]),
|
|
2732
|
+
endCursor: cursor(rows.at(-1)),
|
|
2733
|
+
hasNextPage: backward ? hasOpposite : hasMore,
|
|
2734
|
+
hasPreviousPage: backward ? hasMore : hasOpposite
|
|
2735
|
+
};
|
|
2736
|
+
for (const row of rows) for (const name of [
|
|
2737
|
+
...hidden,
|
|
2738
|
+
ordinal,
|
|
2739
|
+
other
|
|
2740
|
+
]) delete row[name];
|
|
2741
|
+
return {
|
|
2742
|
+
rows: asTableRows(table, rows, opts.with),
|
|
2743
|
+
pageInfo
|
|
2744
|
+
};
|
|
1699
2745
|
},
|
|
1700
2746
|
/**
|
|
1701
2747
|
* Tek-SQL hibrit arama (FR-014): iki kol CTE + FULL OUTER JOIN + RRF
|
|
@@ -1800,7 +2846,7 @@ function createOps(tx) {
|
|
|
1800
2846
|
kwC = `SELECT c.${pidQ} AS pid, c.chunk_seq, ROW_NUMBER() OVER (ORDER BY ts_rank_cd(c.palbase_fts, websearch_to_tsquery('simple', ${qpC})) DESC) AS r FROM ${quoteTable(ck.table)} c${parentJoinC}c.palbase_fts @@ websearch_to_tsquery('simple', ${qpC}) ORDER BY r LIMIT ${cpool}`;
|
|
1801
2847
|
}
|
|
1802
2848
|
const msC = params.minScore === void 0 ? "" : addC(params.minScore);
|
|
1803
|
-
const colListC = cfg.cols.map((c) => `t.${quoteIdent(c)}`).join(", ");
|
|
2849
|
+
const colListC = cfg.cols.map((c) => columnTypeOf(table, c) === "timestamp" ? projectedColumn(table, c, "t") : `t.${quoteIdent(c)}`).join(", ");
|
|
1804
2850
|
const scoreC = scoreMul === "" ? "g._score" : `(g._score)${scoreMul}`;
|
|
1805
2851
|
const assembleC = /* @__PURE__ */ __name((kwIn, withKwn) => {
|
|
1806
2852
|
const kwnCol = withKwn ? `, (SELECT count(*) FROM kw)::int AS _kwn` : "";
|
|
@@ -1882,7 +2928,7 @@ function createOps(tx) {
|
|
|
1882
2928
|
qpRef = qp;
|
|
1883
2929
|
kwSql = `SELECT t.${quoteIdent(cfg.pk)} AS id, ROW_NUMBER() OVER (ORDER BY ts_rank_cd(t.palbase_fts, websearch_to_tsquery('simple', ${qp})) DESC) AS r FROM ${quoteTable(table)} t WHERE t.palbase_fts @@ websearch_to_tsquery('simple', ${qp})${whereSql} ORDER BY r LIMIT ${pool}`;
|
|
1884
2930
|
}
|
|
1885
|
-
const colList = cfg.cols.map((c) => `t.${quoteIdent(c)}`).join(", ");
|
|
2931
|
+
const colList = cfg.cols.map((c) => columnTypeOf(table, c) === "timestamp" ? projectedColumn(table, c, "t") : `t.${quoteIdent(c)}`).join(", ");
|
|
1886
2932
|
const msP = params.minScore === void 0 ? "" : add(params.minScore);
|
|
1887
2933
|
const hlCol = params.highlight === true && wantText ? `, ts_headline('simple', ${ftsExprOf(cfg.ftsCols)}, websearch_to_tsquery('simple', ${qpRef})) AS _highlight` : "";
|
|
1888
2934
|
const assemble = /* @__PURE__ */ __name((kwIn, withKwn) => {
|
|
@@ -2090,7 +3136,7 @@ function createOps(tx) {
|
|
|
2090
3136
|
const params = [];
|
|
2091
3137
|
const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
|
|
2092
3138
|
const tuple = buildValueTuple(table, cols, data, add, "supersede");
|
|
2093
|
-
const rows = await sp.unsafe(`INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING
|
|
3139
|
+
const rows = await sp.unsafe(`INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING ${rowProjection(table)}`, params);
|
|
2094
3140
|
if (!rows[0]) {
|
|
2095
3141
|
throw new Error(`supersede(${table}): INSERT sat\u0131r d\xF6nd\xFCrmedi \u2014 yazma reddedildi (b\xFCy\xFCk olas\u0131l\u0131kla bir RLS policy'si)`);
|
|
2096
3142
|
}
|
|
@@ -2100,7 +3146,7 @@ function createOps(tx) {
|
|
|
2100
3146
|
/** A real SAVEPOINT inside the request's transaction. */
|
|
2101
3147
|
async transaction(cb) {
|
|
2102
3148
|
const live = await at();
|
|
2103
|
-
return live.savepoint(async (sp) => cb(withTables(createOps(sp), currentSchema)));
|
|
3149
|
+
return live.savepoint(async (sp) => cb(withTables(createOps(sp, context), currentSchema)));
|
|
2104
3150
|
},
|
|
2105
3151
|
/**
|
|
2106
3152
|
* Run `fn` against a handle bound to a SAVEPOINT, so a failure inside it
|
|
@@ -2121,7 +3167,7 @@ function createOps(tx) {
|
|
|
2121
3167
|
*/
|
|
2122
3168
|
async attempt(fn) {
|
|
2123
3169
|
const live = await at();
|
|
2124
|
-
return live.savepoint(async (sp) => fn(withTables(createOps(sp), currentSchema)));
|
|
3170
|
+
return live.savepoint(async (sp) => fn(withTables(createOps(sp, context), currentSchema)));
|
|
2125
3171
|
},
|
|
2126
3172
|
/**
|
|
2127
3173
|
* Execute a whole transaction plan — what `Database.$transaction(fn)` builds.
|
|
@@ -2141,6 +3187,62 @@ function createOps(tx) {
|
|
|
2141
3187
|
* "transport.txPlan is not a function" while every test that covered
|
|
2142
3188
|
* transactions passed against a mock that did implement it.
|
|
2143
3189
|
*/
|
|
3190
|
+
async command(plan, options = {}) {
|
|
3191
|
+
if (options.mode !== void 0 && ![
|
|
3192
|
+
"auto",
|
|
3193
|
+
"compiled",
|
|
3194
|
+
"sequential"
|
|
3195
|
+
].includes(options.mode)) throw new Error("command mode must be auto, compiled or sequential");
|
|
3196
|
+
if (options.mode === "sequential") return ops.txPlan(plan);
|
|
3197
|
+
const compiled = context.commands ? compileCommand(plan) : {
|
|
3198
|
+
reason: "the PostgreSQL command executor is not installed"
|
|
3199
|
+
};
|
|
3200
|
+
if ("reason" in compiled) {
|
|
3201
|
+
if (options.mode === "compiled") throw new Error(`Command cannot compile: ${compiled.reason}. Use auto/sequential mode or installCommandExecutor at boot.`);
|
|
3202
|
+
return ops.txPlan(plan);
|
|
3203
|
+
}
|
|
3204
|
+
if (compiled.steps.length === 0) return {
|
|
3205
|
+
results: []
|
|
3206
|
+
};
|
|
3207
|
+
if (compiled.pipeline) {
|
|
3208
|
+
return (await at()).savepoint(async (sp) => {
|
|
3209
|
+
const settled = await Promise.allSettled(compiled.pipeline.map((statement) => sp.unsafe(statement.sql, statement.params)));
|
|
3210
|
+
const failed = settled.find((result) => result.status === "rejected");
|
|
3211
|
+
if (failed?.status === "rejected") {
|
|
3212
|
+
const error = failed.reason;
|
|
3213
|
+
if (sqlstateOf(error) === SQLSTATE.commandGuardFailed && typeof error.detail === "string") {
|
|
3214
|
+
const detail = JSON.parse(error.detail);
|
|
3215
|
+
if (detail.error_code === "tx_guard_failed" && Number.isInteger(detail.slot)) {
|
|
3216
|
+
throw Object.assign(new Error("Command expectation failed"), detail);
|
|
3217
|
+
}
|
|
3218
|
+
}
|
|
3219
|
+
throw error;
|
|
3220
|
+
}
|
|
3221
|
+
return {
|
|
3222
|
+
results: settled.map((result, i) => {
|
|
3223
|
+
const rows2 = result.value[0].rows;
|
|
3224
|
+
return {
|
|
3225
|
+
rows: asTableRows(plan.ops[i].table, rows2),
|
|
3226
|
+
rows_affected: rows2.length
|
|
3227
|
+
};
|
|
3228
|
+
})
|
|
3229
|
+
};
|
|
3230
|
+
});
|
|
3231
|
+
}
|
|
3232
|
+
const rows = await (await at()).savepoint((sp) => sp.unsafe("SELECT palbase_runtime.command_v1($1::jsonb -> 'steps') AS result", [
|
|
3233
|
+
{
|
|
3234
|
+
steps: compiled.steps
|
|
3235
|
+
}
|
|
3236
|
+
]));
|
|
3237
|
+
const response = rows[0].result;
|
|
3238
|
+
if (response.error) throw Object.assign(new Error(response.error.error_code === "tx_ref_unresolved" ? "Command reference has no source row/column; use expectOne before reading it" : "Command expectation failed"), response.error);
|
|
3239
|
+
return {
|
|
3240
|
+
results: response.results.map((result, i) => ({
|
|
3241
|
+
...result,
|
|
3242
|
+
rows: asTableRows(plan.ops[i].table, result.rows)
|
|
3243
|
+
}))
|
|
3244
|
+
};
|
|
3245
|
+
},
|
|
2144
3246
|
async txPlan(plan) {
|
|
2145
3247
|
const live = await at();
|
|
2146
3248
|
return live.savepoint(async (sp) => {
|
|
@@ -2164,27 +3266,55 @@ function createOps(tx) {
|
|
|
2164
3266
|
}
|
|
2165
3267
|
__name(createOps, "createOps");
|
|
2166
3268
|
var currentSchema = {};
|
|
3269
|
+
var columnsMemo = /* @__PURE__ */ new WeakMap();
|
|
3270
|
+
var primaryMemo = /* @__PURE__ */ new WeakMap();
|
|
3271
|
+
var vectorMemo = /* @__PURE__ */ new WeakMap();
|
|
3272
|
+
var transformMemo = /* @__PURE__ */ new WeakMap();
|
|
3273
|
+
var typeMemo = /* @__PURE__ */ new WeakMap();
|
|
3274
|
+
var timestampMemo = /* @__PURE__ */ new WeakMap();
|
|
3275
|
+
var projectionMemo = /* @__PURE__ */ new WeakMap();
|
|
3276
|
+
function schemaColumns(table) {
|
|
3277
|
+
return memoTable(columnsMemo, currentSchema, table, () => computeSchemaColumns(table));
|
|
3278
|
+
}
|
|
3279
|
+
__name(schemaColumns, "schemaColumns");
|
|
3280
|
+
function primaryKeyOf(table) {
|
|
3281
|
+
return memoTable(primaryMemo, currentSchema, table, () => computePrimaryKey(table));
|
|
3282
|
+
}
|
|
3283
|
+
__name(primaryKeyOf, "primaryKeyOf");
|
|
3284
|
+
function vectorColumnsOf(schema, table) {
|
|
3285
|
+
return memoTable(vectorMemo, schema, table, () => computeVectorColumns(schema, table));
|
|
3286
|
+
}
|
|
3287
|
+
__name(vectorColumnsOf, "vectorColumnsOf");
|
|
3288
|
+
function transformsOf(schema, table) {
|
|
3289
|
+
return memoTable(transformMemo, schema, table, () => computeTransforms(schema, table));
|
|
3290
|
+
}
|
|
3291
|
+
__name(transformsOf, "transformsOf");
|
|
3292
|
+
function columnTypeOf(table, col) {
|
|
3293
|
+
const types = memoTable(typeMemo, currentSchema, table, () => new Map([
|
|
3294
|
+
...schemaColumns(table) ?? []
|
|
3295
|
+
].map((c) => [
|
|
3296
|
+
c,
|
|
3297
|
+
computeColumnType(table, c)
|
|
3298
|
+
])));
|
|
3299
|
+
return types.get(col) ?? null;
|
|
3300
|
+
}
|
|
3301
|
+
__name(columnTypeOf, "columnTypeOf");
|
|
2167
3302
|
var currentRelations = /* @__PURE__ */ new Map();
|
|
2168
3303
|
function setSchema(schemas) {
|
|
2169
|
-
|
|
2170
|
-
cachedTrgmSchema = null;
|
|
2171
|
-
const tables = {};
|
|
3304
|
+
const tables = /* @__PURE__ */ Object.create(null);
|
|
2172
3305
|
for (const entry of schemas) {
|
|
2173
3306
|
const mod = entry;
|
|
2174
3307
|
const def = (mod && "default" in mod ? mod.default : mod) ?? {};
|
|
2175
3308
|
const schemaName = def.name ?? "public";
|
|
2176
|
-
for (const [
|
|
2177
|
-
tables[qualifiedTableKey(schemaName, table.name ??
|
|
3309
|
+
for (const [key2, table] of Object.entries(def.tables ?? {})) {
|
|
3310
|
+
tables[qualifiedTableKey(schemaName, table.name ?? key2)] = table;
|
|
2178
3311
|
}
|
|
2179
3312
|
}
|
|
2180
|
-
|
|
2181
|
-
tables
|
|
2182
|
-
};
|
|
2183
|
-
currentRelations = buildRelations(schemas.map((entry) => {
|
|
3313
|
+
const relations = buildRelations(schemas.map((entry) => {
|
|
2184
3314
|
const mod = entry;
|
|
2185
3315
|
const def = (mod && "default" in mod ? mod.default : mod) ?? {};
|
|
2186
|
-
const tables2 =
|
|
2187
|
-
for (const [
|
|
3316
|
+
const tables2 = /* @__PURE__ */ Object.create(null);
|
|
3317
|
+
for (const [key2, t] of Object.entries(def.tables ?? {})) {
|
|
2188
3318
|
const table = t ?? {};
|
|
2189
3319
|
const columns = {};
|
|
2190
3320
|
for (const [cname, c] of Object.entries(table.columns ?? {})) {
|
|
@@ -2194,9 +3324,9 @@ function setSchema(schemas) {
|
|
|
2194
3324
|
_def: cd
|
|
2195
3325
|
};
|
|
2196
3326
|
}
|
|
2197
|
-
tables2[
|
|
3327
|
+
tables2[key2] = {
|
|
2198
3328
|
...table,
|
|
2199
|
-
name: table.name ??
|
|
3329
|
+
name: table.name ?? key2,
|
|
2200
3330
|
columns
|
|
2201
3331
|
};
|
|
2202
3332
|
}
|
|
@@ -2205,40 +3335,86 @@ function setSchema(schemas) {
|
|
|
2205
3335
|
tables: tables2
|
|
2206
3336
|
};
|
|
2207
3337
|
}));
|
|
3338
|
+
for (const key2 of Object.keys(tables)) tables[key2] = snapshotDeclaration(tables[key2]);
|
|
3339
|
+
currentSchema = Object.freeze({
|
|
3340
|
+
tables: Object.freeze(tables)
|
|
3341
|
+
});
|
|
3342
|
+
currentRelations = relations;
|
|
3343
|
+
cachedVectorSchema = null;
|
|
3344
|
+
cachedTrgmSchema = null;
|
|
2208
3345
|
}
|
|
2209
3346
|
__name(setSchema, "setSchema");
|
|
3347
|
+
var tableNamespaces = /* @__PURE__ */ new WeakMap();
|
|
2210
3348
|
function withTables(ops, schema = currentSchema) {
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
3349
|
+
let namespaces = tableNamespaces.get(schema);
|
|
3350
|
+
if (!namespaces) {
|
|
3351
|
+
namespaces = /* @__PURE__ */ new Map();
|
|
3352
|
+
for (const key2 of Object.keys(schema.tables ?? {})) {
|
|
3353
|
+
const dot = key2.indexOf(".");
|
|
3354
|
+
const name = dot === -1 ? "public" : key2.slice(0, dot);
|
|
3355
|
+
const short = dot === -1 ? key2 : key2.slice(dot + 1);
|
|
3356
|
+
if (!namespaces.has(name)) namespaces.set(name, /* @__PURE__ */ new Map());
|
|
3357
|
+
namespaces.get(name).set(short, key2);
|
|
3358
|
+
}
|
|
3359
|
+
if (Object.isFrozen(schema) && Object.isFrozen(schema.tables)) tableNamespaces.set(schema, namespaces);
|
|
3360
|
+
}
|
|
3361
|
+
const bind = /* @__PURE__ */ __name((key2) => ({
|
|
3362
|
+
insert: /* @__PURE__ */ __name((data) => ops.insert(key2, data), "insert"),
|
|
3363
|
+
update: /* @__PURE__ */ __name((id, data) => ops.update(key2, id, data), "update"),
|
|
3364
|
+
delete: /* @__PURE__ */ __name((id) => ops.delete(key2, id), "delete"),
|
|
3365
|
+
findById: /* @__PURE__ */ __name((id) => ops.findById(key2, id), "findById"),
|
|
3366
|
+
findUnique: /* @__PURE__ */ __name((where, opts) => ops.findUnique(key2, where, opts), "findUnique"),
|
|
3367
|
+
findMany: /* @__PURE__ */ __name((query, opts) => ops.findMany(key2, query ?? {}, opts), "findMany"),
|
|
3368
|
+
page: /* @__PURE__ */ __name((query, opts) => ops.page(key2, query ?? {}, opts), "page"),
|
|
3369
|
+
put: /* @__PURE__ */ __name((q) => ops.put(key2, q.data, {
|
|
2218
3370
|
onConflict: q.onConflict
|
|
2219
3371
|
}), "put")
|
|
2220
3372
|
}), "bind");
|
|
2221
|
-
const
|
|
2222
|
-
for (const key of Object.keys(schema.tables ?? {})) {
|
|
2223
|
-
if (!key.includes(".")) tables[key] = bind(key);
|
|
2224
|
-
}
|
|
3373
|
+
const surfaces = /* @__PURE__ */ new Map();
|
|
2225
3374
|
const schemaOf = /* @__PURE__ */ __name((name) => {
|
|
2226
|
-
const
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
3375
|
+
const cached = surfaces.get(name);
|
|
3376
|
+
if (cached) return cached;
|
|
3377
|
+
const names = namespaces.get(name) ?? /* @__PURE__ */ new Map();
|
|
3378
|
+
const target = /* @__PURE__ */ Object.create(null);
|
|
3379
|
+
const get = /* @__PURE__ */ __name((key2) => {
|
|
3380
|
+
if (!Object.hasOwn(target, key2) && names.has(key2)) {
|
|
3381
|
+
Object.defineProperty(target, key2, {
|
|
3382
|
+
value: bind(names.get(key2)),
|
|
3383
|
+
configurable: true,
|
|
3384
|
+
enumerable: true,
|
|
3385
|
+
writable: true
|
|
3386
|
+
});
|
|
2233
3387
|
}
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
3388
|
+
return Reflect.get(target, key2);
|
|
3389
|
+
}, "get");
|
|
3390
|
+
const tables = new Proxy(target, {
|
|
3391
|
+
get: /* @__PURE__ */ __name((t, key2, receiver) => typeof key2 === "string" ? get(key2) : Reflect.get(t, key2, receiver), "get"),
|
|
3392
|
+
has: /* @__PURE__ */ __name((t, key2) => typeof key2 === "string" && names.has(key2) || Reflect.has(t, key2), "has"),
|
|
3393
|
+
ownKeys: /* @__PURE__ */ __name((t) => [
|
|
3394
|
+
.../* @__PURE__ */ new Set([
|
|
3395
|
+
...names.keys(),
|
|
3396
|
+
...Reflect.ownKeys(t)
|
|
3397
|
+
])
|
|
3398
|
+
], "ownKeys"),
|
|
3399
|
+
getOwnPropertyDescriptor: /* @__PURE__ */ __name((t, key2) => Reflect.getOwnPropertyDescriptor(t, key2) ?? (typeof key2 === "string" && names.has(key2) ? {
|
|
3400
|
+
configurable: true,
|
|
3401
|
+
enumerable: true,
|
|
3402
|
+
get: /* @__PURE__ */ __name(() => get(key2), "get")
|
|
3403
|
+
} : void 0), "getOwnPropertyDescriptor"),
|
|
3404
|
+
preventExtensions: /* @__PURE__ */ __name((t) => {
|
|
3405
|
+
for (const key2 of names.keys()) get(key2);
|
|
3406
|
+
return Reflect.preventExtensions(t);
|
|
3407
|
+
}, "preventExtensions")
|
|
3408
|
+
});
|
|
3409
|
+
const surface = {
|
|
3410
|
+
tables
|
|
2237
3411
|
};
|
|
3412
|
+
if (namespaces.has(name) || name === "public") surfaces.set(name, surface);
|
|
3413
|
+
return surface;
|
|
2238
3414
|
}, "schemaOf");
|
|
2239
3415
|
const base = /* @__PURE__ */ Object.create(null);
|
|
2240
3416
|
return Object.assign(base, ops, {
|
|
2241
|
-
tables,
|
|
3417
|
+
tables: schemaOf("public").tables,
|
|
2242
3418
|
schema: schemaOf
|
|
2243
3419
|
});
|
|
2244
3420
|
}
|
|
@@ -2296,8 +3472,8 @@ function diagnosingDriver(sql, surface = "service") {
|
|
|
2296
3472
|
return markEngineRaised(new UniqueViolation(constraintOf(e)));
|
|
2297
3473
|
}
|
|
2298
3474
|
const state = sqlstateOf(e);
|
|
2299
|
-
if (state ===
|
|
2300
|
-
if (state ===
|
|
3475
|
+
if (state === SQLSTATE.serializationFailure) return markEngineRaised(new SerializationFailure(state));
|
|
3476
|
+
if (state === SQLSTATE.deadlockDetected) return markEngineRaised(new DeadlockDetected(state));
|
|
2301
3477
|
if (surface === "service" && isForeignKeyViolation(e)) {
|
|
2302
3478
|
return new Error(`Database.$asService() hit a foreign key with nothing to point at. It runs in its OWN transaction on its OWN connection, so a row this request wrote through \`Database.*\` is not visible to it until the request commits \u2014 and the request cannot commit until the handler returns. If the row it needs is one this request just created, do both writes on ONE surface. (${original(e)})`);
|
|
2303
3479
|
}
|
|
@@ -2307,11 +3483,16 @@ function diagnosingDriver(sql, surface = "service") {
|
|
|
2307
3483
|
return e;
|
|
2308
3484
|
}, "explain");
|
|
2309
3485
|
const wrapTx = /* @__PURE__ */ __name((tx) => ({
|
|
2310
|
-
|
|
3486
|
+
unsafe(text, params) {
|
|
2311
3487
|
try {
|
|
2312
|
-
|
|
3488
|
+
const query = tx.unsafe(text, params);
|
|
3489
|
+
const mapped = query.catch((e) => {
|
|
3490
|
+
throw explain(e);
|
|
3491
|
+
});
|
|
3492
|
+
if (query.cancel) mapped.cancel = () => query.cancel();
|
|
3493
|
+
return mapped;
|
|
2313
3494
|
} catch (e) {
|
|
2314
|
-
|
|
3495
|
+
return Promise.reject(explain(e));
|
|
2315
3496
|
}
|
|
2316
3497
|
},
|
|
2317
3498
|
savepoint(cb) {
|
|
@@ -2320,34 +3501,120 @@ function diagnosingDriver(sql, surface = "service") {
|
|
|
2320
3501
|
}), "wrapTx");
|
|
2321
3502
|
return {
|
|
2322
3503
|
unsafe: /* @__PURE__ */ __name((text, params) => sql.unsafe(text, params), "unsafe"),
|
|
2323
|
-
|
|
3504
|
+
cancelBackend: sql.cancelBackend?.bind(sql),
|
|
3505
|
+
begin: /* @__PURE__ */ __name((cb) => sql.begin((tx) => cb(wrapTx(tx))).catch((e) => {
|
|
3506
|
+
throw explain(e);
|
|
3507
|
+
}), "begin")
|
|
2324
3508
|
};
|
|
2325
3509
|
}
|
|
2326
3510
|
__name(diagnosingDriver, "diagnosingDriver");
|
|
2327
|
-
function createRequestDatabase(sql, identity) {
|
|
2328
|
-
const
|
|
3511
|
+
function createRequestDatabase(sql, identity, diagnostics) {
|
|
3512
|
+
const trace = diagnostics === void 0 ? void 0 : new DatabaseTrace(diagnostics);
|
|
3513
|
+
let requestClosed = false;
|
|
3514
|
+
const contextFor = /* @__PURE__ */ __name((role) => {
|
|
3515
|
+
let scope;
|
|
3516
|
+
return {
|
|
3517
|
+
trace,
|
|
3518
|
+
get cursorScope() {
|
|
3519
|
+
return scope ??= cursorIdentity(role, identity.claimsJson);
|
|
3520
|
+
},
|
|
3521
|
+
get commands() {
|
|
3522
|
+
return hasCommandExecutor(sql);
|
|
3523
|
+
}
|
|
3524
|
+
};
|
|
3525
|
+
}, "contextFor");
|
|
3526
|
+
const userContext = contextFor(identity.role);
|
|
3527
|
+
const serviceContext = contextFor(identity.serviceRole);
|
|
3528
|
+
const atomic = /* @__PURE__ */ __name((role, surface) => async (fn, options = {}) => {
|
|
3529
|
+
validateAtomicOptions(options);
|
|
3530
|
+
if (requestClosed) throw new Error("Database request has settled; it cannot open another transaction");
|
|
3531
|
+
if (atomicScope.getStore() !== void 0) throw new Error("Nested Database.$atomic is not supported; use tx.$attempt for a savepoint");
|
|
3532
|
+
const retries = retryBudget(options.retry);
|
|
3533
|
+
const control = options.timeoutMs !== void 0 || options.signal !== void 0 ? new TransactionControl(options.timeoutMs, options.signal) : void 0;
|
|
3534
|
+
let lease;
|
|
3535
|
+
try {
|
|
3536
|
+
control?.check();
|
|
3537
|
+
if (options.workload) {
|
|
3538
|
+
const queuedAt = trace ? performance.now() : 0;
|
|
3539
|
+
try {
|
|
3540
|
+
lease = await workloadAdmission(sql, options.workload).acquire(workloadTenant(options.workload, identity.claimsJson), options.conflictKeys ?? [], control?.signal);
|
|
3541
|
+
} finally {
|
|
3542
|
+
trace?.time("queueMs", queuedAt);
|
|
3543
|
+
}
|
|
3544
|
+
}
|
|
3545
|
+
for (let attempt = 0; ; attempt++) {
|
|
3546
|
+
control?.check();
|
|
3547
|
+
const timeouts = [
|
|
3548
|
+
options.statementTimeoutMs,
|
|
3549
|
+
control?.remainingMs()
|
|
3550
|
+
].filter((n) => n !== void 0);
|
|
3551
|
+
const scope = /* @__PURE__ */ Symbol("atomic transaction");
|
|
3552
|
+
const root = createLazyTransaction(diagnosingDriver(sql, surface), role, identity.claimsJson, {
|
|
3553
|
+
scope,
|
|
3554
|
+
isolation: options.isolation,
|
|
3555
|
+
readOnly: options.readOnly,
|
|
3556
|
+
control,
|
|
3557
|
+
trace,
|
|
3558
|
+
lockTimeout: `${options.lockTimeoutMs ?? 5e3}ms`,
|
|
3559
|
+
statementTimeout: timeouts.length === 0 ? void 0 : `${Math.min(...timeouts)}ms`
|
|
3560
|
+
});
|
|
3561
|
+
try {
|
|
3562
|
+
const callback = atomicScope.run(scope, () => fn(withTables(createOps(root, surface === "user" ? userContext : serviceContext))));
|
|
3563
|
+
const value = await (control ? control.race(callback) : callback);
|
|
3564
|
+
control?.check();
|
|
3565
|
+
await root.commit();
|
|
3566
|
+
return value;
|
|
3567
|
+
} catch (error) {
|
|
3568
|
+
await root.rollback(error);
|
|
3569
|
+
if (attempt >= retries || !isRetryable(error)) throw error;
|
|
3570
|
+
trace?.retry();
|
|
3571
|
+
const delay = new Promise((resolve) => setTimeout(resolve, Math.min(100, 2 ** attempt)));
|
|
3572
|
+
await (control ? control.race(delay) : delay);
|
|
3573
|
+
}
|
|
3574
|
+
}
|
|
3575
|
+
} finally {
|
|
3576
|
+
control?.dispose();
|
|
3577
|
+
lease?.release();
|
|
3578
|
+
}
|
|
3579
|
+
}, "atomic");
|
|
3580
|
+
const tx = createLazyTransaction(diagnosingDriver(sql, "user"), identity.role, identity.claimsJson, {
|
|
3581
|
+
trace
|
|
3582
|
+
});
|
|
2329
3583
|
let serviceTx = null;
|
|
2330
3584
|
let serviceClient = null;
|
|
2331
3585
|
const asService = /* @__PURE__ */ __name(() => {
|
|
2332
3586
|
if (serviceClient === null) {
|
|
2333
3587
|
serviceTx = createLazyTransaction(diagnosingDriver(sql, "service"), identity.serviceRole, identity.claimsJson, {
|
|
2334
|
-
lockTimeout: SERVICE_LOCK_TIMEOUT
|
|
3588
|
+
lockTimeout: SERVICE_LOCK_TIMEOUT,
|
|
3589
|
+
trace
|
|
3590
|
+
});
|
|
3591
|
+
serviceClient = Object.assign(withTables(createOps(serviceTx, serviceContext)), {
|
|
3592
|
+
atomic: atomic(identity.serviceRole, "service")
|
|
2335
3593
|
});
|
|
2336
|
-
serviceClient = withTables(createOps(serviceTx));
|
|
2337
3594
|
}
|
|
2338
3595
|
return serviceClient;
|
|
2339
3596
|
}, "asService");
|
|
2340
3597
|
return {
|
|
2341
|
-
client: Object.assign(withTables(createOps(tx)), {
|
|
2342
|
-
asService
|
|
3598
|
+
client: Object.assign(withTables(createOps(tx, userContext)), {
|
|
3599
|
+
asService,
|
|
3600
|
+
atomic: atomic(identity.role, "user")
|
|
2343
3601
|
}),
|
|
2344
3602
|
async commit() {
|
|
2345
|
-
|
|
2346
|
-
|
|
3603
|
+
requestClosed = true;
|
|
3604
|
+
try {
|
|
3605
|
+
await tx.commit();
|
|
3606
|
+
await serviceTx?.commit();
|
|
3607
|
+
trace?.complete("committed");
|
|
3608
|
+
} catch (error) {
|
|
3609
|
+
trace?.failed();
|
|
3610
|
+
throw error;
|
|
3611
|
+
}
|
|
2347
3612
|
},
|
|
2348
3613
|
async rollback(reason) {
|
|
3614
|
+
requestClosed = true;
|
|
2349
3615
|
await tx.rollback(reason);
|
|
2350
3616
|
await serviceTx?.rollback(reason);
|
|
3617
|
+
trace?.complete("failed");
|
|
2351
3618
|
}
|
|
2352
3619
|
};
|
|
2353
3620
|
}
|
|
@@ -2357,8 +3624,13 @@ var Args = class Args2 {
|
|
|
2357
3624
|
__name(this, "Args");
|
|
2358
3625
|
}
|
|
2359
3626
|
values = [];
|
|
2360
|
-
|
|
3627
|
+
columns = [];
|
|
3628
|
+
bind(value, column, table) {
|
|
2361
3629
|
this.values.push(value);
|
|
3630
|
+
this.columns.push({
|
|
3631
|
+
column,
|
|
3632
|
+
table
|
|
3633
|
+
});
|
|
2362
3634
|
return `$${this.values.length}`;
|
|
2363
3635
|
}
|
|
2364
3636
|
};
|
|
@@ -2444,7 +3716,7 @@ function planWhere(table, where, args, results) {
|
|
|
2444
3716
|
}
|
|
2445
3717
|
const resolved = resolveRefsDeep(where, results);
|
|
2446
3718
|
const known = schemaColumns(table);
|
|
2447
|
-
const bare = compileWhereBare(table, known, resolved, (v) => args.bind(v), "transaction plan");
|
|
3719
|
+
const bare = compileWhereBare(table, known, resolved, (v, column, from) => args.bind(v, column, from), "transaction plan");
|
|
2448
3720
|
return {
|
|
2449
3721
|
sql: bare === "" ? "" : ` AND ${bare}`,
|
|
2450
3722
|
resolved
|
|
@@ -2453,16 +3725,16 @@ function planWhere(table, where, args, results) {
|
|
|
2453
3725
|
__name(planWhere, "planWhere");
|
|
2454
3726
|
function bindMaybeVector(args, column, vectorCols, value, transforms) {
|
|
2455
3727
|
if (column !== void 0 && vectorCols?.has(column) && Array.isArray(value)) {
|
|
2456
|
-
return args.bind(toVectorLiteral(value));
|
|
3728
|
+
return args.bind(toVectorLiteral(value), column);
|
|
2457
3729
|
}
|
|
2458
3730
|
const t = column !== void 0 ? transforms?.get(column) : void 0;
|
|
2459
3731
|
if (t?.toDb !== void 0 && value !== null && value !== void 0) {
|
|
2460
|
-
return args.bind(t.toDb(value));
|
|
3732
|
+
return args.bind(t.toDb(value), column);
|
|
2461
3733
|
}
|
|
2462
|
-
return args.bind(value);
|
|
3734
|
+
return args.bind(value, column);
|
|
2463
3735
|
}
|
|
2464
3736
|
__name(bindMaybeVector, "bindMaybeVector");
|
|
2465
|
-
|
|
3737
|
+
function compilePlanOp(op, results) {
|
|
2466
3738
|
const opVectorCols = vectorColumnsOf(currentSchema, op.table);
|
|
2467
3739
|
const opTransforms = transformsOf(currentSchema, op.table);
|
|
2468
3740
|
const args = new Args();
|
|
@@ -2472,7 +3744,7 @@ async function runPlanOp(sp, op, results) {
|
|
|
2472
3744
|
case "insert": {
|
|
2473
3745
|
const cols = Object.keys(op.values ?? {});
|
|
2474
3746
|
const rendered = cols.map((c) => renderValue(op.values[c], c, args, results, opVectorCols, opTransforms));
|
|
2475
|
-
sql = cols.length ? `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES (${rendered.join(", ")}) RETURNING
|
|
3747
|
+
sql = cols.length ? `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES (${rendered.join(", ")}) RETURNING ${rowProjection(op.table)}` : `INSERT INTO ${table} DEFAULT VALUES RETURNING ${rowProjection(op.table)}`;
|
|
2476
3748
|
break;
|
|
2477
3749
|
}
|
|
2478
3750
|
case "upsert": {
|
|
@@ -2485,17 +3757,17 @@ async function runPlanOp(sp, op, results) {
|
|
|
2485
3757
|
});
|
|
2486
3758
|
}
|
|
2487
3759
|
const rendered = cols.map((c) => renderValue(op.values[c], c, args, results, opVectorCols, opTransforms));
|
|
2488
|
-
sql = `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES (${rendered.join(", ")}) ${onConflictTail(cols, conflict, "update")} RETURNING
|
|
3760
|
+
sql = `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES (${rendered.join(", ")}) ${onConflictTail(cols, conflict, "update")} RETURNING ${rowProjection(op.table)}`;
|
|
2489
3761
|
break;
|
|
2490
3762
|
}
|
|
2491
3763
|
case "insertMany": {
|
|
2492
3764
|
const rows = op.rows ?? [];
|
|
2493
|
-
if (rows.length === 0 || !rows[0]) return
|
|
3765
|
+
if (rows.length === 0 || !rows[0]) return null;
|
|
2494
3766
|
const cols = Object.keys(rows[0]);
|
|
2495
3767
|
const tuples = rows.map((r) => `(${cols.map((c) => renderValue(r[c], c, args, results, opVectorCols, opTransforms)).join(", ")})`);
|
|
2496
3768
|
const conflictCols = op.onConflict ?? [];
|
|
2497
3769
|
const tail = op.action !== void 0 && conflictCols.length > 0 ? ` ${onConflictTail(cols, conflictCols, op.action)}` : "";
|
|
2498
|
-
sql = `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES ${tuples.join(", ")}${tail} RETURNING
|
|
3770
|
+
sql = `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES ${tuples.join(", ")}${tail} RETURNING ${rowProjection(op.table)}`;
|
|
2499
3771
|
break;
|
|
2500
3772
|
}
|
|
2501
3773
|
case "update": {
|
|
@@ -2511,12 +3783,12 @@ async function runPlanOp(sp, op, results) {
|
|
|
2511
3783
|
const assignments = cols.map((c) => `${quoteIdent(c)} = ${renderValue(op.set[c], c, args, results, opVectorCols, opTransforms)}`);
|
|
2512
3784
|
const planned = planWhere(op.table, op.where, args, results);
|
|
2513
3785
|
const planPk = primaryKeyOf(op.table);
|
|
2514
|
-
sql = isSinglePkEquality(planned.resolved, planPk) || planPk === null ? `UPDATE ${table} AS t SET ${assignments.join(", ")} WHERE true${planned.sql} RETURNING t
|
|
3786
|
+
sql = isSinglePkEquality(planned.resolved, planPk) || planPk === null ? `UPDATE ${table} AS t SET ${assignments.join(", ")} WHERE true${planned.sql} RETURNING ${rowProjection(op.table, "t")}` : `WITH locked AS (SELECT t.${quoteIdent(planPk)} FROM ${table} t WHERE true${planned.sql} ORDER BY t.${quoteIdent(planPk)} FOR NO KEY UPDATE) UPDATE ${table} AS t SET ${assignments.join(", ")} FROM locked WHERE t.${quoteIdent(planPk)} = locked.${quoteIdent(planPk)} RETURNING ${rowProjection(op.table, "t")}`;
|
|
2515
3787
|
break;
|
|
2516
3788
|
}
|
|
2517
3789
|
case "delete": {
|
|
2518
3790
|
assertNotAppendOnly("deleteWhere", op.table);
|
|
2519
|
-
sql = `DELETE FROM ${table} AS t WHERE true${renderWhere(op.table, op.where, args, results)} RETURNING t
|
|
3791
|
+
sql = `DELETE FROM ${table} AS t WHERE true${renderWhere(op.table, op.where, args, results)} RETURNING ${rowProjection(op.table, "t")}`;
|
|
2520
3792
|
break;
|
|
2521
3793
|
}
|
|
2522
3794
|
case "select": {
|
|
@@ -2529,15 +3801,157 @@ async function runPlanOp(sp, op, results) {
|
|
|
2529
3801
|
limit = ` LIMIT ${n}`;
|
|
2530
3802
|
}
|
|
2531
3803
|
const lock = op.lock === "update" ? " FOR UPDATE" : "";
|
|
2532
|
-
sql = `SELECT t
|
|
3804
|
+
sql = `SELECT ${rowProjection(op.table, "t")} FROM ${table} t WHERE true${renderWhere(op.table, op.where, args, results)}${limit}${lock}`;
|
|
2533
3805
|
break;
|
|
2534
3806
|
}
|
|
2535
3807
|
default:
|
|
2536
3808
|
throw new Error(`unknown operation "${String(op.op)}" in a transaction plan`);
|
|
2537
3809
|
}
|
|
2538
|
-
return
|
|
3810
|
+
return {
|
|
3811
|
+
sql,
|
|
3812
|
+
params: args.values,
|
|
3813
|
+
columns: args.columns
|
|
3814
|
+
};
|
|
3815
|
+
}
|
|
3816
|
+
__name(compilePlanOp, "compilePlanOp");
|
|
3817
|
+
async function runPlanOp(sp, op, results) {
|
|
3818
|
+
const statement = compilePlanOp(op, results);
|
|
3819
|
+
return statement ? await sp.unsafe(statement.sql, statement.params) : [];
|
|
2539
3820
|
}
|
|
2540
3821
|
__name(runPlanOp, "runPlanOp");
|
|
3822
|
+
function compileCommand(plan) {
|
|
3823
|
+
const symbols = /* @__PURE__ */ new Map();
|
|
3824
|
+
const results = [];
|
|
3825
|
+
const steps = [];
|
|
3826
|
+
const pipeline = [];
|
|
3827
|
+
let hasRefs = false;
|
|
3828
|
+
const prefix = `command:${crypto.randomUUID()}:`;
|
|
3829
|
+
const containsFragment = /* @__PURE__ */ __name((v) => isSqlFragment(v) || v !== null && typeof v === "object" && Object.values(v).some(containsFragment), "containsFragment");
|
|
3830
|
+
const hasNullableFilterRef = /* @__PURE__ */ __name((v) => {
|
|
3831
|
+
if (isRef(v)) {
|
|
3832
|
+
const source = plan.ops[v.$ref.op];
|
|
3833
|
+
return source !== void 0 && pageSchema(source.table).columns[v.$ref.field]?.nullable !== false;
|
|
3834
|
+
}
|
|
3835
|
+
return v !== null && typeof v === "object" && Object.values(v).some(hasNullableFilterRef);
|
|
3836
|
+
}, "hasNullableFilterRef");
|
|
3837
|
+
for (const op of plan.ops) {
|
|
3838
|
+
if (!schemaColumns(op.table)?.size) return {
|
|
3839
|
+
reason: `table ${op.table} has no installed declaration`
|
|
3840
|
+
};
|
|
3841
|
+
if (transformsOf(currentSchema, op.table).size || vectorColumnsOf(currentSchema, op.table).size) return {
|
|
3842
|
+
reason: `${op.table} uses JavaScript codecs/transforms or vector values`
|
|
3843
|
+
};
|
|
3844
|
+
if (containsFragment(op)) return {
|
|
3845
|
+
reason: "SQL fragments require the sequential path"
|
|
3846
|
+
};
|
|
3847
|
+
if (hasNullableFilterRef(op.where)) return {
|
|
3848
|
+
reason: "nullable references in filters require the sequential path"
|
|
3849
|
+
};
|
|
3850
|
+
}
|
|
3851
|
+
for (const [index, op] of plan.ops.entries()) {
|
|
3852
|
+
const statement = compilePlanOp(op, results);
|
|
3853
|
+
if (statement === null) {
|
|
3854
|
+
steps.push({
|
|
3855
|
+
empty: true,
|
|
3856
|
+
guard: op.guard
|
|
3857
|
+
});
|
|
3858
|
+
pipeline.push({
|
|
3859
|
+
sql: "SELECT palbase_runtime.command_rows_v1('[]'::jsonb,$1::jsonb) AS rows",
|
|
3860
|
+
params: [
|
|
3861
|
+
op.guard ?? null
|
|
3862
|
+
]
|
|
3863
|
+
});
|
|
3864
|
+
} else {
|
|
3865
|
+
if (statement.params.some((v) => v === void 0 || typeof v === "symbol" || typeof v === "function" || typeof v === "number" && (!Number.isInteger(v) || v < -2147483648 || v > 2147483647))) {
|
|
3866
|
+
return {
|
|
3867
|
+
reason: "non-integer/wide numeric or non-JSON parameters require the sequential driver path; use exact decimal strings"
|
|
3868
|
+
};
|
|
3869
|
+
}
|
|
3870
|
+
for (const [i, value] of statement.params.entries()) {
|
|
3871
|
+
const target = statement.columns[i];
|
|
3872
|
+
if (target.column === void 0 || columnTypeOf(target.table ?? op.table, target.column) !== "jsonb") continue;
|
|
3873
|
+
const source = typeof value === "string" ? symbols.get(value) : void 0;
|
|
3874
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint" || source && "ref" in source && ![
|
|
3875
|
+
"text",
|
|
3876
|
+
"uuid",
|
|
3877
|
+
"timestamp",
|
|
3878
|
+
"enum"
|
|
3879
|
+
].includes(columnTypeOf(plan.ops[source.ref.op].table, source.ref.field) ?? "")) {
|
|
3880
|
+
return {
|
|
3881
|
+
reason: "JSONB scalar/ref binding requires the sequential driver path"
|
|
3882
|
+
};
|
|
3883
|
+
}
|
|
3884
|
+
}
|
|
3885
|
+
const wide = [
|
|
3886
|
+
...schemaColumns(op.table)
|
|
3887
|
+
].filter((c) => [
|
|
3888
|
+
"bigint",
|
|
3889
|
+
"numeric"
|
|
3890
|
+
].includes(columnTypeOf(op.table, c) ?? ""));
|
|
3891
|
+
const exact = wide.length ? ` || jsonb_build_object(${wide.map((c) => `${quoteLiteral(c)}, r.${quoteIdent(c)}::text`).join(", ")})` : "";
|
|
3892
|
+
const sql = `WITH command_rows AS (${statement.sql}) SELECT coalesce(jsonb_agg(to_jsonb(r)${exact}), '[]'::jsonb) FROM command_rows r`;
|
|
3893
|
+
pipeline.push({
|
|
3894
|
+
sql: `WITH command_rows AS (${statement.sql}) SELECT palbase_runtime.command_rows_v1(coalesce(jsonb_agg(to_jsonb(r)${exact}), '[]'::jsonb),$${statement.params.length + 1}::jsonb) AS rows FROM command_rows r`,
|
|
3895
|
+
params: [
|
|
3896
|
+
...statement.params,
|
|
3897
|
+
op.guard ?? null
|
|
3898
|
+
]
|
|
3899
|
+
});
|
|
3900
|
+
const bindings = statement.params.map((value, i) => {
|
|
3901
|
+
const target = statement.columns[i];
|
|
3902
|
+
const json = target.column !== void 0 && columnTypeOf(target.table ?? op.table, target.column) === "jsonb";
|
|
3903
|
+
if (typeof value === "string" && symbols.has(value)) {
|
|
3904
|
+
hasRefs = true;
|
|
3905
|
+
return {
|
|
3906
|
+
...symbols.get(value),
|
|
3907
|
+
json
|
|
3908
|
+
};
|
|
3909
|
+
}
|
|
3910
|
+
if (typeof value === "bigint") return {
|
|
3911
|
+
value: value.toString(),
|
|
3912
|
+
json
|
|
3913
|
+
};
|
|
3914
|
+
if (value instanceof Date) return {
|
|
3915
|
+
value: value.toISOString(),
|
|
3916
|
+
json
|
|
3917
|
+
};
|
|
3918
|
+
return {
|
|
3919
|
+
value,
|
|
3920
|
+
json
|
|
3921
|
+
};
|
|
3922
|
+
});
|
|
3923
|
+
steps.push({
|
|
3924
|
+
chunks: parameterChunks(sql),
|
|
3925
|
+
bindings,
|
|
3926
|
+
guard: op.guard
|
|
3927
|
+
});
|
|
3928
|
+
}
|
|
3929
|
+
const row = {};
|
|
3930
|
+
for (const field of schemaColumns(op.table)) {
|
|
3931
|
+
const token = `${prefix}${index}:${field}`;
|
|
3932
|
+
symbols.set(token, {
|
|
3933
|
+
ref: {
|
|
3934
|
+
op: index,
|
|
3935
|
+
field
|
|
3936
|
+
}
|
|
3937
|
+
});
|
|
3938
|
+
row[field] = token;
|
|
3939
|
+
}
|
|
3940
|
+
results.push({
|
|
3941
|
+
rows: [
|
|
3942
|
+
row
|
|
3943
|
+
],
|
|
3944
|
+
rows_affected: 1
|
|
3945
|
+
});
|
|
3946
|
+
}
|
|
3947
|
+
return {
|
|
3948
|
+
steps,
|
|
3949
|
+
...hasRefs ? {} : {
|
|
3950
|
+
pipeline
|
|
3951
|
+
}
|
|
3952
|
+
};
|
|
3953
|
+
}
|
|
3954
|
+
__name(compileCommand, "compileCommand");
|
|
2541
3955
|
function assertGuard(op, result) {
|
|
2542
3956
|
const guard = op.guard;
|
|
2543
3957
|
if (!guard) return;
|
|
@@ -2551,6 +3965,33 @@ function assertGuard(op, result) {
|
|
|
2551
3965
|
}
|
|
2552
3966
|
__name(assertGuard, "assertGuard");
|
|
2553
3967
|
|
|
3968
|
+
// src/engine/cancellation.ts
|
|
3969
|
+
function withQueryCancellation(application, control) {
|
|
3970
|
+
if (application === control) throw new Error("Query cancellation requires a separate control pool");
|
|
3971
|
+
const close = /* @__PURE__ */ __name(async (driver) => {
|
|
3972
|
+
const d = driver;
|
|
3973
|
+
if (d.close) await d.close();
|
|
3974
|
+
else if (d.end) await d.end();
|
|
3975
|
+
}, "close");
|
|
3976
|
+
return {
|
|
3977
|
+
unsafe: /* @__PURE__ */ __name((sql, params) => application.unsafe(sql, params), "unsafe"),
|
|
3978
|
+
begin: /* @__PURE__ */ __name((fn) => application.begin(fn), "begin"),
|
|
3979
|
+
async cancelBackend(pid, startedAt) {
|
|
3980
|
+
await control.unsafe(`SELECT pg_cancel_backend(pid) FROM pg_stat_activity
|
|
3981
|
+
WHERE pid=$1::integer AND backend_start=$2::timestamptz
|
|
3982
|
+
AND usename=session_user AND datname=current_database() AND backend_type='client backend'`, [
|
|
3983
|
+
pid,
|
|
3984
|
+
startedAt
|
|
3985
|
+
]);
|
|
3986
|
+
},
|
|
3987
|
+
async close() {
|
|
3988
|
+
await close(application);
|
|
3989
|
+
await close(control);
|
|
3990
|
+
}
|
|
3991
|
+
};
|
|
3992
|
+
}
|
|
3993
|
+
__name(withQueryCancellation, "withQueryCancellation");
|
|
3994
|
+
|
|
2554
3995
|
// src/engine/router.ts
|
|
2555
3996
|
var ROOM_META = /* @__PURE__ */ Symbol.for("palbase.backend.room");
|
|
2556
3997
|
var CONTROLLER_META = /* @__PURE__ */ Symbol.for("palbase.backend.controllerMeta");
|
|
@@ -3055,10 +4496,13 @@ async function defaultSqlDriver(config) {
|
|
|
3055
4496
|
if (!g.Bun?.SQL) {
|
|
3056
4497
|
throw new BootRefused([], "boot refused: no SQL driver. Running outside Bun means the driver must be supplied \u2014 pass `sql` to createApp().");
|
|
3057
4498
|
}
|
|
3058
|
-
return new g.Bun.SQL({
|
|
4499
|
+
return withQueryCancellation(new g.Bun.SQL({
|
|
3059
4500
|
url: config.databaseUrl,
|
|
3060
4501
|
max: config.poolMax
|
|
3061
|
-
})
|
|
4502
|
+
}), new g.Bun.SQL({
|
|
4503
|
+
url: config.databaseUrl,
|
|
4504
|
+
max: 2
|
|
4505
|
+
}));
|
|
3062
4506
|
}
|
|
3063
4507
|
__name(defaultSqlDriver, "defaultSqlDriver");
|
|
3064
4508
|
var AUTHOR_ANSWERED = /* @__PURE__ */ new Set([
|
|
@@ -3117,6 +4561,11 @@ async function createApp(opts) {
|
|
|
3117
4561
|
const sql = opts.sql ?? await defaultSqlDriver(config);
|
|
3118
4562
|
await sql.unsafe("select 1");
|
|
3119
4563
|
setSchema(opts.schemas ?? []);
|
|
4564
|
+
setCursorKey(config.cursorSecret || config.serviceRoleKey || null);
|
|
4565
|
+
if (config.enableCommands) await installCommandExecutor(sql, [
|
|
4566
|
+
config.dbRole,
|
|
4567
|
+
config.dbServiceRole
|
|
4568
|
+
]);
|
|
3120
4569
|
setSecretReader(opts.secretReader ?? null);
|
|
3121
4570
|
const auth = new AuthVerifier({
|
|
3122
4571
|
jwksUrl: config.authJwksUrl,
|
|
@@ -3162,6 +4611,9 @@ async function createApp(opts) {
|
|
|
3162
4611
|
role: config.dbRole,
|
|
3163
4612
|
serviceRole: config.dbServiceRole,
|
|
3164
4613
|
claimsJson: "{}"
|
|
4614
|
+
}, config.databaseDiagnostics === void 0 ? void 0 : {
|
|
4615
|
+
...config.databaseDiagnostics,
|
|
4616
|
+
endpoint: "scheduled job"
|
|
3165
4617
|
});
|
|
3166
4618
|
try {
|
|
3167
4619
|
const out = await runWithRuntime(buildServices(db), fn);
|
|
@@ -3191,6 +4643,10 @@ async function createApp(opts) {
|
|
|
3191
4643
|
role: config.dbRole,
|
|
3192
4644
|
serviceRole: config.dbServiceRole,
|
|
3193
4645
|
claimsJson: JSON.stringify(callerClaims ?? {})
|
|
4646
|
+
}, config.databaseDiagnostics === void 0 ? void 0 : {
|
|
4647
|
+
...config.databaseDiagnostics,
|
|
4648
|
+
endpoint: "upload authorization",
|
|
4649
|
+
requestId
|
|
3194
4650
|
});
|
|
3195
4651
|
try {
|
|
3196
4652
|
const refusal = await refuseUnlessAuthorized(uploadDb.client, spec, requestId, makeRoleResolver(uploadDb.client));
|
|
@@ -3238,6 +4694,11 @@ async function createApp(opts) {
|
|
|
3238
4694
|
role: config.dbRole,
|
|
3239
4695
|
serviceRole: config.dbServiceRole,
|
|
3240
4696
|
claimsJson: JSON.stringify(claims ?? {})
|
|
4697
|
+
}, config.databaseDiagnostics === void 0 && meta.options.databaseBudget === void 0 ? void 0 : {
|
|
4698
|
+
...config.databaseDiagnostics,
|
|
4699
|
+
...meta.options.databaseBudget,
|
|
4700
|
+
endpoint: hit.entry.id,
|
|
4701
|
+
requestId
|
|
3241
4702
|
});
|
|
3242
4703
|
const rolesOfCaller = makeRoleResolver(db.client);
|
|
3243
4704
|
let dispatched = false;
|
|
@@ -3667,11 +5128,15 @@ export {
|
|
|
3667
5128
|
effectiveAuth,
|
|
3668
5129
|
RateLimiter,
|
|
3669
5130
|
makeMemoryCache,
|
|
5131
|
+
setCursorKey,
|
|
5132
|
+
COMMAND_EXECUTOR_DDL,
|
|
5133
|
+
installCommandExecutor,
|
|
3670
5134
|
quoteIdent,
|
|
3671
5135
|
createLazyTransaction,
|
|
3672
5136
|
createOps,
|
|
3673
5137
|
withTables,
|
|
3674
5138
|
createRequestDatabase,
|
|
5139
|
+
withQueryCancellation,
|
|
3675
5140
|
buildRouteTable,
|
|
3676
5141
|
matchRoute,
|
|
3677
5142
|
scrubSecrets,
|
|
@@ -3679,4 +5144,4 @@ export {
|
|
|
3679
5144
|
installEgressFence,
|
|
3680
5145
|
createApp
|
|
3681
5146
|
};
|
|
3682
|
-
//# sourceMappingURL=chunk-
|
|
5147
|
+
//# sourceMappingURL=chunk-MNGZE2MM.js.map
|