@palbase/backend 33.0.2 → 34.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/palbase-backend.cjs +2273 -358
- 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-YIQ4RS4F.js → chunk-5UGPXKMJ.js} +331 -5
- package/dist/chunk-5UGPXKMJ.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-SI4KGEM3.js → chunk-P7MGSAFT.js} +102 -96
- package/dist/chunk-P7MGSAFT.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-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-X2UYIXDS.js} +59 -4
- package/dist/chunk-X2UYIXDS.js.map +1 -0
- package/dist/{chunk-C6COAB3E.js → chunk-XZPDPYVY.js} +1905 -195
- package/dist/chunk-XZPDPYVY.js.map +1 -0
- package/dist/{chunk-TVCCR6SO.js → chunk-YO5GYM73.js} +58 -9
- package/dist/chunk-YO5GYM73.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 +285 -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-BXPdsTA5.d.ts} +370 -58
- package/dist/{index-DB_nW-AV.d.cts → endpoint-DqhUP7RT.d.cts} +370 -58
- package/dist/engine/index.cjs +2279 -356
- 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-DLveQoOf.d.cts → index-Co0Hj_aO.d.cts} +108 -50
- package/dist/{index-D-3duy8Y.d.ts → index-MhCL7ll4.d.ts} +108 -50
- package/dist/index.cjs +806 -386
- 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 +27 -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-BkIVHPzB.d.cts} +3 -1
- package/dist/{registry-JjF5lcj4.d.ts → registry-CvGLR5j1.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/auth.md +58 -0
- 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 +382 -1
- package/docs/schema.md +15 -0
- package/package.json +1 -2
- package/template/db/public.ts +12 -0
- 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-X2UYIXDS.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-YO5GYM73.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-P7MGSAFT.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) {
|
|
@@ -547,13 +1370,27 @@ function applyFromDb(row, transforms) {
|
|
|
547
1370
|
__name(applyFromDb, "applyFromDb");
|
|
548
1371
|
function asTableRow(table, row) {
|
|
549
1372
|
const revived = reviveVectors(asWireRow(row), vectorColumnsOf(currentSchema, table));
|
|
550
|
-
return applyFromDb(revived, transformsOf(currentSchema, table));
|
|
1373
|
+
return foldCan(table, applyFromDb(revived, transformsOf(currentSchema, table)), false);
|
|
551
1374
|
}
|
|
552
1375
|
__name(asTableRow, "asTableRow");
|
|
553
|
-
function asTableRows(table, rows) {
|
|
1376
|
+
function asTableRows(table, rows, withSpec, partial = false) {
|
|
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
|
+
for (const row of result) foldCan(table, row, partial);
|
|
1393
|
+
return result;
|
|
557
1394
|
}
|
|
558
1395
|
__name(asTableRows, "asTableRows");
|
|
559
1396
|
function asBindParams(table, cols, data, caller) {
|
|
@@ -796,7 +1633,7 @@ function excludeSql(pk, ids, add) {
|
|
|
796
1633
|
return ` AND t.${quoteIdent(pk)}::text <> ALL(ARRAY[${ids.map((x) => add(x)).join(", ")}])`;
|
|
797
1634
|
}
|
|
798
1635
|
__name(excludeSql, "excludeSql");
|
|
799
|
-
function
|
|
1636
|
+
function computePrimaryKey(table) {
|
|
800
1637
|
const t = tableDefOf(table);
|
|
801
1638
|
const columns = t?.columns ?? {};
|
|
802
1639
|
const cols = Object.keys(columns);
|
|
@@ -816,7 +1653,7 @@ function primaryKeyOf(table) {
|
|
|
816
1653
|
const pks = cols.filter((c) => pkOf(columns[c]));
|
|
817
1654
|
return pks.length === 1 ? pks[0] : null;
|
|
818
1655
|
}
|
|
819
|
-
__name(
|
|
1656
|
+
__name(computePrimaryKey, "computePrimaryKey");
|
|
820
1657
|
function isSinglePkEquality(where, pk) {
|
|
821
1658
|
if (pk === null) return false;
|
|
822
1659
|
const keys = Object.keys(where);
|
|
@@ -831,12 +1668,270 @@ function assertNotAppendOnly(caller, table) {
|
|
|
831
1668
|
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
1669
|
}
|
|
833
1670
|
__name(assertNotAppendOnly, "assertNotAppendOnly");
|
|
834
|
-
function
|
|
1671
|
+
function computeSchemaColumns(table) {
|
|
835
1672
|
const t = tableDefOf(table);
|
|
836
1673
|
const cols = t?.columns ? Object.keys(t.columns) : [];
|
|
837
1674
|
return cols.length > 0 ? new Set(cols) : null;
|
|
838
1675
|
}
|
|
839
|
-
__name(
|
|
1676
|
+
__name(computeSchemaColumns, "computeSchemaColumns");
|
|
1677
|
+
function timestampResult(expr) {
|
|
1678
|
+
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`;
|
|
1679
|
+
}
|
|
1680
|
+
__name(timestampResult, "timestampResult");
|
|
1681
|
+
function columnResult(table, column, alias) {
|
|
1682
|
+
const expr = `${alias ? `${quoteIdent(alias)}.` : ""}${quoteIdent(column)}`;
|
|
1683
|
+
return columnTypeOf(table, column) === "timestamp" ? timestampResult(expr) : expr;
|
|
1684
|
+
}
|
|
1685
|
+
__name(columnResult, "columnResult");
|
|
1686
|
+
function projectedColumn(table, column, alias) {
|
|
1687
|
+
const expr = columnResult(table, column, alias);
|
|
1688
|
+
return columnTypeOf(table, column) === "timestamp" ? `${expr} AS ${quoteIdent(column)}` : expr;
|
|
1689
|
+
}
|
|
1690
|
+
__name(projectedColumn, "projectedColumn");
|
|
1691
|
+
var CAN_PREFIX = "can:";
|
|
1692
|
+
var canMemo = /* @__PURE__ */ new WeakMap();
|
|
1693
|
+
function canRoleGate(roles) {
|
|
1694
|
+
if (roles.length === 0) return null;
|
|
1695
|
+
const names = roles.flatMap((r) => [
|
|
1696
|
+
r,
|
|
1697
|
+
`backend_${r}`
|
|
1698
|
+
]).map((r) => `'${r.replace(/'/g, "''")}'`);
|
|
1699
|
+
return `(current_user = ANY (ARRAY[${names.join(", ")}]))`;
|
|
1700
|
+
}
|
|
1701
|
+
__name(canRoleGate, "canRoleGate");
|
|
1702
|
+
function canEntries(def, alias) {
|
|
1703
|
+
const policies = def.policies ?? [];
|
|
1704
|
+
const dropped = [];
|
|
1705
|
+
const restrictive = [];
|
|
1706
|
+
for (const p of policies) {
|
|
1707
|
+
if (p.permissive !== false || p.command !== "update" && p.command !== "all") continue;
|
|
1708
|
+
const r = renderCanUsing(p, alias, def.name);
|
|
1709
|
+
if (r === null) {
|
|
1710
|
+
return {
|
|
1711
|
+
entries: [],
|
|
1712
|
+
dropped: policies.filter((q) => q.command === "update" && q.permissive !== false).map((q) => ({
|
|
1713
|
+
name: q.name,
|
|
1714
|
+
reason: `restrictive policy "${p.name}" cannot be evaluated as a column`
|
|
1715
|
+
}))
|
|
1716
|
+
};
|
|
1717
|
+
}
|
|
1718
|
+
const rGate = canRoleGate(p.roles ?? []);
|
|
1719
|
+
restrictive.push(rGate === null ? r : `((NOT ${rGate}) OR ${r})`);
|
|
1720
|
+
}
|
|
1721
|
+
const entries = [];
|
|
1722
|
+
for (const p of policies) {
|
|
1723
|
+
if (p.command !== "update" || p.permissive === false) continue;
|
|
1724
|
+
if (Buffer.byteLength(CAN_PREFIX + p.name, "utf8") > 63) {
|
|
1725
|
+
dropped.push({
|
|
1726
|
+
name: p.name,
|
|
1727
|
+
reason: "its name is longer than 59 bytes and Postgres would truncate the column alias"
|
|
1728
|
+
});
|
|
1729
|
+
continue;
|
|
1730
|
+
}
|
|
1731
|
+
const using = renderCanUsing(p, alias, def.name);
|
|
1732
|
+
if (using === null) {
|
|
1733
|
+
dropped.push({
|
|
1734
|
+
name: p.name,
|
|
1735
|
+
reason: "its using expression cannot be selected as a column (null; a raw string that is table-qualified, carries a comment or unbalanced parentheses, or calls auth.has_permission outside a (select \u2026) wrapper; or a shape this engine does not render)"
|
|
1736
|
+
});
|
|
1737
|
+
continue;
|
|
1738
|
+
}
|
|
1739
|
+
const gate = canRoleGate(p.roles ?? []);
|
|
1740
|
+
const parts = [
|
|
1741
|
+
...gate === null ? [] : [
|
|
1742
|
+
gate
|
|
1743
|
+
],
|
|
1744
|
+
using,
|
|
1745
|
+
...restrictive
|
|
1746
|
+
];
|
|
1747
|
+
entries.push({
|
|
1748
|
+
name: p.name,
|
|
1749
|
+
using: parts.length === 1 ? using : `(${parts.join(" AND ")})`
|
|
1750
|
+
});
|
|
1751
|
+
}
|
|
1752
|
+
return {
|
|
1753
|
+
entries,
|
|
1754
|
+
dropped
|
|
1755
|
+
};
|
|
1756
|
+
}
|
|
1757
|
+
__name(canEntries, "canEntries");
|
|
1758
|
+
var CanRenderError = class CanRenderError2 extends Error {
|
|
1759
|
+
static {
|
|
1760
|
+
__name(this, "CanRenderError");
|
|
1761
|
+
}
|
|
1762
|
+
};
|
|
1763
|
+
var ENGINE_ALIASES = /^(t|locked|w\d+|wj\d+|x|q|g|p|upd)$/;
|
|
1764
|
+
function renderCanUsing(p, qualifier, tableName) {
|
|
1765
|
+
if (p.using === null || p.using === void 0) return null;
|
|
1766
|
+
if (typeof p.using === "string") {
|
|
1767
|
+
const withoutLiterals = p.using.replace(/'(?:[^']|'')*'/g, "''");
|
|
1768
|
+
if (/--|\/\*/.test(withoutLiterals)) return null;
|
|
1769
|
+
if (withoutLiterals.replace(/''/g, "").includes("'")) return null;
|
|
1770
|
+
let depth = 0;
|
|
1771
|
+
for (const ch of withoutLiterals) {
|
|
1772
|
+
if (ch === "(") depth++;
|
|
1773
|
+
else if (ch === ")" && --depth < 0) return null;
|
|
1774
|
+
}
|
|
1775
|
+
if (depth !== 0) return null;
|
|
1776
|
+
const tbl = tableName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1777
|
+
const qualified = new RegExp(`(^|[^A-Za-z0-9_])(?:"?[A-Za-z_][A-Za-z0-9_]*"?\\s*\\.\\s*)?"?${tbl}"?\\s*\\.`);
|
|
1778
|
+
if (qualified.test(withoutLiterals)) return null;
|
|
1779
|
+
const calls = withoutLiterals.match(/auth\s*\.\s*"?has_permission"?\s*\(/gi) ?? [];
|
|
1780
|
+
const wrapped = withoutLiterals.match(/\(\s*select\s*[(\s]*auth\s*\.\s*"?has_permission"?\s*\(/gi) ?? [];
|
|
1781
|
+
if (calls.length !== wrapped.length) return null;
|
|
1782
|
+
return `(${p.using})`;
|
|
1783
|
+
}
|
|
1784
|
+
try {
|
|
1785
|
+
const rendered = canExprSql(p.using, qualifier);
|
|
1786
|
+
const selfParenthesized = p.using.kind === "binop" || p.using.kind === "and" || p.using.kind === "or" || p.using.kind === "not" || p.using.kind === "isNull" || p.using.kind === "exists";
|
|
1787
|
+
return selfParenthesized ? rendered : `(${rendered})`;
|
|
1788
|
+
} catch (err) {
|
|
1789
|
+
if (err instanceof CanRenderError) return null;
|
|
1790
|
+
throw err;
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
__name(renderCanUsing, "renderCanUsing");
|
|
1794
|
+
var CAN_BINOPS = {
|
|
1795
|
+
eq: "=",
|
|
1796
|
+
neq: "<>",
|
|
1797
|
+
gt: ">",
|
|
1798
|
+
gte: ">=",
|
|
1799
|
+
lt: "<",
|
|
1800
|
+
lte: "<="
|
|
1801
|
+
};
|
|
1802
|
+
function canLiteral(v) {
|
|
1803
|
+
if (v === null) return "NULL";
|
|
1804
|
+
if (typeof v === "boolean") return v ? "TRUE" : "FALSE";
|
|
1805
|
+
if (typeof v === "number") {
|
|
1806
|
+
if (!Number.isFinite(v)) throw new CanRenderError("can: non-finite literal");
|
|
1807
|
+
return String(v);
|
|
1808
|
+
}
|
|
1809
|
+
return `'${v.replace(/'/g, "''")}'`;
|
|
1810
|
+
}
|
|
1811
|
+
__name(canLiteral, "canLiteral");
|
|
1812
|
+
function canExprSql(e, qualifier) {
|
|
1813
|
+
switch (e.kind) {
|
|
1814
|
+
case "col":
|
|
1815
|
+
return e.alias !== void 0 && e.alias !== "" ? `${quoteIdent(e.alias)}.${quoteIdent(e.name)}` : `${qualifier}.${quoteIdent(e.name)}`;
|
|
1816
|
+
case "authUid":
|
|
1817
|
+
return "(SELECT auth.uid())";
|
|
1818
|
+
case "lit":
|
|
1819
|
+
return canLiteral(e.value);
|
|
1820
|
+
case "binop": {
|
|
1821
|
+
const op = CAN_BINOPS[e.op];
|
|
1822
|
+
if (op === void 0) throw new CanRenderError(`can: unknown operator ${String(e.op)}`);
|
|
1823
|
+
return `(${canExprSql(e.left, qualifier)} ${op} ${canExprSql(e.right, qualifier)})`;
|
|
1824
|
+
}
|
|
1825
|
+
case "and":
|
|
1826
|
+
return e.parts.length === 0 ? "(true)" : `(${e.parts.map((p) => canExprSql(p, qualifier)).join(" AND ")})`;
|
|
1827
|
+
case "or":
|
|
1828
|
+
return e.parts.length === 0 ? "(false)" : `(${e.parts.map((p) => canExprSql(p, qualifier)).join(" OR ")})`;
|
|
1829
|
+
case "not":
|
|
1830
|
+
return `(NOT ${canExprSql(e.expr, qualifier)})`;
|
|
1831
|
+
case "isNull":
|
|
1832
|
+
return `(${canExprSql(e.expr, qualifier)} IS ${e.negated ? "NOT " : ""}NULL)`;
|
|
1833
|
+
case "exists":
|
|
1834
|
+
if (e.alias === "" || ENGINE_ALIASES.test(e.alias)) {
|
|
1835
|
+
throw new CanRenderError(`can: exists alias "${e.alias}" collides with the engine's own aliases \u2014 the correlation would silently collapse`);
|
|
1836
|
+
}
|
|
1837
|
+
return `EXISTS (SELECT 1 FROM ${quoteTable(e.table)} ${quoteIdent(e.alias)} WHERE ${canExprSql(e.predicate, qualifier)})`;
|
|
1838
|
+
default:
|
|
1839
|
+
throw new CanRenderError(`can: unknown policy expression kind ${String(e.kind)}`);
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
__name(canExprSql, "canExprSql");
|
|
1843
|
+
function canEntriesOf(table, alias) {
|
|
1844
|
+
const byAlias = memoTable(canMemo, currentSchema, table, () => /* @__PURE__ */ new Map());
|
|
1845
|
+
const hit = byAlias.get(alias);
|
|
1846
|
+
if (hit !== void 0) return hit;
|
|
1847
|
+
const def = tableDefOf(table);
|
|
1848
|
+
const warnOnce = /* @__PURE__ */ __name((msg) => {
|
|
1849
|
+
if (byAlias.has("__warned")) return;
|
|
1850
|
+
byAlias.set("__warned", []);
|
|
1851
|
+
console.warn(msg);
|
|
1852
|
+
}, "warnOnce");
|
|
1853
|
+
if (def === null || def.policies === void 0) {
|
|
1854
|
+
byAlias.set(alias, []);
|
|
1855
|
+
return [];
|
|
1856
|
+
}
|
|
1857
|
+
if (hasCanColumn(table)) {
|
|
1858
|
+
warnOnce(`can(${table}): the table declares a column or relation named "can", so the policy echo is disabled for it \u2014 rename it to get can`);
|
|
1859
|
+
byAlias.set(alias, []);
|
|
1860
|
+
return [];
|
|
1861
|
+
}
|
|
1862
|
+
const { entries, dropped } = canEntries(def, alias === "" ? quoteTable(table) : alias);
|
|
1863
|
+
if (dropped.length > 0) {
|
|
1864
|
+
warnOnce(dropped.map((d) => `can(${table}): policy "${d.name}" is not exposed in can \u2014 ${d.reason}; the database still enforces it`).join("\n"));
|
|
1865
|
+
}
|
|
1866
|
+
byAlias.set(alias, entries);
|
|
1867
|
+
return entries;
|
|
1868
|
+
}
|
|
1869
|
+
__name(canEntriesOf, "canEntriesOf");
|
|
1870
|
+
function canOuter(table) {
|
|
1871
|
+
const cols = canProjection(table, "q");
|
|
1872
|
+
return cols.length === 0 ? "" : `, ${cols.join(", ")}`;
|
|
1873
|
+
}
|
|
1874
|
+
__name(canOuter, "canOuter");
|
|
1875
|
+
function canProjection(table, alias) {
|
|
1876
|
+
return canEntriesOf(table, alias).map((e) => `${e.using} AS ${quoteIdent(CAN_PREFIX + e.name)}`);
|
|
1877
|
+
}
|
|
1878
|
+
__name(canProjection, "canProjection");
|
|
1879
|
+
function canJsonPairs(table, alias) {
|
|
1880
|
+
return canEntriesOf(table, alias).map((e) => `${quoteLiteral(CAN_PREFIX + e.name)}, ${e.using}`);
|
|
1881
|
+
}
|
|
1882
|
+
__name(canJsonPairs, "canJsonPairs");
|
|
1883
|
+
function foldCan(table, row, partial) {
|
|
1884
|
+
let can = null;
|
|
1885
|
+
for (const key2 of Object.keys(row)) {
|
|
1886
|
+
if (!key2.startsWith(CAN_PREFIX)) continue;
|
|
1887
|
+
can ??= [];
|
|
1888
|
+
if (row[key2] === true) can.push(key2.slice(CAN_PREFIX.length));
|
|
1889
|
+
delete row[key2];
|
|
1890
|
+
}
|
|
1891
|
+
if (hasCanColumn(table)) return row;
|
|
1892
|
+
if (can !== null) row.can = can;
|
|
1893
|
+
else if (!partial && tableDefOf(table) !== null) {
|
|
1894
|
+
if (canEntriesOf(table, "").length > 0) {
|
|
1895
|
+
const flags = memoTable(canMemo, currentSchema, table, () => /* @__PURE__ */ new Map());
|
|
1896
|
+
if (!flags.has("__missing_warned")) {
|
|
1897
|
+
flags.set("__missing_warned", []);
|
|
1898
|
+
console.warn(`can(${table}): a full-row read returned no "can:*" columns although the table declares update policies \u2014 this statement shape does not carry can; the row gets can: []`);
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
row.can = [];
|
|
1902
|
+
}
|
|
1903
|
+
return row;
|
|
1904
|
+
}
|
|
1905
|
+
__name(foldCan, "foldCan");
|
|
1906
|
+
function hasCanColumn(table) {
|
|
1907
|
+
const cols = tableDefOf(table)?.columns;
|
|
1908
|
+
if (cols !== void 0 && Object.prototype.hasOwnProperty.call(cols, "can")) return true;
|
|
1909
|
+
return (currentRelations.get(table) ?? []).some((rel) => rel.name === "can");
|
|
1910
|
+
}
|
|
1911
|
+
__name(hasCanColumn, "hasCanColumn");
|
|
1912
|
+
function rowProjection(table, alias, wildcard = alias ? `${alias}.*` : "*") {
|
|
1913
|
+
const cols = schemaColumns(table);
|
|
1914
|
+
let base = wildcard;
|
|
1915
|
+
if (cols && memoTable(timestampMemo, currentSchema, table, () => [
|
|
1916
|
+
...cols
|
|
1917
|
+
].some((c) => columnTypeOf(table, c) === "timestamp"))) {
|
|
1918
|
+
const projections = memoTable(projectionMemo, currentSchema, table, () => /* @__PURE__ */ new Map());
|
|
1919
|
+
const key2 = alias ?? "";
|
|
1920
|
+
let sql = projections.get(key2);
|
|
1921
|
+
if (sql === void 0) {
|
|
1922
|
+
sql = [
|
|
1923
|
+
...cols
|
|
1924
|
+
].map((c) => projectedColumn(table, c, alias)).join(", ");
|
|
1925
|
+
projections.set(key2, sql);
|
|
1926
|
+
}
|
|
1927
|
+
base = sql;
|
|
1928
|
+
}
|
|
1929
|
+
const can = canProjection(table, alias ?? "");
|
|
1930
|
+
if (can.length === 0) return base;
|
|
1931
|
+
const rows = base === "*" && alias !== void 0 ? `${alias}.*` : base;
|
|
1932
|
+
return `${rows}, ${can.join(", ")}`;
|
|
1933
|
+
}
|
|
1934
|
+
__name(rowProjection, "rowProjection");
|
|
840
1935
|
function colRefSql(table, known, ref, caller, alias = "t") {
|
|
841
1936
|
const name = ref.$col;
|
|
842
1937
|
const shapeOK = /^[A-Za-z_][A-Za-z0-9_]*$/.test(name);
|
|
@@ -850,13 +1945,13 @@ function selectClause(table, known, select) {
|
|
|
850
1945
|
if (select !== void 0 && select.length === 0) {
|
|
851
1946
|
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
1947
|
}
|
|
853
|
-
if (select === void 0) return "*";
|
|
1948
|
+
if (select === void 0) return rowProjection(table, "t", "*");
|
|
854
1949
|
return select.map((col) => {
|
|
855
1950
|
const shapeOK = /^[A-Za-z_][A-Za-z0-9_]*$/.test(col);
|
|
856
1951
|
if (known !== null ? !known.has(col) : !shapeOK) {
|
|
857
1952
|
throw new Error(`findMany(${table}): select kolonu "${col}" tabloda yok`);
|
|
858
1953
|
}
|
|
859
|
-
return `t.${quoteIdent(col)}`;
|
|
1954
|
+
return columnTypeOf(table, col) === "timestamp" ? projectedColumn(table, col, "t") : `t.${quoteIdent(col)}`;
|
|
860
1955
|
}).join(", ");
|
|
861
1956
|
}
|
|
862
1957
|
__name(selectClause, "selectClause");
|
|
@@ -926,7 +2021,7 @@ function compileWhereBare(table, colSet, where, add, caller = "search", alias =
|
|
|
926
2021
|
const transforms = transformsOf(currentSchema, table);
|
|
927
2022
|
const bindFor = /* @__PURE__ */ __name((col) => {
|
|
928
2023
|
const t = transforms.get(col);
|
|
929
|
-
return t?.toDb === void 0
|
|
2024
|
+
return (v) => add(t?.toDb === void 0 || v === null || v === void 0 ? v : t.toDb(v), col, table);
|
|
930
2025
|
}, "bindFor");
|
|
931
2026
|
assertUsableFilter(caller, table, where);
|
|
932
2027
|
if (isSqlFragment(where)) {
|
|
@@ -957,8 +2052,7 @@ function compileWhereBare(table, colSet, where, add, caller = "search", alias =
|
|
|
957
2052
|
const known = rels.map((r) => r.name).sort();
|
|
958
2053
|
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
2054
|
}
|
|
960
|
-
const ourCol =
|
|
961
|
-
const theirCol = rel.kind === "many" ? rel.via : primaryKeyOf(rel.to);
|
|
2055
|
+
const [ourCol, theirCol] = relationColumns(table, rel);
|
|
962
2056
|
if (ourCol === null || theirCol === null) {
|
|
963
2057
|
const missing = ourCol === null ? table : rel.to;
|
|
964
2058
|
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 +2190,8 @@ async function embedQuery(embed, text) {
|
|
|
1096
2190
|
if (secretReader === null) {
|
|
1097
2191
|
throw new Error(`query embed: secret reader ba\u011Flanmam\u0131\u015F \u2014 ${embed.apiKeyName} okunam\u0131yor`);
|
|
1098
2192
|
}
|
|
1099
|
-
const
|
|
1100
|
-
if (
|
|
2193
|
+
const key2 = await secretReader(embed.apiKeyName);
|
|
2194
|
+
if (key2 === null || key2 === "") {
|
|
1101
2195
|
throw new Error(`query embed: vault'ta ${embed.apiKeyName} yok (FR-021/FR-025)`);
|
|
1102
2196
|
}
|
|
1103
2197
|
const url = (embed.baseURL ?? "https://api.openai.com/v1").replace(/\/$/, "") + "/embeddings";
|
|
@@ -1108,7 +2202,7 @@ async function embedQuery(embed, text) {
|
|
|
1108
2202
|
method: "POST",
|
|
1109
2203
|
headers: {
|
|
1110
2204
|
"Content-Type": "application/json",
|
|
1111
|
-
Authorization: `Bearer ${
|
|
2205
|
+
Authorization: `Bearer ${key2}`
|
|
1112
2206
|
},
|
|
1113
2207
|
body: JSON.stringify({
|
|
1114
2208
|
model: embed.model,
|
|
@@ -1166,14 +2260,14 @@ function buildValueTuple(table, cols, data, add, caller) {
|
|
|
1166
2260
|
}).join(", ");
|
|
1167
2261
|
}
|
|
1168
2262
|
__name(buildValueTuple, "buildValueTuple");
|
|
1169
|
-
function
|
|
2263
|
+
function computeColumnType(table, col) {
|
|
1170
2264
|
const def = tableDefOf(table);
|
|
1171
2265
|
const c = def?.columns?.[col];
|
|
1172
2266
|
if (c === void 0 || c === null) return null;
|
|
1173
2267
|
const d = typeof c === "object" && "_def" in c ? c._def : c;
|
|
1174
2268
|
return d !== null && typeof d === "object" && typeof d.type === "string" ? d.type : null;
|
|
1175
2269
|
}
|
|
1176
|
-
__name(
|
|
2270
|
+
__name(computeColumnType, "computeColumnType");
|
|
1177
2271
|
var NUMERIC_PG_TYPES = /* @__PURE__ */ new Set([
|
|
1178
2272
|
"integer",
|
|
1179
2273
|
"bigint",
|
|
@@ -1183,23 +2277,44 @@ function aggregateExpr(table, fn, col, alias) {
|
|
|
1183
2277
|
const q = `t.${quoteIdent(col)}`;
|
|
1184
2278
|
const t = columnTypeOf(table, col);
|
|
1185
2279
|
if (fn === "sum" || fn === "avg") return `${fn}(${q})::text AS ${quoteIdent(alias)}`;
|
|
2280
|
+
if (t === "timestamp") return `${timestampResult(`${fn}(${q})`)} AS ${quoteIdent(alias)}`;
|
|
1186
2281
|
return t !== null && NUMERIC_PG_TYPES.has(t) && t !== "integer" ? `${fn}(${q})::text AS ${quoteIdent(alias)}` : `${fn}(${q}) AS ${quoteIdent(alias)}`;
|
|
1187
2282
|
}
|
|
1188
2283
|
__name(aggregateExpr, "aggregateExpr");
|
|
1189
|
-
function jsonObjectFor(table, cols, alias) {
|
|
2284
|
+
function jsonObjectFor(table, cols, alias, extra = []) {
|
|
1190
2285
|
const pairs = cols.map((c) => {
|
|
1191
2286
|
const t = columnTypeOf(table, c);
|
|
1192
|
-
const expr = t !== null && (t === "numeric" || t === "bigint") ? `${alias}.${quoteIdent(c)}::text` : `${alias}.${quoteIdent(c)}`;
|
|
2287
|
+
const expr = t !== null && (t === "numeric" || t === "bigint") ? `${alias}.${quoteIdent(c)}::text` : t === "timestamp" ? timestampResult(`${alias}.${quoteIdent(c)}`) : `${alias}.${quoteIdent(c)}`;
|
|
1193
2288
|
return `${quoteLiteral(c)}, ${expr}`;
|
|
1194
2289
|
});
|
|
1195
|
-
return `json_build_object(${
|
|
2290
|
+
return `json_build_object(${[
|
|
2291
|
+
...pairs,
|
|
2292
|
+
...extra
|
|
2293
|
+
].join(", ")})`;
|
|
1196
2294
|
}
|
|
1197
2295
|
__name(jsonObjectFor, "jsonObjectFor");
|
|
1198
|
-
function
|
|
2296
|
+
function relationColumns(table, relation) {
|
|
2297
|
+
const child = relation.kind === "many" ? relation.to : table;
|
|
2298
|
+
const value = tableDefOf(child)?.columns?.[relation.via];
|
|
2299
|
+
const column = value && typeof value === "object" && "_def" in value ? value._def : value;
|
|
2300
|
+
const target = column?.references?.column ?? primaryKeyOf(relation.kind === "many" ? table : relation.to);
|
|
2301
|
+
return relation.kind === "many" ? [
|
|
2302
|
+
target,
|
|
2303
|
+
relation.via
|
|
2304
|
+
] : [
|
|
2305
|
+
relation.via,
|
|
2306
|
+
target
|
|
2307
|
+
];
|
|
2308
|
+
}
|
|
2309
|
+
__name(relationColumns, "relationColumns");
|
|
2310
|
+
function withClauses(table, spec, add, caller, parentAlias = "t", depth = 0, counter = {
|
|
2311
|
+
value: 0
|
|
2312
|
+
}) {
|
|
2313
|
+
if (depth >= 3 && Object.values(spec).some(Boolean)) throw new Error(`${caller}(${table}): with supports at most three relation levels`);
|
|
1199
2314
|
const rels = currentRelations.get(table) ?? [];
|
|
1200
2315
|
const select = [];
|
|
1201
2316
|
const joins = [];
|
|
1202
|
-
|
|
2317
|
+
const jsonPairs = [];
|
|
1203
2318
|
for (const [name, opt] of Object.entries(spec)) {
|
|
1204
2319
|
if (opt === false || opt === void 0) continue;
|
|
1205
2320
|
const rel = rels.find((r) => r.name === name);
|
|
@@ -1207,13 +2322,13 @@ function withClauses(table, spec, add, caller) {
|
|
|
1207
2322
|
const known = rels.map((r) => r.name).sort();
|
|
1208
2323
|
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
2324
|
}
|
|
1210
|
-
const ourCol =
|
|
1211
|
-
const theirCol = rel.kind === "many" ? rel.via : primaryKeyOf(rel.to);
|
|
2325
|
+
const [ourCol, theirCol] = relationColumns(table, rel);
|
|
1212
2326
|
if (ourCol === null || theirCol === null) {
|
|
1213
2327
|
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
2328
|
}
|
|
1215
2329
|
const o = typeof opt === "object" && opt !== null ? opt : {};
|
|
1216
|
-
const
|
|
2330
|
+
const i = ++counter.value;
|
|
2331
|
+
const childAlias = `w${i}`;
|
|
1217
2332
|
const childKnown = schemaColumns(rel.to);
|
|
1218
2333
|
const childCols = Array.isArray(o["select"]) && o["select"].length > 0 ? o["select"] : childKnown !== null ? [
|
|
1219
2334
|
...childKnown
|
|
@@ -1226,28 +2341,103 @@ function withClauses(table, spec, add, caller) {
|
|
|
1226
2341
|
throw new Error(`${caller}(${table}): with.${name}.select kolonu "${c}" "${rel.to}" tablosunda yok`);
|
|
1227
2342
|
}
|
|
1228
2343
|
}
|
|
1229
|
-
const
|
|
2344
|
+
const nestedSpec = o["with"];
|
|
2345
|
+
if (nestedSpec !== void 0 && (nestedSpec === null || typeof nestedSpec !== "object" || Array.isArray(nestedSpec))) {
|
|
2346
|
+
throw new Error(`${caller}(${table}): with.${name}.with must be a relation object`);
|
|
2347
|
+
}
|
|
2348
|
+
const nested = nestedSpec === void 0 ? {
|
|
2349
|
+
joins: [],
|
|
2350
|
+
jsonPairs: []
|
|
2351
|
+
} : withClauses(rel.to, nestedSpec, add, caller, childAlias, depth + 1, counter);
|
|
2352
|
+
const nestedJoins = nested.joins.length ? ` ${nested.joins.join(" ")}` : "";
|
|
2353
|
+
const obj = jsonObjectFor(rel.to, childCols, childAlias, [
|
|
2354
|
+
...nested.jsonPairs,
|
|
2355
|
+
...canJsonPairs(rel.to, childAlias)
|
|
2356
|
+
]);
|
|
1230
2357
|
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)} =
|
|
2358
|
+
const link = `${childAlias}.${quoteIdent(theirCol)} = ${parentAlias}.${quoteIdent(ourCol)}`;
|
|
1232
2359
|
const cond = innerWhere === "" ? link : `${link} AND ${innerWhere}`;
|
|
1233
|
-
const
|
|
2360
|
+
const requestedLimit = o["limit"];
|
|
2361
|
+
limitClause(requestedLimit);
|
|
2362
|
+
const relationLimit = rel.kind === "many" ? requestedLimit : Math.min(1, requestedLimit ?? 1);
|
|
2363
|
+
const inner = orderClauseFor(rel.to, childKnown, o["orderBy"], childAlias) + limitClause(relationLimit);
|
|
1234
2364
|
const outAlias = `wj${i}`;
|
|
1235
2365
|
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`);
|
|
2366
|
+
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
2367
|
} else {
|
|
1238
|
-
joins.push(`LEFT JOIN LATERAL (SELECT ${obj} AS d FROM ${quoteTable(rel.to)} ${childAlias} WHERE ${cond}${inner}
|
|
2368
|
+
joins.push(`LEFT JOIN LATERAL (SELECT ${obj} AS d FROM ${quoteTable(rel.to)} ${childAlias}${nestedJoins} WHERE ${cond}${inner}) ${quoteIdent(outAlias)} ON true`);
|
|
1239
2369
|
}
|
|
1240
2370
|
select.push(`${quoteIdent(outAlias)}."d" AS ${quoteIdent(name)}`);
|
|
2371
|
+
jsonPairs.push(`${quoteLiteral(name)}, ${quoteIdent(outAlias)}."d"`);
|
|
1241
2372
|
}
|
|
1242
2373
|
return {
|
|
1243
2374
|
select,
|
|
1244
|
-
joins
|
|
2375
|
+
joins,
|
|
2376
|
+
jsonPairs
|
|
1245
2377
|
};
|
|
1246
2378
|
}
|
|
1247
2379
|
__name(withClauses, "withClauses");
|
|
1248
|
-
function
|
|
2380
|
+
function compileFindMany(table, query = {}, opts = {}) {
|
|
2381
|
+
const params = [];
|
|
2382
|
+
const add = /* @__PURE__ */ __name((v) => {
|
|
2383
|
+
params.push(v);
|
|
2384
|
+
return `$${params.length}`;
|
|
2385
|
+
}, "add");
|
|
2386
|
+
const known = schemaColumns(table);
|
|
2387
|
+
const whereSql = compileWhere(table, known, query, add, "findMany");
|
|
2388
|
+
const order = orderClause(table, known, opts.orderBy);
|
|
2389
|
+
const limit = limitClause(opts.limit);
|
|
2390
|
+
const offset = offsetClause(opts.offset, opts.limit);
|
|
2391
|
+
const cols = selectClause(table, known, opts.select);
|
|
2392
|
+
const w = opts.with !== void 0 && Object.keys(opts.with).length > 0 ? withClauses(table, opts.with, add, "findMany") : {
|
|
2393
|
+
select: [],
|
|
2394
|
+
joins: []
|
|
2395
|
+
};
|
|
2396
|
+
const selectList = w.select.length > 0 ? `${cols === "*" ? "t.*" : cols}, ${w.select.join(", ")}` : cols;
|
|
2397
|
+
const joinSql = w.joins.length > 0 ? ` ${w.joins.join(" ")}` : "";
|
|
2398
|
+
const sql = `SELECT ${selectList} FROM ${quoteTable(table)} t${joinSql} WHERE true${whereSql}${order}${limit}${offset}`;
|
|
2399
|
+
return {
|
|
2400
|
+
sql,
|
|
2401
|
+
params
|
|
2402
|
+
};
|
|
2403
|
+
}
|
|
2404
|
+
__name(compileFindMany, "compileFindMany");
|
|
2405
|
+
var uniqueKeysCache = /* @__PURE__ */ new WeakMap();
|
|
2406
|
+
var pageSchemaCache = /* @__PURE__ */ new WeakMap();
|
|
2407
|
+
function pageSchema(table) {
|
|
2408
|
+
return memoTable(pageSchemaCache, currentSchema, table, () => {
|
|
2409
|
+
const def = tableDefOf(table);
|
|
2410
|
+
if (!def?.columns) throw new Error(`page(${table}) requires an installed table declaration`);
|
|
2411
|
+
const columns = /* @__PURE__ */ Object.create(null);
|
|
2412
|
+
const inlineKeys = [];
|
|
2413
|
+
for (const [name, column] of Object.entries(def.columns)) {
|
|
2414
|
+
const c = column;
|
|
2415
|
+
const d = c._def ?? c;
|
|
2416
|
+
columns[name] = {
|
|
2417
|
+
type: d.type,
|
|
2418
|
+
nullable: d.nullable
|
|
2419
|
+
};
|
|
2420
|
+
if (d.primaryKey) inlineKeys.push(name);
|
|
2421
|
+
}
|
|
2422
|
+
const primaryKey = def.primaryKey ?? inlineKeys;
|
|
2423
|
+
for (const name of primaryKey) if (columns[name]) columns[name].nullable = false;
|
|
2424
|
+
return {
|
|
2425
|
+
columns,
|
|
2426
|
+
primaryKey,
|
|
2427
|
+
stamp: fingerprint({
|
|
2428
|
+
columns,
|
|
2429
|
+
primaryKey
|
|
2430
|
+
})
|
|
2431
|
+
};
|
|
2432
|
+
});
|
|
2433
|
+
}
|
|
2434
|
+
__name(pageSchema, "pageSchema");
|
|
2435
|
+
function createOps(tx, context = {}) {
|
|
1249
2436
|
const at = /* @__PURE__ */ __name(() => resolveTx(tx), "at");
|
|
1250
2437
|
const ops = {
|
|
2438
|
+
diagnostics() {
|
|
2439
|
+
return context.trace?.snapshot ?? null;
|
|
2440
|
+
},
|
|
1251
2441
|
// Generic dönüş, çağıranın İDDİASIDIR (FR-021). Motor her zaman satır objeleri
|
|
1252
2442
|
// üretir; `T` onlara bir ad vermekten ibarettir ve verilmezse `unknown` kalır —
|
|
1253
2443
|
// yani "bilmiyorum", ki ham SQL için doğru cevap odur.
|
|
@@ -1261,7 +2451,7 @@ function createOps(tx) {
|
|
|
1261
2451
|
const params = [];
|
|
1262
2452
|
const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
|
|
1263
2453
|
const tuple = buildValueTuple(table, cols, data, add, "insert");
|
|
1264
|
-
const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING
|
|
2454
|
+
const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING ${rowProjection(table)}`;
|
|
1265
2455
|
const rows = await (await at()).unsafe(sql, params);
|
|
1266
2456
|
const inserted = rows[0];
|
|
1267
2457
|
if (!inserted) {
|
|
@@ -1283,8 +2473,9 @@ function createOps(tx) {
|
|
|
1283
2473
|
* bir satırın fazla kolonu SESSİZCE YAZILMAZDI. Eksik kolon için `null`
|
|
1284
2474
|
* yazın ya da varsayılanı istiyorsanız o satırları ayrı çağrıda ekleyin.
|
|
1285
2475
|
*/
|
|
1286
|
-
async
|
|
1287
|
-
|
|
2476
|
+
insertMany: /* @__PURE__ */ __name(async function(table, rows, opts) {
|
|
2477
|
+
validateInsertManyOptions(opts);
|
|
2478
|
+
if (rows.length === 0) return opts?.returning === false ? 0 : [];
|
|
1288
2479
|
const first = rows[0];
|
|
1289
2480
|
const cols = Object.keys(first);
|
|
1290
2481
|
if (cols.length === 0) throw new Error(`insertMany into ${table}: no columns given`);
|
|
@@ -1299,11 +2490,15 @@ function createOps(tx) {
|
|
|
1299
2490
|
const params = [];
|
|
1300
2491
|
const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
|
|
1301
2492
|
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
|
|
2493
|
+
const tail = opts?.onConflict !== void 0 && opts.onConflict.length > 0 ? ` ${onConflictTail(cols, opts.onConflict, opts.action ?? "ignore")}` : "";
|
|
2494
|
+
const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES ${tuples.join(", ")}${tail}${opts?.returning === false ? "" : ` RETURNING ${rowProjection(table)}`}`;
|
|
1304
2495
|
const out = await (await at()).unsafe(sql, params);
|
|
2496
|
+
if (opts?.returning === false) {
|
|
2497
|
+
if (!Number.isSafeInteger(out.count) || out.count < 0) throw new Error("insertMany returning:false requires a driver with an affected row count");
|
|
2498
|
+
return out.count;
|
|
2499
|
+
}
|
|
1305
2500
|
return out.map((r) => asTableRow(table, r));
|
|
1306
|
-
},
|
|
2501
|
+
}, "insertMany"),
|
|
1307
2502
|
/**
|
|
1308
2503
|
* Bir ADI kilitle — satırı değil (FR-054).
|
|
1309
2504
|
*
|
|
@@ -1317,9 +2512,9 @@ function createOps(tx) {
|
|
|
1317
2512
|
* Transaction kapsamlısı commit/rollback'te Postgres tarafından bırakılır,
|
|
1318
2513
|
* yani unutulması imkânsız.
|
|
1319
2514
|
*/
|
|
1320
|
-
async advisoryXactLock(
|
|
2515
|
+
async advisoryXactLock(key2) {
|
|
1321
2516
|
await (await at()).unsafe("select pg_advisory_xact_lock(hashtextextended($1, 0))", [
|
|
1322
|
-
|
|
2517
|
+
key2
|
|
1323
2518
|
]);
|
|
1324
2519
|
},
|
|
1325
2520
|
/**
|
|
@@ -1393,7 +2588,7 @@ function createOps(tx) {
|
|
|
1393
2588
|
const params = [];
|
|
1394
2589
|
const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
|
|
1395
2590
|
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
|
|
2591
|
+
const rows = await sp.unsafe(`INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING ${rowProjection(table)}`, params);
|
|
1397
2592
|
if (!rows[0]) {
|
|
1398
2593
|
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
2594
|
}
|
|
@@ -1410,7 +2605,7 @@ function createOps(tx) {
|
|
|
1410
2605
|
return `$${params.length}`;
|
|
1411
2606
|
}, "add");
|
|
1412
2607
|
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);
|
|
2608
|
+
const found = await (await at()).unsafe(`SELECT ${rowProjection(table, "t", "*")} FROM ${quoteTable(table)} t WHERE ${where} LIMIT 1`, params);
|
|
1414
2609
|
if (!found[0]) {
|
|
1415
2610
|
const named = constraintOf(e);
|
|
1416
2611
|
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 +2642,13 @@ function createOps(tx) {
|
|
|
1447
2642
|
const bindData = [];
|
|
1448
2643
|
const addPut = /* @__PURE__ */ __name((v) => `$${bindData.push(v)}`, "addPut");
|
|
1449
2644
|
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
|
|
2645
|
+
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
2646
|
const live = await at();
|
|
1452
2647
|
const first = await live.unsafe(insertSql, bindData);
|
|
1453
2648
|
if (first[0]) return asTableRow(table, first[0]);
|
|
1454
2649
|
const condSql = conflict.map((c, i) => `${quoteIdent(c)} = $${i + 1}`).join(" AND ");
|
|
1455
2650
|
const condBind = conflict.map((c) => data[c]);
|
|
1456
|
-
const cur = await live.unsafe(`SELECT
|
|
2651
|
+
const cur = await live.unsafe(`SELECT ${rowProjection(table)} FROM ${quoteTable(table)} WHERE ${condSql} AND "valid_to" IS NULL`, condBind);
|
|
1457
2652
|
const old = cur[0];
|
|
1458
2653
|
if (old === void 0) {
|
|
1459
2654
|
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 +2674,7 @@ function createOps(tx) {
|
|
|
1479
2674
|
const conflictSet = new Set(conflict);
|
|
1480
2675
|
const assignments = cols.filter((c) => !conflictSet.has(c)).map((c) => `${quoteIdent(c)} = EXCLUDED.${quoteIdent(c)}`);
|
|
1481
2676
|
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
|
|
2677
|
+
const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${placeholders}) ON CONFLICT (${conflict.map(quoteIdent).join(", ")}) ${action} RETURNING ${rowProjection(table)}`;
|
|
1483
2678
|
const rows = await (await at()).unsafe(sql, params);
|
|
1484
2679
|
const row = rows[0];
|
|
1485
2680
|
if (!row) {
|
|
@@ -1497,7 +2692,7 @@ function createOps(tx) {
|
|
|
1497
2692
|
return `$${params.length}`;
|
|
1498
2693
|
}, "add");
|
|
1499
2694
|
const assignments = buildSetAssignments(table, cols, data, add, "update");
|
|
1500
|
-
const sql = `UPDATE ${quoteTable(table)} SET ${assignments} WHERE id = ${add(id)} RETURNING
|
|
2695
|
+
const sql = `UPDATE ${quoteTable(table)} SET ${assignments} WHERE id = ${add(id)} RETURNING ${rowProjection(table)}`;
|
|
1501
2696
|
const rows = await (await at()).unsafe(sql, params);
|
|
1502
2697
|
return rows[0] ? asTableRow(table, rows[0]) : null;
|
|
1503
2698
|
},
|
|
@@ -1549,10 +2744,10 @@ function createOps(tx) {
|
|
|
1549
2744
|
assertHasPredicate(whereSql, "updateMany", table);
|
|
1550
2745
|
const pk = primaryKeyOf(table);
|
|
1551
2746
|
const wantRows = opts?.returning !== false;
|
|
1552
|
-
const tail = wantRows ?
|
|
2747
|
+
const tail = wantRows ? `RETURNING ${rowProjection(table, "t", "*")}` : "RETURNING 1";
|
|
1553
2748
|
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
|
|
1555
|
-
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) `;
|
|
2749
|
+
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."__pb_lock" ` + (wantRows ? `RETURNING ${rowProjection(table, "t")}` : "RETURNING 1");
|
|
2750
|
+
const lockedCte = simple ? "" : `WITH locked AS (SELECT t.${quoteIdent(pk)} AS "__pb_lock" FROM ${quoteTable(table)} t WHERE true${whereSql} ORDER BY t.${quoteIdent(pk)} FOR NO KEY UPDATE) `;
|
|
1556
2751
|
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
2752
|
const out = await (await at()).unsafe(sql, params);
|
|
1558
2753
|
if (!wantRows) return Number(out[0]?.["n"] ?? 0);
|
|
@@ -1636,7 +2831,7 @@ function createOps(tx) {
|
|
|
1636
2831
|
}
|
|
1637
2832
|
const groups = q.groupBy ?? [];
|
|
1638
2833
|
for (const col of groups) check(col, "groupBy");
|
|
1639
|
-
const groupSel = groups.map((c) => `t.${quoteIdent(c)} AS ${quoteIdent(`g__${c}`)}`);
|
|
2834
|
+
const groupSel = groups.map((c) => `${columnTypeOf(table, c) === "timestamp" ? columnResult(table, c, "t") : `t.${quoteIdent(c)}`} AS ${quoteIdent(`g__${c}`)}`);
|
|
1640
2835
|
const params = [];
|
|
1641
2836
|
const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
|
|
1642
2837
|
const whereSql = compileWhere(table, known, q.where ?? {}, add, "aggregate");
|
|
@@ -1670,32 +2865,114 @@ function createOps(tx) {
|
|
|
1670
2865
|
const rows = await (await at()).unsafe(sql, params);
|
|
1671
2866
|
return Number(rows[0]?.n ?? 0);
|
|
1672
2867
|
},
|
|
2868
|
+
async findUnique(table, where, opts = {}) {
|
|
2869
|
+
const keys = memoTable(uniqueKeysCache, currentSchema, table, () => uniqueKeysOf(tableDefOf(table) ?? {}));
|
|
2870
|
+
assertUniqueLookup(table, keys, where);
|
|
2871
|
+
const rows = await ops.findMany(table, where, {
|
|
2872
|
+
...opts,
|
|
2873
|
+
select: opts.select === void 0 ? void 0 : [
|
|
2874
|
+
...opts.select
|
|
2875
|
+
],
|
|
2876
|
+
limit: 2
|
|
2877
|
+
});
|
|
2878
|
+
if (rows.length > 1) throw new Error(`findUnique(${table}): deployed constraints disagree with the schema; multiple rows matched`);
|
|
2879
|
+
return rows[0] ?? null;
|
|
2880
|
+
},
|
|
1673
2881
|
async findById(table, id) {
|
|
1674
|
-
const rows = await (await at()).unsafe(`SELECT
|
|
2882
|
+
const rows = await (await at()).unsafe(`SELECT ${rowProjection(table)} FROM ${quoteTable(table)} WHERE id = $1`, [
|
|
1675
2883
|
id
|
|
1676
2884
|
]);
|
|
1677
2885
|
return rows[0] ? asTableRow(table, rows[0]) : null;
|
|
1678
2886
|
},
|
|
2887
|
+
compileQuery(query) {
|
|
2888
|
+
assertReadContract(query, tableDefOf(query.table)?.columns);
|
|
2889
|
+
return compileFindMany(query.table, query.where, query.options);
|
|
2890
|
+
},
|
|
2891
|
+
async readQuery(query) {
|
|
2892
|
+
const compiled = ops.compileQuery(query);
|
|
2893
|
+
return asTableRows(query.table, await (await at()).unsafe(compiled.sql, compiled.params, {
|
|
2894
|
+
name: query.name
|
|
2895
|
+
}), void 0, true);
|
|
2896
|
+
},
|
|
2897
|
+
async explainQuery(query) {
|
|
2898
|
+
const compiled = ops.compileQuery(query);
|
|
2899
|
+
return await (await at()).unsafe(`EXPLAIN (FORMAT JSON) ${compiled.sql}`, compiled.params, {
|
|
2900
|
+
name: `${query.name}.explain`
|
|
2901
|
+
});
|
|
2902
|
+
},
|
|
1679
2903
|
async findMany(table, query = {}, opts = {}) {
|
|
2904
|
+
const compiled = compileFindMany(table, query, opts);
|
|
2905
|
+
return asTableRows(table, await (await at()).unsafe(compiled.sql, compiled.params), opts.with, opts.select !== void 0);
|
|
2906
|
+
},
|
|
2907
|
+
async page(table, query = {}, opts = {}) {
|
|
2908
|
+
const secret = cursorKey();
|
|
2909
|
+
const schema = pageSchema(table);
|
|
2910
|
+
const { order, size, backward, token } = pageOrder(schema.columns, schema.primaryKey, opts);
|
|
1680
2911
|
const params = [];
|
|
1681
|
-
const add = /* @__PURE__ */ __name((v) => {
|
|
1682
|
-
params.push(v);
|
|
1683
|
-
return `$${params.length}`;
|
|
1684
|
-
}, "add");
|
|
2912
|
+
const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
|
|
1685
2913
|
const known = schemaColumns(table);
|
|
1686
|
-
const
|
|
1687
|
-
const
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
const
|
|
1691
|
-
|
|
2914
|
+
const where = compileWhere(table, known, query, add, "page");
|
|
2915
|
+
const cols = selectClause(table, known, opts.select === void 0 ? void 0 : [
|
|
2916
|
+
...opts.select
|
|
2917
|
+
]);
|
|
2918
|
+
const scope = fingerprint({
|
|
2919
|
+
table,
|
|
2920
|
+
schema: schema.stamp,
|
|
2921
|
+
order,
|
|
2922
|
+
where,
|
|
2923
|
+
params,
|
|
2924
|
+
select: opts.select,
|
|
2925
|
+
with: opts.with,
|
|
2926
|
+
identity: context.cursorScope ?? "direct"
|
|
2927
|
+
});
|
|
2928
|
+
const values = token === void 0 ? void 0 : decodeCursor(secret, scope, token, order.length);
|
|
2929
|
+
const column = /* @__PURE__ */ __name((name) => `t.${quoteIdent(name)}`, "column");
|
|
2930
|
+
const boundary = values ? ` AND ${pageBoundary(order, values, backward, add, column)}` : "";
|
|
2931
|
+
const opposite = values ? `EXISTS (SELECT 1 FROM ${quoteTable(table)} t WHERE true${where} AND ${pageBoundary(order, values, !backward, add, column, true)})` : "false";
|
|
2932
|
+
let prefix = "__pb_page_";
|
|
2933
|
+
const names = [
|
|
2934
|
+
...Object.keys(schema.columns),
|
|
2935
|
+
...Object.keys(opts.with ?? {})
|
|
2936
|
+
];
|
|
2937
|
+
while (names.some((n) => n.startsWith(prefix))) prefix += "_";
|
|
2938
|
+
const ordinal = `${prefix}ordinal`, other = `${prefix}opposite`;
|
|
2939
|
+
const hidden = order.map((o, i) => `${prefix}${i}`);
|
|
2940
|
+
const cursorCols = order.map((o, i) => `${columnTypeOf(table, o.column) === "timestamp" ? timestampResult(column(o.column)) : `${column(o.column)}::text`} AS ${quoteIdent(hidden[i])}`);
|
|
2941
|
+
const w = opts.with && Object.keys(opts.with).length ? withClauses(table, opts.with, add, "page") : {
|
|
1692
2942
|
select: [],
|
|
1693
2943
|
joins: []
|
|
1694
2944
|
};
|
|
1695
|
-
const
|
|
1696
|
-
const
|
|
1697
|
-
const
|
|
1698
|
-
|
|
2945
|
+
const nativeOrder = order.map((o) => `${column(o.column)} ${o.direction === "asc" !== backward ? "ASC" : "DESC"} NULLS ${o.nulls === "first" !== backward ? "FIRST" : "LAST"}`).join(", ");
|
|
2946
|
+
const visible = cols === "*" ? "t.*" : cols;
|
|
2947
|
+
const inner = `SELECT ${[
|
|
2948
|
+
visible,
|
|
2949
|
+
...w.select,
|
|
2950
|
+
...cursorCols,
|
|
2951
|
+
`row_number() OVER (ORDER BY ${nativeOrder}) AS ${quoteIdent(ordinal)}`
|
|
2952
|
+
].join(", ")} FROM ${quoteTable(table)} t ${w.joins.join(" ")} WHERE true${where}${boundary} ORDER BY ${nativeOrder} LIMIT ${size + 1}`;
|
|
2953
|
+
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)}`;
|
|
2954
|
+
const result = await (await at()).unsafe(sql, params);
|
|
2955
|
+
const hasOpposite = result[0]?.[other] === true;
|
|
2956
|
+
let rows = result.filter((row) => row[ordinal] !== null && row[ordinal] !== void 0);
|
|
2957
|
+
const hasMore = rows.length > size;
|
|
2958
|
+
rows = rows.slice(0, size);
|
|
2959
|
+
if (backward) rows.reverse();
|
|
2960
|
+
const cursor = /* @__PURE__ */ __name((row) => row ? encodeCursor(secret, scope, hidden.map((name) => row[name] === null ? null : String(row[name]))) : null, "cursor");
|
|
2961
|
+
const pageInfo = {
|
|
2962
|
+
startCursor: cursor(rows[0]),
|
|
2963
|
+
endCursor: cursor(rows.at(-1)),
|
|
2964
|
+
hasNextPage: backward ? hasOpposite : hasMore,
|
|
2965
|
+
hasPreviousPage: backward ? hasMore : hasOpposite
|
|
2966
|
+
};
|
|
2967
|
+
for (const row of rows) for (const name of [
|
|
2968
|
+
...hidden,
|
|
2969
|
+
ordinal,
|
|
2970
|
+
other
|
|
2971
|
+
]) delete row[name];
|
|
2972
|
+
return {
|
|
2973
|
+
rows: asTableRows(table, rows, opts.with, opts.select !== void 0),
|
|
2974
|
+
pageInfo
|
|
2975
|
+
};
|
|
1699
2976
|
},
|
|
1700
2977
|
/**
|
|
1701
2978
|
* Tek-SQL hibrit arama (FR-014): iki kol CTE + FULL OUTER JOIN + RRF
|
|
@@ -1800,7 +3077,9 @@ function createOps(tx) {
|
|
|
1800
3077
|
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
3078
|
}
|
|
1802
3079
|
const msC = params.minScore === void 0 ? "" : addC(params.minScore);
|
|
1803
|
-
const colListC =
|
|
3080
|
+
const colListC = [
|
|
3081
|
+
...cfg.cols.map((c) => columnTypeOf(table, c) === "timestamp" ? projectedColumn(table, c, "t") : `t.${quoteIdent(c)}`)
|
|
3082
|
+
].join(", ");
|
|
1804
3083
|
const scoreC = scoreMul === "" ? "g._score" : `(g._score)${scoreMul}`;
|
|
1805
3084
|
const assembleC = /* @__PURE__ */ __name((kwIn, withKwn) => {
|
|
1806
3085
|
const kwnCol = withKwn ? `, (SELECT count(*) FROM kw)::int AS _kwn` : "";
|
|
@@ -1813,7 +3092,7 @@ function createOps(tx) {
|
|
|
1813
3092
|
fusedSrc = `WITH kw AS (${kwIn}), fused AS (SELECT kw.pid, kw.chunk_seq, (1.0/(${K2} + kw.r))::float8 AS cscore FROM kw)`;
|
|
1814
3093
|
}
|
|
1815
3094
|
const inner = `${fusedSrc}, ranked AS (SELECT f.pid, f.chunk_seq, f.cscore, ROW_NUMBER() OVER (PARTITION BY f.pid ORDER BY f.cscore DESC, f.chunk_seq) AS rn FROM fused f), grouped AS (SELECT r.pid, MAX(r.cscore) AS _score, json_agg(json_build_object('content', c.content, 'score', r.cscore, 'chunkSeq', r.chunk_seq, 'charStart', c.char_start) ORDER BY r.cscore DESC, r.chunk_seq) FILTER (WHERE r.rn <= ${bpr}) AS blocks FROM ranked r JOIN ${quoteTable(ck.table)} c ON c.${pidQ} = r.pid AND c.chunk_seq = r.chunk_seq GROUP BY r.pid) SELECT ${colListC}, ${scoreC}::float8 AS _score, g.blocks AS blocks${kwnCol} FROM grouped g JOIN ${quoteTable(table)} t ON t.${quoteIdent(cfg.pk)} = g.pid`;
|
|
1816
|
-
return msC === "" ? `SELECT
|
|
3095
|
+
return msC === "" ? `SELECT q.*${canOuter(table)} FROM (${inner}) q ORDER BY q._score DESC, q.${quoteIdent(cfg.pk)} LIMIT ${limit}` : `SELECT q.*${canOuter(table)} FROM (${inner}) q WHERE q._score >= ${msC} ORDER BY q._score DESC, q.${quoteIdent(cfg.pk)} LIMIT ${limit}`;
|
|
1817
3096
|
}, "assembleC");
|
|
1818
3097
|
let rowsC = await liveC.unsafe(assembleC(kwC, kwC !== ""), bindC);
|
|
1819
3098
|
if (wantTextC && kwC !== "" && (rowsC.length === 0 || rowsC[0]._kwn === 0)) {
|
|
@@ -1882,26 +3161,26 @@ function createOps(tx) {
|
|
|
1882
3161
|
qpRef = qp;
|
|
1883
3162
|
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
3163
|
}
|
|
1885
|
-
const colList =
|
|
3164
|
+
const colList = [
|
|
3165
|
+
...cfg.cols.map((c) => columnTypeOf(table, c) === "timestamp" ? projectedColumn(table, c, "t") : `t.${quoteIdent(c)}`)
|
|
3166
|
+
].join(", ");
|
|
1886
3167
|
const msP = params.minScore === void 0 ? "" : add(params.minScore);
|
|
1887
3168
|
const hlCol = params.highlight === true && wantText ? `, ts_headline('simple', ${ftsExprOf(cfg.ftsCols)}, websearch_to_tsquery('simple', ${qpRef})) AS _highlight` : "";
|
|
1888
3169
|
const assemble = /* @__PURE__ */ __name((kwIn, withKwn) => {
|
|
1889
3170
|
const kwnCol = withKwn ? `, (SELECT count(*) FROM kw)::int AS _kwn` : "";
|
|
1890
3171
|
let inner;
|
|
1891
|
-
let orderScore;
|
|
1892
3172
|
if (semSql !== "" && kwIn !== "") {
|
|
1893
3173
|
const score = scoreMul === "" ? "fused._score" : `(fused._score)${scoreMul}`;
|
|
1894
3174
|
inner = `WITH sem AS (${semSql}), kw AS (${kwIn}), fused AS (SELECT COALESCE(sem.id, kw.id) AS id, (COALESCE(1.0/(${K} + sem.r), 0) + COALESCE(1.0/(${K} + kw.r), 0))::float8 AS _score FROM sem FULL OUTER JOIN kw ON sem.id = kw.id) SELECT ${colList}, ${score} AS _score${hlCol}${kwnCol} FROM fused JOIN ${quoteTable(table)} t ON t.${quoteIdent(cfg.pk)} = fused.id`;
|
|
1895
|
-
orderScore = scoreMul === "" ? "fused._score" : "_score";
|
|
1896
3175
|
} else {
|
|
1897
3176
|
const single = semSql !== "" ? `sem AS (${semSql})` : `kw AS (${kwIn})`;
|
|
1898
3177
|
const alias = semSql !== "" ? "sem" : "kw";
|
|
1899
3178
|
const base = `(1.0/(${K} + ${alias}.r))::float8`;
|
|
1900
3179
|
const score = scoreMul === "" ? base : `(${base})${scoreMul}`;
|
|
1901
3180
|
inner = `WITH ${single} SELECT ${colList}, ${score} AS _score${hlCol}${semSql !== "" ? "" : kwnCol} FROM ${alias} JOIN ${quoteTable(table)} t ON t.${quoteIdent(cfg.pk)} = ${alias}.id`;
|
|
1902
|
-
orderScore = "_score";
|
|
1903
3181
|
}
|
|
1904
|
-
|
|
3182
|
+
const ms = msP === "" ? "" : ` WHERE q._score >= ${msP}`;
|
|
3183
|
+
return `SELECT q.*${canOuter(table)} FROM (${inner}) q${ms} ORDER BY q._score DESC, q.${quoteIdent(cfg.pk)} LIMIT ${limit}`;
|
|
1905
3184
|
}, "assemble");
|
|
1906
3185
|
let rows = await live.unsafe(assemble(kwSql, kwSql !== ""), bind);
|
|
1907
3186
|
if (wantText && kwSql !== "" && (rows.length === 0 || rows[0]._kwn === 0)) {
|
|
@@ -2090,7 +3369,7 @@ function createOps(tx) {
|
|
|
2090
3369
|
const params = [];
|
|
2091
3370
|
const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
|
|
2092
3371
|
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
|
|
3372
|
+
const rows = await sp.unsafe(`INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING ${rowProjection(table)}`, params);
|
|
2094
3373
|
if (!rows[0]) {
|
|
2095
3374
|
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
3375
|
}
|
|
@@ -2100,7 +3379,7 @@ function createOps(tx) {
|
|
|
2100
3379
|
/** A real SAVEPOINT inside the request's transaction. */
|
|
2101
3380
|
async transaction(cb) {
|
|
2102
3381
|
const live = await at();
|
|
2103
|
-
return live.savepoint(async (sp) => cb(withTables(createOps(sp), currentSchema)));
|
|
3382
|
+
return live.savepoint(async (sp) => cb(withTables(createOps(sp, context), currentSchema)));
|
|
2104
3383
|
},
|
|
2105
3384
|
/**
|
|
2106
3385
|
* Run `fn` against a handle bound to a SAVEPOINT, so a failure inside it
|
|
@@ -2121,7 +3400,7 @@ function createOps(tx) {
|
|
|
2121
3400
|
*/
|
|
2122
3401
|
async attempt(fn) {
|
|
2123
3402
|
const live = await at();
|
|
2124
|
-
return live.savepoint(async (sp) => fn(withTables(createOps(sp), currentSchema)));
|
|
3403
|
+
return live.savepoint(async (sp) => fn(withTables(createOps(sp, context), currentSchema)));
|
|
2125
3404
|
},
|
|
2126
3405
|
/**
|
|
2127
3406
|
* Execute a whole transaction plan — what `Database.$transaction(fn)` builds.
|
|
@@ -2141,6 +3420,62 @@ function createOps(tx) {
|
|
|
2141
3420
|
* "transport.txPlan is not a function" while every test that covered
|
|
2142
3421
|
* transactions passed against a mock that did implement it.
|
|
2143
3422
|
*/
|
|
3423
|
+
async command(plan, options = {}) {
|
|
3424
|
+
if (options.mode !== void 0 && ![
|
|
3425
|
+
"auto",
|
|
3426
|
+
"compiled",
|
|
3427
|
+
"sequential"
|
|
3428
|
+
].includes(options.mode)) throw new Error("command mode must be auto, compiled or sequential");
|
|
3429
|
+
if (options.mode === "sequential") return ops.txPlan(plan);
|
|
3430
|
+
const compiled = context.commands ? compileCommand(plan) : {
|
|
3431
|
+
reason: "the PostgreSQL command executor is not installed"
|
|
3432
|
+
};
|
|
3433
|
+
if ("reason" in compiled) {
|
|
3434
|
+
if (options.mode === "compiled") throw new Error(`Command cannot compile: ${compiled.reason}. Use auto/sequential mode or installCommandExecutor at boot.`);
|
|
3435
|
+
return ops.txPlan(plan);
|
|
3436
|
+
}
|
|
3437
|
+
if (compiled.steps.length === 0) return {
|
|
3438
|
+
results: []
|
|
3439
|
+
};
|
|
3440
|
+
if (compiled.pipeline) {
|
|
3441
|
+
return (await at()).savepoint(async (sp) => {
|
|
3442
|
+
const settled = await Promise.allSettled(compiled.pipeline.map((statement) => sp.unsafe(statement.sql, statement.params)));
|
|
3443
|
+
const failed = settled.find((result) => result.status === "rejected");
|
|
3444
|
+
if (failed?.status === "rejected") {
|
|
3445
|
+
const error = failed.reason;
|
|
3446
|
+
if (sqlstateOf(error) === SQLSTATE.commandGuardFailed && typeof error.detail === "string") {
|
|
3447
|
+
const detail = JSON.parse(error.detail);
|
|
3448
|
+
if (detail.error_code === "tx_guard_failed" && Number.isInteger(detail.slot)) {
|
|
3449
|
+
throw Object.assign(new Error("Command expectation failed"), detail);
|
|
3450
|
+
}
|
|
3451
|
+
}
|
|
3452
|
+
throw error;
|
|
3453
|
+
}
|
|
3454
|
+
return {
|
|
3455
|
+
results: settled.map((result, i) => {
|
|
3456
|
+
const rows2 = result.value[0].rows;
|
|
3457
|
+
return {
|
|
3458
|
+
rows: asTableRows(plan.ops[i].table, rows2),
|
|
3459
|
+
rows_affected: rows2.length
|
|
3460
|
+
};
|
|
3461
|
+
})
|
|
3462
|
+
};
|
|
3463
|
+
});
|
|
3464
|
+
}
|
|
3465
|
+
const rows = await (await at()).savepoint((sp) => sp.unsafe("SELECT palbase_runtime.command_v1($1::jsonb -> 'steps') AS result", [
|
|
3466
|
+
{
|
|
3467
|
+
steps: compiled.steps
|
|
3468
|
+
}
|
|
3469
|
+
]));
|
|
3470
|
+
const response = rows[0].result;
|
|
3471
|
+
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);
|
|
3472
|
+
return {
|
|
3473
|
+
results: response.results.map((result, i) => ({
|
|
3474
|
+
...result,
|
|
3475
|
+
rows: asTableRows(plan.ops[i].table, result.rows)
|
|
3476
|
+
}))
|
|
3477
|
+
};
|
|
3478
|
+
},
|
|
2144
3479
|
async txPlan(plan) {
|
|
2145
3480
|
const live = await at();
|
|
2146
3481
|
return live.savepoint(async (sp) => {
|
|
@@ -2164,27 +3499,55 @@ function createOps(tx) {
|
|
|
2164
3499
|
}
|
|
2165
3500
|
__name(createOps, "createOps");
|
|
2166
3501
|
var currentSchema = {};
|
|
3502
|
+
var columnsMemo = /* @__PURE__ */ new WeakMap();
|
|
3503
|
+
var primaryMemo = /* @__PURE__ */ new WeakMap();
|
|
3504
|
+
var vectorMemo = /* @__PURE__ */ new WeakMap();
|
|
3505
|
+
var transformMemo = /* @__PURE__ */ new WeakMap();
|
|
3506
|
+
var typeMemo = /* @__PURE__ */ new WeakMap();
|
|
3507
|
+
var timestampMemo = /* @__PURE__ */ new WeakMap();
|
|
3508
|
+
var projectionMemo = /* @__PURE__ */ new WeakMap();
|
|
3509
|
+
function schemaColumns(table) {
|
|
3510
|
+
return memoTable(columnsMemo, currentSchema, table, () => computeSchemaColumns(table));
|
|
3511
|
+
}
|
|
3512
|
+
__name(schemaColumns, "schemaColumns");
|
|
3513
|
+
function primaryKeyOf(table) {
|
|
3514
|
+
return memoTable(primaryMemo, currentSchema, table, () => computePrimaryKey(table));
|
|
3515
|
+
}
|
|
3516
|
+
__name(primaryKeyOf, "primaryKeyOf");
|
|
3517
|
+
function vectorColumnsOf(schema, table) {
|
|
3518
|
+
return memoTable(vectorMemo, schema, table, () => computeVectorColumns(schema, table));
|
|
3519
|
+
}
|
|
3520
|
+
__name(vectorColumnsOf, "vectorColumnsOf");
|
|
3521
|
+
function transformsOf(schema, table) {
|
|
3522
|
+
return memoTable(transformMemo, schema, table, () => computeTransforms(schema, table));
|
|
3523
|
+
}
|
|
3524
|
+
__name(transformsOf, "transformsOf");
|
|
3525
|
+
function columnTypeOf(table, col) {
|
|
3526
|
+
const types = memoTable(typeMemo, currentSchema, table, () => new Map([
|
|
3527
|
+
...schemaColumns(table) ?? []
|
|
3528
|
+
].map((c) => [
|
|
3529
|
+
c,
|
|
3530
|
+
computeColumnType(table, c)
|
|
3531
|
+
])));
|
|
3532
|
+
return types.get(col) ?? null;
|
|
3533
|
+
}
|
|
3534
|
+
__name(columnTypeOf, "columnTypeOf");
|
|
2167
3535
|
var currentRelations = /* @__PURE__ */ new Map();
|
|
2168
3536
|
function setSchema(schemas) {
|
|
2169
|
-
|
|
2170
|
-
cachedTrgmSchema = null;
|
|
2171
|
-
const tables = {};
|
|
3537
|
+
const tables = /* @__PURE__ */ Object.create(null);
|
|
2172
3538
|
for (const entry of schemas) {
|
|
2173
3539
|
const mod = entry;
|
|
2174
3540
|
const def = (mod && "default" in mod ? mod.default : mod) ?? {};
|
|
2175
3541
|
const schemaName = def.name ?? "public";
|
|
2176
|
-
for (const [
|
|
2177
|
-
tables[qualifiedTableKey(schemaName, table.name ??
|
|
3542
|
+
for (const [key2, table] of Object.entries(def.tables ?? {})) {
|
|
3543
|
+
tables[qualifiedTableKey(schemaName, table.name ?? key2)] = table;
|
|
2178
3544
|
}
|
|
2179
3545
|
}
|
|
2180
|
-
|
|
2181
|
-
tables
|
|
2182
|
-
};
|
|
2183
|
-
currentRelations = buildRelations(schemas.map((entry) => {
|
|
3546
|
+
const relations = buildRelations(schemas.map((entry) => {
|
|
2184
3547
|
const mod = entry;
|
|
2185
3548
|
const def = (mod && "default" in mod ? mod.default : mod) ?? {};
|
|
2186
|
-
const tables2 =
|
|
2187
|
-
for (const [
|
|
3549
|
+
const tables2 = /* @__PURE__ */ Object.create(null);
|
|
3550
|
+
for (const [key2, t] of Object.entries(def.tables ?? {})) {
|
|
2188
3551
|
const table = t ?? {};
|
|
2189
3552
|
const columns = {};
|
|
2190
3553
|
for (const [cname, c] of Object.entries(table.columns ?? {})) {
|
|
@@ -2194,9 +3557,9 @@ function setSchema(schemas) {
|
|
|
2194
3557
|
_def: cd
|
|
2195
3558
|
};
|
|
2196
3559
|
}
|
|
2197
|
-
tables2[
|
|
3560
|
+
tables2[key2] = {
|
|
2198
3561
|
...table,
|
|
2199
|
-
name: table.name ??
|
|
3562
|
+
name: table.name ?? key2,
|
|
2200
3563
|
columns
|
|
2201
3564
|
};
|
|
2202
3565
|
}
|
|
@@ -2205,40 +3568,86 @@ function setSchema(schemas) {
|
|
|
2205
3568
|
tables: tables2
|
|
2206
3569
|
};
|
|
2207
3570
|
}));
|
|
3571
|
+
for (const key2 of Object.keys(tables)) tables[key2] = snapshotDeclaration(tables[key2]);
|
|
3572
|
+
currentSchema = Object.freeze({
|
|
3573
|
+
tables: Object.freeze(tables)
|
|
3574
|
+
});
|
|
3575
|
+
currentRelations = relations;
|
|
3576
|
+
cachedVectorSchema = null;
|
|
3577
|
+
cachedTrgmSchema = null;
|
|
2208
3578
|
}
|
|
2209
3579
|
__name(setSchema, "setSchema");
|
|
3580
|
+
var tableNamespaces = /* @__PURE__ */ new WeakMap();
|
|
2210
3581
|
function withTables(ops, schema = currentSchema) {
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
3582
|
+
let namespaces = tableNamespaces.get(schema);
|
|
3583
|
+
if (!namespaces) {
|
|
3584
|
+
namespaces = /* @__PURE__ */ new Map();
|
|
3585
|
+
for (const key2 of Object.keys(schema.tables ?? {})) {
|
|
3586
|
+
const dot = key2.indexOf(".");
|
|
3587
|
+
const name = dot === -1 ? "public" : key2.slice(0, dot);
|
|
3588
|
+
const short = dot === -1 ? key2 : key2.slice(dot + 1);
|
|
3589
|
+
if (!namespaces.has(name)) namespaces.set(name, /* @__PURE__ */ new Map());
|
|
3590
|
+
namespaces.get(name).set(short, key2);
|
|
3591
|
+
}
|
|
3592
|
+
if (Object.isFrozen(schema) && Object.isFrozen(schema.tables)) tableNamespaces.set(schema, namespaces);
|
|
3593
|
+
}
|
|
3594
|
+
const bind = /* @__PURE__ */ __name((key2) => ({
|
|
3595
|
+
insert: /* @__PURE__ */ __name((data) => ops.insert(key2, data), "insert"),
|
|
3596
|
+
update: /* @__PURE__ */ __name((id, data) => ops.update(key2, id, data), "update"),
|
|
3597
|
+
delete: /* @__PURE__ */ __name((id) => ops.delete(key2, id), "delete"),
|
|
3598
|
+
findById: /* @__PURE__ */ __name((id) => ops.findById(key2, id), "findById"),
|
|
3599
|
+
findUnique: /* @__PURE__ */ __name((where, opts) => ops.findUnique(key2, where, opts), "findUnique"),
|
|
3600
|
+
findMany: /* @__PURE__ */ __name((query, opts) => ops.findMany(key2, query ?? {}, opts), "findMany"),
|
|
3601
|
+
page: /* @__PURE__ */ __name((query, opts) => ops.page(key2, query ?? {}, opts), "page"),
|
|
3602
|
+
put: /* @__PURE__ */ __name((q) => ops.put(key2, q.data, {
|
|
2218
3603
|
onConflict: q.onConflict
|
|
2219
3604
|
}), "put")
|
|
2220
3605
|
}), "bind");
|
|
2221
|
-
const
|
|
2222
|
-
for (const key of Object.keys(schema.tables ?? {})) {
|
|
2223
|
-
if (!key.includes(".")) tables[key] = bind(key);
|
|
2224
|
-
}
|
|
3606
|
+
const surfaces = /* @__PURE__ */ new Map();
|
|
2225
3607
|
const schemaOf = /* @__PURE__ */ __name((name) => {
|
|
2226
|
-
const
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
3608
|
+
const cached = surfaces.get(name);
|
|
3609
|
+
if (cached) return cached;
|
|
3610
|
+
const names = namespaces.get(name) ?? /* @__PURE__ */ new Map();
|
|
3611
|
+
const target = /* @__PURE__ */ Object.create(null);
|
|
3612
|
+
const get = /* @__PURE__ */ __name((key2) => {
|
|
3613
|
+
if (!Object.hasOwn(target, key2) && names.has(key2)) {
|
|
3614
|
+
Object.defineProperty(target, key2, {
|
|
3615
|
+
value: bind(names.get(key2)),
|
|
3616
|
+
configurable: true,
|
|
3617
|
+
enumerable: true,
|
|
3618
|
+
writable: true
|
|
3619
|
+
});
|
|
2233
3620
|
}
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
3621
|
+
return Reflect.get(target, key2);
|
|
3622
|
+
}, "get");
|
|
3623
|
+
const tables = new Proxy(target, {
|
|
3624
|
+
get: /* @__PURE__ */ __name((t, key2, receiver) => typeof key2 === "string" ? get(key2) : Reflect.get(t, key2, receiver), "get"),
|
|
3625
|
+
has: /* @__PURE__ */ __name((t, key2) => typeof key2 === "string" && names.has(key2) || Reflect.has(t, key2), "has"),
|
|
3626
|
+
ownKeys: /* @__PURE__ */ __name((t) => [
|
|
3627
|
+
.../* @__PURE__ */ new Set([
|
|
3628
|
+
...names.keys(),
|
|
3629
|
+
...Reflect.ownKeys(t)
|
|
3630
|
+
])
|
|
3631
|
+
], "ownKeys"),
|
|
3632
|
+
getOwnPropertyDescriptor: /* @__PURE__ */ __name((t, key2) => Reflect.getOwnPropertyDescriptor(t, key2) ?? (typeof key2 === "string" && names.has(key2) ? {
|
|
3633
|
+
configurable: true,
|
|
3634
|
+
enumerable: true,
|
|
3635
|
+
get: /* @__PURE__ */ __name(() => get(key2), "get")
|
|
3636
|
+
} : void 0), "getOwnPropertyDescriptor"),
|
|
3637
|
+
preventExtensions: /* @__PURE__ */ __name((t) => {
|
|
3638
|
+
for (const key2 of names.keys()) get(key2);
|
|
3639
|
+
return Reflect.preventExtensions(t);
|
|
3640
|
+
}, "preventExtensions")
|
|
3641
|
+
});
|
|
3642
|
+
const surface = {
|
|
3643
|
+
tables
|
|
2237
3644
|
};
|
|
3645
|
+
if (namespaces.has(name) || name === "public") surfaces.set(name, surface);
|
|
3646
|
+
return surface;
|
|
2238
3647
|
}, "schemaOf");
|
|
2239
3648
|
const base = /* @__PURE__ */ Object.create(null);
|
|
2240
3649
|
return Object.assign(base, ops, {
|
|
2241
|
-
tables,
|
|
3650
|
+
tables: schemaOf("public").tables,
|
|
2242
3651
|
schema: schemaOf
|
|
2243
3652
|
});
|
|
2244
3653
|
}
|
|
@@ -2296,8 +3705,8 @@ function diagnosingDriver(sql, surface = "service") {
|
|
|
2296
3705
|
return markEngineRaised(new UniqueViolation(constraintOf(e)));
|
|
2297
3706
|
}
|
|
2298
3707
|
const state = sqlstateOf(e);
|
|
2299
|
-
if (state ===
|
|
2300
|
-
if (state ===
|
|
3708
|
+
if (state === SQLSTATE.serializationFailure) return markEngineRaised(new SerializationFailure(state));
|
|
3709
|
+
if (state === SQLSTATE.deadlockDetected) return markEngineRaised(new DeadlockDetected(state));
|
|
2301
3710
|
if (surface === "service" && isForeignKeyViolation(e)) {
|
|
2302
3711
|
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
3712
|
}
|
|
@@ -2307,11 +3716,16 @@ function diagnosingDriver(sql, surface = "service") {
|
|
|
2307
3716
|
return e;
|
|
2308
3717
|
}, "explain");
|
|
2309
3718
|
const wrapTx = /* @__PURE__ */ __name((tx) => ({
|
|
2310
|
-
|
|
3719
|
+
unsafe(text, params) {
|
|
2311
3720
|
try {
|
|
2312
|
-
|
|
3721
|
+
const query = tx.unsafe(text, params);
|
|
3722
|
+
const mapped = query.catch((e) => {
|
|
3723
|
+
throw explain(e);
|
|
3724
|
+
});
|
|
3725
|
+
if (query.cancel) mapped.cancel = () => query.cancel();
|
|
3726
|
+
return mapped;
|
|
2313
3727
|
} catch (e) {
|
|
2314
|
-
|
|
3728
|
+
return Promise.reject(explain(e));
|
|
2315
3729
|
}
|
|
2316
3730
|
},
|
|
2317
3731
|
savepoint(cb) {
|
|
@@ -2320,34 +3734,120 @@ function diagnosingDriver(sql, surface = "service") {
|
|
|
2320
3734
|
}), "wrapTx");
|
|
2321
3735
|
return {
|
|
2322
3736
|
unsafe: /* @__PURE__ */ __name((text, params) => sql.unsafe(text, params), "unsafe"),
|
|
2323
|
-
|
|
3737
|
+
cancelBackend: sql.cancelBackend?.bind(sql),
|
|
3738
|
+
begin: /* @__PURE__ */ __name((cb) => sql.begin((tx) => cb(wrapTx(tx))).catch((e) => {
|
|
3739
|
+
throw explain(e);
|
|
3740
|
+
}), "begin")
|
|
2324
3741
|
};
|
|
2325
3742
|
}
|
|
2326
3743
|
__name(diagnosingDriver, "diagnosingDriver");
|
|
2327
|
-
function createRequestDatabase(sql, identity) {
|
|
2328
|
-
const
|
|
3744
|
+
function createRequestDatabase(sql, identity, diagnostics) {
|
|
3745
|
+
const trace = diagnostics === void 0 ? void 0 : new DatabaseTrace(diagnostics);
|
|
3746
|
+
let requestClosed = false;
|
|
3747
|
+
const contextFor = /* @__PURE__ */ __name((role) => {
|
|
3748
|
+
let scope;
|
|
3749
|
+
return {
|
|
3750
|
+
trace,
|
|
3751
|
+
get cursorScope() {
|
|
3752
|
+
return scope ??= cursorIdentity(role, identity.claimsJson);
|
|
3753
|
+
},
|
|
3754
|
+
get commands() {
|
|
3755
|
+
return hasCommandExecutor(sql);
|
|
3756
|
+
}
|
|
3757
|
+
};
|
|
3758
|
+
}, "contextFor");
|
|
3759
|
+
const userContext = contextFor(identity.role);
|
|
3760
|
+
const serviceContext = contextFor(identity.serviceRole);
|
|
3761
|
+
const atomic = /* @__PURE__ */ __name((role, surface) => async (fn, options = {}) => {
|
|
3762
|
+
validateAtomicOptions(options);
|
|
3763
|
+
if (requestClosed) throw new Error("Database request has settled; it cannot open another transaction");
|
|
3764
|
+
if (atomicScope.getStore() !== void 0) throw new Error("Nested Database.$atomic is not supported; use tx.$attempt for a savepoint");
|
|
3765
|
+
const retries = retryBudget(options.retry);
|
|
3766
|
+
const control = options.timeoutMs !== void 0 || options.signal !== void 0 ? new TransactionControl(options.timeoutMs, options.signal) : void 0;
|
|
3767
|
+
let lease;
|
|
3768
|
+
try {
|
|
3769
|
+
control?.check();
|
|
3770
|
+
if (options.workload) {
|
|
3771
|
+
const queuedAt = trace ? performance.now() : 0;
|
|
3772
|
+
try {
|
|
3773
|
+
lease = await workloadAdmission(sql, options.workload).acquire(workloadTenant(options.workload, identity.claimsJson), options.conflictKeys ?? [], control?.signal);
|
|
3774
|
+
} finally {
|
|
3775
|
+
trace?.time("queueMs", queuedAt);
|
|
3776
|
+
}
|
|
3777
|
+
}
|
|
3778
|
+
for (let attempt = 0; ; attempt++) {
|
|
3779
|
+
control?.check();
|
|
3780
|
+
const timeouts = [
|
|
3781
|
+
options.statementTimeoutMs,
|
|
3782
|
+
control?.remainingMs()
|
|
3783
|
+
].filter((n) => n !== void 0);
|
|
3784
|
+
const scope = /* @__PURE__ */ Symbol("atomic transaction");
|
|
3785
|
+
const root = createLazyTransaction(diagnosingDriver(sql, surface), role, identity.claimsJson, {
|
|
3786
|
+
scope,
|
|
3787
|
+
isolation: options.isolation,
|
|
3788
|
+
readOnly: options.readOnly,
|
|
3789
|
+
control,
|
|
3790
|
+
trace,
|
|
3791
|
+
lockTimeout: `${options.lockTimeoutMs ?? 5e3}ms`,
|
|
3792
|
+
statementTimeout: timeouts.length === 0 ? void 0 : `${Math.min(...timeouts)}ms`
|
|
3793
|
+
});
|
|
3794
|
+
try {
|
|
3795
|
+
const callback = atomicScope.run(scope, () => fn(withTables(createOps(root, surface === "user" ? userContext : serviceContext))));
|
|
3796
|
+
const value = await (control ? control.race(callback) : callback);
|
|
3797
|
+
control?.check();
|
|
3798
|
+
await root.commit();
|
|
3799
|
+
return value;
|
|
3800
|
+
} catch (error) {
|
|
3801
|
+
await root.rollback(error);
|
|
3802
|
+
if (attempt >= retries || !isRetryable(error)) throw error;
|
|
3803
|
+
trace?.retry();
|
|
3804
|
+
const delay = new Promise((resolve) => setTimeout(resolve, Math.min(100, 2 ** attempt)));
|
|
3805
|
+
await (control ? control.race(delay) : delay);
|
|
3806
|
+
}
|
|
3807
|
+
}
|
|
3808
|
+
} finally {
|
|
3809
|
+
control?.dispose();
|
|
3810
|
+
lease?.release();
|
|
3811
|
+
}
|
|
3812
|
+
}, "atomic");
|
|
3813
|
+
const tx = createLazyTransaction(diagnosingDriver(sql, "user"), identity.role, identity.claimsJson, {
|
|
3814
|
+
trace
|
|
3815
|
+
});
|
|
2329
3816
|
let serviceTx = null;
|
|
2330
3817
|
let serviceClient = null;
|
|
2331
3818
|
const asService = /* @__PURE__ */ __name(() => {
|
|
2332
3819
|
if (serviceClient === null) {
|
|
2333
3820
|
serviceTx = createLazyTransaction(diagnosingDriver(sql, "service"), identity.serviceRole, identity.claimsJson, {
|
|
2334
|
-
lockTimeout: SERVICE_LOCK_TIMEOUT
|
|
3821
|
+
lockTimeout: SERVICE_LOCK_TIMEOUT,
|
|
3822
|
+
trace
|
|
3823
|
+
});
|
|
3824
|
+
serviceClient = Object.assign(withTables(createOps(serviceTx, serviceContext)), {
|
|
3825
|
+
atomic: atomic(identity.serviceRole, "service")
|
|
2335
3826
|
});
|
|
2336
|
-
serviceClient = withTables(createOps(serviceTx));
|
|
2337
3827
|
}
|
|
2338
3828
|
return serviceClient;
|
|
2339
3829
|
}, "asService");
|
|
2340
3830
|
return {
|
|
2341
|
-
client: Object.assign(withTables(createOps(tx)), {
|
|
2342
|
-
asService
|
|
3831
|
+
client: Object.assign(withTables(createOps(tx, userContext)), {
|
|
3832
|
+
asService,
|
|
3833
|
+
atomic: atomic(identity.role, "user")
|
|
2343
3834
|
}),
|
|
2344
3835
|
async commit() {
|
|
2345
|
-
|
|
2346
|
-
|
|
3836
|
+
requestClosed = true;
|
|
3837
|
+
try {
|
|
3838
|
+
await tx.commit();
|
|
3839
|
+
await serviceTx?.commit();
|
|
3840
|
+
trace?.complete("committed");
|
|
3841
|
+
} catch (error) {
|
|
3842
|
+
trace?.failed();
|
|
3843
|
+
throw error;
|
|
3844
|
+
}
|
|
2347
3845
|
},
|
|
2348
3846
|
async rollback(reason) {
|
|
3847
|
+
requestClosed = true;
|
|
2349
3848
|
await tx.rollback(reason);
|
|
2350
3849
|
await serviceTx?.rollback(reason);
|
|
3850
|
+
trace?.complete("failed");
|
|
2351
3851
|
}
|
|
2352
3852
|
};
|
|
2353
3853
|
}
|
|
@@ -2357,8 +3857,13 @@ var Args = class Args2 {
|
|
|
2357
3857
|
__name(this, "Args");
|
|
2358
3858
|
}
|
|
2359
3859
|
values = [];
|
|
2360
|
-
|
|
3860
|
+
columns = [];
|
|
3861
|
+
bind(value, column, table) {
|
|
2361
3862
|
this.values.push(value);
|
|
3863
|
+
this.columns.push({
|
|
3864
|
+
column,
|
|
3865
|
+
table
|
|
3866
|
+
});
|
|
2362
3867
|
return `$${this.values.length}`;
|
|
2363
3868
|
}
|
|
2364
3869
|
};
|
|
@@ -2444,7 +3949,7 @@ function planWhere(table, where, args, results) {
|
|
|
2444
3949
|
}
|
|
2445
3950
|
const resolved = resolveRefsDeep(where, results);
|
|
2446
3951
|
const known = schemaColumns(table);
|
|
2447
|
-
const bare = compileWhereBare(table, known, resolved, (v) => args.bind(v), "transaction plan");
|
|
3952
|
+
const bare = compileWhereBare(table, known, resolved, (v, column, from) => args.bind(v, column, from), "transaction plan");
|
|
2448
3953
|
return {
|
|
2449
3954
|
sql: bare === "" ? "" : ` AND ${bare}`,
|
|
2450
3955
|
resolved
|
|
@@ -2453,16 +3958,16 @@ function planWhere(table, where, args, results) {
|
|
|
2453
3958
|
__name(planWhere, "planWhere");
|
|
2454
3959
|
function bindMaybeVector(args, column, vectorCols, value, transforms) {
|
|
2455
3960
|
if (column !== void 0 && vectorCols?.has(column) && Array.isArray(value)) {
|
|
2456
|
-
return args.bind(toVectorLiteral(value));
|
|
3961
|
+
return args.bind(toVectorLiteral(value), column);
|
|
2457
3962
|
}
|
|
2458
3963
|
const t = column !== void 0 ? transforms?.get(column) : void 0;
|
|
2459
3964
|
if (t?.toDb !== void 0 && value !== null && value !== void 0) {
|
|
2460
|
-
return args.bind(t.toDb(value));
|
|
3965
|
+
return args.bind(t.toDb(value), column);
|
|
2461
3966
|
}
|
|
2462
|
-
return args.bind(value);
|
|
3967
|
+
return args.bind(value, column);
|
|
2463
3968
|
}
|
|
2464
3969
|
__name(bindMaybeVector, "bindMaybeVector");
|
|
2465
|
-
|
|
3970
|
+
function compilePlanOp(op, results) {
|
|
2466
3971
|
const opVectorCols = vectorColumnsOf(currentSchema, op.table);
|
|
2467
3972
|
const opTransforms = transformsOf(currentSchema, op.table);
|
|
2468
3973
|
const args = new Args();
|
|
@@ -2472,7 +3977,7 @@ async function runPlanOp(sp, op, results) {
|
|
|
2472
3977
|
case "insert": {
|
|
2473
3978
|
const cols = Object.keys(op.values ?? {});
|
|
2474
3979
|
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
|
|
3980
|
+
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
3981
|
break;
|
|
2477
3982
|
}
|
|
2478
3983
|
case "upsert": {
|
|
@@ -2485,17 +3990,17 @@ async function runPlanOp(sp, op, results) {
|
|
|
2485
3990
|
});
|
|
2486
3991
|
}
|
|
2487
3992
|
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
|
|
3993
|
+
sql = `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES (${rendered.join(", ")}) ${onConflictTail(cols, conflict, "update")} RETURNING ${rowProjection(op.table)}`;
|
|
2489
3994
|
break;
|
|
2490
3995
|
}
|
|
2491
3996
|
case "insertMany": {
|
|
2492
3997
|
const rows = op.rows ?? [];
|
|
2493
|
-
if (rows.length === 0 || !rows[0]) return
|
|
3998
|
+
if (rows.length === 0 || !rows[0]) return null;
|
|
2494
3999
|
const cols = Object.keys(rows[0]);
|
|
2495
4000
|
const tuples = rows.map((r) => `(${cols.map((c) => renderValue(r[c], c, args, results, opVectorCols, opTransforms)).join(", ")})`);
|
|
2496
4001
|
const conflictCols = op.onConflict ?? [];
|
|
2497
4002
|
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
|
|
4003
|
+
sql = `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES ${tuples.join(", ")}${tail} RETURNING ${rowProjection(op.table)}`;
|
|
2499
4004
|
break;
|
|
2500
4005
|
}
|
|
2501
4006
|
case "update": {
|
|
@@ -2511,12 +4016,12 @@ async function runPlanOp(sp, op, results) {
|
|
|
2511
4016
|
const assignments = cols.map((c) => `${quoteIdent(c)} = ${renderValue(op.set[c], c, args, results, opVectorCols, opTransforms)}`);
|
|
2512
4017
|
const planned = planWhere(op.table, op.where, args, results);
|
|
2513
4018
|
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
|
|
4019
|
+
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)} AS "__pb_lock" 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."__pb_lock" RETURNING ${rowProjection(op.table, "t")}`;
|
|
2515
4020
|
break;
|
|
2516
4021
|
}
|
|
2517
4022
|
case "delete": {
|
|
2518
4023
|
assertNotAppendOnly("deleteWhere", op.table);
|
|
2519
|
-
sql = `DELETE FROM ${table} AS t WHERE true${renderWhere(op.table, op.where, args, results)} RETURNING t
|
|
4024
|
+
sql = `DELETE FROM ${table} AS t WHERE true${renderWhere(op.table, op.where, args, results)} RETURNING ${rowProjection(op.table, "t")}`;
|
|
2520
4025
|
break;
|
|
2521
4026
|
}
|
|
2522
4027
|
case "select": {
|
|
@@ -2529,15 +4034,157 @@ async function runPlanOp(sp, op, results) {
|
|
|
2529
4034
|
limit = ` LIMIT ${n}`;
|
|
2530
4035
|
}
|
|
2531
4036
|
const lock = op.lock === "update" ? " FOR UPDATE" : "";
|
|
2532
|
-
sql = `SELECT t
|
|
4037
|
+
sql = `SELECT ${rowProjection(op.table, "t")} FROM ${table} t WHERE true${renderWhere(op.table, op.where, args, results)}${limit}${lock}`;
|
|
2533
4038
|
break;
|
|
2534
4039
|
}
|
|
2535
4040
|
default:
|
|
2536
4041
|
throw new Error(`unknown operation "${String(op.op)}" in a transaction plan`);
|
|
2537
4042
|
}
|
|
2538
|
-
return
|
|
4043
|
+
return {
|
|
4044
|
+
sql,
|
|
4045
|
+
params: args.values,
|
|
4046
|
+
columns: args.columns
|
|
4047
|
+
};
|
|
4048
|
+
}
|
|
4049
|
+
__name(compilePlanOp, "compilePlanOp");
|
|
4050
|
+
async function runPlanOp(sp, op, results) {
|
|
4051
|
+
const statement = compilePlanOp(op, results);
|
|
4052
|
+
return statement ? await sp.unsafe(statement.sql, statement.params) : [];
|
|
2539
4053
|
}
|
|
2540
4054
|
__name(runPlanOp, "runPlanOp");
|
|
4055
|
+
function compileCommand(plan) {
|
|
4056
|
+
const symbols = /* @__PURE__ */ new Map();
|
|
4057
|
+
const results = [];
|
|
4058
|
+
const steps = [];
|
|
4059
|
+
const pipeline = [];
|
|
4060
|
+
let hasRefs = false;
|
|
4061
|
+
const prefix = `command:${crypto.randomUUID()}:`;
|
|
4062
|
+
const containsFragment = /* @__PURE__ */ __name((v) => isSqlFragment(v) || v !== null && typeof v === "object" && Object.values(v).some(containsFragment), "containsFragment");
|
|
4063
|
+
const hasNullableFilterRef = /* @__PURE__ */ __name((v) => {
|
|
4064
|
+
if (isRef(v)) {
|
|
4065
|
+
const source = plan.ops[v.$ref.op];
|
|
4066
|
+
return source !== void 0 && pageSchema(source.table).columns[v.$ref.field]?.nullable !== false;
|
|
4067
|
+
}
|
|
4068
|
+
return v !== null && typeof v === "object" && Object.values(v).some(hasNullableFilterRef);
|
|
4069
|
+
}, "hasNullableFilterRef");
|
|
4070
|
+
for (const op of plan.ops) {
|
|
4071
|
+
if (!schemaColumns(op.table)?.size) return {
|
|
4072
|
+
reason: `table ${op.table} has no installed declaration`
|
|
4073
|
+
};
|
|
4074
|
+
if (transformsOf(currentSchema, op.table).size || vectorColumnsOf(currentSchema, op.table).size) return {
|
|
4075
|
+
reason: `${op.table} uses JavaScript codecs/transforms or vector values`
|
|
4076
|
+
};
|
|
4077
|
+
if (containsFragment(op)) return {
|
|
4078
|
+
reason: "SQL fragments require the sequential path"
|
|
4079
|
+
};
|
|
4080
|
+
if (hasNullableFilterRef(op.where)) return {
|
|
4081
|
+
reason: "nullable references in filters require the sequential path"
|
|
4082
|
+
};
|
|
4083
|
+
}
|
|
4084
|
+
for (const [index, op] of plan.ops.entries()) {
|
|
4085
|
+
const statement = compilePlanOp(op, results);
|
|
4086
|
+
if (statement === null) {
|
|
4087
|
+
steps.push({
|
|
4088
|
+
empty: true,
|
|
4089
|
+
guard: op.guard
|
|
4090
|
+
});
|
|
4091
|
+
pipeline.push({
|
|
4092
|
+
sql: "SELECT palbase_runtime.command_rows_v1('[]'::jsonb,$1::jsonb) AS rows",
|
|
4093
|
+
params: [
|
|
4094
|
+
op.guard ?? null
|
|
4095
|
+
]
|
|
4096
|
+
});
|
|
4097
|
+
} else {
|
|
4098
|
+
if (statement.params.some((v) => v === void 0 || typeof v === "symbol" || typeof v === "function" || typeof v === "number" && (!Number.isInteger(v) || v < -2147483648 || v > 2147483647))) {
|
|
4099
|
+
return {
|
|
4100
|
+
reason: "non-integer/wide numeric or non-JSON parameters require the sequential driver path; use exact decimal strings"
|
|
4101
|
+
};
|
|
4102
|
+
}
|
|
4103
|
+
for (const [i, value] of statement.params.entries()) {
|
|
4104
|
+
const target = statement.columns[i];
|
|
4105
|
+
if (target.column === void 0 || columnTypeOf(target.table ?? op.table, target.column) !== "jsonb") continue;
|
|
4106
|
+
const source = typeof value === "string" ? symbols.get(value) : void 0;
|
|
4107
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint" || source && "ref" in source && ![
|
|
4108
|
+
"text",
|
|
4109
|
+
"uuid",
|
|
4110
|
+
"timestamp",
|
|
4111
|
+
"enum"
|
|
4112
|
+
].includes(columnTypeOf(plan.ops[source.ref.op].table, source.ref.field) ?? "")) {
|
|
4113
|
+
return {
|
|
4114
|
+
reason: "JSONB scalar/ref binding requires the sequential driver path"
|
|
4115
|
+
};
|
|
4116
|
+
}
|
|
4117
|
+
}
|
|
4118
|
+
const wide = [
|
|
4119
|
+
...schemaColumns(op.table)
|
|
4120
|
+
].filter((c) => [
|
|
4121
|
+
"bigint",
|
|
4122
|
+
"numeric"
|
|
4123
|
+
].includes(columnTypeOf(op.table, c) ?? ""));
|
|
4124
|
+
const exact = wide.length ? ` || jsonb_build_object(${wide.map((c) => `${quoteLiteral(c)}, r.${quoteIdent(c)}::text`).join(", ")})` : "";
|
|
4125
|
+
const sql = `WITH command_rows AS (${statement.sql}) SELECT coalesce(jsonb_agg(to_jsonb(r)${exact}), '[]'::jsonb) FROM command_rows r`;
|
|
4126
|
+
pipeline.push({
|
|
4127
|
+
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`,
|
|
4128
|
+
params: [
|
|
4129
|
+
...statement.params,
|
|
4130
|
+
op.guard ?? null
|
|
4131
|
+
]
|
|
4132
|
+
});
|
|
4133
|
+
const bindings = statement.params.map((value, i) => {
|
|
4134
|
+
const target = statement.columns[i];
|
|
4135
|
+
const json = target.column !== void 0 && columnTypeOf(target.table ?? op.table, target.column) === "jsonb";
|
|
4136
|
+
if (typeof value === "string" && symbols.has(value)) {
|
|
4137
|
+
hasRefs = true;
|
|
4138
|
+
return {
|
|
4139
|
+
...symbols.get(value),
|
|
4140
|
+
json
|
|
4141
|
+
};
|
|
4142
|
+
}
|
|
4143
|
+
if (typeof value === "bigint") return {
|
|
4144
|
+
value: value.toString(),
|
|
4145
|
+
json
|
|
4146
|
+
};
|
|
4147
|
+
if (value instanceof Date) return {
|
|
4148
|
+
value: value.toISOString(),
|
|
4149
|
+
json
|
|
4150
|
+
};
|
|
4151
|
+
return {
|
|
4152
|
+
value,
|
|
4153
|
+
json
|
|
4154
|
+
};
|
|
4155
|
+
});
|
|
4156
|
+
steps.push({
|
|
4157
|
+
chunks: parameterChunks(sql),
|
|
4158
|
+
bindings,
|
|
4159
|
+
guard: op.guard
|
|
4160
|
+
});
|
|
4161
|
+
}
|
|
4162
|
+
const row = {};
|
|
4163
|
+
for (const field of schemaColumns(op.table)) {
|
|
4164
|
+
const token = `${prefix}${index}:${field}`;
|
|
4165
|
+
symbols.set(token, {
|
|
4166
|
+
ref: {
|
|
4167
|
+
op: index,
|
|
4168
|
+
field
|
|
4169
|
+
}
|
|
4170
|
+
});
|
|
4171
|
+
row[field] = token;
|
|
4172
|
+
}
|
|
4173
|
+
results.push({
|
|
4174
|
+
rows: [
|
|
4175
|
+
row
|
|
4176
|
+
],
|
|
4177
|
+
rows_affected: 1
|
|
4178
|
+
});
|
|
4179
|
+
}
|
|
4180
|
+
return {
|
|
4181
|
+
steps,
|
|
4182
|
+
...hasRefs ? {} : {
|
|
4183
|
+
pipeline
|
|
4184
|
+
}
|
|
4185
|
+
};
|
|
4186
|
+
}
|
|
4187
|
+
__name(compileCommand, "compileCommand");
|
|
2541
4188
|
function assertGuard(op, result) {
|
|
2542
4189
|
const guard = op.guard;
|
|
2543
4190
|
if (!guard) return;
|
|
@@ -2551,6 +4198,33 @@ function assertGuard(op, result) {
|
|
|
2551
4198
|
}
|
|
2552
4199
|
__name(assertGuard, "assertGuard");
|
|
2553
4200
|
|
|
4201
|
+
// src/engine/cancellation.ts
|
|
4202
|
+
function withQueryCancellation(application, control) {
|
|
4203
|
+
if (application === control) throw new Error("Query cancellation requires a separate control pool");
|
|
4204
|
+
const close = /* @__PURE__ */ __name(async (driver) => {
|
|
4205
|
+
const d = driver;
|
|
4206
|
+
if (d.close) await d.close();
|
|
4207
|
+
else if (d.end) await d.end();
|
|
4208
|
+
}, "close");
|
|
4209
|
+
return {
|
|
4210
|
+
unsafe: /* @__PURE__ */ __name((sql, params) => application.unsafe(sql, params), "unsafe"),
|
|
4211
|
+
begin: /* @__PURE__ */ __name((fn) => application.begin(fn), "begin"),
|
|
4212
|
+
async cancelBackend(pid, startedAt) {
|
|
4213
|
+
await control.unsafe(`SELECT pg_cancel_backend(pid) FROM pg_stat_activity
|
|
4214
|
+
WHERE pid=$1::integer AND backend_start=$2::timestamptz
|
|
4215
|
+
AND usename=session_user AND datname=current_database() AND backend_type='client backend'`, [
|
|
4216
|
+
pid,
|
|
4217
|
+
startedAt
|
|
4218
|
+
]);
|
|
4219
|
+
},
|
|
4220
|
+
async close() {
|
|
4221
|
+
await close(application);
|
|
4222
|
+
await close(control);
|
|
4223
|
+
}
|
|
4224
|
+
};
|
|
4225
|
+
}
|
|
4226
|
+
__name(withQueryCancellation, "withQueryCancellation");
|
|
4227
|
+
|
|
2554
4228
|
// src/engine/router.ts
|
|
2555
4229
|
var ROOM_META = /* @__PURE__ */ Symbol.for("palbase.backend.room");
|
|
2556
4230
|
var CONTROLLER_META = /* @__PURE__ */ Symbol.for("palbase.backend.controllerMeta");
|
|
@@ -3055,10 +4729,13 @@ async function defaultSqlDriver(config) {
|
|
|
3055
4729
|
if (!g.Bun?.SQL) {
|
|
3056
4730
|
throw new BootRefused([], "boot refused: no SQL driver. Running outside Bun means the driver must be supplied \u2014 pass `sql` to createApp().");
|
|
3057
4731
|
}
|
|
3058
|
-
return new g.Bun.SQL({
|
|
4732
|
+
return withQueryCancellation(new g.Bun.SQL({
|
|
3059
4733
|
url: config.databaseUrl,
|
|
3060
4734
|
max: config.poolMax
|
|
3061
|
-
})
|
|
4735
|
+
}), new g.Bun.SQL({
|
|
4736
|
+
url: config.databaseUrl,
|
|
4737
|
+
max: 2
|
|
4738
|
+
}));
|
|
3062
4739
|
}
|
|
3063
4740
|
__name(defaultSqlDriver, "defaultSqlDriver");
|
|
3064
4741
|
var AUTHOR_ANSWERED = /* @__PURE__ */ new Set([
|
|
@@ -3075,13 +4752,25 @@ async function refuseUnlessAuthorized(client, spec, requestId, rolesOfCaller) {
|
|
|
3075
4752
|
spec.permission
|
|
3076
4753
|
]);
|
|
3077
4754
|
if (row?.ok !== true) {
|
|
3078
|
-
return envelope(
|
|
4755
|
+
return envelope(
|
|
4756
|
+
"forbidden",
|
|
4757
|
+
`This endpoint requires the "${spec.permission}" permission`,
|
|
4758
|
+
403,
|
|
4759
|
+
requestId,
|
|
4760
|
+
// FR-008 / D-7: reddin ÇARESİ gövdede — istemci `required`'ı görünce ilgili
|
|
4761
|
+
// nesneyi yeniden çeker (Entra CAE deseni); cümle değişmez.
|
|
4762
|
+
{
|
|
4763
|
+
required: spec.permission
|
|
4764
|
+
}
|
|
4765
|
+
);
|
|
3079
4766
|
}
|
|
3080
4767
|
}
|
|
3081
4768
|
if (spec.role !== void 0) {
|
|
3082
4769
|
const held = await rolesOfCaller();
|
|
3083
4770
|
if (!held.includes(spec.role)) {
|
|
3084
|
-
return envelope("forbidden", `This endpoint requires the "${spec.role}" role`, 403, requestId
|
|
4771
|
+
return envelope("forbidden", `This endpoint requires the "${spec.role}" role`, 403, requestId, {
|
|
4772
|
+
required_role: spec.role
|
|
4773
|
+
});
|
|
3085
4774
|
}
|
|
3086
4775
|
}
|
|
3087
4776
|
return null;
|
|
@@ -3117,6 +4806,11 @@ async function createApp(opts) {
|
|
|
3117
4806
|
const sql = opts.sql ?? await defaultSqlDriver(config);
|
|
3118
4807
|
await sql.unsafe("select 1");
|
|
3119
4808
|
setSchema(opts.schemas ?? []);
|
|
4809
|
+
setCursorKey(config.cursorSecret || config.serviceRoleKey || null);
|
|
4810
|
+
if (config.enableCommands) await installCommandExecutor(sql, [
|
|
4811
|
+
config.dbRole,
|
|
4812
|
+
config.dbServiceRole
|
|
4813
|
+
]);
|
|
3120
4814
|
setSecretReader(opts.secretReader ?? null);
|
|
3121
4815
|
const auth = new AuthVerifier({
|
|
3122
4816
|
jwksUrl: config.authJwksUrl,
|
|
@@ -3162,6 +4856,9 @@ async function createApp(opts) {
|
|
|
3162
4856
|
role: config.dbRole,
|
|
3163
4857
|
serviceRole: config.dbServiceRole,
|
|
3164
4858
|
claimsJson: "{}"
|
|
4859
|
+
}, config.databaseDiagnostics === void 0 ? void 0 : {
|
|
4860
|
+
...config.databaseDiagnostics,
|
|
4861
|
+
endpoint: "scheduled job"
|
|
3165
4862
|
});
|
|
3166
4863
|
try {
|
|
3167
4864
|
const out = await runWithRuntime(buildServices(db), fn);
|
|
@@ -3191,6 +4888,10 @@ async function createApp(opts) {
|
|
|
3191
4888
|
role: config.dbRole,
|
|
3192
4889
|
serviceRole: config.dbServiceRole,
|
|
3193
4890
|
claimsJson: JSON.stringify(callerClaims ?? {})
|
|
4891
|
+
}, config.databaseDiagnostics === void 0 ? void 0 : {
|
|
4892
|
+
...config.databaseDiagnostics,
|
|
4893
|
+
endpoint: "upload authorization",
|
|
4894
|
+
requestId
|
|
3194
4895
|
});
|
|
3195
4896
|
try {
|
|
3196
4897
|
const refusal = await refuseUnlessAuthorized(uploadDb.client, spec, requestId, makeRoleResolver(uploadDb.client));
|
|
@@ -3238,6 +4939,11 @@ async function createApp(opts) {
|
|
|
3238
4939
|
role: config.dbRole,
|
|
3239
4940
|
serviceRole: config.dbServiceRole,
|
|
3240
4941
|
claimsJson: JSON.stringify(claims ?? {})
|
|
4942
|
+
}, config.databaseDiagnostics === void 0 && meta.options.databaseBudget === void 0 ? void 0 : {
|
|
4943
|
+
...config.databaseDiagnostics,
|
|
4944
|
+
...meta.options.databaseBudget,
|
|
4945
|
+
endpoint: hit.entry.id,
|
|
4946
|
+
requestId
|
|
3241
4947
|
});
|
|
3242
4948
|
const rolesOfCaller = makeRoleResolver(db.client);
|
|
3243
4949
|
let dispatched = false;
|
|
@@ -3667,11 +5373,15 @@ export {
|
|
|
3667
5373
|
effectiveAuth,
|
|
3668
5374
|
RateLimiter,
|
|
3669
5375
|
makeMemoryCache,
|
|
5376
|
+
setCursorKey,
|
|
5377
|
+
COMMAND_EXECUTOR_DDL,
|
|
5378
|
+
installCommandExecutor,
|
|
3670
5379
|
quoteIdent,
|
|
3671
5380
|
createLazyTransaction,
|
|
3672
5381
|
createOps,
|
|
3673
5382
|
withTables,
|
|
3674
5383
|
createRequestDatabase,
|
|
5384
|
+
withQueryCancellation,
|
|
3675
5385
|
buildRouteTable,
|
|
3676
5386
|
matchRoute,
|
|
3677
5387
|
scrubSecrets,
|
|
@@ -3679,4 +5389,4 @@ export {
|
|
|
3679
5389
|
installEgressFence,
|
|
3680
5390
|
createApp
|
|
3681
5391
|
};
|
|
3682
|
-
//# sourceMappingURL=chunk-
|
|
5392
|
+
//# sourceMappingURL=chunk-XZPDPYVY.js.map
|