@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
package/dist/engine/index.cjs
CHANGED
|
@@ -10,9 +10,9 @@ var __export = (target, all) => {
|
|
|
10
10
|
};
|
|
11
11
|
var __copyProps = (to, from, except, desc) => {
|
|
12
12
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
-
for (let
|
|
14
|
-
if (!__hasOwnProp.call(to,
|
|
15
|
-
__defProp(to,
|
|
13
|
+
for (let key2 of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key2) && key2 !== except)
|
|
15
|
+
__defProp(to, key2, { get: () => from[key2], enumerable: !(desc = __getOwnPropDesc(from, key2)) || desc.enumerable });
|
|
16
16
|
}
|
|
17
17
|
return to;
|
|
18
18
|
};
|
|
@@ -23,6 +23,7 @@ var engine_exports = {};
|
|
|
23
23
|
__export(engine_exports, {
|
|
24
24
|
AuthVerifier: () => AuthVerifier,
|
|
25
25
|
BootRefused: () => BootRefused,
|
|
26
|
+
COMMAND_EXECUTOR_DDL: () => COMMAND_EXECUTOR_DDL,
|
|
26
27
|
DECLARATION_REFUSAL: () => DECLARATION_REFUSAL,
|
|
27
28
|
DeclarationRefused: () => DeclarationRefused,
|
|
28
29
|
RateLimiter: () => RateLimiter,
|
|
@@ -33,6 +34,7 @@ __export(engine_exports, {
|
|
|
33
34
|
createRequestDatabase: () => createRequestDatabase,
|
|
34
35
|
effectiveAuth: () => effectiveAuth,
|
|
35
36
|
hostAllowed: () => hostAllowed,
|
|
37
|
+
installCommandExecutor: () => installCommandExecutor,
|
|
36
38
|
installEgressFence: () => installEgressFence,
|
|
37
39
|
isDeclarationRefused: () => isDeclarationRefused,
|
|
38
40
|
loadConfig: () => loadConfig,
|
|
@@ -40,6 +42,8 @@ __export(engine_exports, {
|
|
|
40
42
|
matchRoute: () => matchRoute,
|
|
41
43
|
quoteIdent: () => quoteIdent,
|
|
42
44
|
scrubSecrets: () => scrubSecrets,
|
|
45
|
+
setCursorKey: () => setCursorKey,
|
|
46
|
+
withQueryCancellation: () => withQueryCancellation,
|
|
43
47
|
withTables: () => withTables
|
|
44
48
|
});
|
|
45
49
|
module.exports = __toCommonJS(engine_exports);
|
|
@@ -388,10 +392,10 @@ function encodeFilterValue(value, column) {
|
|
|
388
392
|
__name(encodeFilterValue, "encodeFilterValue");
|
|
389
393
|
function encodeFilterMap(map) {
|
|
390
394
|
const out = {};
|
|
391
|
-
for (const
|
|
392
|
-
const value = map[
|
|
395
|
+
for (const key2 of Object.keys(map).sort()) {
|
|
396
|
+
const value = map[key2];
|
|
393
397
|
if (value === void 0) continue;
|
|
394
|
-
out[
|
|
398
|
+
out[key2] = encodeFilterValue(value, key2);
|
|
395
399
|
}
|
|
396
400
|
return out;
|
|
397
401
|
}
|
|
@@ -413,12 +417,12 @@ function assertNoNestedHandles(value, column) {
|
|
|
413
417
|
__name(assertNoNestedHandles, "assertNoNestedHandles");
|
|
414
418
|
function encodeMap(map, allowColumnExpr) {
|
|
415
419
|
const out = {};
|
|
416
|
-
for (const
|
|
417
|
-
const value = map[
|
|
420
|
+
for (const key2 of Object.keys(map).sort()) {
|
|
421
|
+
const value = map[key2];
|
|
418
422
|
if (value === void 0) {
|
|
419
|
-
throw new TxPlanError(`${
|
|
423
|
+
throw new TxPlanError(`${key2} de\u011Feri undefined \u2014 bu bir yazma de\u011Feri de\u011Fil. Anahtar duruyor ama de\u011Feri yok, yani kolon sessizce YAZILMAZDI (istek g\xF6vdesinden gelen bir alan\u0131n eksik olmas\u0131 bu \u015Fekilde g\xF6r\xFCn\xFCr). Kolon varsay\u0131lan\u0131n\u0131 istiyorsan\u0131z anahtar\u0131 hi\xE7 koymay\u0131n; NULL yazmak istiyorsan\u0131z a\xE7\u0131k\xE7a null yaz\u0131n.`);
|
|
420
424
|
}
|
|
421
|
-
out[
|
|
425
|
+
out[key2] = encodeValue(value, key2, allowColumnExpr);
|
|
422
426
|
}
|
|
423
427
|
return out;
|
|
424
428
|
}
|
|
@@ -650,7 +654,7 @@ function materializeResult(value, results) {
|
|
|
650
654
|
if (Array.isArray(value)) return value.map((item) => materializeResult(item, results));
|
|
651
655
|
if (isPlainObject(value)) {
|
|
652
656
|
const out = {};
|
|
653
|
-
for (const [
|
|
657
|
+
for (const [key2, item] of Object.entries(value)) out[key2] = materializeResult(item, results);
|
|
654
658
|
return out;
|
|
655
659
|
}
|
|
656
660
|
return value;
|
|
@@ -699,162 +703,71 @@ function translateRejection(err, builder) {
|
|
|
699
703
|
}
|
|
700
704
|
__name(translateRejection, "translateRejection");
|
|
701
705
|
|
|
702
|
-
// src/
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
706
|
+
// src/db/workload.ts
|
|
707
|
+
function validateWorkload(value) {
|
|
708
|
+
if (typeof value.name !== "string" || !/^[a-zA-Z][a-zA-Z0-9_.-]{0,63}$/.test(value.name)) throw new Error("workload name must be 1\u201364 letters/digits/dots/dashes");
|
|
709
|
+
for (const name of [
|
|
710
|
+
"concurrency",
|
|
711
|
+
"tenantConcurrency",
|
|
712
|
+
"queueLimit",
|
|
713
|
+
"tenantQueueLimit",
|
|
714
|
+
"queueTimeoutMs"
|
|
715
|
+
]) {
|
|
716
|
+
const n = value[name], min = name.endsWith("Limit") ? 0 : 1;
|
|
717
|
+
const max = name === "queueTimeoutMs" ? 6e4 : name.endsWith("Limit") ? 1e4 : 1e3;
|
|
718
|
+
if (!Number.isInteger(n) || n < min || n > max) throw new Error(`workload ${name} must be an integer between ${min} and ${max}`);
|
|
719
|
+
}
|
|
720
|
+
if (value.tenantConcurrency > value.concurrency || value.tenantQueueLimit > value.queueLimit) throw new Error("workload tenant limits cannot exceed global limits");
|
|
721
|
+
if (typeof value.tenantClaim !== "string" || !/^[a-zA-Z][a-zA-Z0-9_]{0,63}$/.test(value.tenantClaim)) throw new Error("workload tenantClaim must be a top-level claim name");
|
|
722
|
+
}
|
|
723
|
+
__name(validateWorkload, "validateWorkload");
|
|
724
|
+
|
|
725
|
+
// src/db/transaction-options.ts
|
|
726
|
+
function retryBudget(value) {
|
|
727
|
+
const n = value ?? 0;
|
|
728
|
+
if (!Number.isInteger(n) || n < 0 || n > 10) {
|
|
729
|
+
throw new Error("transaction retry must be an integer between 0 and 10");
|
|
730
|
+
}
|
|
731
|
+
return n;
|
|
718
732
|
}
|
|
719
|
-
__name(
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
733
|
+
__name(retryBudget, "retryBudget");
|
|
734
|
+
function validateAtomicOptions(options) {
|
|
735
|
+
retryBudget(options.retry);
|
|
736
|
+
if (options.isolation !== void 0 && ![
|
|
737
|
+
"read committed",
|
|
738
|
+
"repeatable read",
|
|
739
|
+
"serializable"
|
|
740
|
+
].includes(options.isolation)) {
|
|
741
|
+
throw new Error("transaction isolation must be read committed, repeatable read or serializable");
|
|
723
742
|
}
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
errorDescription;
|
|
727
|
-
data;
|
|
728
|
-
/** See {@link HTTP_ERROR_BRAND} — how the engine recognises this across SDK copies. */
|
|
729
|
-
[HTTP_ERROR_BRAND] = true;
|
|
730
|
-
constructor(status, error, errorDescription, data) {
|
|
731
|
-
super(errorDescription);
|
|
732
|
-
this.name = "HttpError";
|
|
733
|
-
this.status = status;
|
|
734
|
-
this.error = error;
|
|
735
|
-
this.errorDescription = errorDescription;
|
|
736
|
-
if (data !== void 0) {
|
|
737
|
-
this.data = data;
|
|
738
|
-
}
|
|
743
|
+
if (options.readOnly !== void 0 && typeof options.readOnly !== "boolean") {
|
|
744
|
+
throw new Error("transaction readOnly must be a boolean");
|
|
739
745
|
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
error: this.error,
|
|
749
|
-
error_description: this.errorDescription,
|
|
750
|
-
status: this.status
|
|
751
|
-
};
|
|
752
|
-
if (requestId) {
|
|
753
|
-
result.request_id = requestId;
|
|
746
|
+
for (const name of [
|
|
747
|
+
"lockTimeoutMs",
|
|
748
|
+
"statementTimeoutMs",
|
|
749
|
+
"timeoutMs"
|
|
750
|
+
]) {
|
|
751
|
+
const n = options[name];
|
|
752
|
+
if (n !== void 0 && (!Number.isInteger(n) || n < 1 || n > 2147483647)) {
|
|
753
|
+
throw new Error(`transaction ${name} must be a positive integer in milliseconds`);
|
|
754
754
|
}
|
|
755
|
-
if (this.data !== void 0) {
|
|
756
|
-
result.data = this.data;
|
|
757
|
-
}
|
|
758
|
-
return result;
|
|
759
755
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
__name(this, "NamedHttpError");
|
|
756
|
+
if (options.workload !== void 0) validateWorkload(options.workload);
|
|
757
|
+
if (options.conflictKeys !== void 0 && (!options.workload || !Array.isArray(options.conflictKeys) || options.conflictKeys.length > 64 || options.conflictKeys.some((k) => typeof k !== "string" || k.length < 1 || k.length > 256))) {
|
|
758
|
+
throw new Error("conflictKeys requires a workload and at most 64 nonempty keys of at most 256 characters");
|
|
764
759
|
}
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
this.name = name;
|
|
760
|
+
if (options.signal !== void 0 && (typeof options.signal.aborted !== "boolean" || typeof options.signal.addEventListener !== "function")) {
|
|
761
|
+
throw new Error("transaction signal must be an AbortSignal");
|
|
768
762
|
}
|
|
769
|
-
};
|
|
770
|
-
function defaultMessage(name) {
|
|
771
|
-
const spaced = name.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
|
|
772
|
-
return spaced.charAt(0).toUpperCase() + spaced.slice(1).toLowerCase();
|
|
773
763
|
}
|
|
774
|
-
__name(
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
764
|
+
__name(validateAtomicOptions, "validateAtomicOptions");
|
|
765
|
+
function assertPlanRetry(options) {
|
|
766
|
+
if (retryBudget(options?.retry) !== 0) {
|
|
767
|
+
throw new Error("Database.$transaction is a savepoint plan on the request transaction and cannot retry its snapshot or COMMIT. Use Database.$atomic(async tx => { ... }, { retry, isolation }) and put all decision reads and writes inside that callback.");
|
|
778
768
|
}
|
|
779
|
-
constructor(message, code, data) {
|
|
780
|
-
super(409, "conflict", "Conflict", message, code, data);
|
|
781
|
-
}
|
|
782
|
-
};
|
|
783
|
-
var UniqueViolation = class extends Conflict {
|
|
784
|
-
static {
|
|
785
|
-
__name(this, "UniqueViolation");
|
|
786
|
-
}
|
|
787
|
-
/**
|
|
788
|
-
* Whether `e` is a unique violation — REGARDLESS of which copy of this SDK
|
|
789
|
-
* constructed it.
|
|
790
|
-
*
|
|
791
|
-
* Use this instead of `instanceof`. Measured on a live stack: a controller
|
|
792
|
-
* bundle INLINES its own copy of `@palbase/backend`, and the engine that
|
|
793
|
-
* raises this error is the runtime's copy. Two copies, two class identities,
|
|
794
|
-
* and `e instanceof UniqueViolation` is false in the one place a caller
|
|
795
|
-
* writes it — a check that reads as correct and silently never matches.
|
|
796
|
-
*/
|
|
797
|
-
static is(e) {
|
|
798
|
-
return typeof e === "object" && e !== null && e.name === "UniqueViolation" && typeof e.constraint === "string";
|
|
799
|
-
}
|
|
800
|
-
/** The unique constraint the statement violated, as Postgres named it.
|
|
801
|
-
* `""` when the driver did not say which — see `engine/db.ts`. */
|
|
802
|
-
constraint;
|
|
803
|
-
constructor(constraint, message, code, data) {
|
|
804
|
-
super(message ?? "Unique constraint violated", code ?? "unique_violation", data);
|
|
805
|
-
this.name = "UniqueViolation";
|
|
806
|
-
this.constraint = constraint;
|
|
807
|
-
}
|
|
808
|
-
};
|
|
809
|
-
function isRetryable(e) {
|
|
810
|
-
return typeof e === "object" && e !== null && e.retryable === true && typeof e.sqlstate === "string";
|
|
811
769
|
}
|
|
812
|
-
__name(
|
|
813
|
-
var SerializationFailure = class extends Conflict {
|
|
814
|
-
static {
|
|
815
|
-
__name(this, "SerializationFailure");
|
|
816
|
-
}
|
|
817
|
-
retryable = true;
|
|
818
|
-
sqlstate;
|
|
819
|
-
static is(e) {
|
|
820
|
-
return typeof e === "object" && e !== null && e.name === "SerializationFailure";
|
|
821
|
-
}
|
|
822
|
-
constructor(sqlstate = "40001", message, data) {
|
|
823
|
-
super(message ?? "i\u015Flem e\u015Fzamanl\u0131 bir i\u015Flemle serile\u015Ftirilemedi \u2014 ayn\u0131 i\u015Flemi ba\u015Ftan \xE7al\u0131\u015Ft\u0131r\u0131n ($transaction'a { retry: n } verebilirsiniz)", "serialization_failure", data);
|
|
824
|
-
this.name = "SerializationFailure";
|
|
825
|
-
this.sqlstate = sqlstate;
|
|
826
|
-
}
|
|
827
|
-
};
|
|
828
|
-
var DeadlockDetected = class extends Conflict {
|
|
829
|
-
static {
|
|
830
|
-
__name(this, "DeadlockDetected");
|
|
831
|
-
}
|
|
832
|
-
retryable = true;
|
|
833
|
-
sqlstate;
|
|
834
|
-
static is(e) {
|
|
835
|
-
return typeof e === "object" && e !== null && e.name === "DeadlockDetected";
|
|
836
|
-
}
|
|
837
|
-
constructor(sqlstate = "40P01", message, data) {
|
|
838
|
-
super(message ?? "deadlock: iki i\u015Flem birbirinin kilidini bekledi ve bu i\u015Flem iptal edildi \u2014 kilit s\u0131ras\u0131n\u0131 sabitlemek i\xE7in Database.$lockRows(tablo, id'ler) kullan\u0131n", "deadlock_detected", data);
|
|
839
|
-
this.name = "DeadlockDetected";
|
|
840
|
-
this.sqlstate = sqlstate;
|
|
841
|
-
}
|
|
842
|
-
};
|
|
843
|
-
|
|
844
|
-
// src/db/typed-db.ts
|
|
845
|
-
async function withRetry(fn, opts = {}) {
|
|
846
|
-
const budget = opts.retry ?? 0;
|
|
847
|
-
let attempt = 0;
|
|
848
|
-
for (; ; ) {
|
|
849
|
-
try {
|
|
850
|
-
return await fn();
|
|
851
|
-
} catch (e) {
|
|
852
|
-
if (attempt >= budget || !isRetryable(e)) throw e;
|
|
853
|
-
attempt += 1;
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
__name(withRetry, "withRetry");
|
|
770
|
+
__name(assertPlanRetry, "assertPlanRetry");
|
|
858
771
|
|
|
859
772
|
// src/db/schema-json.ts
|
|
860
773
|
function qualifiedTableKey(schemaName, tableName) {
|
|
@@ -951,10 +864,10 @@ async function __runStartHooks() {
|
|
|
951
864
|
};
|
|
952
865
|
}
|
|
953
866
|
__name(__runStartHooks, "__runStartHooks");
|
|
954
|
-
function makeServiceProxy(
|
|
867
|
+
function makeServiceProxy(key2) {
|
|
955
868
|
const handler = {
|
|
956
869
|
get(_target, prop, receiver) {
|
|
957
|
-
const client = __getRuntime()[
|
|
870
|
+
const client = __getRuntime()[key2];
|
|
958
871
|
const value = Reflect.get(client, prop, receiver);
|
|
959
872
|
return typeof value === "function" ? value.bind(client) : value;
|
|
960
873
|
}
|
|
@@ -977,6 +890,14 @@ function makeTableProxy(ops, prefix) {
|
|
|
977
890
|
update: /* @__PURE__ */ __name((q) => ops().update(name, q.where.id, q.set), "update"),
|
|
978
891
|
delete: /* @__PURE__ */ __name((id) => ops().delete(name, id), "delete"),
|
|
979
892
|
findById: /* @__PURE__ */ __name((id) => ops().findById(name, id), "findById"),
|
|
893
|
+
findUnique: /* @__PURE__ */ __name((q) => ops().findUnique(name, q.where, {
|
|
894
|
+
select: q.select,
|
|
895
|
+
with: q.with
|
|
896
|
+
}), "findUnique"),
|
|
897
|
+
page: /* @__PURE__ */ __name((q) => {
|
|
898
|
+
const { where, ...opts } = q ?? {};
|
|
899
|
+
return ops().page(name, where, opts);
|
|
900
|
+
}, "page"),
|
|
980
901
|
findMany: /* @__PURE__ */ __name((q) => {
|
|
981
902
|
const { where, ...opts } = q ?? {};
|
|
982
903
|
return ops().findMany(name, where, opts);
|
|
@@ -1014,10 +935,16 @@ function makeTypedSurface(raw) {
|
|
|
1014
935
|
const reco = raw;
|
|
1015
936
|
const ops = {
|
|
1016
937
|
$query: /* @__PURE__ */ __name((sql, params) => raw.query(sql, params), "$query"),
|
|
938
|
+
$diagnostics: /* @__PURE__ */ __name(() => raw.diagnostics(), "$diagnostics"),
|
|
939
|
+
$readQuery: /* @__PURE__ */ __name((query) => raw.readQuery(query), "$readQuery"),
|
|
940
|
+
$compileQuery: /* @__PURE__ */ __name((query) => raw.compileQuery(query), "$compileQuery"),
|
|
941
|
+
$explainQuery: /* @__PURE__ */ __name((query) => raw.explainQuery(query), "$explainQuery"),
|
|
1017
942
|
$insert: /* @__PURE__ */ __name((table, data) => raw.insert(table, data), "$insert"),
|
|
1018
943
|
$update: /* @__PURE__ */ __name((table, id, data) => raw.update(table, id, data), "$update"),
|
|
1019
944
|
$delete: /* @__PURE__ */ __name((table, id) => raw.delete(table, id), "$delete"),
|
|
1020
945
|
$findById: /* @__PURE__ */ __name((table, id) => raw.findById(table, id), "$findById"),
|
|
946
|
+
$findUnique: /* @__PURE__ */ __name((table, where, opts) => raw.findUnique(table, where, opts), "$findUnique"),
|
|
947
|
+
$page: /* @__PURE__ */ __name((table, query, opts) => raw.page(table, query, opts), "$page"),
|
|
1021
948
|
$findMany: /* @__PURE__ */ __name((table, query, opts) => raw.findMany(table, query, opts), "$findMany"),
|
|
1022
949
|
$put: /* @__PURE__ */ __name((table, data, opts) => raw.put(table, data, opts), "$put"),
|
|
1023
950
|
// `opts` İLETİLİR. Düşürüldüğü sürece `Database.$updateMany(t, w, s,
|
|
@@ -1034,20 +961,55 @@ function makeTypedSurface(raw) {
|
|
|
1034
961
|
$facets: /* @__PURE__ */ __name((table, params) => reco.facets(table, params), "$facets"),
|
|
1035
962
|
$claim: /* @__PURE__ */ __name((table, unique, extra) => reco.claim(table, unique, extra), "$claim"),
|
|
1036
963
|
$lockRows: /* @__PURE__ */ __name((table, ids) => reco.lockRows(table, ids), "$lockRows"),
|
|
1037
|
-
$advisoryXactLock: /* @__PURE__ */ __name((
|
|
964
|
+
$advisoryXactLock: /* @__PURE__ */ __name((key2) => reco.advisoryXactLock(key2), "$advisoryXactLock"),
|
|
1038
965
|
$aggregate: /* @__PURE__ */ __name((table, q) => raw.aggregate(table, q), "$aggregate"),
|
|
1039
966
|
$insertMany: /* @__PURE__ */ __name((table, rows, opts) => raw.insertMany(table, rows, opts), "$insertMany"),
|
|
1040
967
|
$supersede: /* @__PURE__ */ __name((table, id, row) => raw.supersede(table, id, row), "$supersede")
|
|
1041
968
|
};
|
|
1042
969
|
const base = Object.assign(ops, {
|
|
970
|
+
$atomic(fn, options) {
|
|
971
|
+
if (typeof raw.atomic !== "function") {
|
|
972
|
+
throw new Error("This Database handle cannot open a root transaction; nested $atomic is not supported");
|
|
973
|
+
}
|
|
974
|
+
const parent = __requestALS.getStore();
|
|
975
|
+
let runtime2;
|
|
976
|
+
try {
|
|
977
|
+
runtime2 = __getRuntime();
|
|
978
|
+
} catch {
|
|
979
|
+
}
|
|
980
|
+
return raw.atomic(async (tx) => {
|
|
981
|
+
const typed = makeTypedSurface(tx);
|
|
982
|
+
const invoke = /* @__PURE__ */ __name(() => fn(typed), "invoke");
|
|
983
|
+
if (!runtime2) return invoke();
|
|
984
|
+
const refuse = /* @__PURE__ */ __name(() => {
|
|
985
|
+
throw new Error("Database.$atomic owns one transaction and identity; nested roots and $asService are not allowed");
|
|
986
|
+
}, "refuse");
|
|
987
|
+
const ambient = Object.assign({}, tx, {
|
|
988
|
+
atomic: refuse,
|
|
989
|
+
asService: refuse
|
|
990
|
+
});
|
|
991
|
+
return __requestALS.run({
|
|
992
|
+
...parent,
|
|
993
|
+
runtime: {
|
|
994
|
+
...runtime2,
|
|
995
|
+
Database: ambient
|
|
996
|
+
}
|
|
997
|
+
}, invoke);
|
|
998
|
+
}, options);
|
|
999
|
+
},
|
|
1043
1000
|
// Both surfaces get it: a savepoint on the service transaction is as useful
|
|
1044
1001
|
// as one on the request's, and each is bound to its own connection.
|
|
1045
1002
|
$attempt: /* @__PURE__ */ __name((fn) => raw.attempt(fn), "$attempt"),
|
|
1046
1003
|
$transaction(fn, opts) {
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1004
|
+
assertPlanRetry(opts);
|
|
1005
|
+
const builder = new TxPlanBuilder();
|
|
1006
|
+
return runTxPlan(raw, makeTxPlanHandle(builder), builder, fn);
|
|
1007
|
+
},
|
|
1008
|
+
$command(fn, options) {
|
|
1009
|
+
const builder = new TxPlanBuilder();
|
|
1010
|
+
return runTxPlan({
|
|
1011
|
+
txPlan: /* @__PURE__ */ __name((plan) => raw.command(plan, options), "txPlan")
|
|
1012
|
+
}, makeTxPlanHandle(builder), builder, fn);
|
|
1051
1013
|
}
|
|
1052
1014
|
});
|
|
1053
1015
|
return new Proxy(base, {
|
|
@@ -1148,8 +1110,8 @@ var Flags = Object.assign({
|
|
|
1148
1110
|
get(flagName, defaultOrContext, maybeContext) {
|
|
1149
1111
|
return rawFlags.get(flagName, defaultOrContext, maybeContext);
|
|
1150
1112
|
},
|
|
1151
|
-
setOverride(
|
|
1152
|
-
return rawFlags.setOverride(
|
|
1113
|
+
setOverride(key2, value) {
|
|
1114
|
+
return rawFlags.setOverride(key2, value);
|
|
1153
1115
|
}
|
|
1154
1116
|
}, {
|
|
1155
1117
|
/**
|
|
@@ -1177,6 +1139,175 @@ var Flags = Object.assign({
|
|
|
1177
1139
|
});
|
|
1178
1140
|
var Realtime = makeServiceProxy("Realtime");
|
|
1179
1141
|
|
|
1142
|
+
// src/db/sqlstate.ts
|
|
1143
|
+
var SQLSTATE = Object.freeze({
|
|
1144
|
+
serializationFailure: "40001",
|
|
1145
|
+
deadlockDetected: "40P01",
|
|
1146
|
+
commandGuardFailed: "PBC01"
|
|
1147
|
+
});
|
|
1148
|
+
function isTransactionRollbackState(state) {
|
|
1149
|
+
return state === SQLSTATE.serializationFailure || state === SQLSTATE.deadlockDetected;
|
|
1150
|
+
}
|
|
1151
|
+
__name(isTransactionRollbackState, "isTransactionRollbackState");
|
|
1152
|
+
|
|
1153
|
+
// src/errors.ts
|
|
1154
|
+
var HTTP_ERROR_BRAND = /* @__PURE__ */ Symbol.for("palbase.backend.httpError");
|
|
1155
|
+
var ENGINE_RAISED = /* @__PURE__ */ Symbol.for("palbase.backend.engineRaised");
|
|
1156
|
+
function markEngineRaised(e) {
|
|
1157
|
+
e[ENGINE_RAISED] = true;
|
|
1158
|
+
return e;
|
|
1159
|
+
}
|
|
1160
|
+
__name(markEngineRaised, "markEngineRaised");
|
|
1161
|
+
function isEngineRaised(e) {
|
|
1162
|
+
return typeof e === "object" && e !== null && e[ENGINE_RAISED] === true;
|
|
1163
|
+
}
|
|
1164
|
+
__name(isEngineRaised, "isEngineRaised");
|
|
1165
|
+
function isHttpError(err) {
|
|
1166
|
+
if (typeof err !== "object" || err === null) return false;
|
|
1167
|
+
const e = err;
|
|
1168
|
+
return e[HTTP_ERROR_BRAND] === true && typeof e.status === "number" && typeof e.error === "string" && typeof e.errorDescription === "string";
|
|
1169
|
+
}
|
|
1170
|
+
__name(isHttpError, "isHttpError");
|
|
1171
|
+
var HttpError = class extends Error {
|
|
1172
|
+
static {
|
|
1173
|
+
__name(this, "HttpError");
|
|
1174
|
+
}
|
|
1175
|
+
status;
|
|
1176
|
+
error;
|
|
1177
|
+
errorDescription;
|
|
1178
|
+
data;
|
|
1179
|
+
/** See {@link HTTP_ERROR_BRAND} — how the engine recognises this across SDK copies. */
|
|
1180
|
+
[HTTP_ERROR_BRAND] = true;
|
|
1181
|
+
constructor(status, error, errorDescription, data) {
|
|
1182
|
+
super(errorDescription);
|
|
1183
|
+
this.name = "HttpError";
|
|
1184
|
+
this.status = status;
|
|
1185
|
+
this.error = error;
|
|
1186
|
+
this.errorDescription = errorDescription;
|
|
1187
|
+
if (data !== void 0) {
|
|
1188
|
+
this.data = data;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
/**
|
|
1192
|
+
* Serialize to the standard Palbase error response format.
|
|
1193
|
+
* The `requestId` is injected by the runtime layer from the request context.
|
|
1194
|
+
* When called without arguments (e.g. JSON.stringify), request_id is omitted.
|
|
1195
|
+
* When `data` is set, it is appended as a strict-superset field.
|
|
1196
|
+
*/
|
|
1197
|
+
toJSON(requestId) {
|
|
1198
|
+
const result = {
|
|
1199
|
+
error: this.error,
|
|
1200
|
+
error_description: this.errorDescription,
|
|
1201
|
+
status: this.status
|
|
1202
|
+
};
|
|
1203
|
+
if (requestId) {
|
|
1204
|
+
result.request_id = requestId;
|
|
1205
|
+
}
|
|
1206
|
+
if (this.data !== void 0) {
|
|
1207
|
+
result.data = this.data;
|
|
1208
|
+
}
|
|
1209
|
+
return result;
|
|
1210
|
+
}
|
|
1211
|
+
};
|
|
1212
|
+
var NamedHttpError = class NamedHttpError2 extends HttpError {
|
|
1213
|
+
static {
|
|
1214
|
+
__name(this, "NamedHttpError");
|
|
1215
|
+
}
|
|
1216
|
+
constructor(status, defaultCode, name, message, code, data) {
|
|
1217
|
+
super(status, code ?? defaultCode, message ?? defaultMessage(name), data);
|
|
1218
|
+
this.name = name;
|
|
1219
|
+
}
|
|
1220
|
+
};
|
|
1221
|
+
function defaultMessage(name) {
|
|
1222
|
+
const spaced = name.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
|
|
1223
|
+
return spaced.charAt(0).toUpperCase() + spaced.slice(1).toLowerCase();
|
|
1224
|
+
}
|
|
1225
|
+
__name(defaultMessage, "defaultMessage");
|
|
1226
|
+
var BadRequest = class extends NamedHttpError {
|
|
1227
|
+
static {
|
|
1228
|
+
__name(this, "BadRequest");
|
|
1229
|
+
}
|
|
1230
|
+
constructor(data, message) {
|
|
1231
|
+
super(400, "bad_request", "BadRequest", message, void 0, data);
|
|
1232
|
+
}
|
|
1233
|
+
};
|
|
1234
|
+
var Conflict = class extends NamedHttpError {
|
|
1235
|
+
static {
|
|
1236
|
+
__name(this, "Conflict");
|
|
1237
|
+
}
|
|
1238
|
+
constructor(message, code, data) {
|
|
1239
|
+
super(409, "conflict", "Conflict", message, code, data);
|
|
1240
|
+
}
|
|
1241
|
+
};
|
|
1242
|
+
var UniqueViolation = class extends Conflict {
|
|
1243
|
+
static {
|
|
1244
|
+
__name(this, "UniqueViolation");
|
|
1245
|
+
}
|
|
1246
|
+
/**
|
|
1247
|
+
* Whether `e` is a unique violation — REGARDLESS of which copy of this SDK
|
|
1248
|
+
* constructed it.
|
|
1249
|
+
*
|
|
1250
|
+
* Use this instead of `instanceof`. Measured on a live stack: a controller
|
|
1251
|
+
* bundle INLINES its own copy of `@palbase/backend`, and the engine that
|
|
1252
|
+
* raises this error is the runtime's copy. Two copies, two class identities,
|
|
1253
|
+
* and `e instanceof UniqueViolation` is false in the one place a caller
|
|
1254
|
+
* writes it — a check that reads as correct and silently never matches.
|
|
1255
|
+
*/
|
|
1256
|
+
static is(e) {
|
|
1257
|
+
return typeof e === "object" && e !== null && e.name === "UniqueViolation" && typeof e.constraint === "string";
|
|
1258
|
+
}
|
|
1259
|
+
/** The unique constraint the statement violated, as Postgres named it.
|
|
1260
|
+
* `""` when the driver did not say which — see `engine/db.ts`. */
|
|
1261
|
+
constraint;
|
|
1262
|
+
constructor(constraint, message, code, data) {
|
|
1263
|
+
super(message ?? "Unique constraint violated", code ?? "unique_violation", data);
|
|
1264
|
+
this.name = "UniqueViolation";
|
|
1265
|
+
this.constraint = constraint;
|
|
1266
|
+
}
|
|
1267
|
+
};
|
|
1268
|
+
function isRetryable(e) {
|
|
1269
|
+
return typeof e === "object" && e !== null && e.retryable === true && isTransactionRollbackState(e.sqlstate);
|
|
1270
|
+
}
|
|
1271
|
+
__name(isRetryable, "isRetryable");
|
|
1272
|
+
var SerializationFailure = class extends Conflict {
|
|
1273
|
+
static {
|
|
1274
|
+
__name(this, "SerializationFailure");
|
|
1275
|
+
}
|
|
1276
|
+
retryable = true;
|
|
1277
|
+
sqlstate;
|
|
1278
|
+
static is(e) {
|
|
1279
|
+
return typeof e === "object" && e !== null && e.name === "SerializationFailure";
|
|
1280
|
+
}
|
|
1281
|
+
constructor(sqlstate = SQLSTATE.serializationFailure, message, data) {
|
|
1282
|
+
super(message ?? "i\u015Flem e\u015Fzamanl\u0131 bir i\u015Flemle serile\u015Ftirilemedi \u2014 ayn\u0131 i\u015Flemi ba\u015Ftan \xE7al\u0131\u015Ft\u0131r\u0131n (karar okumalar\u0131 ve yazmalar\u0131 $atomic i\xE7ine al\u0131p { retry: n } verin)", "serialization_failure", data);
|
|
1283
|
+
this.name = "SerializationFailure";
|
|
1284
|
+
this.sqlstate = sqlstate;
|
|
1285
|
+
}
|
|
1286
|
+
};
|
|
1287
|
+
var DeadlockDetected = class extends Conflict {
|
|
1288
|
+
static {
|
|
1289
|
+
__name(this, "DeadlockDetected");
|
|
1290
|
+
}
|
|
1291
|
+
retryable = true;
|
|
1292
|
+
sqlstate;
|
|
1293
|
+
static is(e) {
|
|
1294
|
+
return typeof e === "object" && e !== null && e.name === "DeadlockDetected";
|
|
1295
|
+
}
|
|
1296
|
+
constructor(sqlstate = SQLSTATE.deadlockDetected, message, data) {
|
|
1297
|
+
super(message ?? "deadlock: iki i\u015Flem birbirinin kilidini bekledi ve bu i\u015Flem iptal edildi \u2014 kilit s\u0131ras\u0131n\u0131 sabitlemek i\xE7in Database.$lockRows(tablo, id'ler) kullan\u0131n", "deadlock_detected", data);
|
|
1298
|
+
this.name = "DeadlockDetected";
|
|
1299
|
+
this.sqlstate = sqlstate;
|
|
1300
|
+
}
|
|
1301
|
+
};
|
|
1302
|
+
var TooManyRequests = class extends NamedHttpError {
|
|
1303
|
+
static {
|
|
1304
|
+
__name(this, "TooManyRequests");
|
|
1305
|
+
}
|
|
1306
|
+
constructor(data, message) {
|
|
1307
|
+
super(429, "too_many_requests", "TooManyRequests", message, void 0, data);
|
|
1308
|
+
}
|
|
1309
|
+
};
|
|
1310
|
+
|
|
1180
1311
|
// src/engine/config.ts
|
|
1181
1312
|
var BootRefused = class extends Error {
|
|
1182
1313
|
static {
|
|
@@ -1227,6 +1358,8 @@ ${detail}`);
|
|
|
1227
1358
|
uploadSecret: env.PALBASE_UPLOAD_SECRET ?? "",
|
|
1228
1359
|
anonKey: env.PALBASE_ANON_KEY ?? "",
|
|
1229
1360
|
serviceRoleKey: env.PALBASE_SERVICE_ROLE_KEY ?? "",
|
|
1361
|
+
cursorSecret: env.PALBASE_CURSOR_SECRET,
|
|
1362
|
+
enableCommands: env.PALBASE_DB_COMMANDS === "true",
|
|
1230
1363
|
realtimeSecret: env.REALTIME_INGESTION_SECRET ?? "",
|
|
1231
1364
|
port,
|
|
1232
1365
|
dbRole: env.DB_ROLE ?? "backend_authenticated",
|
|
@@ -1235,7 +1368,14 @@ ${detail}`);
|
|
|
1235
1368
|
// (`roleBackendServiceRole = "backend_service_role"`, NOLOGIN BYPASSRLS),
|
|
1236
1369
|
// and the live database agrees (pg_roles.rolbypassrls = true).
|
|
1237
1370
|
dbServiceRole: env.DB_SERVICE_ROLE ?? "backend_service_role",
|
|
1238
|
-
poolMax
|
|
1371
|
+
poolMax,
|
|
1372
|
+
...env.PALBASE_DB_DIAGNOSTICS === "true" ? {
|
|
1373
|
+
databaseDiagnostics: {
|
|
1374
|
+
onComplete: /* @__PURE__ */ __name((diagnostics) => {
|
|
1375
|
+
console.log("palbase.database", JSON.stringify(diagnostics));
|
|
1376
|
+
}, "onComplete")
|
|
1377
|
+
}
|
|
1378
|
+
} : {}
|
|
1239
1379
|
};
|
|
1240
1380
|
}
|
|
1241
1381
|
__name(loadConfig, "loadConfig");
|
|
@@ -1351,14 +1491,14 @@ var AuthVerifier = class {
|
|
|
1351
1491
|
return null;
|
|
1352
1492
|
}
|
|
1353
1493
|
if (header.alg !== "ES256" || !header.kid) return null;
|
|
1354
|
-
const
|
|
1355
|
-
if (!
|
|
1494
|
+
const key2 = await this.key(header.kid);
|
|
1495
|
+
if (!key2) return null;
|
|
1356
1496
|
let ok = false;
|
|
1357
1497
|
try {
|
|
1358
1498
|
ok = await crypto.subtle.verify({
|
|
1359
1499
|
name: "ECDSA",
|
|
1360
1500
|
hash: "SHA-256"
|
|
1361
|
-
},
|
|
1501
|
+
}, key2, b64urlToBytes(sig), new TextEncoder().encode(`${h}.${p}`));
|
|
1362
1502
|
} catch {
|
|
1363
1503
|
return null;
|
|
1364
1504
|
}
|
|
@@ -1424,12 +1564,12 @@ var RateLimiter = class {
|
|
|
1424
1564
|
* wait (never 0 — a caller told to wait 0 comes straight back to the same
|
|
1425
1565
|
* refusal).
|
|
1426
1566
|
*/
|
|
1427
|
-
check(rule,
|
|
1567
|
+
check(rule, key2, now) {
|
|
1428
1568
|
if (!rule || !(rule.max > 0) || !(rule.window > 0)) return null;
|
|
1429
|
-
const bucket = this.buckets.get(
|
|
1569
|
+
const bucket = this.buckets.get(key2);
|
|
1430
1570
|
if (!bucket || now >= bucket.resetAt) {
|
|
1431
1571
|
if (this.buckets.size >= this.maxKeys) this.evict(now);
|
|
1432
|
-
this.buckets.set(
|
|
1572
|
+
this.buckets.set(key2, {
|
|
1433
1573
|
count: 1,
|
|
1434
1574
|
resetAt: now + rule.window * 1e3
|
|
1435
1575
|
});
|
|
@@ -1472,11 +1612,11 @@ function makeMemoryCache(opts = {}) {
|
|
|
1472
1612
|
const now = opts.now ?? (() => Date.now());
|
|
1473
1613
|
const store = /* @__PURE__ */ new Map();
|
|
1474
1614
|
const inflight = /* @__PURE__ */ new Map();
|
|
1475
|
-
const live = /* @__PURE__ */ __name((
|
|
1476
|
-
const e = store.get(
|
|
1615
|
+
const live = /* @__PURE__ */ __name((key2) => {
|
|
1616
|
+
const e = store.get(key2);
|
|
1477
1617
|
if (!e) return void 0;
|
|
1478
1618
|
if (e.expiresAt !== 0 && e.expiresAt <= now()) {
|
|
1479
|
-
store.delete(
|
|
1619
|
+
store.delete(key2);
|
|
1480
1620
|
return void 0;
|
|
1481
1621
|
}
|
|
1482
1622
|
return e;
|
|
@@ -1499,51 +1639,830 @@ function makeMemoryCache(opts = {}) {
|
|
|
1499
1639
|
if (victim) store.delete(victim[0]);
|
|
1500
1640
|
}
|
|
1501
1641
|
}, "evict");
|
|
1502
|
-
const set = /* @__PURE__ */ __name(async (
|
|
1503
|
-
if (store.size >= maxEntries && !store.has(
|
|
1504
|
-
store.set(
|
|
1642
|
+
const set = /* @__PURE__ */ __name(async (key2, value, ttl) => {
|
|
1643
|
+
if (store.size >= maxEntries && !store.has(key2)) evict();
|
|
1644
|
+
store.set(key2, {
|
|
1505
1645
|
value,
|
|
1506
1646
|
expiresAt: ttl && ttl > 0 ? now() + ttl * 1e3 : 0
|
|
1507
1647
|
});
|
|
1508
1648
|
}, "set");
|
|
1509
1649
|
return {
|
|
1510
|
-
async get(
|
|
1511
|
-
const e = live(
|
|
1650
|
+
async get(key2) {
|
|
1651
|
+
const e = live(key2);
|
|
1512
1652
|
return e ? e.value : null;
|
|
1513
1653
|
},
|
|
1514
1654
|
set,
|
|
1515
|
-
async del(
|
|
1516
|
-
store.delete(
|
|
1655
|
+
async del(key2) {
|
|
1656
|
+
store.delete(key2);
|
|
1517
1657
|
},
|
|
1518
|
-
async incr(
|
|
1519
|
-
const e = live(
|
|
1658
|
+
async incr(key2) {
|
|
1659
|
+
const e = live(key2);
|
|
1520
1660
|
const next = (typeof e?.value === "number" ? e.value : 0) + 1;
|
|
1521
|
-
store.set(
|
|
1661
|
+
store.set(key2, {
|
|
1522
1662
|
value: next,
|
|
1523
1663
|
expiresAt: e?.expiresAt ?? 0
|
|
1524
1664
|
});
|
|
1525
1665
|
return next;
|
|
1526
1666
|
},
|
|
1527
|
-
async getOrSet(
|
|
1528
|
-
const hit = live(
|
|
1667
|
+
async getOrSet(key2, ttl, fn) {
|
|
1668
|
+
const hit = live(key2);
|
|
1529
1669
|
if (hit) return hit.value;
|
|
1530
|
-
const running = inflight.get(
|
|
1670
|
+
const running = inflight.get(key2);
|
|
1531
1671
|
if (running) return running;
|
|
1532
1672
|
const fill = (async () => {
|
|
1533
1673
|
try {
|
|
1534
1674
|
const value = await fn();
|
|
1535
|
-
await set(
|
|
1675
|
+
await set(key2, value, ttl);
|
|
1536
1676
|
return value;
|
|
1537
1677
|
} finally {
|
|
1538
|
-
inflight.delete(
|
|
1678
|
+
inflight.delete(key2);
|
|
1539
1679
|
}
|
|
1540
1680
|
})();
|
|
1541
|
-
inflight.set(
|
|
1681
|
+
inflight.set(key2, fill);
|
|
1542
1682
|
return fill;
|
|
1543
1683
|
}
|
|
1544
|
-
};
|
|
1545
|
-
}
|
|
1546
|
-
__name(makeMemoryCache, "makeMemoryCache");
|
|
1684
|
+
};
|
|
1685
|
+
}
|
|
1686
|
+
__name(makeMemoryCache, "makeMemoryCache");
|
|
1687
|
+
|
|
1688
|
+
// src/engine/db.ts
|
|
1689
|
+
var import_node_async_hooks2 = require("async_hooks");
|
|
1690
|
+
|
|
1691
|
+
// src/engine/schema-cache.ts
|
|
1692
|
+
function memoTable(cache, schema, table, compute) {
|
|
1693
|
+
if (!schema.tables || !Object.hasOwn(schema.tables, table)) return compute();
|
|
1694
|
+
let tables = cache.get(schema);
|
|
1695
|
+
if (!tables) {
|
|
1696
|
+
tables = /* @__PURE__ */ new Map();
|
|
1697
|
+
cache.set(schema, tables);
|
|
1698
|
+
}
|
|
1699
|
+
if (!tables.has(table)) tables.set(table, compute());
|
|
1700
|
+
return tables.get(table);
|
|
1701
|
+
}
|
|
1702
|
+
__name(memoTable, "memoTable");
|
|
1703
|
+
function snapshotDeclaration(value, seen = /* @__PURE__ */ new WeakMap()) {
|
|
1704
|
+
if (value === null || typeof value !== "object") return value;
|
|
1705
|
+
const previous = seen.get(value);
|
|
1706
|
+
if (previous) return previous;
|
|
1707
|
+
if (value instanceof Date) return new Date(value.getTime());
|
|
1708
|
+
const result = Array.isArray(value) ? [] : Object.create(Object.getPrototypeOf(value));
|
|
1709
|
+
seen.set(value, result);
|
|
1710
|
+
for (const key2 of Reflect.ownKeys(value)) {
|
|
1711
|
+
if (Array.isArray(value) && key2 === "length") continue;
|
|
1712
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key2);
|
|
1713
|
+
if ("value" in descriptor) descriptor.value = snapshotDeclaration(descriptor.value, seen);
|
|
1714
|
+
Object.defineProperty(result, key2, descriptor);
|
|
1715
|
+
}
|
|
1716
|
+
return Object.freeze(result);
|
|
1717
|
+
}
|
|
1718
|
+
__name(snapshotDeclaration, "snapshotDeclaration");
|
|
1719
|
+
|
|
1720
|
+
// src/engine/cursor.ts
|
|
1721
|
+
var import_node_crypto = require("crypto");
|
|
1722
|
+
var key = null;
|
|
1723
|
+
var invalidPage = /* @__PURE__ */ __name((message, field = "page") => new BadRequest({
|
|
1724
|
+
fields: [
|
|
1725
|
+
{
|
|
1726
|
+
field,
|
|
1727
|
+
message
|
|
1728
|
+
}
|
|
1729
|
+
]
|
|
1730
|
+
}, message), "invalidPage");
|
|
1731
|
+
function setCursorKey(secret) {
|
|
1732
|
+
key = secret ? (0, import_node_crypto.createHash)("sha256").update("palbase:cursor:v1\0").update(secret).digest() : null;
|
|
1733
|
+
}
|
|
1734
|
+
__name(setCursorKey, "setCursorKey");
|
|
1735
|
+
function cursorKey() {
|
|
1736
|
+
if (!key) throw new Error("Database.page needs PALBASE_CURSOR_SECRET or PALBASE_SERVICE_ROLE_KEY shared by all replicas");
|
|
1737
|
+
return key;
|
|
1738
|
+
}
|
|
1739
|
+
__name(cursorKey, "cursorKey");
|
|
1740
|
+
function fingerprint(value) {
|
|
1741
|
+
const stable = /* @__PURE__ */ __name((v) => {
|
|
1742
|
+
if (typeof v === "bigint") return {
|
|
1743
|
+
$bigint: v.toString()
|
|
1744
|
+
};
|
|
1745
|
+
if (v instanceof Date) return {
|
|
1746
|
+
$date: v.toISOString()
|
|
1747
|
+
};
|
|
1748
|
+
if (Array.isArray(v)) return v.map(stable);
|
|
1749
|
+
if (v && typeof v === "object") {
|
|
1750
|
+
return Object.fromEntries(Object.keys(v).sort().map((k) => [
|
|
1751
|
+
k,
|
|
1752
|
+
stable(v[k])
|
|
1753
|
+
]));
|
|
1754
|
+
}
|
|
1755
|
+
return v;
|
|
1756
|
+
}, "stable");
|
|
1757
|
+
return (0, import_node_crypto.createHash)("sha256").update(JSON.stringify(stable(value))).digest("base64url");
|
|
1758
|
+
}
|
|
1759
|
+
__name(fingerprint, "fingerprint");
|
|
1760
|
+
function cursorIdentity(role, claimsJson) {
|
|
1761
|
+
const claims = JSON.parse(claimsJson);
|
|
1762
|
+
const { iat, exp, nbf, jti, ...authorization } = claims;
|
|
1763
|
+
return fingerprint({
|
|
1764
|
+
role,
|
|
1765
|
+
authorization
|
|
1766
|
+
});
|
|
1767
|
+
}
|
|
1768
|
+
__name(cursorIdentity, "cursorIdentity");
|
|
1769
|
+
function encodeCursor(secret, scope, values) {
|
|
1770
|
+
const nonce = (0, import_node_crypto.randomBytes)(12);
|
|
1771
|
+
const cipher = (0, import_node_crypto.createCipheriv)("aes-256-gcm", secret, nonce);
|
|
1772
|
+
cipher.setAAD(Buffer.from(scope));
|
|
1773
|
+
const body = Buffer.concat([
|
|
1774
|
+
cipher.update(JSON.stringify(values), "utf8"),
|
|
1775
|
+
cipher.final()
|
|
1776
|
+
]);
|
|
1777
|
+
const token = `p1.${Buffer.concat([
|
|
1778
|
+
nonce,
|
|
1779
|
+
cipher.getAuthTag(),
|
|
1780
|
+
body
|
|
1781
|
+
]).toString("base64url")}`;
|
|
1782
|
+
if (token.length > 16384) throw invalidPage("page cursor values are too large; choose shorter ordering columns");
|
|
1783
|
+
return token;
|
|
1784
|
+
}
|
|
1785
|
+
__name(encodeCursor, "encodeCursor");
|
|
1786
|
+
function decodeCursor(secret, scope, token, count) {
|
|
1787
|
+
try {
|
|
1788
|
+
if (typeof token !== "string" || token.length > 16384 || !/^p1\.[\w-]+$/.test(token)) throw new Error();
|
|
1789
|
+
const data = Buffer.from(token.slice(3), "base64url");
|
|
1790
|
+
if (data.length < 30 || data.toString("base64url") !== token.slice(3)) throw new Error();
|
|
1791
|
+
const cipher = (0, import_node_crypto.createDecipheriv)("aes-256-gcm", secret, data.subarray(0, 12));
|
|
1792
|
+
cipher.setAAD(Buffer.from(scope));
|
|
1793
|
+
cipher.setAuthTag(data.subarray(12, 28));
|
|
1794
|
+
const values = JSON.parse(Buffer.concat([
|
|
1795
|
+
cipher.update(data.subarray(28)),
|
|
1796
|
+
cipher.final()
|
|
1797
|
+
]).toString("utf8"));
|
|
1798
|
+
if (!Array.isArray(values) || values.length !== count || values.some((v) => v !== null && typeof v !== "string")) throw new Error();
|
|
1799
|
+
return values;
|
|
1800
|
+
} catch {
|
|
1801
|
+
throw invalidPage("Invalid cursor for this query or identity; restart from the first page", "cursor");
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
__name(decodeCursor, "decodeCursor");
|
|
1805
|
+
function pageOrder(columns, primaryKey, opts) {
|
|
1806
|
+
const raw = opts;
|
|
1807
|
+
if ("limit" in raw || "offset" in raw) throw invalidPage("page uses first/after or last/before, not limit/offset");
|
|
1808
|
+
const backward = opts.last !== void 0 || opts.before !== void 0;
|
|
1809
|
+
if (backward && (opts.first !== void 0 || opts.after !== void 0)) throw invalidPage("Use first/after or last/before in one page request");
|
|
1810
|
+
const size = (backward ? opts.last : opts.first) ?? 50;
|
|
1811
|
+
if (!Number.isInteger(size) || size < 1 || size > 1e3) throw invalidPage("page size must be an integer between 1 and 1000");
|
|
1812
|
+
if (primaryKey.length === 0) throw new Error("page requires a declared primary key for a unique, stable ordering");
|
|
1813
|
+
const input = opts.orderBy === void 0 ? [] : Array.isArray(opts.orderBy) ? opts.orderBy : [
|
|
1814
|
+
opts.orderBy
|
|
1815
|
+
];
|
|
1816
|
+
if (input.length > 16) throw invalidPage("page supports at most 16 ordering columns");
|
|
1817
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1818
|
+
const order = [];
|
|
1819
|
+
const add = /* @__PURE__ */ __name((o) => {
|
|
1820
|
+
const def = columns[o?.column];
|
|
1821
|
+
if (!Object.hasOwn(columns, o?.column) || !def) throw invalidPage(`page order column ${o?.column} is not declared`);
|
|
1822
|
+
if (!(/* @__PURE__ */ new Set([
|
|
1823
|
+
"uuid",
|
|
1824
|
+
"text",
|
|
1825
|
+
"integer",
|
|
1826
|
+
"bigint",
|
|
1827
|
+
"boolean",
|
|
1828
|
+
"timestamp",
|
|
1829
|
+
"numeric",
|
|
1830
|
+
"enum"
|
|
1831
|
+
])).has(def.type ?? "")) {
|
|
1832
|
+
throw invalidPage(`page cannot order ${o.column}: ${def.type ?? "unknown"} is not a supported scalar column`);
|
|
1833
|
+
}
|
|
1834
|
+
if (seen.has(o.column)) throw invalidPage(`page order column ${o.column} is repeated`);
|
|
1835
|
+
if (o.direction !== void 0 && o.direction !== "asc" && o.direction !== "desc") throw invalidPage("page direction must be asc or desc");
|
|
1836
|
+
if (o.nulls !== void 0 && o.nulls !== "first" && o.nulls !== "last") throw invalidPage("page nulls must be first or last");
|
|
1837
|
+
const direction = o.direction ?? "asc";
|
|
1838
|
+
seen.add(o.column);
|
|
1839
|
+
order.push({
|
|
1840
|
+
column: o.column,
|
|
1841
|
+
direction,
|
|
1842
|
+
nulls: o.nulls ?? (direction === "asc" ? "last" : "first"),
|
|
1843
|
+
nullable: def.nullable !== false
|
|
1844
|
+
});
|
|
1845
|
+
}, "add");
|
|
1846
|
+
for (const o of input) add(o);
|
|
1847
|
+
for (const column of primaryKey) if (!seen.has(column)) add({
|
|
1848
|
+
column,
|
|
1849
|
+
direction: order.at(-1)?.direction ?? "asc"
|
|
1850
|
+
});
|
|
1851
|
+
return {
|
|
1852
|
+
order,
|
|
1853
|
+
size,
|
|
1854
|
+
backward,
|
|
1855
|
+
token: backward ? opts.before : opts.after
|
|
1856
|
+
};
|
|
1857
|
+
}
|
|
1858
|
+
__name(pageOrder, "pageOrder");
|
|
1859
|
+
function pageBoundary(order, values, backward, bind, column, inclusive = false) {
|
|
1860
|
+
const refs = values.map((v) => bind(v));
|
|
1861
|
+
if (order.every((o) => !o.nullable && o.direction === order[0].direction) && values.every((v) => v !== null)) {
|
|
1862
|
+
const greater = order[0].direction === "asc" !== backward;
|
|
1863
|
+
return `(${order.map((o) => column(o.column)).join(", ")}) ${greater ? ">" : "<"}${inclusive ? "=" : ""} (${refs.join(", ")})`;
|
|
1864
|
+
}
|
|
1865
|
+
const parts = [];
|
|
1866
|
+
const prefix = [];
|
|
1867
|
+
for (let i = 0; i < order.length; i++) {
|
|
1868
|
+
const o = order[i], c = column(o.column), r = refs[i];
|
|
1869
|
+
const nullsLast = o.nulls === "last" !== backward;
|
|
1870
|
+
const cmp = o.direction === "asc" !== backward ? ">" : "<";
|
|
1871
|
+
const tail = values[i] === null ? nullsLast ? "false" : `${c} IS NOT NULL` : `(${c} ${cmp} ${r}${nullsLast ? ` OR ${c} IS NULL` : ""})`;
|
|
1872
|
+
parts.push(`(${[
|
|
1873
|
+
...prefix,
|
|
1874
|
+
tail
|
|
1875
|
+
].join(" AND ")})`);
|
|
1876
|
+
prefix.push(`${c} IS NOT DISTINCT FROM ${r}`);
|
|
1877
|
+
}
|
|
1878
|
+
if (inclusive) parts.push(`(${prefix.join(" AND ")})`);
|
|
1879
|
+
return `(${parts.join(" OR ")})`;
|
|
1880
|
+
}
|
|
1881
|
+
__name(pageBoundary, "pageBoundary");
|
|
1882
|
+
|
|
1883
|
+
// src/db/bulk.ts
|
|
1884
|
+
function validateInsertManyOptions(opts) {
|
|
1885
|
+
if (!opts) return;
|
|
1886
|
+
if (opts.returning !== void 0 && typeof opts.returning !== "boolean") throw new Error("insertMany returning must be a boolean");
|
|
1887
|
+
if (opts.action !== void 0 && opts.action !== "ignore" && opts.action !== "update") throw new Error("insertMany action must be ignore or update");
|
|
1888
|
+
if (opts.onConflict !== void 0 && (!Array.isArray(opts.onConflict) || opts.onConflict.some((c) => typeof c !== "string" || c.length === 0))) {
|
|
1889
|
+
throw new Error("insertMany onConflict must be an array of column names");
|
|
1890
|
+
}
|
|
1891
|
+
if (opts.action !== void 0 && !opts.onConflict?.length) throw new Error("insertMany action requires nonempty onConflict columns");
|
|
1892
|
+
}
|
|
1893
|
+
__name(validateInsertManyOptions, "validateInsertManyOptions");
|
|
1894
|
+
|
|
1895
|
+
// src/db/query-contract.ts
|
|
1896
|
+
function queryColumnContract(column) {
|
|
1897
|
+
const def = column && typeof column === "object" && "_def" in column ? column._def : column;
|
|
1898
|
+
if (!def?.type) throw new Error("Query contract requires declared column types");
|
|
1899
|
+
if (def.transform !== void 0 && def.codec === void 0) throw new Error("Query contracts require a named codec instead of a free-form column transform");
|
|
1900
|
+
return {
|
|
1901
|
+
type: def.type,
|
|
1902
|
+
nullable: def.nullable === true,
|
|
1903
|
+
...def.codec === void 0 ? {} : {
|
|
1904
|
+
codec: def.codec
|
|
1905
|
+
},
|
|
1906
|
+
...def.enumValues === void 0 ? {} : {
|
|
1907
|
+
enumValues: [
|
|
1908
|
+
...def.enumValues
|
|
1909
|
+
]
|
|
1910
|
+
},
|
|
1911
|
+
...def.dimensions === void 0 ? {} : {
|
|
1912
|
+
dimensions: def.dimensions
|
|
1913
|
+
}
|
|
1914
|
+
};
|
|
1915
|
+
}
|
|
1916
|
+
__name(queryColumnContract, "queryColumnContract");
|
|
1917
|
+
function stableContract(value) {
|
|
1918
|
+
if (Array.isArray(value)) return `[${value.map(stableContract).join(",")}]`;
|
|
1919
|
+
if (value && typeof value === "object") return `{${Object.keys(value).sort().map((key2) => `${JSON.stringify(key2)}:${stableContract(value[key2])}`).join(",")}}`;
|
|
1920
|
+
return JSON.stringify(value) ?? "null";
|
|
1921
|
+
}
|
|
1922
|
+
__name(stableContract, "stableContract");
|
|
1923
|
+
function assertReadContract(query, columns) {
|
|
1924
|
+
if (!query.options.select.length || new Set(query.options.select).size !== query.options.select.length) {
|
|
1925
|
+
throw new Error(`Query ${query.name}: select must contain distinct, explicit columns`);
|
|
1926
|
+
}
|
|
1927
|
+
for (const name of query.options.select) {
|
|
1928
|
+
if (!columns || !Object.hasOwn(columns, name) || !Object.hasOwn(query.columns, name) || stableContract(queryColumnContract(columns[name])) !== stableContract(query.columns[name])) {
|
|
1929
|
+
throw new Error(`Query ${query.name}: schema drift at ${query.table}.${name}; regenerate and review its response contract`);
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
__name(assertReadContract, "assertReadContract");
|
|
1934
|
+
|
|
1935
|
+
// src/db/unique.ts
|
|
1936
|
+
function uniqueKeysOf(table) {
|
|
1937
|
+
const columns = table.columns ?? {};
|
|
1938
|
+
const inlinePrimary = [], keys = [];
|
|
1939
|
+
for (const [name, value] of Object.entries(columns)) {
|
|
1940
|
+
const col = value && typeof value === "object" && "_def" in value ? value._def : value;
|
|
1941
|
+
if (col?.primaryKey) inlinePrimary.push(name);
|
|
1942
|
+
if (col?.unique) keys.push([
|
|
1943
|
+
name
|
|
1944
|
+
]);
|
|
1945
|
+
}
|
|
1946
|
+
const primary = table.primaryKey ?? inlinePrimary;
|
|
1947
|
+
if (primary.length) keys.unshift([
|
|
1948
|
+
...primary
|
|
1949
|
+
]);
|
|
1950
|
+
for (const key2 of table.unique ?? []) keys.push([
|
|
1951
|
+
...key2.columns
|
|
1952
|
+
]);
|
|
1953
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1954
|
+
return keys.filter((key2) => {
|
|
1955
|
+
if (!key2.length || new Set(key2).size !== key2.length || key2.some((col) => !Object.hasOwn(columns, col))) {
|
|
1956
|
+
throw new Error("Unique lookup requires a nonempty declared key of distinct, known columns");
|
|
1957
|
+
}
|
|
1958
|
+
const identity = JSON.stringify([
|
|
1959
|
+
...key2
|
|
1960
|
+
].sort());
|
|
1961
|
+
if (seen.has(identity)) return false;
|
|
1962
|
+
seen.add(identity);
|
|
1963
|
+
return true;
|
|
1964
|
+
});
|
|
1965
|
+
}
|
|
1966
|
+
__name(uniqueKeysOf, "uniqueKeysOf");
|
|
1967
|
+
function assertUniqueLookup(table, keys, where) {
|
|
1968
|
+
if (!where || typeof where !== "object" || Array.isArray(where)) throw new Error(`findUnique(${table}): where must contain one declared unique key`);
|
|
1969
|
+
const columns = Object.keys(where);
|
|
1970
|
+
if (!keys.some((key2) => key2.length === columns.length && key2.every((col) => Object.hasOwn(where, col)))) {
|
|
1971
|
+
throw new Error(`findUnique(${table}): supply exactly one declared unique key (${keys.map((key2) => key2.join(" + ")).join("; ") || "none declared"}); use findMany for other filters`);
|
|
1972
|
+
}
|
|
1973
|
+
for (const [name, value] of Object.entries(where)) {
|
|
1974
|
+
if (value === null || value === void 0 || typeof value === "object" && !(value instanceof Date) || typeof value === "function" || typeof value === "symbol") {
|
|
1975
|
+
throw new Error(`findUnique(${table}): ${name} requires a non-null scalar equality value`);
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
__name(assertUniqueLookup, "assertUniqueLookup");
|
|
1980
|
+
|
|
1981
|
+
// src/engine/commands.ts
|
|
1982
|
+
var installed = /* @__PURE__ */ new WeakSet();
|
|
1983
|
+
var hasCommandExecutor = /* @__PURE__ */ __name((sql) => installed.has(sql), "hasCommandExecutor");
|
|
1984
|
+
var COMMAND_EXECUTOR_DDL = `
|
|
1985
|
+
CREATE SCHEMA IF NOT EXISTS palbase_runtime;
|
|
1986
|
+
CREATE OR REPLACE FUNCTION palbase_runtime.command_v1(steps jsonb) RETURNS jsonb
|
|
1987
|
+
LANGUAGE plpgsql VOLATILE SECURITY INVOKER SET search_path = pg_catalog, public AS $pb_command$
|
|
1988
|
+
DECLARE
|
|
1989
|
+
results jsonb := '[]'::jsonb;
|
|
1990
|
+
step jsonb; chunk jsonb; binding jsonb; source jsonb; rows jsonb;
|
|
1991
|
+
statement text; rendered text; detail text; guard jsonb;
|
|
1992
|
+
index integer := 0; affected integer; passed boolean;
|
|
1993
|
+
BEGIN
|
|
1994
|
+
FOR step IN SELECT value FROM jsonb_array_elements(steps) LOOP
|
|
1995
|
+
IF step->>'empty' = 'true' THEN rows := '[]'::jsonb;
|
|
1996
|
+
ELSE
|
|
1997
|
+
statement := '';
|
|
1998
|
+
FOR chunk IN SELECT value FROM jsonb_array_elements(step->'chunks') LOOP
|
|
1999
|
+
IF jsonb_typeof(chunk) = 'string' THEN statement := statement || (chunk #>> '{}');
|
|
2000
|
+
ELSE
|
|
2001
|
+
binding := step->'bindings'->((chunk->>'bind')::integer);
|
|
2002
|
+
IF binding ? 'ref' THEN
|
|
2003
|
+
source := results->((binding->'ref'->>'op')::integer)->'rows'->0;
|
|
2004
|
+
IF source IS NULL OR NOT source ? (binding->'ref'->>'field') THEN
|
|
2005
|
+
RAISE EXCEPTION 'Command reference has no source row/column; use expectOne before reading it'
|
|
2006
|
+
USING ERRCODE = 'PBC02', DETAIL = jsonb_build_object('error_code','tx_ref_unresolved','op',index)::text;
|
|
2007
|
+
END IF;
|
|
2008
|
+
source := source->(binding->'ref'->>'field');
|
|
2009
|
+
ELSE source := binding->'value'; END IF;
|
|
2010
|
+
rendered := CASE WHEN source IS NULL OR source = 'null'::jsonb THEN NULL
|
|
2011
|
+
WHEN binding->>'json' = 'true' THEN source::text
|
|
2012
|
+
WHEN jsonb_typeof(source) = 'string' THEN source #>> '{}' ELSE source::text END;
|
|
2013
|
+
-- PostgreSQL quotes the value itself. Values reach this function as
|
|
2014
|
+
-- one bound JSON parameter, never as application-generated SQL text.
|
|
2015
|
+
statement := statement || quote_nullable(rendered);
|
|
2016
|
+
END IF;
|
|
2017
|
+
END LOOP;
|
|
2018
|
+
EXECUTE statement INTO rows;
|
|
2019
|
+
END IF;
|
|
2020
|
+
affected := jsonb_array_length(rows);
|
|
2021
|
+
guard := step->'guard';
|
|
2022
|
+
IF guard IS NOT NULL AND guard <> 'null'::jsonb THEN
|
|
2023
|
+
passed := CASE guard->>'kind'
|
|
2024
|
+
WHEN 'one' THEN affected = 1 WHEN 'none' THEN affected = 0
|
|
2025
|
+
WHEN 'atLeast' THEN affected >= (guard->>'n')::integer
|
|
2026
|
+
WHEN 'atMost' THEN affected <= (guard->>'n')::integer ELSE false END;
|
|
2027
|
+
IF NOT passed THEN
|
|
2028
|
+
RAISE EXCEPTION 'Command expectation failed'
|
|
2029
|
+
USING ERRCODE = 'PBC01', DETAIL = jsonb_build_object('error_code','tx_guard_failed','slot',guard->'slot','op',index)::text;
|
|
2030
|
+
END IF;
|
|
2031
|
+
END IF;
|
|
2032
|
+
results := results || jsonb_build_array(jsonb_build_object('rows',rows,'rows_affected',affected));
|
|
2033
|
+
index := index + 1;
|
|
2034
|
+
END LOOP;
|
|
2035
|
+
RETURN jsonb_build_object('results',results);
|
|
2036
|
+
EXCEPTION WHEN SQLSTATE 'PBC01' OR SQLSTATE 'PBC02' THEN
|
|
2037
|
+
GET STACKED DIAGNOSTICS detail = PG_EXCEPTION_DETAIL;
|
|
2038
|
+
RETURN jsonb_build_object('error',detail::jsonb);
|
|
2039
|
+
END
|
|
2040
|
+
$pb_command$;
|
|
2041
|
+
REVOKE ALL ON FUNCTION palbase_runtime.command_v1(jsonb) FROM PUBLIC;
|
|
2042
|
+
CREATE OR REPLACE FUNCTION palbase_runtime.command_rows_v1(rows jsonb, guard jsonb) RETURNS jsonb
|
|
2043
|
+
LANGUAGE plpgsql VOLATILE SECURITY INVOKER SET search_path = pg_catalog, public AS $pb_rows$
|
|
2044
|
+
DECLARE affected integer := jsonb_array_length(rows); passed boolean;
|
|
2045
|
+
BEGIN
|
|
2046
|
+
IF guard IS NOT NULL AND guard <> 'null'::jsonb THEN
|
|
2047
|
+
passed := CASE guard->>'kind'
|
|
2048
|
+
WHEN 'one' THEN affected = 1 WHEN 'none' THEN affected = 0
|
|
2049
|
+
WHEN 'atLeast' THEN affected >= (guard->>'n')::integer
|
|
2050
|
+
WHEN 'atMost' THEN affected <= (guard->>'n')::integer ELSE false END;
|
|
2051
|
+
IF NOT passed THEN
|
|
2052
|
+
RAISE EXCEPTION 'Command expectation failed'
|
|
2053
|
+
USING ERRCODE = 'PBC01', DETAIL = jsonb_build_object('error_code','tx_guard_failed','slot',guard->'slot')::text;
|
|
2054
|
+
END IF;
|
|
2055
|
+
END IF;
|
|
2056
|
+
RETURN rows;
|
|
2057
|
+
END
|
|
2058
|
+
$pb_rows$;
|
|
2059
|
+
REVOKE ALL ON FUNCTION palbase_runtime.command_rows_v1(jsonb,jsonb) FROM PUBLIC;
|
|
2060
|
+
`;
|
|
2061
|
+
async function installCommandExecutor(sql, roles) {
|
|
2062
|
+
const quote = /* @__PURE__ */ __name((s) => `"${s.replace(/"/g, '""')}"`, "quote");
|
|
2063
|
+
if (roles.length === 0) throw new Error("Command executor needs explicit runtime roles");
|
|
2064
|
+
await sql.begin(async (tx) => {
|
|
2065
|
+
await tx.unsafe("SELECT pg_advisory_xact_lock(196754721, 1)");
|
|
2066
|
+
await tx.unsafe(COMMAND_EXECUTOR_DDL);
|
|
2067
|
+
const list = [
|
|
2068
|
+
...new Set(roles)
|
|
2069
|
+
].map(quote).join(", ");
|
|
2070
|
+
await tx.unsafe(`GRANT USAGE ON SCHEMA palbase_runtime TO ${list}`);
|
|
2071
|
+
await tx.unsafe(`GRANT EXECUTE ON FUNCTION palbase_runtime.command_v1(jsonb) TO ${list}`);
|
|
2072
|
+
await tx.unsafe(`GRANT EXECUTE ON FUNCTION palbase_runtime.command_rows_v1(jsonb,jsonb) TO ${list}`);
|
|
2073
|
+
});
|
|
2074
|
+
installed.add(sql);
|
|
2075
|
+
}
|
|
2076
|
+
__name(installCommandExecutor, "installCommandExecutor");
|
|
2077
|
+
function parameterChunks(sql) {
|
|
2078
|
+
const chunks = [];
|
|
2079
|
+
let start = 0, quote = null;
|
|
2080
|
+
for (let i = 0; i < sql.length; i++) {
|
|
2081
|
+
const c = sql[i];
|
|
2082
|
+
if (quote) {
|
|
2083
|
+
if (c === quote) {
|
|
2084
|
+
if (sql[i + 1] === quote) i++;
|
|
2085
|
+
else quote = null;
|
|
2086
|
+
}
|
|
2087
|
+
continue;
|
|
2088
|
+
}
|
|
2089
|
+
if (c === "'" || c === '"') {
|
|
2090
|
+
quote = c;
|
|
2091
|
+
continue;
|
|
2092
|
+
}
|
|
2093
|
+
if (c !== "$") continue;
|
|
2094
|
+
const parameter = /^\$(\d+)\b/.exec(sql.slice(i));
|
|
2095
|
+
if (!parameter) continue;
|
|
2096
|
+
chunks.push(sql.slice(start, i), {
|
|
2097
|
+
bind: Number(parameter[1]) - 1
|
|
2098
|
+
});
|
|
2099
|
+
i += parameter[0].length - 1;
|
|
2100
|
+
start = i + 1;
|
|
2101
|
+
}
|
|
2102
|
+
chunks.push(sql.slice(start));
|
|
2103
|
+
return chunks;
|
|
2104
|
+
}
|
|
2105
|
+
__name(parameterChunks, "parameterChunks");
|
|
2106
|
+
|
|
2107
|
+
// src/engine/admission.ts
|
|
2108
|
+
var WorkloadAdmission = class {
|
|
2109
|
+
static {
|
|
2110
|
+
__name(this, "WorkloadAdmission");
|
|
2111
|
+
}
|
|
2112
|
+
definition;
|
|
2113
|
+
pending = [];
|
|
2114
|
+
active = 0;
|
|
2115
|
+
activeKeys = /* @__PURE__ */ new Set();
|
|
2116
|
+
tenants = /* @__PURE__ */ new Map();
|
|
2117
|
+
draining = false;
|
|
2118
|
+
constructor(definition) {
|
|
2119
|
+
this.definition = definition;
|
|
2120
|
+
validateWorkload(definition);
|
|
2121
|
+
}
|
|
2122
|
+
get snapshot() {
|
|
2123
|
+
return {
|
|
2124
|
+
active: this.active,
|
|
2125
|
+
queued: this.pending.length,
|
|
2126
|
+
tenants: this.tenants.size,
|
|
2127
|
+
keys: this.activeKeys.size
|
|
2128
|
+
};
|
|
2129
|
+
}
|
|
2130
|
+
acquire(tenant, keys, signal) {
|
|
2131
|
+
if (signal?.aborted) return Promise.reject(signal.reason ?? new Error("Database admission cancelled"));
|
|
2132
|
+
const normalized = [
|
|
2133
|
+
...new Set(keys)
|
|
2134
|
+
].sort();
|
|
2135
|
+
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"));
|
|
2136
|
+
const scoped = normalized;
|
|
2137
|
+
const globalFull = this.pending.length >= this.definition.queueLimit;
|
|
2138
|
+
const tenantFull = this.pending.filter((w) => w.tenant === tenant).length >= this.definition.tenantQueueLimit;
|
|
2139
|
+
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)));
|
|
2140
|
+
if (!canStart && (globalFull || tenantFull)) {
|
|
2141
|
+
return Promise.reject(new TooManyRequests({
|
|
2142
|
+
retryAfter: 1
|
|
2143
|
+
}, `Database workload ${this.definition.name}: ${tenantFull ? "tenant" : "global"} queue is full`));
|
|
2144
|
+
}
|
|
2145
|
+
return new Promise((grant, reject) => {
|
|
2146
|
+
const waiter = {
|
|
2147
|
+
tenant,
|
|
2148
|
+
keys: scoped,
|
|
2149
|
+
entered: performance.now(),
|
|
2150
|
+
grant,
|
|
2151
|
+
reject,
|
|
2152
|
+
cleanup: /* @__PURE__ */ __name(() => {
|
|
2153
|
+
}, "cleanup")
|
|
2154
|
+
};
|
|
2155
|
+
const remove = /* @__PURE__ */ __name((reason2) => {
|
|
2156
|
+
const index = this.pending.indexOf(waiter);
|
|
2157
|
+
if (index === -1) return;
|
|
2158
|
+
this.pending.splice(index, 1);
|
|
2159
|
+
waiter.cleanup();
|
|
2160
|
+
reject(reason2);
|
|
2161
|
+
this.drain();
|
|
2162
|
+
}, "remove");
|
|
2163
|
+
const abort = /* @__PURE__ */ __name(() => remove(signal?.reason ?? new Error("Database admission cancelled")), "abort");
|
|
2164
|
+
const timer = setTimeout(() => remove(new TooManyRequests({
|
|
2165
|
+
retryAfter: 1
|
|
2166
|
+
}, `Database workload ${this.definition.name}: queue deadline exceeded`)), this.definition.queueTimeoutMs);
|
|
2167
|
+
waiter.cleanup = () => {
|
|
2168
|
+
clearTimeout(timer);
|
|
2169
|
+
signal?.removeEventListener("abort", abort);
|
|
2170
|
+
};
|
|
2171
|
+
signal?.addEventListener("abort", abort, {
|
|
2172
|
+
once: true
|
|
2173
|
+
});
|
|
2174
|
+
this.pending.push(waiter);
|
|
2175
|
+
if (signal?.aborted) abort();
|
|
2176
|
+
else this.drain();
|
|
2177
|
+
});
|
|
2178
|
+
}
|
|
2179
|
+
drain() {
|
|
2180
|
+
if (this.draining) return;
|
|
2181
|
+
this.draining = true;
|
|
2182
|
+
try {
|
|
2183
|
+
const blockedKeys = /* @__PURE__ */ new Set();
|
|
2184
|
+
for (let index = 0; index < this.pending.length && this.active < this.definition.concurrency; ) {
|
|
2185
|
+
const waiter = this.pending[index];
|
|
2186
|
+
if ((this.tenants.get(waiter.tenant) ?? 0) >= this.definition.tenantConcurrency || waiter.keys.some((key2) => this.activeKeys.has(key2) || blockedKeys.has(key2))) {
|
|
2187
|
+
for (const key2 of waiter.keys) blockedKeys.add(key2);
|
|
2188
|
+
index++;
|
|
2189
|
+
continue;
|
|
2190
|
+
}
|
|
2191
|
+
this.pending.splice(index, 1);
|
|
2192
|
+
waiter.cleanup();
|
|
2193
|
+
this.active++;
|
|
2194
|
+
this.tenants.set(waiter.tenant, (this.tenants.get(waiter.tenant) ?? 0) + 1);
|
|
2195
|
+
for (const key2 of waiter.keys) this.activeKeys.add(key2);
|
|
2196
|
+
let released = false;
|
|
2197
|
+
waiter.grant({
|
|
2198
|
+
queueMs: performance.now() - waiter.entered,
|
|
2199
|
+
release: /* @__PURE__ */ __name(() => {
|
|
2200
|
+
if (released) return;
|
|
2201
|
+
released = true;
|
|
2202
|
+
this.active--;
|
|
2203
|
+
const left = this.tenants.get(waiter.tenant) - 1;
|
|
2204
|
+
if (left === 0) this.tenants.delete(waiter.tenant);
|
|
2205
|
+
else this.tenants.set(waiter.tenant, left);
|
|
2206
|
+
for (const key2 of waiter.keys) this.activeKeys.delete(key2);
|
|
2207
|
+
this.drain();
|
|
2208
|
+
}, "release")
|
|
2209
|
+
});
|
|
2210
|
+
}
|
|
2211
|
+
} finally {
|
|
2212
|
+
this.draining = false;
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
};
|
|
2216
|
+
var registries = /* @__PURE__ */ new WeakMap();
|
|
2217
|
+
function workloadAdmission(owner, definition) {
|
|
2218
|
+
validateWorkload(definition);
|
|
2219
|
+
let registry = registries.get(owner);
|
|
2220
|
+
if (!registry) {
|
|
2221
|
+
registry = /* @__PURE__ */ new Map();
|
|
2222
|
+
registries.set(owner, registry);
|
|
2223
|
+
}
|
|
2224
|
+
const existing = registry.get(definition.name);
|
|
2225
|
+
if (existing) {
|
|
2226
|
+
const d = existing.definition;
|
|
2227
|
+
if (Object.keys(d).some((k) => d[k] !== definition[k])) throw new Error(`Workload ${definition.name} was already registered with different limits`);
|
|
2228
|
+
return existing;
|
|
2229
|
+
}
|
|
2230
|
+
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");
|
|
2231
|
+
const admission = new WorkloadAdmission(Object.freeze({
|
|
2232
|
+
...definition
|
|
2233
|
+
}));
|
|
2234
|
+
registry.set(definition.name, admission);
|
|
2235
|
+
return admission;
|
|
2236
|
+
}
|
|
2237
|
+
__name(workloadAdmission, "workloadAdmission");
|
|
2238
|
+
function workloadTenant(definition, claimsJson) {
|
|
2239
|
+
const claims = JSON.parse(claimsJson);
|
|
2240
|
+
const value = claims[definition.tenantClaim] ?? claims.sub;
|
|
2241
|
+
return typeof value === "string" && value.length > 0 ? value : "__anonymous__";
|
|
2242
|
+
}
|
|
2243
|
+
__name(workloadTenant, "workloadTenant");
|
|
2244
|
+
|
|
2245
|
+
// src/engine/transaction-control.ts
|
|
2246
|
+
var TransactionControl = class {
|
|
2247
|
+
static {
|
|
2248
|
+
__name(this, "TransactionControl");
|
|
2249
|
+
}
|
|
2250
|
+
controller = new AbortController();
|
|
2251
|
+
active = /* @__PURE__ */ new Set();
|
|
2252
|
+
cancelBackend;
|
|
2253
|
+
cancellation = Promise.resolve();
|
|
2254
|
+
timer;
|
|
2255
|
+
unlink;
|
|
2256
|
+
rejection;
|
|
2257
|
+
deadline;
|
|
2258
|
+
get signal() {
|
|
2259
|
+
return this.controller.signal;
|
|
2260
|
+
}
|
|
2261
|
+
constructor(timeoutMs, external) {
|
|
2262
|
+
this.deadline = timeoutMs === void 0 ? void 0 : performance.now() + timeoutMs;
|
|
2263
|
+
this.rejection = new Promise((_, reject) => {
|
|
2264
|
+
this.signal.addEventListener("abort", () => {
|
|
2265
|
+
reject(this.signal.reason);
|
|
2266
|
+
if (this.active.size && this.cancelBackend) {
|
|
2267
|
+
this.cancellation = this.cancelBackend().catch(() => void 0);
|
|
2268
|
+
} else {
|
|
2269
|
+
for (const query of this.active) {
|
|
2270
|
+
try {
|
|
2271
|
+
query.cancel?.();
|
|
2272
|
+
} catch {
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
}, {
|
|
2277
|
+
once: true
|
|
2278
|
+
});
|
|
2279
|
+
});
|
|
2280
|
+
void this.rejection.catch(() => void 0);
|
|
2281
|
+
const abort = /* @__PURE__ */ __name(() => this.controller.abort(external?.reason ?? new DOMException("Database transaction cancelled", "AbortError")), "abort");
|
|
2282
|
+
external?.addEventListener("abort", abort, {
|
|
2283
|
+
once: true
|
|
2284
|
+
});
|
|
2285
|
+
this.unlink = () => external?.removeEventListener("abort", abort);
|
|
2286
|
+
if (external?.aborted) abort();
|
|
2287
|
+
if (timeoutMs !== void 0) this.timer = setTimeout(() => this.controller.abort(new HttpError(503, "database_deadline_exceeded", "Database transaction exceeded its deadline")), timeoutMs);
|
|
2288
|
+
}
|
|
2289
|
+
check() {
|
|
2290
|
+
if (this.deadline !== void 0 && performance.now() >= this.deadline && !this.signal.aborted) {
|
|
2291
|
+
this.controller.abort(new HttpError(503, "database_deadline_exceeded", "Database transaction exceeded its deadline"));
|
|
2292
|
+
}
|
|
2293
|
+
this.signal.throwIfAborted();
|
|
2294
|
+
}
|
|
2295
|
+
remainingMs() {
|
|
2296
|
+
this.check();
|
|
2297
|
+
return this.deadline === void 0 ? void 0 : Math.max(1, Math.ceil(this.deadline - performance.now()));
|
|
2298
|
+
}
|
|
2299
|
+
race(work) {
|
|
2300
|
+
return Promise.race([
|
|
2301
|
+
work,
|
|
2302
|
+
this.rejection
|
|
2303
|
+
]);
|
|
2304
|
+
}
|
|
2305
|
+
bindBackend(cancel) {
|
|
2306
|
+
this.cancelBackend = cancel;
|
|
2307
|
+
}
|
|
2308
|
+
/** Keep the application's connection parked until the cancellation request
|
|
2309
|
+
* and all statements settle; a late cancel must not hit its next borrower. */
|
|
2310
|
+
async drainCancellation() {
|
|
2311
|
+
await this.cancellation;
|
|
2312
|
+
await Promise.allSettled([
|
|
2313
|
+
...this.active
|
|
2314
|
+
]);
|
|
2315
|
+
}
|
|
2316
|
+
async watch(query) {
|
|
2317
|
+
this.active.add(query);
|
|
2318
|
+
try {
|
|
2319
|
+
if (this.signal.aborted) query.cancel?.();
|
|
2320
|
+
return await query;
|
|
2321
|
+
} finally {
|
|
2322
|
+
this.active.delete(query);
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
dispose() {
|
|
2326
|
+
if (this.timer !== void 0) clearTimeout(this.timer);
|
|
2327
|
+
this.unlink();
|
|
2328
|
+
}
|
|
2329
|
+
};
|
|
2330
|
+
|
|
2331
|
+
// src/engine/diagnostics.ts
|
|
2332
|
+
var import_node_crypto2 = require("crypto");
|
|
2333
|
+
var DatabaseTrace = class {
|
|
2334
|
+
static {
|
|
2335
|
+
__name(this, "DatabaseTrace");
|
|
2336
|
+
}
|
|
2337
|
+
options;
|
|
2338
|
+
start = performance.now();
|
|
2339
|
+
ended;
|
|
2340
|
+
failure;
|
|
2341
|
+
state;
|
|
2342
|
+
constructor(options) {
|
|
2343
|
+
this.options = options;
|
|
2344
|
+
for (const key2 of [
|
|
2345
|
+
"maxQueries",
|
|
2346
|
+
"maxRows",
|
|
2347
|
+
"maxBytes"
|
|
2348
|
+
]) {
|
|
2349
|
+
const value = options[key2];
|
|
2350
|
+
if (value !== void 0 && (!Number.isSafeInteger(value) || value < 0)) throw new Error(`database ${key2} must be a nonnegative safe integer`);
|
|
2351
|
+
}
|
|
2352
|
+
this.state = {
|
|
2353
|
+
endpoint: options.endpoint,
|
|
2354
|
+
requestId: options.requestId,
|
|
2355
|
+
outcome: "active",
|
|
2356
|
+
elapsedMs: 0,
|
|
2357
|
+
queries: 0,
|
|
2358
|
+
rows: 0,
|
|
2359
|
+
bytes: 0,
|
|
2360
|
+
queueMs: 0,
|
|
2361
|
+
poolMs: 0,
|
|
2362
|
+
setupMs: 0,
|
|
2363
|
+
sqlMs: 0,
|
|
2364
|
+
commitMs: 0,
|
|
2365
|
+
rollbackMs: 0,
|
|
2366
|
+
commits: 0,
|
|
2367
|
+
commitFailures: 0,
|
|
2368
|
+
rollbackRequests: 0,
|
|
2369
|
+
retries: 0,
|
|
2370
|
+
statements: []
|
|
2371
|
+
};
|
|
2372
|
+
}
|
|
2373
|
+
check() {
|
|
2374
|
+
if (this.failure) throw this.failure;
|
|
2375
|
+
}
|
|
2376
|
+
time(key2, started) {
|
|
2377
|
+
this.state[key2] += performance.now() - started;
|
|
2378
|
+
}
|
|
2379
|
+
retry() {
|
|
2380
|
+
this.state.retries++;
|
|
2381
|
+
}
|
|
2382
|
+
committed() {
|
|
2383
|
+
this.state.commits++;
|
|
2384
|
+
}
|
|
2385
|
+
commitFailed() {
|
|
2386
|
+
this.state.commitFailures++;
|
|
2387
|
+
}
|
|
2388
|
+
failed() {
|
|
2389
|
+
this.state.outcome = "failed";
|
|
2390
|
+
}
|
|
2391
|
+
rolledBack() {
|
|
2392
|
+
this.state.rollbackRequests++;
|
|
2393
|
+
}
|
|
2394
|
+
refuse(kind) {
|
|
2395
|
+
this.failure ??= new HttpError(500, "database_budget_exceeded", `Database ${kind} budget exceeded${this.options.endpoint ? ` in ${this.options.endpoint}` : ""}`);
|
|
2396
|
+
throw this.failure;
|
|
2397
|
+
}
|
|
2398
|
+
emit(callback, event) {
|
|
2399
|
+
try {
|
|
2400
|
+
const result = callback?.(event);
|
|
2401
|
+
if (result) void Promise.resolve(result).catch(() => void 0);
|
|
2402
|
+
} catch {
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
async query(sql, name, run) {
|
|
2406
|
+
this.check();
|
|
2407
|
+
if (this.options.maxQueries !== void 0 && this.state.queries >= this.options.maxQueries) this.refuse("query count");
|
|
2408
|
+
const ordinal = ++this.state.queries;
|
|
2409
|
+
const event = {
|
|
2410
|
+
ordinal,
|
|
2411
|
+
fingerprint: (0, import_node_crypto2.createHash)("sha256").update(sql).digest("hex").slice(0, 24),
|
|
2412
|
+
...name === void 0 ? {} : {
|
|
2413
|
+
name: name.slice(0, 128)
|
|
2414
|
+
},
|
|
2415
|
+
...this.options.includeSql ? {
|
|
2416
|
+
sql: sql.slice(0, 4096)
|
|
2417
|
+
} : {},
|
|
2418
|
+
durationMs: 0,
|
|
2419
|
+
rows: 0,
|
|
2420
|
+
bytes: 0
|
|
2421
|
+
};
|
|
2422
|
+
if (this.options.captureSource) event.source = new Error().stack?.split("\n").slice(1, 9).join("\n").slice(0, 2500);
|
|
2423
|
+
const started = performance.now();
|
|
2424
|
+
let measured = false;
|
|
2425
|
+
try {
|
|
2426
|
+
const result = await run();
|
|
2427
|
+
event.durationMs = performance.now() - started;
|
|
2428
|
+
measured = true;
|
|
2429
|
+
if (Array.isArray(result)) {
|
|
2430
|
+
event.rows = result.length;
|
|
2431
|
+
event.bytes = Buffer.byteLength(JSON.stringify(result, (_, value) => typeof value === "bigint" ? value.toString() : value));
|
|
2432
|
+
}
|
|
2433
|
+
this.state.rows += event.rows;
|
|
2434
|
+
this.state.bytes += event.bytes;
|
|
2435
|
+
if (this.options.maxRows !== void 0 && this.state.rows > this.options.maxRows) this.refuse("returned rows");
|
|
2436
|
+
if (this.options.maxBytes !== void 0 && this.state.bytes > this.options.maxBytes) this.refuse("result bytes");
|
|
2437
|
+
return result;
|
|
2438
|
+
} catch (error) {
|
|
2439
|
+
const e = error;
|
|
2440
|
+
event.errorCode = String(e?.sqlstate ?? e?.errno ?? e?.code ?? "query_failed").slice(0, 80);
|
|
2441
|
+
throw error;
|
|
2442
|
+
} finally {
|
|
2443
|
+
if (!measured) event.durationMs = performance.now() - started;
|
|
2444
|
+
this.state.sqlMs += event.durationMs;
|
|
2445
|
+
const frozen = Object.freeze(event);
|
|
2446
|
+
if (this.state.statements.length < 200) this.state.statements.push(frozen);
|
|
2447
|
+
this.emit(this.options.onQuery, frozen);
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
get snapshot() {
|
|
2451
|
+
return Object.freeze({
|
|
2452
|
+
...this.state,
|
|
2453
|
+
elapsedMs: (this.ended ?? performance.now()) - this.start,
|
|
2454
|
+
statements: Object.freeze([
|
|
2455
|
+
...this.state.statements
|
|
2456
|
+
].sort((a, b) => a.ordinal - b.ordinal))
|
|
2457
|
+
});
|
|
2458
|
+
}
|
|
2459
|
+
complete(outcome) {
|
|
2460
|
+
if (this.ended !== void 0) return;
|
|
2461
|
+
this.ended = performance.now();
|
|
2462
|
+
this.state.outcome = outcome;
|
|
2463
|
+
this.emit(this.options.onComplete, this.snapshot);
|
|
2464
|
+
}
|
|
2465
|
+
};
|
|
1547
2466
|
|
|
1548
2467
|
// src/db/columns.ts
|
|
1549
2468
|
var CODECS = {
|
|
@@ -1602,9 +2521,9 @@ function buildRelations(schemas) {
|
|
|
1602
2521
|
const taken = /* @__PURE__ */ new Map();
|
|
1603
2522
|
for (const schema of schemas) {
|
|
1604
2523
|
for (const t of Object.values(schema.tables)) {
|
|
1605
|
-
const
|
|
1606
|
-
out.set(
|
|
1607
|
-
taken.set(
|
|
2524
|
+
const key2 = qualifiedTableKey(schema.name, t.name);
|
|
2525
|
+
out.set(key2, []);
|
|
2526
|
+
taken.set(key2, /* @__PURE__ */ new Map());
|
|
1608
2527
|
}
|
|
1609
2528
|
}
|
|
1610
2529
|
const claim = /* @__PURE__ */ __name((tableKey, name, origin, edge) => {
|
|
@@ -1668,10 +2587,10 @@ function quoteIdent(name) {
|
|
|
1668
2587
|
return `"${name.replace(/"/g, '""')}"`;
|
|
1669
2588
|
}
|
|
1670
2589
|
__name(quoteIdent, "quoteIdent");
|
|
1671
|
-
function quoteTable(
|
|
1672
|
-
const dot =
|
|
1673
|
-
if (dot === -1) return quoteIdent(
|
|
1674
|
-
return `${quoteIdent(
|
|
2590
|
+
function quoteTable(key2) {
|
|
2591
|
+
const dot = key2.indexOf(".");
|
|
2592
|
+
if (dot === -1) return quoteIdent(key2);
|
|
2593
|
+
return `${quoteIdent(key2.slice(0, dot))}.${quoteIdent(key2.slice(dot + 1))}`;
|
|
1675
2594
|
}
|
|
1676
2595
|
__name(quoteTable, "quoteTable");
|
|
1677
2596
|
function encodePgArray(value) {
|
|
@@ -1684,9 +2603,10 @@ function encodePgArray(value) {
|
|
|
1684
2603
|
}
|
|
1685
2604
|
__name(encodePgArray, "encodePgArray");
|
|
1686
2605
|
var BIND_SQL = "select set_config('role',$1,true), set_config('search_path','public',true), set_config('request.jwt.claims',$2,true)";
|
|
2606
|
+
var atomicScope = new import_node_async_hooks2.AsyncLocalStorage();
|
|
1687
2607
|
function createLazyTransaction(sql, role, claimsJson, options = {}) {
|
|
1688
2608
|
const { lockTimeout } = options;
|
|
1689
|
-
|
|
2609
|
+
let bindSql = lockTimeout ? `${BIND_SQL}, set_config('lock_timeout',$3,true)` : BIND_SQL;
|
|
1690
2610
|
const bindParams = lockTimeout ? [
|
|
1691
2611
|
role,
|
|
1692
2612
|
claimsJson,
|
|
@@ -1695,25 +2615,118 @@ function createLazyTransaction(sql, role, claimsJson, options = {}) {
|
|
|
1695
2615
|
role,
|
|
1696
2616
|
claimsJson
|
|
1697
2617
|
];
|
|
2618
|
+
if (options.statementTimeout !== void 0) {
|
|
2619
|
+
bindParams.push(options.statementTimeout);
|
|
2620
|
+
bindSql += `, set_config('statement_timeout',$${bindParams.length},true)`;
|
|
2621
|
+
}
|
|
1698
2622
|
let opening = null;
|
|
1699
2623
|
let release = null;
|
|
1700
2624
|
let fail = null;
|
|
1701
2625
|
let settled = null;
|
|
1702
|
-
|
|
2626
|
+
let closed = false;
|
|
2627
|
+
const assertAccess = /* @__PURE__ */ __name(() => {
|
|
2628
|
+
if (closed) throw new Error("Database transaction has settled; its handle cannot be used again");
|
|
2629
|
+
options.control?.check();
|
|
2630
|
+
options.trace?.check();
|
|
2631
|
+
if (atomicScope.getStore() !== options.scope) {
|
|
2632
|
+
throw new Error("Database.$atomic cannot use a handle from another transaction or identity; use its tx argument");
|
|
2633
|
+
}
|
|
2634
|
+
}, "assertAccess");
|
|
2635
|
+
let savepointALS;
|
|
2636
|
+
let activeSavepoint;
|
|
2637
|
+
const pendingSavepoints = /* @__PURE__ */ new Set();
|
|
2638
|
+
const scoped = /* @__PURE__ */ __name((tx, checkParent = assertAccess, owner, children = /* @__PURE__ */ new Set()) => {
|
|
2639
|
+
const access = /* @__PURE__ */ __name(() => {
|
|
2640
|
+
checkParent();
|
|
2641
|
+
if (activeSavepoint !== owner || savepointALS?.getStore() !== owner) {
|
|
2642
|
+
throw new Error("Database connection belongs to another active savepoint; use its tx argument and await it before using the parent handle");
|
|
2643
|
+
}
|
|
2644
|
+
}, "access");
|
|
2645
|
+
return {
|
|
2646
|
+
async unsafe(text, params, metadata) {
|
|
2647
|
+
access();
|
|
2648
|
+
const run = /* @__PURE__ */ __name(() => {
|
|
2649
|
+
const query = tx.unsafe(text, params);
|
|
2650
|
+
return options.control ? options.control.watch(query) : query;
|
|
2651
|
+
}, "run");
|
|
2652
|
+
return options.trace ? options.trace.query(text, metadata?.name, run) : run();
|
|
2653
|
+
},
|
|
2654
|
+
async savepoint(fn) {
|
|
2655
|
+
access();
|
|
2656
|
+
const token = /* @__PURE__ */ Symbol("savepoint"), descendants = /* @__PURE__ */ new Set();
|
|
2657
|
+
savepointALS ??= new import_node_async_hooks2.AsyncLocalStorage();
|
|
2658
|
+
activeSavepoint = token;
|
|
2659
|
+
const work = (async () => {
|
|
2660
|
+
try {
|
|
2661
|
+
return await tx.savepoint(async (sp) => {
|
|
2662
|
+
let ended = false;
|
|
2663
|
+
const check = /* @__PURE__ */ __name(() => {
|
|
2664
|
+
checkParent();
|
|
2665
|
+
if (ended) throw new Error("Database savepoint has settled; its handle cannot be used again");
|
|
2666
|
+
}, "check");
|
|
2667
|
+
try {
|
|
2668
|
+
const callback = savepointALS.run(token, () => fn(scoped(sp, check, token, descendants)));
|
|
2669
|
+
return await (options.control ? options.control.race(callback) : callback);
|
|
2670
|
+
} finally {
|
|
2671
|
+
if (descendants.size) await Promise.allSettled([
|
|
2672
|
+
...descendants
|
|
2673
|
+
]);
|
|
2674
|
+
ended = true;
|
|
2675
|
+
}
|
|
2676
|
+
});
|
|
2677
|
+
} finally {
|
|
2678
|
+
activeSavepoint = owner;
|
|
2679
|
+
}
|
|
2680
|
+
})();
|
|
2681
|
+
pendingSavepoints.add(work);
|
|
2682
|
+
children.add(work);
|
|
2683
|
+
try {
|
|
2684
|
+
return await work;
|
|
2685
|
+
} finally {
|
|
2686
|
+
pendingSavepoints.delete(work);
|
|
2687
|
+
children.delete(work);
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2690
|
+
};
|
|
2691
|
+
}, "scoped");
|
|
2692
|
+
const ensure = /* @__PURE__ */ __name(async () => {
|
|
2693
|
+
assertAccess();
|
|
1703
2694
|
if (opening) return opening;
|
|
1704
2695
|
opening = new Promise((resolveTx2, rejectTx) => {
|
|
1705
2696
|
const parked = new Promise((res, rej) => {
|
|
1706
2697
|
release = res;
|
|
1707
2698
|
fail = rej;
|
|
1708
2699
|
});
|
|
2700
|
+
void parked.catch(() => void 0);
|
|
2701
|
+
const openingAt = options.trace ? performance.now() : 0;
|
|
1709
2702
|
settled = sql.begin(async (tx) => {
|
|
1710
|
-
|
|
1711
|
-
|
|
2703
|
+
options.trace?.time("poolMs", openingAt);
|
|
2704
|
+
const setupAt = options.trace ? performance.now() : 0;
|
|
2705
|
+
options.control?.check();
|
|
2706
|
+
if (options.isolation !== void 0 || options.readOnly !== void 0) {
|
|
2707
|
+
const isolation = options.isolation ?? "read committed";
|
|
2708
|
+
validateAtomicOptions({
|
|
2709
|
+
isolation,
|
|
2710
|
+
readOnly: options.readOnly
|
|
2711
|
+
});
|
|
2712
|
+
await tx.unsafe(`SET TRANSACTION ISOLATION LEVEL ${isolation.toUpperCase()}${options.readOnly === void 0 ? "" : options.readOnly ? " READ ONLY" : " READ WRITE"}`);
|
|
2713
|
+
}
|
|
2714
|
+
if (options.control && sql.cancelBackend) {
|
|
2715
|
+
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()");
|
|
2716
|
+
if (!Number.isInteger(ticket[0]?.pid) || typeof ticket[0]?.started !== "string") throw new Error("Database cancellation cannot identify its own backend");
|
|
2717
|
+
options.control.bindBackend(() => sql.cancelBackend(ticket[0].pid, ticket[0].started));
|
|
2718
|
+
options.control.check();
|
|
2719
|
+
}
|
|
2720
|
+
const binding = tx.unsafe(bindSql, bindParams);
|
|
2721
|
+
await (options.control ? options.control.watch(binding) : binding);
|
|
2722
|
+
options.trace?.time("setupMs", setupAt);
|
|
2723
|
+
resolveTx2(scoped(tx));
|
|
1712
2724
|
await parked;
|
|
1713
2725
|
}).catch((e) => {
|
|
1714
2726
|
rejectTx(e);
|
|
1715
2727
|
throw e;
|
|
1716
2728
|
});
|
|
2729
|
+
void settled.catch(() => void 0);
|
|
1717
2730
|
});
|
|
1718
2731
|
return opening;
|
|
1719
2732
|
}, "ensure");
|
|
@@ -1723,14 +2736,37 @@ function createLazyTransaction(sql, role, claimsJson, options = {}) {
|
|
|
1723
2736
|
return opening !== null;
|
|
1724
2737
|
},
|
|
1725
2738
|
async commit() {
|
|
2739
|
+
if (pendingSavepoints.size) await Promise.allSettled([
|
|
2740
|
+
...pendingSavepoints
|
|
2741
|
+
]);
|
|
2742
|
+
options.control?.check();
|
|
2743
|
+
options.trace?.check();
|
|
2744
|
+
closed = true;
|
|
1726
2745
|
if (!opening) return;
|
|
2746
|
+
const started = options.trace ? performance.now() : 0;
|
|
1727
2747
|
release();
|
|
1728
|
-
|
|
2748
|
+
try {
|
|
2749
|
+
await settled;
|
|
2750
|
+
options.trace?.committed();
|
|
2751
|
+
} catch (error) {
|
|
2752
|
+
options.trace?.commitFailed();
|
|
2753
|
+
throw error;
|
|
2754
|
+
} finally {
|
|
2755
|
+
options.trace?.time("commitMs", started);
|
|
2756
|
+
}
|
|
1729
2757
|
},
|
|
1730
2758
|
async rollback(reason2) {
|
|
2759
|
+
closed = true;
|
|
1731
2760
|
if (!opening) return;
|
|
2761
|
+
await options.control?.drainCancellation();
|
|
2762
|
+
if (pendingSavepoints.size) await Promise.allSettled([
|
|
2763
|
+
...pendingSavepoints
|
|
2764
|
+
]);
|
|
2765
|
+
const started = options.trace ? performance.now() : 0;
|
|
1732
2766
|
fail(reason2);
|
|
1733
2767
|
await settled?.catch(() => void 0);
|
|
2768
|
+
options.trace?.time("rollbackMs", started);
|
|
2769
|
+
options.trace?.rolledBack();
|
|
1734
2770
|
}
|
|
1735
2771
|
};
|
|
1736
2772
|
}
|
|
@@ -1745,7 +2781,7 @@ __name(asWireValue, "asWireValue");
|
|
|
1745
2781
|
function asWireRow(row) {
|
|
1746
2782
|
if (row === null || typeof row !== "object") return row;
|
|
1747
2783
|
const out = {};
|
|
1748
|
-
for (const [
|
|
2784
|
+
for (const [key2, value] of Object.entries(row)) out[key2] = asWireValue(value);
|
|
1749
2785
|
return out;
|
|
1750
2786
|
}
|
|
1751
2787
|
__name(asWireRow, "asWireRow");
|
|
@@ -1757,7 +2793,7 @@ function toVectorLiteral(v) {
|
|
|
1757
2793
|
return `[${v.join(",")}]`;
|
|
1758
2794
|
}
|
|
1759
2795
|
__name(toVectorLiteral, "toVectorLiteral");
|
|
1760
|
-
function
|
|
2796
|
+
function computeVectorColumns(schema, table) {
|
|
1761
2797
|
const out = /* @__PURE__ */ new Set();
|
|
1762
2798
|
const def = tableDefOf(table, schema);
|
|
1763
2799
|
if (def) {
|
|
@@ -1768,9 +2804,9 @@ function vectorColumnsOf(schema, table) {
|
|
|
1768
2804
|
}
|
|
1769
2805
|
return out;
|
|
1770
2806
|
}
|
|
1771
|
-
__name(
|
|
1772
|
-
function tableDefOf(
|
|
1773
|
-
return schema.tables?.[
|
|
2807
|
+
__name(computeVectorColumns, "computeVectorColumns");
|
|
2808
|
+
function tableDefOf(key2, schema = currentSchema) {
|
|
2809
|
+
return schema.tables?.[key2] ?? null;
|
|
1774
2810
|
}
|
|
1775
2811
|
__name(tableDefOf, "tableDefOf");
|
|
1776
2812
|
function reviveVectors(row, vectorCols) {
|
|
@@ -1783,7 +2819,7 @@ function reviveVectors(row, vectorCols) {
|
|
|
1783
2819
|
return row;
|
|
1784
2820
|
}
|
|
1785
2821
|
__name(reviveVectors, "reviveVectors");
|
|
1786
|
-
function
|
|
2822
|
+
function computeTransforms(schema, table) {
|
|
1787
2823
|
const out = /* @__PURE__ */ new Map();
|
|
1788
2824
|
const def = tableDefOf(table, schema);
|
|
1789
2825
|
if (def) {
|
|
@@ -1806,7 +2842,7 @@ function transformsOf(schema, table) {
|
|
|
1806
2842
|
}
|
|
1807
2843
|
return out;
|
|
1808
2844
|
}
|
|
1809
|
-
__name(
|
|
2845
|
+
__name(computeTransforms, "computeTransforms");
|
|
1810
2846
|
function applyFromDb(row, transforms) {
|
|
1811
2847
|
if (transforms.size === 0) return row;
|
|
1812
2848
|
for (const [col, t] of transforms) {
|
|
@@ -1820,13 +2856,27 @@ function applyFromDb(row, transforms) {
|
|
|
1820
2856
|
__name(applyFromDb, "applyFromDb");
|
|
1821
2857
|
function asTableRow(table, row) {
|
|
1822
2858
|
const revived = reviveVectors(asWireRow(row), vectorColumnsOf(currentSchema, table));
|
|
1823
|
-
return applyFromDb(revived, transformsOf(currentSchema, table));
|
|
2859
|
+
return foldCan(table, applyFromDb(revived, transformsOf(currentSchema, table)), false);
|
|
1824
2860
|
}
|
|
1825
2861
|
__name(asTableRow, "asTableRow");
|
|
1826
|
-
function asTableRows(table, rows) {
|
|
2862
|
+
function asTableRows(table, rows, withSpec, partial = false) {
|
|
1827
2863
|
const vectorCols = vectorColumnsOf(currentSchema, table);
|
|
1828
2864
|
const transforms = transformsOf(currentSchema, table);
|
|
1829
|
-
|
|
2865
|
+
const result = rows.map((row) => applyFromDb(reviveVectors(asWireRow(row), vectorCols), transforms));
|
|
2866
|
+
if (withSpec) for (const rel of currentRelations.get(table) ?? []) {
|
|
2867
|
+
const spec = withSpec[rel.name];
|
|
2868
|
+
if (!spec) continue;
|
|
2869
|
+
const nested = typeof spec === "object" ? spec.with : void 0;
|
|
2870
|
+
for (const row of result) {
|
|
2871
|
+
const value = row[rel.name];
|
|
2872
|
+
if (Array.isArray(value)) row[rel.name] = asTableRows(rel.to, value, nested);
|
|
2873
|
+
else if (value && typeof value === "object") row[rel.name] = asTableRows(rel.to, [
|
|
2874
|
+
value
|
|
2875
|
+
], nested)[0];
|
|
2876
|
+
}
|
|
2877
|
+
}
|
|
2878
|
+
for (const row of result) foldCan(table, row, partial);
|
|
2879
|
+
return result;
|
|
1830
2880
|
}
|
|
1831
2881
|
__name(asTableRows, "asTableRows");
|
|
1832
2882
|
function asBindParams(table, cols, data, caller) {
|
|
@@ -2069,7 +3119,7 @@ function excludeSql(pk, ids, add) {
|
|
|
2069
3119
|
return ` AND t.${quoteIdent(pk)}::text <> ALL(ARRAY[${ids.map((x) => add(x)).join(", ")}])`;
|
|
2070
3120
|
}
|
|
2071
3121
|
__name(excludeSql, "excludeSql");
|
|
2072
|
-
function
|
|
3122
|
+
function computePrimaryKey(table) {
|
|
2073
3123
|
const t = tableDefOf(table);
|
|
2074
3124
|
const columns = t?.columns ?? {};
|
|
2075
3125
|
const cols = Object.keys(columns);
|
|
@@ -2089,7 +3139,7 @@ function primaryKeyOf(table) {
|
|
|
2089
3139
|
const pks = cols.filter((c) => pkOf(columns[c]));
|
|
2090
3140
|
return pks.length === 1 ? pks[0] : null;
|
|
2091
3141
|
}
|
|
2092
|
-
__name(
|
|
3142
|
+
__name(computePrimaryKey, "computePrimaryKey");
|
|
2093
3143
|
function isSinglePkEquality(where, pk) {
|
|
2094
3144
|
if (pk === null) return false;
|
|
2095
3145
|
const keys = Object.keys(where);
|
|
@@ -2104,12 +3154,270 @@ function assertNotAppendOnly(caller, table) {
|
|
|
2104
3154
|
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).`);
|
|
2105
3155
|
}
|
|
2106
3156
|
__name(assertNotAppendOnly, "assertNotAppendOnly");
|
|
2107
|
-
function
|
|
3157
|
+
function computeSchemaColumns(table) {
|
|
2108
3158
|
const t = tableDefOf(table);
|
|
2109
3159
|
const cols = t?.columns ? Object.keys(t.columns) : [];
|
|
2110
3160
|
return cols.length > 0 ? new Set(cols) : null;
|
|
2111
3161
|
}
|
|
2112
|
-
__name(
|
|
3162
|
+
__name(computeSchemaColumns, "computeSchemaColumns");
|
|
3163
|
+
function timestampResult(expr) {
|
|
3164
|
+
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`;
|
|
3165
|
+
}
|
|
3166
|
+
__name(timestampResult, "timestampResult");
|
|
3167
|
+
function columnResult(table, column, alias) {
|
|
3168
|
+
const expr = `${alias ? `${quoteIdent(alias)}.` : ""}${quoteIdent(column)}`;
|
|
3169
|
+
return columnTypeOf(table, column) === "timestamp" ? timestampResult(expr) : expr;
|
|
3170
|
+
}
|
|
3171
|
+
__name(columnResult, "columnResult");
|
|
3172
|
+
function projectedColumn(table, column, alias) {
|
|
3173
|
+
const expr = columnResult(table, column, alias);
|
|
3174
|
+
return columnTypeOf(table, column) === "timestamp" ? `${expr} AS ${quoteIdent(column)}` : expr;
|
|
3175
|
+
}
|
|
3176
|
+
__name(projectedColumn, "projectedColumn");
|
|
3177
|
+
var CAN_PREFIX = "can:";
|
|
3178
|
+
var canMemo = /* @__PURE__ */ new WeakMap();
|
|
3179
|
+
function canRoleGate(roles) {
|
|
3180
|
+
if (roles.length === 0) return null;
|
|
3181
|
+
const names = roles.flatMap((r) => [
|
|
3182
|
+
r,
|
|
3183
|
+
`backend_${r}`
|
|
3184
|
+
]).map((r) => `'${r.replace(/'/g, "''")}'`);
|
|
3185
|
+
return `(current_user = ANY (ARRAY[${names.join(", ")}]))`;
|
|
3186
|
+
}
|
|
3187
|
+
__name(canRoleGate, "canRoleGate");
|
|
3188
|
+
function canEntries(def, alias) {
|
|
3189
|
+
const policies = def.policies ?? [];
|
|
3190
|
+
const dropped = [];
|
|
3191
|
+
const restrictive = [];
|
|
3192
|
+
for (const p of policies) {
|
|
3193
|
+
if (p.permissive !== false || p.command !== "update" && p.command !== "all") continue;
|
|
3194
|
+
const r = renderCanUsing(p, alias, def.name);
|
|
3195
|
+
if (r === null) {
|
|
3196
|
+
return {
|
|
3197
|
+
entries: [],
|
|
3198
|
+
dropped: policies.filter((q) => q.command === "update" && q.permissive !== false).map((q) => ({
|
|
3199
|
+
name: q.name,
|
|
3200
|
+
reason: `restrictive policy "${p.name}" cannot be evaluated as a column`
|
|
3201
|
+
}))
|
|
3202
|
+
};
|
|
3203
|
+
}
|
|
3204
|
+
const rGate = canRoleGate(p.roles ?? []);
|
|
3205
|
+
restrictive.push(rGate === null ? r : `((NOT ${rGate}) OR ${r})`);
|
|
3206
|
+
}
|
|
3207
|
+
const entries = [];
|
|
3208
|
+
for (const p of policies) {
|
|
3209
|
+
if (p.command !== "update" || p.permissive === false) continue;
|
|
3210
|
+
if (Buffer.byteLength(CAN_PREFIX + p.name, "utf8") > 63) {
|
|
3211
|
+
dropped.push({
|
|
3212
|
+
name: p.name,
|
|
3213
|
+
reason: "its name is longer than 59 bytes and Postgres would truncate the column alias"
|
|
3214
|
+
});
|
|
3215
|
+
continue;
|
|
3216
|
+
}
|
|
3217
|
+
const using = renderCanUsing(p, alias, def.name);
|
|
3218
|
+
if (using === null) {
|
|
3219
|
+
dropped.push({
|
|
3220
|
+
name: p.name,
|
|
3221
|
+
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)"
|
|
3222
|
+
});
|
|
3223
|
+
continue;
|
|
3224
|
+
}
|
|
3225
|
+
const gate = canRoleGate(p.roles ?? []);
|
|
3226
|
+
const parts = [
|
|
3227
|
+
...gate === null ? [] : [
|
|
3228
|
+
gate
|
|
3229
|
+
],
|
|
3230
|
+
using,
|
|
3231
|
+
...restrictive
|
|
3232
|
+
];
|
|
3233
|
+
entries.push({
|
|
3234
|
+
name: p.name,
|
|
3235
|
+
using: parts.length === 1 ? using : `(${parts.join(" AND ")})`
|
|
3236
|
+
});
|
|
3237
|
+
}
|
|
3238
|
+
return {
|
|
3239
|
+
entries,
|
|
3240
|
+
dropped
|
|
3241
|
+
};
|
|
3242
|
+
}
|
|
3243
|
+
__name(canEntries, "canEntries");
|
|
3244
|
+
var CanRenderError = class CanRenderError2 extends Error {
|
|
3245
|
+
static {
|
|
3246
|
+
__name(this, "CanRenderError");
|
|
3247
|
+
}
|
|
3248
|
+
};
|
|
3249
|
+
var ENGINE_ALIASES = /^(t|locked|w\d+|wj\d+|x|q|g|p|upd)$/;
|
|
3250
|
+
function renderCanUsing(p, qualifier, tableName) {
|
|
3251
|
+
if (p.using === null || p.using === void 0) return null;
|
|
3252
|
+
if (typeof p.using === "string") {
|
|
3253
|
+
const withoutLiterals = p.using.replace(/'(?:[^']|'')*'/g, "''");
|
|
3254
|
+
if (/--|\/\*/.test(withoutLiterals)) return null;
|
|
3255
|
+
if (withoutLiterals.replace(/''/g, "").includes("'")) return null;
|
|
3256
|
+
let depth = 0;
|
|
3257
|
+
for (const ch of withoutLiterals) {
|
|
3258
|
+
if (ch === "(") depth++;
|
|
3259
|
+
else if (ch === ")" && --depth < 0) return null;
|
|
3260
|
+
}
|
|
3261
|
+
if (depth !== 0) return null;
|
|
3262
|
+
const tbl = tableName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3263
|
+
const qualified = new RegExp(`(^|[^A-Za-z0-9_])(?:"?[A-Za-z_][A-Za-z0-9_]*"?\\s*\\.\\s*)?"?${tbl}"?\\s*\\.`);
|
|
3264
|
+
if (qualified.test(withoutLiterals)) return null;
|
|
3265
|
+
const calls = withoutLiterals.match(/auth\s*\.\s*"?has_permission"?\s*\(/gi) ?? [];
|
|
3266
|
+
const wrapped = withoutLiterals.match(/\(\s*select\s*[(\s]*auth\s*\.\s*"?has_permission"?\s*\(/gi) ?? [];
|
|
3267
|
+
if (calls.length !== wrapped.length) return null;
|
|
3268
|
+
return `(${p.using})`;
|
|
3269
|
+
}
|
|
3270
|
+
try {
|
|
3271
|
+
const rendered = canExprSql(p.using, qualifier);
|
|
3272
|
+
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";
|
|
3273
|
+
return selfParenthesized ? rendered : `(${rendered})`;
|
|
3274
|
+
} catch (err) {
|
|
3275
|
+
if (err instanceof CanRenderError) return null;
|
|
3276
|
+
throw err;
|
|
3277
|
+
}
|
|
3278
|
+
}
|
|
3279
|
+
__name(renderCanUsing, "renderCanUsing");
|
|
3280
|
+
var CAN_BINOPS = {
|
|
3281
|
+
eq: "=",
|
|
3282
|
+
neq: "<>",
|
|
3283
|
+
gt: ">",
|
|
3284
|
+
gte: ">=",
|
|
3285
|
+
lt: "<",
|
|
3286
|
+
lte: "<="
|
|
3287
|
+
};
|
|
3288
|
+
function canLiteral(v) {
|
|
3289
|
+
if (v === null) return "NULL";
|
|
3290
|
+
if (typeof v === "boolean") return v ? "TRUE" : "FALSE";
|
|
3291
|
+
if (typeof v === "number") {
|
|
3292
|
+
if (!Number.isFinite(v)) throw new CanRenderError("can: non-finite literal");
|
|
3293
|
+
return String(v);
|
|
3294
|
+
}
|
|
3295
|
+
return `'${v.replace(/'/g, "''")}'`;
|
|
3296
|
+
}
|
|
3297
|
+
__name(canLiteral, "canLiteral");
|
|
3298
|
+
function canExprSql(e, qualifier) {
|
|
3299
|
+
switch (e.kind) {
|
|
3300
|
+
case "col":
|
|
3301
|
+
return e.alias !== void 0 && e.alias !== "" ? `${quoteIdent(e.alias)}.${quoteIdent(e.name)}` : `${qualifier}.${quoteIdent(e.name)}`;
|
|
3302
|
+
case "authUid":
|
|
3303
|
+
return "(SELECT auth.uid())";
|
|
3304
|
+
case "lit":
|
|
3305
|
+
return canLiteral(e.value);
|
|
3306
|
+
case "binop": {
|
|
3307
|
+
const op = CAN_BINOPS[e.op];
|
|
3308
|
+
if (op === void 0) throw new CanRenderError(`can: unknown operator ${String(e.op)}`);
|
|
3309
|
+
return `(${canExprSql(e.left, qualifier)} ${op} ${canExprSql(e.right, qualifier)})`;
|
|
3310
|
+
}
|
|
3311
|
+
case "and":
|
|
3312
|
+
return e.parts.length === 0 ? "(true)" : `(${e.parts.map((p) => canExprSql(p, qualifier)).join(" AND ")})`;
|
|
3313
|
+
case "or":
|
|
3314
|
+
return e.parts.length === 0 ? "(false)" : `(${e.parts.map((p) => canExprSql(p, qualifier)).join(" OR ")})`;
|
|
3315
|
+
case "not":
|
|
3316
|
+
return `(NOT ${canExprSql(e.expr, qualifier)})`;
|
|
3317
|
+
case "isNull":
|
|
3318
|
+
return `(${canExprSql(e.expr, qualifier)} IS ${e.negated ? "NOT " : ""}NULL)`;
|
|
3319
|
+
case "exists":
|
|
3320
|
+
if (e.alias === "" || ENGINE_ALIASES.test(e.alias)) {
|
|
3321
|
+
throw new CanRenderError(`can: exists alias "${e.alias}" collides with the engine's own aliases \u2014 the correlation would silently collapse`);
|
|
3322
|
+
}
|
|
3323
|
+
return `EXISTS (SELECT 1 FROM ${quoteTable(e.table)} ${quoteIdent(e.alias)} WHERE ${canExprSql(e.predicate, qualifier)})`;
|
|
3324
|
+
default:
|
|
3325
|
+
throw new CanRenderError(`can: unknown policy expression kind ${String(e.kind)}`);
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3328
|
+
__name(canExprSql, "canExprSql");
|
|
3329
|
+
function canEntriesOf(table, alias) {
|
|
3330
|
+
const byAlias = memoTable(canMemo, currentSchema, table, () => /* @__PURE__ */ new Map());
|
|
3331
|
+
const hit = byAlias.get(alias);
|
|
3332
|
+
if (hit !== void 0) return hit;
|
|
3333
|
+
const def = tableDefOf(table);
|
|
3334
|
+
const warnOnce = /* @__PURE__ */ __name((msg) => {
|
|
3335
|
+
if (byAlias.has("__warned")) return;
|
|
3336
|
+
byAlias.set("__warned", []);
|
|
3337
|
+
console.warn(msg);
|
|
3338
|
+
}, "warnOnce");
|
|
3339
|
+
if (def === null || def.policies === void 0) {
|
|
3340
|
+
byAlias.set(alias, []);
|
|
3341
|
+
return [];
|
|
3342
|
+
}
|
|
3343
|
+
if (hasCanColumn(table)) {
|
|
3344
|
+
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`);
|
|
3345
|
+
byAlias.set(alias, []);
|
|
3346
|
+
return [];
|
|
3347
|
+
}
|
|
3348
|
+
const { entries, dropped } = canEntries(def, alias === "" ? quoteTable(table) : alias);
|
|
3349
|
+
if (dropped.length > 0) {
|
|
3350
|
+
warnOnce(dropped.map((d) => `can(${table}): policy "${d.name}" is not exposed in can \u2014 ${d.reason}; the database still enforces it`).join("\n"));
|
|
3351
|
+
}
|
|
3352
|
+
byAlias.set(alias, entries);
|
|
3353
|
+
return entries;
|
|
3354
|
+
}
|
|
3355
|
+
__name(canEntriesOf, "canEntriesOf");
|
|
3356
|
+
function canOuter(table) {
|
|
3357
|
+
const cols = canProjection(table, "q");
|
|
3358
|
+
return cols.length === 0 ? "" : `, ${cols.join(", ")}`;
|
|
3359
|
+
}
|
|
3360
|
+
__name(canOuter, "canOuter");
|
|
3361
|
+
function canProjection(table, alias) {
|
|
3362
|
+
return canEntriesOf(table, alias).map((e) => `${e.using} AS ${quoteIdent(CAN_PREFIX + e.name)}`);
|
|
3363
|
+
}
|
|
3364
|
+
__name(canProjection, "canProjection");
|
|
3365
|
+
function canJsonPairs(table, alias) {
|
|
3366
|
+
return canEntriesOf(table, alias).map((e) => `${quoteLiteral(CAN_PREFIX + e.name)}, ${e.using}`);
|
|
3367
|
+
}
|
|
3368
|
+
__name(canJsonPairs, "canJsonPairs");
|
|
3369
|
+
function foldCan(table, row, partial) {
|
|
3370
|
+
let can = null;
|
|
3371
|
+
for (const key2 of Object.keys(row)) {
|
|
3372
|
+
if (!key2.startsWith(CAN_PREFIX)) continue;
|
|
3373
|
+
can ??= [];
|
|
3374
|
+
if (row[key2] === true) can.push(key2.slice(CAN_PREFIX.length));
|
|
3375
|
+
delete row[key2];
|
|
3376
|
+
}
|
|
3377
|
+
if (hasCanColumn(table)) return row;
|
|
3378
|
+
if (can !== null) row.can = can;
|
|
3379
|
+
else if (!partial && tableDefOf(table) !== null) {
|
|
3380
|
+
if (canEntriesOf(table, "").length > 0) {
|
|
3381
|
+
const flags = memoTable(canMemo, currentSchema, table, () => /* @__PURE__ */ new Map());
|
|
3382
|
+
if (!flags.has("__missing_warned")) {
|
|
3383
|
+
flags.set("__missing_warned", []);
|
|
3384
|
+
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: []`);
|
|
3385
|
+
}
|
|
3386
|
+
}
|
|
3387
|
+
row.can = [];
|
|
3388
|
+
}
|
|
3389
|
+
return row;
|
|
3390
|
+
}
|
|
3391
|
+
__name(foldCan, "foldCan");
|
|
3392
|
+
function hasCanColumn(table) {
|
|
3393
|
+
const cols = tableDefOf(table)?.columns;
|
|
3394
|
+
if (cols !== void 0 && Object.prototype.hasOwnProperty.call(cols, "can")) return true;
|
|
3395
|
+
return (currentRelations.get(table) ?? []).some((rel) => rel.name === "can");
|
|
3396
|
+
}
|
|
3397
|
+
__name(hasCanColumn, "hasCanColumn");
|
|
3398
|
+
function rowProjection(table, alias, wildcard = alias ? `${alias}.*` : "*") {
|
|
3399
|
+
const cols = schemaColumns(table);
|
|
3400
|
+
let base = wildcard;
|
|
3401
|
+
if (cols && memoTable(timestampMemo, currentSchema, table, () => [
|
|
3402
|
+
...cols
|
|
3403
|
+
].some((c) => columnTypeOf(table, c) === "timestamp"))) {
|
|
3404
|
+
const projections = memoTable(projectionMemo, currentSchema, table, () => /* @__PURE__ */ new Map());
|
|
3405
|
+
const key2 = alias ?? "";
|
|
3406
|
+
let sql = projections.get(key2);
|
|
3407
|
+
if (sql === void 0) {
|
|
3408
|
+
sql = [
|
|
3409
|
+
...cols
|
|
3410
|
+
].map((c) => projectedColumn(table, c, alias)).join(", ");
|
|
3411
|
+
projections.set(key2, sql);
|
|
3412
|
+
}
|
|
3413
|
+
base = sql;
|
|
3414
|
+
}
|
|
3415
|
+
const can = canProjection(table, alias ?? "");
|
|
3416
|
+
if (can.length === 0) return base;
|
|
3417
|
+
const rows = base === "*" && alias !== void 0 ? `${alias}.*` : base;
|
|
3418
|
+
return `${rows}, ${can.join(", ")}`;
|
|
3419
|
+
}
|
|
3420
|
+
__name(rowProjection, "rowProjection");
|
|
2113
3421
|
function colRefSql(table, known, ref, caller, alias = "t") {
|
|
2114
3422
|
const name = ref.$col;
|
|
2115
3423
|
const shapeOK = /^[A-Za-z_][A-Za-z0-9_]*$/.test(name);
|
|
@@ -2123,13 +3431,13 @@ function selectClause(table, known, select) {
|
|
|
2123
3431
|
if (select !== void 0 && select.length === 0) {
|
|
2124
3432
|
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.`);
|
|
2125
3433
|
}
|
|
2126
|
-
if (select === void 0) return "*";
|
|
3434
|
+
if (select === void 0) return rowProjection(table, "t", "*");
|
|
2127
3435
|
return select.map((col) => {
|
|
2128
3436
|
const shapeOK = /^[A-Za-z_][A-Za-z0-9_]*$/.test(col);
|
|
2129
3437
|
if (known !== null ? !known.has(col) : !shapeOK) {
|
|
2130
3438
|
throw new Error(`findMany(${table}): select kolonu "${col}" tabloda yok`);
|
|
2131
3439
|
}
|
|
2132
|
-
return `t.${quoteIdent(col)}`;
|
|
3440
|
+
return columnTypeOf(table, col) === "timestamp" ? projectedColumn(table, col, "t") : `t.${quoteIdent(col)}`;
|
|
2133
3441
|
}).join(", ");
|
|
2134
3442
|
}
|
|
2135
3443
|
__name(selectClause, "selectClause");
|
|
@@ -2199,7 +3507,7 @@ function compileWhereBare(table, colSet, where, add, caller = "search", alias =
|
|
|
2199
3507
|
const transforms = transformsOf(currentSchema, table);
|
|
2200
3508
|
const bindFor = /* @__PURE__ */ __name((col) => {
|
|
2201
3509
|
const t = transforms.get(col);
|
|
2202
|
-
return t?.toDb === void 0
|
|
3510
|
+
return (v) => add(t?.toDb === void 0 || v === null || v === void 0 ? v : t.toDb(v), col, table);
|
|
2203
3511
|
}, "bindFor");
|
|
2204
3512
|
assertUsableFilter(caller, table, where);
|
|
2205
3513
|
if (isSqlFragment(where)) {
|
|
@@ -2230,8 +3538,7 @@ function compileWhereBare(table, colSet, where, add, caller = "search", alias =
|
|
|
2230
3538
|
const known = rels.map((r) => r.name).sort();
|
|
2231
3539
|
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.`));
|
|
2232
3540
|
}
|
|
2233
|
-
const ourCol =
|
|
2234
|
-
const theirCol = rel.kind === "many" ? rel.via : primaryKeyOf(rel.to);
|
|
3541
|
+
const [ourCol, theirCol] = relationColumns(table, rel);
|
|
2235
3542
|
if (ourCol === null || theirCol === null) {
|
|
2236
3543
|
const missing = ourCol === null ? table : rel.to;
|
|
2237
3544
|
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.`);
|
|
@@ -2369,8 +3676,8 @@ async function embedQuery(embed, text) {
|
|
|
2369
3676
|
if (secretReader === null) {
|
|
2370
3677
|
throw new Error(`query embed: secret reader ba\u011Flanmam\u0131\u015F \u2014 ${embed.apiKeyName} okunam\u0131yor`);
|
|
2371
3678
|
}
|
|
2372
|
-
const
|
|
2373
|
-
if (
|
|
3679
|
+
const key2 = await secretReader(embed.apiKeyName);
|
|
3680
|
+
if (key2 === null || key2 === "") {
|
|
2374
3681
|
throw new Error(`query embed: vault'ta ${embed.apiKeyName} yok (FR-021/FR-025)`);
|
|
2375
3682
|
}
|
|
2376
3683
|
const url = (embed.baseURL ?? "https://api.openai.com/v1").replace(/\/$/, "") + "/embeddings";
|
|
@@ -2381,7 +3688,7 @@ async function embedQuery(embed, text) {
|
|
|
2381
3688
|
method: "POST",
|
|
2382
3689
|
headers: {
|
|
2383
3690
|
"Content-Type": "application/json",
|
|
2384
|
-
Authorization: `Bearer ${
|
|
3691
|
+
Authorization: `Bearer ${key2}`
|
|
2385
3692
|
},
|
|
2386
3693
|
body: JSON.stringify({
|
|
2387
3694
|
model: embed.model,
|
|
@@ -2439,14 +3746,14 @@ function buildValueTuple(table, cols, data, add, caller) {
|
|
|
2439
3746
|
}).join(", ");
|
|
2440
3747
|
}
|
|
2441
3748
|
__name(buildValueTuple, "buildValueTuple");
|
|
2442
|
-
function
|
|
3749
|
+
function computeColumnType(table, col) {
|
|
2443
3750
|
const def = tableDefOf(table);
|
|
2444
3751
|
const c = def?.columns?.[col];
|
|
2445
3752
|
if (c === void 0 || c === null) return null;
|
|
2446
3753
|
const d = typeof c === "object" && "_def" in c ? c._def : c;
|
|
2447
3754
|
return d !== null && typeof d === "object" && typeof d.type === "string" ? d.type : null;
|
|
2448
3755
|
}
|
|
2449
|
-
__name(
|
|
3756
|
+
__name(computeColumnType, "computeColumnType");
|
|
2450
3757
|
var NUMERIC_PG_TYPES = /* @__PURE__ */ new Set([
|
|
2451
3758
|
"integer",
|
|
2452
3759
|
"bigint",
|
|
@@ -2456,23 +3763,44 @@ function aggregateExpr(table, fn, col, alias) {
|
|
|
2456
3763
|
const q = `t.${quoteIdent(col)}`;
|
|
2457
3764
|
const t = columnTypeOf(table, col);
|
|
2458
3765
|
if (fn === "sum" || fn === "avg") return `${fn}(${q})::text AS ${quoteIdent(alias)}`;
|
|
3766
|
+
if (t === "timestamp") return `${timestampResult(`${fn}(${q})`)} AS ${quoteIdent(alias)}`;
|
|
2459
3767
|
return t !== null && NUMERIC_PG_TYPES.has(t) && t !== "integer" ? `${fn}(${q})::text AS ${quoteIdent(alias)}` : `${fn}(${q}) AS ${quoteIdent(alias)}`;
|
|
2460
3768
|
}
|
|
2461
3769
|
__name(aggregateExpr, "aggregateExpr");
|
|
2462
|
-
function jsonObjectFor(table, cols, alias) {
|
|
3770
|
+
function jsonObjectFor(table, cols, alias, extra = []) {
|
|
2463
3771
|
const pairs = cols.map((c) => {
|
|
2464
3772
|
const t = columnTypeOf(table, c);
|
|
2465
|
-
const expr = t !== null && (t === "numeric" || t === "bigint") ? `${alias}.${quoteIdent(c)}::text` : `${alias}.${quoteIdent(c)}`;
|
|
3773
|
+
const expr = t !== null && (t === "numeric" || t === "bigint") ? `${alias}.${quoteIdent(c)}::text` : t === "timestamp" ? timestampResult(`${alias}.${quoteIdent(c)}`) : `${alias}.${quoteIdent(c)}`;
|
|
2466
3774
|
return `${quoteLiteral(c)}, ${expr}`;
|
|
2467
3775
|
});
|
|
2468
|
-
return `json_build_object(${
|
|
3776
|
+
return `json_build_object(${[
|
|
3777
|
+
...pairs,
|
|
3778
|
+
...extra
|
|
3779
|
+
].join(", ")})`;
|
|
2469
3780
|
}
|
|
2470
3781
|
__name(jsonObjectFor, "jsonObjectFor");
|
|
2471
|
-
function
|
|
3782
|
+
function relationColumns(table, relation) {
|
|
3783
|
+
const child = relation.kind === "many" ? relation.to : table;
|
|
3784
|
+
const value = tableDefOf(child)?.columns?.[relation.via];
|
|
3785
|
+
const column = value && typeof value === "object" && "_def" in value ? value._def : value;
|
|
3786
|
+
const target = column?.references?.column ?? primaryKeyOf(relation.kind === "many" ? table : relation.to);
|
|
3787
|
+
return relation.kind === "many" ? [
|
|
3788
|
+
target,
|
|
3789
|
+
relation.via
|
|
3790
|
+
] : [
|
|
3791
|
+
relation.via,
|
|
3792
|
+
target
|
|
3793
|
+
];
|
|
3794
|
+
}
|
|
3795
|
+
__name(relationColumns, "relationColumns");
|
|
3796
|
+
function withClauses(table, spec, add, caller, parentAlias = "t", depth = 0, counter = {
|
|
3797
|
+
value: 0
|
|
3798
|
+
}) {
|
|
3799
|
+
if (depth >= 3 && Object.values(spec).some(Boolean)) throw new Error(`${caller}(${table}): with supports at most three relation levels`);
|
|
2472
3800
|
const rels = currentRelations.get(table) ?? [];
|
|
2473
3801
|
const select = [];
|
|
2474
3802
|
const joins = [];
|
|
2475
|
-
|
|
3803
|
+
const jsonPairs = [];
|
|
2476
3804
|
for (const [name, opt] of Object.entries(spec)) {
|
|
2477
3805
|
if (opt === false || opt === void 0) continue;
|
|
2478
3806
|
const rel = rels.find((r) => r.name === name);
|
|
@@ -2480,13 +3808,13 @@ function withClauses(table, spec, add, caller) {
|
|
|
2480
3808
|
const known = rels.map((r) => r.name).sort();
|
|
2481
3809
|
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.`);
|
|
2482
3810
|
}
|
|
2483
|
-
const ourCol =
|
|
2484
|
-
const theirCol = rel.kind === "many" ? rel.via : primaryKeyOf(rel.to);
|
|
3811
|
+
const [ourCol, theirCol] = relationColumns(table, rel);
|
|
2485
3812
|
if (ourCol === null || theirCol === null) {
|
|
2486
3813
|
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.`);
|
|
2487
3814
|
}
|
|
2488
3815
|
const o = typeof opt === "object" && opt !== null ? opt : {};
|
|
2489
|
-
const
|
|
3816
|
+
const i = ++counter.value;
|
|
3817
|
+
const childAlias = `w${i}`;
|
|
2490
3818
|
const childKnown = schemaColumns(rel.to);
|
|
2491
3819
|
const childCols = Array.isArray(o["select"]) && o["select"].length > 0 ? o["select"] : childKnown !== null ? [
|
|
2492
3820
|
...childKnown
|
|
@@ -2499,28 +3827,103 @@ function withClauses(table, spec, add, caller) {
|
|
|
2499
3827
|
throw new Error(`${caller}(${table}): with.${name}.select kolonu "${c}" "${rel.to}" tablosunda yok`);
|
|
2500
3828
|
}
|
|
2501
3829
|
}
|
|
2502
|
-
const
|
|
3830
|
+
const nestedSpec = o["with"];
|
|
3831
|
+
if (nestedSpec !== void 0 && (nestedSpec === null || typeof nestedSpec !== "object" || Array.isArray(nestedSpec))) {
|
|
3832
|
+
throw new Error(`${caller}(${table}): with.${name}.with must be a relation object`);
|
|
3833
|
+
}
|
|
3834
|
+
const nested = nestedSpec === void 0 ? {
|
|
3835
|
+
joins: [],
|
|
3836
|
+
jsonPairs: []
|
|
3837
|
+
} : withClauses(rel.to, nestedSpec, add, caller, childAlias, depth + 1, counter);
|
|
3838
|
+
const nestedJoins = nested.joins.length ? ` ${nested.joins.join(" ")}` : "";
|
|
3839
|
+
const obj = jsonObjectFor(rel.to, childCols, childAlias, [
|
|
3840
|
+
...nested.jsonPairs,
|
|
3841
|
+
...canJsonPairs(rel.to, childAlias)
|
|
3842
|
+
]);
|
|
2503
3843
|
const innerWhere = o["where"] !== void 0 && o["where"] !== null ? compileWhereBare(rel.to, childKnown, o["where"], add, caller, childAlias, i) : "";
|
|
2504
|
-
const link = `${childAlias}.${quoteIdent(theirCol)} =
|
|
3844
|
+
const link = `${childAlias}.${quoteIdent(theirCol)} = ${parentAlias}.${quoteIdent(ourCol)}`;
|
|
2505
3845
|
const cond = innerWhere === "" ? link : `${link} AND ${innerWhere}`;
|
|
2506
|
-
const
|
|
3846
|
+
const requestedLimit = o["limit"];
|
|
3847
|
+
limitClause(requestedLimit);
|
|
3848
|
+
const relationLimit = rel.kind === "many" ? requestedLimit : Math.min(1, requestedLimit ?? 1);
|
|
3849
|
+
const inner = orderClauseFor(rel.to, childKnown, o["orderBy"], childAlias) + limitClause(relationLimit);
|
|
2507
3850
|
const outAlias = `wj${i}`;
|
|
2508
3851
|
if (rel.kind === "many") {
|
|
2509
|
-
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`);
|
|
3852
|
+
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`);
|
|
2510
3853
|
} else {
|
|
2511
|
-
joins.push(`LEFT JOIN LATERAL (SELECT ${obj} AS d FROM ${quoteTable(rel.to)} ${childAlias} WHERE ${cond}${inner}
|
|
3854
|
+
joins.push(`LEFT JOIN LATERAL (SELECT ${obj} AS d FROM ${quoteTable(rel.to)} ${childAlias}${nestedJoins} WHERE ${cond}${inner}) ${quoteIdent(outAlias)} ON true`);
|
|
2512
3855
|
}
|
|
2513
3856
|
select.push(`${quoteIdent(outAlias)}."d" AS ${quoteIdent(name)}`);
|
|
3857
|
+
jsonPairs.push(`${quoteLiteral(name)}, ${quoteIdent(outAlias)}."d"`);
|
|
2514
3858
|
}
|
|
2515
3859
|
return {
|
|
2516
3860
|
select,
|
|
2517
|
-
joins
|
|
3861
|
+
joins,
|
|
3862
|
+
jsonPairs
|
|
2518
3863
|
};
|
|
2519
3864
|
}
|
|
2520
3865
|
__name(withClauses, "withClauses");
|
|
2521
|
-
function
|
|
3866
|
+
function compileFindMany(table, query = {}, opts = {}) {
|
|
3867
|
+
const params = [];
|
|
3868
|
+
const add = /* @__PURE__ */ __name((v) => {
|
|
3869
|
+
params.push(v);
|
|
3870
|
+
return `$${params.length}`;
|
|
3871
|
+
}, "add");
|
|
3872
|
+
const known = schemaColumns(table);
|
|
3873
|
+
const whereSql = compileWhere(table, known, query, add, "findMany");
|
|
3874
|
+
const order = orderClause(table, known, opts.orderBy);
|
|
3875
|
+
const limit = limitClause(opts.limit);
|
|
3876
|
+
const offset = offsetClause(opts.offset, opts.limit);
|
|
3877
|
+
const cols = selectClause(table, known, opts.select);
|
|
3878
|
+
const w = opts.with !== void 0 && Object.keys(opts.with).length > 0 ? withClauses(table, opts.with, add, "findMany") : {
|
|
3879
|
+
select: [],
|
|
3880
|
+
joins: []
|
|
3881
|
+
};
|
|
3882
|
+
const selectList = w.select.length > 0 ? `${cols === "*" ? "t.*" : cols}, ${w.select.join(", ")}` : cols;
|
|
3883
|
+
const joinSql = w.joins.length > 0 ? ` ${w.joins.join(" ")}` : "";
|
|
3884
|
+
const sql = `SELECT ${selectList} FROM ${quoteTable(table)} t${joinSql} WHERE true${whereSql}${order}${limit}${offset}`;
|
|
3885
|
+
return {
|
|
3886
|
+
sql,
|
|
3887
|
+
params
|
|
3888
|
+
};
|
|
3889
|
+
}
|
|
3890
|
+
__name(compileFindMany, "compileFindMany");
|
|
3891
|
+
var uniqueKeysCache = /* @__PURE__ */ new WeakMap();
|
|
3892
|
+
var pageSchemaCache = /* @__PURE__ */ new WeakMap();
|
|
3893
|
+
function pageSchema(table) {
|
|
3894
|
+
return memoTable(pageSchemaCache, currentSchema, table, () => {
|
|
3895
|
+
const def = tableDefOf(table);
|
|
3896
|
+
if (!def?.columns) throw new Error(`page(${table}) requires an installed table declaration`);
|
|
3897
|
+
const columns = /* @__PURE__ */ Object.create(null);
|
|
3898
|
+
const inlineKeys = [];
|
|
3899
|
+
for (const [name, column] of Object.entries(def.columns)) {
|
|
3900
|
+
const c = column;
|
|
3901
|
+
const d = c._def ?? c;
|
|
3902
|
+
columns[name] = {
|
|
3903
|
+
type: d.type,
|
|
3904
|
+
nullable: d.nullable
|
|
3905
|
+
};
|
|
3906
|
+
if (d.primaryKey) inlineKeys.push(name);
|
|
3907
|
+
}
|
|
3908
|
+
const primaryKey = def.primaryKey ?? inlineKeys;
|
|
3909
|
+
for (const name of primaryKey) if (columns[name]) columns[name].nullable = false;
|
|
3910
|
+
return {
|
|
3911
|
+
columns,
|
|
3912
|
+
primaryKey,
|
|
3913
|
+
stamp: fingerprint({
|
|
3914
|
+
columns,
|
|
3915
|
+
primaryKey
|
|
3916
|
+
})
|
|
3917
|
+
};
|
|
3918
|
+
});
|
|
3919
|
+
}
|
|
3920
|
+
__name(pageSchema, "pageSchema");
|
|
3921
|
+
function createOps(tx, context = {}) {
|
|
2522
3922
|
const at = /* @__PURE__ */ __name(() => resolveTx(tx), "at");
|
|
2523
3923
|
const ops = {
|
|
3924
|
+
diagnostics() {
|
|
3925
|
+
return context.trace?.snapshot ?? null;
|
|
3926
|
+
},
|
|
2524
3927
|
// Generic dönüş, çağıranın İDDİASIDIR (FR-021). Motor her zaman satır objeleri
|
|
2525
3928
|
// üretir; `T` onlara bir ad vermekten ibarettir ve verilmezse `unknown` kalır —
|
|
2526
3929
|
// yani "bilmiyorum", ki ham SQL için doğru cevap odur.
|
|
@@ -2534,7 +3937,7 @@ function createOps(tx) {
|
|
|
2534
3937
|
const params = [];
|
|
2535
3938
|
const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
|
|
2536
3939
|
const tuple = buildValueTuple(table, cols, data, add, "insert");
|
|
2537
|
-
const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING
|
|
3940
|
+
const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING ${rowProjection(table)}`;
|
|
2538
3941
|
const rows = await (await at()).unsafe(sql, params);
|
|
2539
3942
|
const inserted = rows[0];
|
|
2540
3943
|
if (!inserted) {
|
|
@@ -2556,8 +3959,9 @@ function createOps(tx) {
|
|
|
2556
3959
|
* bir satırın fazla kolonu SESSİZCE YAZILMAZDI. Eksik kolon için `null`
|
|
2557
3960
|
* yazın ya da varsayılanı istiyorsanız o satırları ayrı çağrıda ekleyin.
|
|
2558
3961
|
*/
|
|
2559
|
-
async
|
|
2560
|
-
|
|
3962
|
+
insertMany: /* @__PURE__ */ __name(async function(table, rows, opts) {
|
|
3963
|
+
validateInsertManyOptions(opts);
|
|
3964
|
+
if (rows.length === 0) return opts?.returning === false ? 0 : [];
|
|
2561
3965
|
const first = rows[0];
|
|
2562
3966
|
const cols = Object.keys(first);
|
|
2563
3967
|
if (cols.length === 0) throw new Error(`insertMany into ${table}: no columns given`);
|
|
@@ -2572,11 +3976,15 @@ function createOps(tx) {
|
|
|
2572
3976
|
const params = [];
|
|
2573
3977
|
const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
|
|
2574
3978
|
const tuples = rows.map((r) => `(${buildValueTuple(table, cols, r, add, "insertMany")})`);
|
|
2575
|
-
const tail = opts !== void 0 && opts.onConflict.length > 0 ? ` ${onConflictTail(cols, opts.onConflict, opts.action ?? "ignore")}` : "";
|
|
2576
|
-
const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES ${tuples.join(", ")}${tail} RETURNING
|
|
3979
|
+
const tail = opts?.onConflict !== void 0 && opts.onConflict.length > 0 ? ` ${onConflictTail(cols, opts.onConflict, opts.action ?? "ignore")}` : "";
|
|
3980
|
+
const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES ${tuples.join(", ")}${tail}${opts?.returning === false ? "" : ` RETURNING ${rowProjection(table)}`}`;
|
|
2577
3981
|
const out = await (await at()).unsafe(sql, params);
|
|
3982
|
+
if (opts?.returning === false) {
|
|
3983
|
+
if (!Number.isSafeInteger(out.count) || out.count < 0) throw new Error("insertMany returning:false requires a driver with an affected row count");
|
|
3984
|
+
return out.count;
|
|
3985
|
+
}
|
|
2578
3986
|
return out.map((r) => asTableRow(table, r));
|
|
2579
|
-
},
|
|
3987
|
+
}, "insertMany"),
|
|
2580
3988
|
/**
|
|
2581
3989
|
* Bir ADI kilitle — satırı değil (FR-054).
|
|
2582
3990
|
*
|
|
@@ -2590,9 +3998,9 @@ function createOps(tx) {
|
|
|
2590
3998
|
* Transaction kapsamlısı commit/rollback'te Postgres tarafından bırakılır,
|
|
2591
3999
|
* yani unutulması imkânsız.
|
|
2592
4000
|
*/
|
|
2593
|
-
async advisoryXactLock(
|
|
4001
|
+
async advisoryXactLock(key2) {
|
|
2594
4002
|
await (await at()).unsafe("select pg_advisory_xact_lock(hashtextextended($1, 0))", [
|
|
2595
|
-
|
|
4003
|
+
key2
|
|
2596
4004
|
]);
|
|
2597
4005
|
},
|
|
2598
4006
|
/**
|
|
@@ -2666,7 +4074,7 @@ function createOps(tx) {
|
|
|
2666
4074
|
const params = [];
|
|
2667
4075
|
const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
|
|
2668
4076
|
const tuple = buildValueTuple(table, cols, data, add, "claim");
|
|
2669
|
-
const rows = await sp.unsafe(`INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING
|
|
4077
|
+
const rows = await sp.unsafe(`INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING ${rowProjection(table)}`, params);
|
|
2670
4078
|
if (!rows[0]) {
|
|
2671
4079
|
throw new Error(`claim(${table}): INSERT sat\u0131r d\xF6nd\xFCrmedi \u2014 yazma reddedildi (b\xFCy\xFCk olas\u0131l\u0131kla bir RLS policy'si).`);
|
|
2672
4080
|
}
|
|
@@ -2683,7 +4091,7 @@ function createOps(tx) {
|
|
|
2683
4091
|
return `$${params.length}`;
|
|
2684
4092
|
}, "add");
|
|
2685
4093
|
const where = keyCols.map((c) => `t.${quoteIdent(c)} = ${add(unique[c])}`).join(" AND ");
|
|
2686
|
-
const found = await (await at()).unsafe(`SELECT * FROM ${quoteTable(table)} t WHERE ${where} LIMIT 1`, params);
|
|
4094
|
+
const found = await (await at()).unsafe(`SELECT ${rowProjection(table, "t", "*")} FROM ${quoteTable(table)} t WHERE ${where} LIMIT 1`, params);
|
|
2687
4095
|
if (!found[0]) {
|
|
2688
4096
|
const named = constraintOf(e);
|
|
2689
4097
|
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.`);
|
|
@@ -2720,13 +4128,13 @@ function createOps(tx) {
|
|
|
2720
4128
|
const bindData = [];
|
|
2721
4129
|
const addPut = /* @__PURE__ */ __name((v) => `$${bindData.push(v)}`, "addPut");
|
|
2722
4130
|
const ph = buildValueTuple(table, cols, data, addPut, "put");
|
|
2723
|
-
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
|
|
4131
|
+
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)}`;
|
|
2724
4132
|
const live = await at();
|
|
2725
4133
|
const first = await live.unsafe(insertSql, bindData);
|
|
2726
4134
|
if (first[0]) return asTableRow(table, first[0]);
|
|
2727
4135
|
const condSql = conflict.map((c, i) => `${quoteIdent(c)} = $${i + 1}`).join(" AND ");
|
|
2728
4136
|
const condBind = conflict.map((c) => data[c]);
|
|
2729
|
-
const cur = await live.unsafe(`SELECT
|
|
4137
|
+
const cur = await live.unsafe(`SELECT ${rowProjection(table)} FROM ${quoteTable(table)} WHERE ${condSql} AND "valid_to" IS NULL`, condBind);
|
|
2730
4138
|
const old = cur[0];
|
|
2731
4139
|
if (old === void 0) {
|
|
2732
4140
|
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)`);
|
|
@@ -2752,7 +4160,7 @@ function createOps(tx) {
|
|
|
2752
4160
|
const conflictSet = new Set(conflict);
|
|
2753
4161
|
const assignments = cols.filter((c) => !conflictSet.has(c)).map((c) => `${quoteIdent(c)} = EXCLUDED.${quoteIdent(c)}`);
|
|
2754
4162
|
const action = assignments.length ? `DO UPDATE SET ${assignments.join(", ")}` : `DO UPDATE SET ${quoteIdent(conflict[0])} = EXCLUDED.${quoteIdent(conflict[0])}`;
|
|
2755
|
-
const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${placeholders}) ON CONFLICT (${conflict.map(quoteIdent).join(", ")}) ${action} RETURNING
|
|
4163
|
+
const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${placeholders}) ON CONFLICT (${conflict.map(quoteIdent).join(", ")}) ${action} RETURNING ${rowProjection(table)}`;
|
|
2756
4164
|
const rows = await (await at()).unsafe(sql, params);
|
|
2757
4165
|
const row = rows[0];
|
|
2758
4166
|
if (!row) {
|
|
@@ -2770,7 +4178,7 @@ function createOps(tx) {
|
|
|
2770
4178
|
return `$${params.length}`;
|
|
2771
4179
|
}, "add");
|
|
2772
4180
|
const assignments = buildSetAssignments(table, cols, data, add, "update");
|
|
2773
|
-
const sql = `UPDATE ${quoteTable(table)} SET ${assignments} WHERE id = ${add(id)} RETURNING
|
|
4181
|
+
const sql = `UPDATE ${quoteTable(table)} SET ${assignments} WHERE id = ${add(id)} RETURNING ${rowProjection(table)}`;
|
|
2774
4182
|
const rows = await (await at()).unsafe(sql, params);
|
|
2775
4183
|
return rows[0] ? asTableRow(table, rows[0]) : null;
|
|
2776
4184
|
},
|
|
@@ -2822,10 +4230,10 @@ function createOps(tx) {
|
|
|
2822
4230
|
assertHasPredicate(whereSql, "updateMany", table);
|
|
2823
4231
|
const pk = primaryKeyOf(table);
|
|
2824
4232
|
const wantRows = opts?.returning !== false;
|
|
2825
|
-
const tail = wantRows ?
|
|
4233
|
+
const tail = wantRows ? `RETURNING ${rowProjection(table, "t", "*")}` : "RETURNING 1";
|
|
2826
4234
|
const simple = isSinglePkEquality(where, pk) || pk === null;
|
|
2827
|
-
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
|
|
2828
|
-
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) `;
|
|
4235
|
+
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");
|
|
4236
|
+
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) `;
|
|
2829
4237
|
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`;
|
|
2830
4238
|
const out = await (await at()).unsafe(sql, params);
|
|
2831
4239
|
if (!wantRows) return Number(out[0]?.["n"] ?? 0);
|
|
@@ -2909,7 +4317,7 @@ function createOps(tx) {
|
|
|
2909
4317
|
}
|
|
2910
4318
|
const groups = q.groupBy ?? [];
|
|
2911
4319
|
for (const col of groups) check(col, "groupBy");
|
|
2912
|
-
const groupSel = groups.map((c) => `t.${quoteIdent(c)} AS ${quoteIdent(`g__${c}`)}`);
|
|
4320
|
+
const groupSel = groups.map((c) => `${columnTypeOf(table, c) === "timestamp" ? columnResult(table, c, "t") : `t.${quoteIdent(c)}`} AS ${quoteIdent(`g__${c}`)}`);
|
|
2913
4321
|
const params = [];
|
|
2914
4322
|
const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
|
|
2915
4323
|
const whereSql = compileWhere(table, known, q.where ?? {}, add, "aggregate");
|
|
@@ -2943,32 +4351,114 @@ function createOps(tx) {
|
|
|
2943
4351
|
const rows = await (await at()).unsafe(sql, params);
|
|
2944
4352
|
return Number(rows[0]?.n ?? 0);
|
|
2945
4353
|
},
|
|
4354
|
+
async findUnique(table, where, opts = {}) {
|
|
4355
|
+
const keys = memoTable(uniqueKeysCache, currentSchema, table, () => uniqueKeysOf(tableDefOf(table) ?? {}));
|
|
4356
|
+
assertUniqueLookup(table, keys, where);
|
|
4357
|
+
const rows = await ops.findMany(table, where, {
|
|
4358
|
+
...opts,
|
|
4359
|
+
select: opts.select === void 0 ? void 0 : [
|
|
4360
|
+
...opts.select
|
|
4361
|
+
],
|
|
4362
|
+
limit: 2
|
|
4363
|
+
});
|
|
4364
|
+
if (rows.length > 1) throw new Error(`findUnique(${table}): deployed constraints disagree with the schema; multiple rows matched`);
|
|
4365
|
+
return rows[0] ?? null;
|
|
4366
|
+
},
|
|
2946
4367
|
async findById(table, id) {
|
|
2947
|
-
const rows = await (await at()).unsafe(`SELECT
|
|
4368
|
+
const rows = await (await at()).unsafe(`SELECT ${rowProjection(table)} FROM ${quoteTable(table)} WHERE id = $1`, [
|
|
2948
4369
|
id
|
|
2949
4370
|
]);
|
|
2950
4371
|
return rows[0] ? asTableRow(table, rows[0]) : null;
|
|
2951
4372
|
},
|
|
4373
|
+
compileQuery(query) {
|
|
4374
|
+
assertReadContract(query, tableDefOf(query.table)?.columns);
|
|
4375
|
+
return compileFindMany(query.table, query.where, query.options);
|
|
4376
|
+
},
|
|
4377
|
+
async readQuery(query) {
|
|
4378
|
+
const compiled = ops.compileQuery(query);
|
|
4379
|
+
return asTableRows(query.table, await (await at()).unsafe(compiled.sql, compiled.params, {
|
|
4380
|
+
name: query.name
|
|
4381
|
+
}), void 0, true);
|
|
4382
|
+
},
|
|
4383
|
+
async explainQuery(query) {
|
|
4384
|
+
const compiled = ops.compileQuery(query);
|
|
4385
|
+
return await (await at()).unsafe(`EXPLAIN (FORMAT JSON) ${compiled.sql}`, compiled.params, {
|
|
4386
|
+
name: `${query.name}.explain`
|
|
4387
|
+
});
|
|
4388
|
+
},
|
|
2952
4389
|
async findMany(table, query = {}, opts = {}) {
|
|
4390
|
+
const compiled = compileFindMany(table, query, opts);
|
|
4391
|
+
return asTableRows(table, await (await at()).unsafe(compiled.sql, compiled.params), opts.with, opts.select !== void 0);
|
|
4392
|
+
},
|
|
4393
|
+
async page(table, query = {}, opts = {}) {
|
|
4394
|
+
const secret = cursorKey();
|
|
4395
|
+
const schema = pageSchema(table);
|
|
4396
|
+
const { order, size, backward, token } = pageOrder(schema.columns, schema.primaryKey, opts);
|
|
2953
4397
|
const params = [];
|
|
2954
|
-
const add = /* @__PURE__ */ __name((v) => {
|
|
2955
|
-
params.push(v);
|
|
2956
|
-
return `$${params.length}`;
|
|
2957
|
-
}, "add");
|
|
4398
|
+
const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
|
|
2958
4399
|
const known = schemaColumns(table);
|
|
2959
|
-
const
|
|
2960
|
-
const
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
const
|
|
2964
|
-
|
|
4400
|
+
const where = compileWhere(table, known, query, add, "page");
|
|
4401
|
+
const cols = selectClause(table, known, opts.select === void 0 ? void 0 : [
|
|
4402
|
+
...opts.select
|
|
4403
|
+
]);
|
|
4404
|
+
const scope = fingerprint({
|
|
4405
|
+
table,
|
|
4406
|
+
schema: schema.stamp,
|
|
4407
|
+
order,
|
|
4408
|
+
where,
|
|
4409
|
+
params,
|
|
4410
|
+
select: opts.select,
|
|
4411
|
+
with: opts.with,
|
|
4412
|
+
identity: context.cursorScope ?? "direct"
|
|
4413
|
+
});
|
|
4414
|
+
const values = token === void 0 ? void 0 : decodeCursor(secret, scope, token, order.length);
|
|
4415
|
+
const column = /* @__PURE__ */ __name((name) => `t.${quoteIdent(name)}`, "column");
|
|
4416
|
+
const boundary = values ? ` AND ${pageBoundary(order, values, backward, add, column)}` : "";
|
|
4417
|
+
const opposite = values ? `EXISTS (SELECT 1 FROM ${quoteTable(table)} t WHERE true${where} AND ${pageBoundary(order, values, !backward, add, column, true)})` : "false";
|
|
4418
|
+
let prefix = "__pb_page_";
|
|
4419
|
+
const names = [
|
|
4420
|
+
...Object.keys(schema.columns),
|
|
4421
|
+
...Object.keys(opts.with ?? {})
|
|
4422
|
+
];
|
|
4423
|
+
while (names.some((n) => n.startsWith(prefix))) prefix += "_";
|
|
4424
|
+
const ordinal = `${prefix}ordinal`, other = `${prefix}opposite`;
|
|
4425
|
+
const hidden = order.map((o, i) => `${prefix}${i}`);
|
|
4426
|
+
const cursorCols = order.map((o, i) => `${columnTypeOf(table, o.column) === "timestamp" ? timestampResult(column(o.column)) : `${column(o.column)}::text`} AS ${quoteIdent(hidden[i])}`);
|
|
4427
|
+
const w = opts.with && Object.keys(opts.with).length ? withClauses(table, opts.with, add, "page") : {
|
|
2965
4428
|
select: [],
|
|
2966
4429
|
joins: []
|
|
2967
4430
|
};
|
|
2968
|
-
const
|
|
2969
|
-
const
|
|
2970
|
-
const
|
|
2971
|
-
|
|
4431
|
+
const nativeOrder = order.map((o) => `${column(o.column)} ${o.direction === "asc" !== backward ? "ASC" : "DESC"} NULLS ${o.nulls === "first" !== backward ? "FIRST" : "LAST"}`).join(", ");
|
|
4432
|
+
const visible = cols === "*" ? "t.*" : cols;
|
|
4433
|
+
const inner = `SELECT ${[
|
|
4434
|
+
visible,
|
|
4435
|
+
...w.select,
|
|
4436
|
+
...cursorCols,
|
|
4437
|
+
`row_number() OVER (ORDER BY ${nativeOrder}) AS ${quoteIdent(ordinal)}`
|
|
4438
|
+
].join(", ")} FROM ${quoteTable(table)} t ${w.joins.join(" ")} WHERE true${where}${boundary} ORDER BY ${nativeOrder} LIMIT ${size + 1}`;
|
|
4439
|
+
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)}`;
|
|
4440
|
+
const result = await (await at()).unsafe(sql, params);
|
|
4441
|
+
const hasOpposite = result[0]?.[other] === true;
|
|
4442
|
+
let rows = result.filter((row) => row[ordinal] !== null && row[ordinal] !== void 0);
|
|
4443
|
+
const hasMore = rows.length > size;
|
|
4444
|
+
rows = rows.slice(0, size);
|
|
4445
|
+
if (backward) rows.reverse();
|
|
4446
|
+
const cursor = /* @__PURE__ */ __name((row) => row ? encodeCursor(secret, scope, hidden.map((name) => row[name] === null ? null : String(row[name]))) : null, "cursor");
|
|
4447
|
+
const pageInfo = {
|
|
4448
|
+
startCursor: cursor(rows[0]),
|
|
4449
|
+
endCursor: cursor(rows.at(-1)),
|
|
4450
|
+
hasNextPage: backward ? hasOpposite : hasMore,
|
|
4451
|
+
hasPreviousPage: backward ? hasMore : hasOpposite
|
|
4452
|
+
};
|
|
4453
|
+
for (const row of rows) for (const name of [
|
|
4454
|
+
...hidden,
|
|
4455
|
+
ordinal,
|
|
4456
|
+
other
|
|
4457
|
+
]) delete row[name];
|
|
4458
|
+
return {
|
|
4459
|
+
rows: asTableRows(table, rows, opts.with, opts.select !== void 0),
|
|
4460
|
+
pageInfo
|
|
4461
|
+
};
|
|
2972
4462
|
},
|
|
2973
4463
|
/**
|
|
2974
4464
|
* Tek-SQL hibrit arama (FR-014): iki kol CTE + FULL OUTER JOIN + RRF
|
|
@@ -3073,7 +4563,9 @@ function createOps(tx) {
|
|
|
3073
4563
|
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}`;
|
|
3074
4564
|
}
|
|
3075
4565
|
const msC = params.minScore === void 0 ? "" : addC(params.minScore);
|
|
3076
|
-
const colListC =
|
|
4566
|
+
const colListC = [
|
|
4567
|
+
...cfg.cols.map((c) => columnTypeOf(table, c) === "timestamp" ? projectedColumn(table, c, "t") : `t.${quoteIdent(c)}`)
|
|
4568
|
+
].join(", ");
|
|
3077
4569
|
const scoreC = scoreMul === "" ? "g._score" : `(g._score)${scoreMul}`;
|
|
3078
4570
|
const assembleC = /* @__PURE__ */ __name((kwIn, withKwn) => {
|
|
3079
4571
|
const kwnCol = withKwn ? `, (SELECT count(*) FROM kw)::int AS _kwn` : "";
|
|
@@ -3086,7 +4578,7 @@ function createOps(tx) {
|
|
|
3086
4578
|
fusedSrc = `WITH kw AS (${kwIn}), fused AS (SELECT kw.pid, kw.chunk_seq, (1.0/(${K2} + kw.r))::float8 AS cscore FROM kw)`;
|
|
3087
4579
|
}
|
|
3088
4580
|
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`;
|
|
3089
|
-
return msC === "" ? `SELECT
|
|
4581
|
+
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}`;
|
|
3090
4582
|
}, "assembleC");
|
|
3091
4583
|
let rowsC = await liveC.unsafe(assembleC(kwC, kwC !== ""), bindC);
|
|
3092
4584
|
if (wantTextC && kwC !== "" && (rowsC.length === 0 || rowsC[0]._kwn === 0)) {
|
|
@@ -3155,26 +4647,26 @@ function createOps(tx) {
|
|
|
3155
4647
|
qpRef = qp;
|
|
3156
4648
|
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}`;
|
|
3157
4649
|
}
|
|
3158
|
-
const colList =
|
|
4650
|
+
const colList = [
|
|
4651
|
+
...cfg.cols.map((c) => columnTypeOf(table, c) === "timestamp" ? projectedColumn(table, c, "t") : `t.${quoteIdent(c)}`)
|
|
4652
|
+
].join(", ");
|
|
3159
4653
|
const msP = params.minScore === void 0 ? "" : add(params.minScore);
|
|
3160
4654
|
const hlCol = params.highlight === true && wantText ? `, ts_headline('simple', ${ftsExprOf(cfg.ftsCols)}, websearch_to_tsquery('simple', ${qpRef})) AS _highlight` : "";
|
|
3161
4655
|
const assemble = /* @__PURE__ */ __name((kwIn, withKwn) => {
|
|
3162
4656
|
const kwnCol = withKwn ? `, (SELECT count(*) FROM kw)::int AS _kwn` : "";
|
|
3163
4657
|
let inner;
|
|
3164
|
-
let orderScore;
|
|
3165
4658
|
if (semSql !== "" && kwIn !== "") {
|
|
3166
4659
|
const score = scoreMul === "" ? "fused._score" : `(fused._score)${scoreMul}`;
|
|
3167
4660
|
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`;
|
|
3168
|
-
orderScore = scoreMul === "" ? "fused._score" : "_score";
|
|
3169
4661
|
} else {
|
|
3170
4662
|
const single = semSql !== "" ? `sem AS (${semSql})` : `kw AS (${kwIn})`;
|
|
3171
4663
|
const alias = semSql !== "" ? "sem" : "kw";
|
|
3172
4664
|
const base = `(1.0/(${K} + ${alias}.r))::float8`;
|
|
3173
4665
|
const score = scoreMul === "" ? base : `(${base})${scoreMul}`;
|
|
3174
4666
|
inner = `WITH ${single} SELECT ${colList}, ${score} AS _score${hlCol}${semSql !== "" ? "" : kwnCol} FROM ${alias} JOIN ${quoteTable(table)} t ON t.${quoteIdent(cfg.pk)} = ${alias}.id`;
|
|
3175
|
-
orderScore = "_score";
|
|
3176
4667
|
}
|
|
3177
|
-
|
|
4668
|
+
const ms = msP === "" ? "" : ` WHERE q._score >= ${msP}`;
|
|
4669
|
+
return `SELECT q.*${canOuter(table)} FROM (${inner}) q${ms} ORDER BY q._score DESC, q.${quoteIdent(cfg.pk)} LIMIT ${limit}`;
|
|
3178
4670
|
}, "assemble");
|
|
3179
4671
|
let rows = await live.unsafe(assemble(kwSql, kwSql !== ""), bind);
|
|
3180
4672
|
if (wantText && kwSql !== "" && (rows.length === 0 || rows[0]._kwn === 0)) {
|
|
@@ -3363,7 +4855,7 @@ function createOps(tx) {
|
|
|
3363
4855
|
const params = [];
|
|
3364
4856
|
const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
|
|
3365
4857
|
const tuple = buildValueTuple(table, cols, data, add, "supersede");
|
|
3366
|
-
const rows = await sp.unsafe(`INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING
|
|
4858
|
+
const rows = await sp.unsafe(`INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING ${rowProjection(table)}`, params);
|
|
3367
4859
|
if (!rows[0]) {
|
|
3368
4860
|
throw new Error(`supersede(${table}): INSERT sat\u0131r d\xF6nd\xFCrmedi \u2014 yazma reddedildi (b\xFCy\xFCk olas\u0131l\u0131kla bir RLS policy'si)`);
|
|
3369
4861
|
}
|
|
@@ -3373,7 +4865,7 @@ function createOps(tx) {
|
|
|
3373
4865
|
/** A real SAVEPOINT inside the request's transaction. */
|
|
3374
4866
|
async transaction(cb) {
|
|
3375
4867
|
const live = await at();
|
|
3376
|
-
return live.savepoint(async (sp) => cb(withTables(createOps(sp), currentSchema)));
|
|
4868
|
+
return live.savepoint(async (sp) => cb(withTables(createOps(sp, context), currentSchema)));
|
|
3377
4869
|
},
|
|
3378
4870
|
/**
|
|
3379
4871
|
* Run `fn` against a handle bound to a SAVEPOINT, so a failure inside it
|
|
@@ -3394,7 +4886,7 @@ function createOps(tx) {
|
|
|
3394
4886
|
*/
|
|
3395
4887
|
async attempt(fn) {
|
|
3396
4888
|
const live = await at();
|
|
3397
|
-
return live.savepoint(async (sp) => fn(withTables(createOps(sp), currentSchema)));
|
|
4889
|
+
return live.savepoint(async (sp) => fn(withTables(createOps(sp, context), currentSchema)));
|
|
3398
4890
|
},
|
|
3399
4891
|
/**
|
|
3400
4892
|
* Execute a whole transaction plan — what `Database.$transaction(fn)` builds.
|
|
@@ -3414,6 +4906,62 @@ function createOps(tx) {
|
|
|
3414
4906
|
* "transport.txPlan is not a function" while every test that covered
|
|
3415
4907
|
* transactions passed against a mock that did implement it.
|
|
3416
4908
|
*/
|
|
4909
|
+
async command(plan, options = {}) {
|
|
4910
|
+
if (options.mode !== void 0 && ![
|
|
4911
|
+
"auto",
|
|
4912
|
+
"compiled",
|
|
4913
|
+
"sequential"
|
|
4914
|
+
].includes(options.mode)) throw new Error("command mode must be auto, compiled or sequential");
|
|
4915
|
+
if (options.mode === "sequential") return ops.txPlan(plan);
|
|
4916
|
+
const compiled = context.commands ? compileCommand(plan) : {
|
|
4917
|
+
reason: "the PostgreSQL command executor is not installed"
|
|
4918
|
+
};
|
|
4919
|
+
if ("reason" in compiled) {
|
|
4920
|
+
if (options.mode === "compiled") throw new Error(`Command cannot compile: ${compiled.reason}. Use auto/sequential mode or installCommandExecutor at boot.`);
|
|
4921
|
+
return ops.txPlan(plan);
|
|
4922
|
+
}
|
|
4923
|
+
if (compiled.steps.length === 0) return {
|
|
4924
|
+
results: []
|
|
4925
|
+
};
|
|
4926
|
+
if (compiled.pipeline) {
|
|
4927
|
+
return (await at()).savepoint(async (sp) => {
|
|
4928
|
+
const settled = await Promise.allSettled(compiled.pipeline.map((statement) => sp.unsafe(statement.sql, statement.params)));
|
|
4929
|
+
const failed = settled.find((result) => result.status === "rejected");
|
|
4930
|
+
if (failed?.status === "rejected") {
|
|
4931
|
+
const error = failed.reason;
|
|
4932
|
+
if (sqlstateOf(error) === SQLSTATE.commandGuardFailed && typeof error.detail === "string") {
|
|
4933
|
+
const detail = JSON.parse(error.detail);
|
|
4934
|
+
if (detail.error_code === "tx_guard_failed" && Number.isInteger(detail.slot)) {
|
|
4935
|
+
throw Object.assign(new Error("Command expectation failed"), detail);
|
|
4936
|
+
}
|
|
4937
|
+
}
|
|
4938
|
+
throw error;
|
|
4939
|
+
}
|
|
4940
|
+
return {
|
|
4941
|
+
results: settled.map((result, i) => {
|
|
4942
|
+
const rows2 = result.value[0].rows;
|
|
4943
|
+
return {
|
|
4944
|
+
rows: asTableRows(plan.ops[i].table, rows2),
|
|
4945
|
+
rows_affected: rows2.length
|
|
4946
|
+
};
|
|
4947
|
+
})
|
|
4948
|
+
};
|
|
4949
|
+
});
|
|
4950
|
+
}
|
|
4951
|
+
const rows = await (await at()).savepoint((sp) => sp.unsafe("SELECT palbase_runtime.command_v1($1::jsonb -> 'steps') AS result", [
|
|
4952
|
+
{
|
|
4953
|
+
steps: compiled.steps
|
|
4954
|
+
}
|
|
4955
|
+
]));
|
|
4956
|
+
const response = rows[0].result;
|
|
4957
|
+
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);
|
|
4958
|
+
return {
|
|
4959
|
+
results: response.results.map((result, i) => ({
|
|
4960
|
+
...result,
|
|
4961
|
+
rows: asTableRows(plan.ops[i].table, result.rows)
|
|
4962
|
+
}))
|
|
4963
|
+
};
|
|
4964
|
+
},
|
|
3417
4965
|
async txPlan(plan) {
|
|
3418
4966
|
const live = await at();
|
|
3419
4967
|
return live.savepoint(async (sp) => {
|
|
@@ -3437,27 +4985,55 @@ function createOps(tx) {
|
|
|
3437
4985
|
}
|
|
3438
4986
|
__name(createOps, "createOps");
|
|
3439
4987
|
var currentSchema = {};
|
|
4988
|
+
var columnsMemo = /* @__PURE__ */ new WeakMap();
|
|
4989
|
+
var primaryMemo = /* @__PURE__ */ new WeakMap();
|
|
4990
|
+
var vectorMemo = /* @__PURE__ */ new WeakMap();
|
|
4991
|
+
var transformMemo = /* @__PURE__ */ new WeakMap();
|
|
4992
|
+
var typeMemo = /* @__PURE__ */ new WeakMap();
|
|
4993
|
+
var timestampMemo = /* @__PURE__ */ new WeakMap();
|
|
4994
|
+
var projectionMemo = /* @__PURE__ */ new WeakMap();
|
|
4995
|
+
function schemaColumns(table) {
|
|
4996
|
+
return memoTable(columnsMemo, currentSchema, table, () => computeSchemaColumns(table));
|
|
4997
|
+
}
|
|
4998
|
+
__name(schemaColumns, "schemaColumns");
|
|
4999
|
+
function primaryKeyOf(table) {
|
|
5000
|
+
return memoTable(primaryMemo, currentSchema, table, () => computePrimaryKey(table));
|
|
5001
|
+
}
|
|
5002
|
+
__name(primaryKeyOf, "primaryKeyOf");
|
|
5003
|
+
function vectorColumnsOf(schema, table) {
|
|
5004
|
+
return memoTable(vectorMemo, schema, table, () => computeVectorColumns(schema, table));
|
|
5005
|
+
}
|
|
5006
|
+
__name(vectorColumnsOf, "vectorColumnsOf");
|
|
5007
|
+
function transformsOf(schema, table) {
|
|
5008
|
+
return memoTable(transformMemo, schema, table, () => computeTransforms(schema, table));
|
|
5009
|
+
}
|
|
5010
|
+
__name(transformsOf, "transformsOf");
|
|
5011
|
+
function columnTypeOf(table, col) {
|
|
5012
|
+
const types = memoTable(typeMemo, currentSchema, table, () => new Map([
|
|
5013
|
+
...schemaColumns(table) ?? []
|
|
5014
|
+
].map((c) => [
|
|
5015
|
+
c,
|
|
5016
|
+
computeColumnType(table, c)
|
|
5017
|
+
])));
|
|
5018
|
+
return types.get(col) ?? null;
|
|
5019
|
+
}
|
|
5020
|
+
__name(columnTypeOf, "columnTypeOf");
|
|
3440
5021
|
var currentRelations = /* @__PURE__ */ new Map();
|
|
3441
5022
|
function setSchema(schemas) {
|
|
3442
|
-
|
|
3443
|
-
cachedTrgmSchema = null;
|
|
3444
|
-
const tables = {};
|
|
5023
|
+
const tables = /* @__PURE__ */ Object.create(null);
|
|
3445
5024
|
for (const entry of schemas) {
|
|
3446
5025
|
const mod = entry;
|
|
3447
5026
|
const def = (mod && "default" in mod ? mod.default : mod) ?? {};
|
|
3448
5027
|
const schemaName = def.name ?? "public";
|
|
3449
|
-
for (const [
|
|
3450
|
-
tables[qualifiedTableKey(schemaName, table.name ??
|
|
5028
|
+
for (const [key2, table] of Object.entries(def.tables ?? {})) {
|
|
5029
|
+
tables[qualifiedTableKey(schemaName, table.name ?? key2)] = table;
|
|
3451
5030
|
}
|
|
3452
5031
|
}
|
|
3453
|
-
|
|
3454
|
-
tables
|
|
3455
|
-
};
|
|
3456
|
-
currentRelations = buildRelations(schemas.map((entry) => {
|
|
5032
|
+
const relations = buildRelations(schemas.map((entry) => {
|
|
3457
5033
|
const mod = entry;
|
|
3458
5034
|
const def = (mod && "default" in mod ? mod.default : mod) ?? {};
|
|
3459
|
-
const tables2 =
|
|
3460
|
-
for (const [
|
|
5035
|
+
const tables2 = /* @__PURE__ */ Object.create(null);
|
|
5036
|
+
for (const [key2, t] of Object.entries(def.tables ?? {})) {
|
|
3461
5037
|
const table = t ?? {};
|
|
3462
5038
|
const columns = {};
|
|
3463
5039
|
for (const [cname, c] of Object.entries(table.columns ?? {})) {
|
|
@@ -3467,9 +5043,9 @@ function setSchema(schemas) {
|
|
|
3467
5043
|
_def: cd
|
|
3468
5044
|
};
|
|
3469
5045
|
}
|
|
3470
|
-
tables2[
|
|
5046
|
+
tables2[key2] = {
|
|
3471
5047
|
...table,
|
|
3472
|
-
name: table.name ??
|
|
5048
|
+
name: table.name ?? key2,
|
|
3473
5049
|
columns
|
|
3474
5050
|
};
|
|
3475
5051
|
}
|
|
@@ -3478,40 +5054,86 @@ function setSchema(schemas) {
|
|
|
3478
5054
|
tables: tables2
|
|
3479
5055
|
};
|
|
3480
5056
|
}));
|
|
5057
|
+
for (const key2 of Object.keys(tables)) tables[key2] = snapshotDeclaration(tables[key2]);
|
|
5058
|
+
currentSchema = Object.freeze({
|
|
5059
|
+
tables: Object.freeze(tables)
|
|
5060
|
+
});
|
|
5061
|
+
currentRelations = relations;
|
|
5062
|
+
cachedVectorSchema = null;
|
|
5063
|
+
cachedTrgmSchema = null;
|
|
3481
5064
|
}
|
|
3482
5065
|
__name(setSchema, "setSchema");
|
|
5066
|
+
var tableNamespaces = /* @__PURE__ */ new WeakMap();
|
|
3483
5067
|
function withTables(ops, schema = currentSchema) {
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
5068
|
+
let namespaces = tableNamespaces.get(schema);
|
|
5069
|
+
if (!namespaces) {
|
|
5070
|
+
namespaces = /* @__PURE__ */ new Map();
|
|
5071
|
+
for (const key2 of Object.keys(schema.tables ?? {})) {
|
|
5072
|
+
const dot = key2.indexOf(".");
|
|
5073
|
+
const name = dot === -1 ? "public" : key2.slice(0, dot);
|
|
5074
|
+
const short = dot === -1 ? key2 : key2.slice(dot + 1);
|
|
5075
|
+
if (!namespaces.has(name)) namespaces.set(name, /* @__PURE__ */ new Map());
|
|
5076
|
+
namespaces.get(name).set(short, key2);
|
|
5077
|
+
}
|
|
5078
|
+
if (Object.isFrozen(schema) && Object.isFrozen(schema.tables)) tableNamespaces.set(schema, namespaces);
|
|
5079
|
+
}
|
|
5080
|
+
const bind = /* @__PURE__ */ __name((key2) => ({
|
|
5081
|
+
insert: /* @__PURE__ */ __name((data) => ops.insert(key2, data), "insert"),
|
|
5082
|
+
update: /* @__PURE__ */ __name((id, data) => ops.update(key2, id, data), "update"),
|
|
5083
|
+
delete: /* @__PURE__ */ __name((id) => ops.delete(key2, id), "delete"),
|
|
5084
|
+
findById: /* @__PURE__ */ __name((id) => ops.findById(key2, id), "findById"),
|
|
5085
|
+
findUnique: /* @__PURE__ */ __name((where, opts) => ops.findUnique(key2, where, opts), "findUnique"),
|
|
5086
|
+
findMany: /* @__PURE__ */ __name((query, opts) => ops.findMany(key2, query ?? {}, opts), "findMany"),
|
|
5087
|
+
page: /* @__PURE__ */ __name((query, opts) => ops.page(key2, query ?? {}, opts), "page"),
|
|
5088
|
+
put: /* @__PURE__ */ __name((q) => ops.put(key2, q.data, {
|
|
3491
5089
|
onConflict: q.onConflict
|
|
3492
5090
|
}), "put")
|
|
3493
5091
|
}), "bind");
|
|
3494
|
-
const
|
|
3495
|
-
for (const key of Object.keys(schema.tables ?? {})) {
|
|
3496
|
-
if (!key.includes(".")) tables[key] = bind(key);
|
|
3497
|
-
}
|
|
5092
|
+
const surfaces = /* @__PURE__ */ new Map();
|
|
3498
5093
|
const schemaOf = /* @__PURE__ */ __name((name) => {
|
|
3499
|
-
const
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
5094
|
+
const cached = surfaces.get(name);
|
|
5095
|
+
if (cached) return cached;
|
|
5096
|
+
const names = namespaces.get(name) ?? /* @__PURE__ */ new Map();
|
|
5097
|
+
const target = /* @__PURE__ */ Object.create(null);
|
|
5098
|
+
const get = /* @__PURE__ */ __name((key2) => {
|
|
5099
|
+
if (!Object.hasOwn(target, key2) && names.has(key2)) {
|
|
5100
|
+
Object.defineProperty(target, key2, {
|
|
5101
|
+
value: bind(names.get(key2)),
|
|
5102
|
+
configurable: true,
|
|
5103
|
+
enumerable: true,
|
|
5104
|
+
writable: true
|
|
5105
|
+
});
|
|
3506
5106
|
}
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
5107
|
+
return Reflect.get(target, key2);
|
|
5108
|
+
}, "get");
|
|
5109
|
+
const tables = new Proxy(target, {
|
|
5110
|
+
get: /* @__PURE__ */ __name((t, key2, receiver) => typeof key2 === "string" ? get(key2) : Reflect.get(t, key2, receiver), "get"),
|
|
5111
|
+
has: /* @__PURE__ */ __name((t, key2) => typeof key2 === "string" && names.has(key2) || Reflect.has(t, key2), "has"),
|
|
5112
|
+
ownKeys: /* @__PURE__ */ __name((t) => [
|
|
5113
|
+
.../* @__PURE__ */ new Set([
|
|
5114
|
+
...names.keys(),
|
|
5115
|
+
...Reflect.ownKeys(t)
|
|
5116
|
+
])
|
|
5117
|
+
], "ownKeys"),
|
|
5118
|
+
getOwnPropertyDescriptor: /* @__PURE__ */ __name((t, key2) => Reflect.getOwnPropertyDescriptor(t, key2) ?? (typeof key2 === "string" && names.has(key2) ? {
|
|
5119
|
+
configurable: true,
|
|
5120
|
+
enumerable: true,
|
|
5121
|
+
get: /* @__PURE__ */ __name(() => get(key2), "get")
|
|
5122
|
+
} : void 0), "getOwnPropertyDescriptor"),
|
|
5123
|
+
preventExtensions: /* @__PURE__ */ __name((t) => {
|
|
5124
|
+
for (const key2 of names.keys()) get(key2);
|
|
5125
|
+
return Reflect.preventExtensions(t);
|
|
5126
|
+
}, "preventExtensions")
|
|
5127
|
+
});
|
|
5128
|
+
const surface = {
|
|
5129
|
+
tables
|
|
3510
5130
|
};
|
|
5131
|
+
if (namespaces.has(name) || name === "public") surfaces.set(name, surface);
|
|
5132
|
+
return surface;
|
|
3511
5133
|
}, "schemaOf");
|
|
3512
5134
|
const base = /* @__PURE__ */ Object.create(null);
|
|
3513
5135
|
return Object.assign(base, ops, {
|
|
3514
|
-
tables,
|
|
5136
|
+
tables: schemaOf("public").tables,
|
|
3515
5137
|
schema: schemaOf
|
|
3516
5138
|
});
|
|
3517
5139
|
}
|
|
@@ -3569,8 +5191,8 @@ function diagnosingDriver(sql, surface = "service") {
|
|
|
3569
5191
|
return markEngineRaised(new UniqueViolation(constraintOf(e)));
|
|
3570
5192
|
}
|
|
3571
5193
|
const state = sqlstateOf(e);
|
|
3572
|
-
if (state ===
|
|
3573
|
-
if (state ===
|
|
5194
|
+
if (state === SQLSTATE.serializationFailure) return markEngineRaised(new SerializationFailure(state));
|
|
5195
|
+
if (state === SQLSTATE.deadlockDetected) return markEngineRaised(new DeadlockDetected(state));
|
|
3574
5196
|
if (surface === "service" && isForeignKeyViolation(e)) {
|
|
3575
5197
|
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)})`);
|
|
3576
5198
|
}
|
|
@@ -3580,11 +5202,16 @@ function diagnosingDriver(sql, surface = "service") {
|
|
|
3580
5202
|
return e;
|
|
3581
5203
|
}, "explain");
|
|
3582
5204
|
const wrapTx = /* @__PURE__ */ __name((tx) => ({
|
|
3583
|
-
|
|
5205
|
+
unsafe(text, params) {
|
|
3584
5206
|
try {
|
|
3585
|
-
|
|
5207
|
+
const query = tx.unsafe(text, params);
|
|
5208
|
+
const mapped = query.catch((e) => {
|
|
5209
|
+
throw explain(e);
|
|
5210
|
+
});
|
|
5211
|
+
if (query.cancel) mapped.cancel = () => query.cancel();
|
|
5212
|
+
return mapped;
|
|
3586
5213
|
} catch (e) {
|
|
3587
|
-
|
|
5214
|
+
return Promise.reject(explain(e));
|
|
3588
5215
|
}
|
|
3589
5216
|
},
|
|
3590
5217
|
savepoint(cb) {
|
|
@@ -3593,34 +5220,120 @@ function diagnosingDriver(sql, surface = "service") {
|
|
|
3593
5220
|
}), "wrapTx");
|
|
3594
5221
|
return {
|
|
3595
5222
|
unsafe: /* @__PURE__ */ __name((text, params) => sql.unsafe(text, params), "unsafe"),
|
|
3596
|
-
|
|
5223
|
+
cancelBackend: sql.cancelBackend?.bind(sql),
|
|
5224
|
+
begin: /* @__PURE__ */ __name((cb) => sql.begin((tx) => cb(wrapTx(tx))).catch((e) => {
|
|
5225
|
+
throw explain(e);
|
|
5226
|
+
}), "begin")
|
|
3597
5227
|
};
|
|
3598
5228
|
}
|
|
3599
5229
|
__name(diagnosingDriver, "diagnosingDriver");
|
|
3600
|
-
function createRequestDatabase(sql, identity) {
|
|
3601
|
-
const
|
|
5230
|
+
function createRequestDatabase(sql, identity, diagnostics) {
|
|
5231
|
+
const trace = diagnostics === void 0 ? void 0 : new DatabaseTrace(diagnostics);
|
|
5232
|
+
let requestClosed = false;
|
|
5233
|
+
const contextFor = /* @__PURE__ */ __name((role) => {
|
|
5234
|
+
let scope;
|
|
5235
|
+
return {
|
|
5236
|
+
trace,
|
|
5237
|
+
get cursorScope() {
|
|
5238
|
+
return scope ??= cursorIdentity(role, identity.claimsJson);
|
|
5239
|
+
},
|
|
5240
|
+
get commands() {
|
|
5241
|
+
return hasCommandExecutor(sql);
|
|
5242
|
+
}
|
|
5243
|
+
};
|
|
5244
|
+
}, "contextFor");
|
|
5245
|
+
const userContext = contextFor(identity.role);
|
|
5246
|
+
const serviceContext = contextFor(identity.serviceRole);
|
|
5247
|
+
const atomic = /* @__PURE__ */ __name((role, surface) => async (fn, options = {}) => {
|
|
5248
|
+
validateAtomicOptions(options);
|
|
5249
|
+
if (requestClosed) throw new Error("Database request has settled; it cannot open another transaction");
|
|
5250
|
+
if (atomicScope.getStore() !== void 0) throw new Error("Nested Database.$atomic is not supported; use tx.$attempt for a savepoint");
|
|
5251
|
+
const retries = retryBudget(options.retry);
|
|
5252
|
+
const control = options.timeoutMs !== void 0 || options.signal !== void 0 ? new TransactionControl(options.timeoutMs, options.signal) : void 0;
|
|
5253
|
+
let lease;
|
|
5254
|
+
try {
|
|
5255
|
+
control?.check();
|
|
5256
|
+
if (options.workload) {
|
|
5257
|
+
const queuedAt = trace ? performance.now() : 0;
|
|
5258
|
+
try {
|
|
5259
|
+
lease = await workloadAdmission(sql, options.workload).acquire(workloadTenant(options.workload, identity.claimsJson), options.conflictKeys ?? [], control?.signal);
|
|
5260
|
+
} finally {
|
|
5261
|
+
trace?.time("queueMs", queuedAt);
|
|
5262
|
+
}
|
|
5263
|
+
}
|
|
5264
|
+
for (let attempt = 0; ; attempt++) {
|
|
5265
|
+
control?.check();
|
|
5266
|
+
const timeouts = [
|
|
5267
|
+
options.statementTimeoutMs,
|
|
5268
|
+
control?.remainingMs()
|
|
5269
|
+
].filter((n) => n !== void 0);
|
|
5270
|
+
const scope = /* @__PURE__ */ Symbol("atomic transaction");
|
|
5271
|
+
const root = createLazyTransaction(diagnosingDriver(sql, surface), role, identity.claimsJson, {
|
|
5272
|
+
scope,
|
|
5273
|
+
isolation: options.isolation,
|
|
5274
|
+
readOnly: options.readOnly,
|
|
5275
|
+
control,
|
|
5276
|
+
trace,
|
|
5277
|
+
lockTimeout: `${options.lockTimeoutMs ?? 5e3}ms`,
|
|
5278
|
+
statementTimeout: timeouts.length === 0 ? void 0 : `${Math.min(...timeouts)}ms`
|
|
5279
|
+
});
|
|
5280
|
+
try {
|
|
5281
|
+
const callback = atomicScope.run(scope, () => fn(withTables(createOps(root, surface === "user" ? userContext : serviceContext))));
|
|
5282
|
+
const value = await (control ? control.race(callback) : callback);
|
|
5283
|
+
control?.check();
|
|
5284
|
+
await root.commit();
|
|
5285
|
+
return value;
|
|
5286
|
+
} catch (error) {
|
|
5287
|
+
await root.rollback(error);
|
|
5288
|
+
if (attempt >= retries || !isRetryable(error)) throw error;
|
|
5289
|
+
trace?.retry();
|
|
5290
|
+
const delay = new Promise((resolve) => setTimeout(resolve, Math.min(100, 2 ** attempt)));
|
|
5291
|
+
await (control ? control.race(delay) : delay);
|
|
5292
|
+
}
|
|
5293
|
+
}
|
|
5294
|
+
} finally {
|
|
5295
|
+
control?.dispose();
|
|
5296
|
+
lease?.release();
|
|
5297
|
+
}
|
|
5298
|
+
}, "atomic");
|
|
5299
|
+
const tx = createLazyTransaction(diagnosingDriver(sql, "user"), identity.role, identity.claimsJson, {
|
|
5300
|
+
trace
|
|
5301
|
+
});
|
|
3602
5302
|
let serviceTx = null;
|
|
3603
5303
|
let serviceClient = null;
|
|
3604
5304
|
const asService = /* @__PURE__ */ __name(() => {
|
|
3605
5305
|
if (serviceClient === null) {
|
|
3606
5306
|
serviceTx = createLazyTransaction(diagnosingDriver(sql, "service"), identity.serviceRole, identity.claimsJson, {
|
|
3607
|
-
lockTimeout: SERVICE_LOCK_TIMEOUT
|
|
5307
|
+
lockTimeout: SERVICE_LOCK_TIMEOUT,
|
|
5308
|
+
trace
|
|
5309
|
+
});
|
|
5310
|
+
serviceClient = Object.assign(withTables(createOps(serviceTx, serviceContext)), {
|
|
5311
|
+
atomic: atomic(identity.serviceRole, "service")
|
|
3608
5312
|
});
|
|
3609
|
-
serviceClient = withTables(createOps(serviceTx));
|
|
3610
5313
|
}
|
|
3611
5314
|
return serviceClient;
|
|
3612
5315
|
}, "asService");
|
|
3613
5316
|
return {
|
|
3614
|
-
client: Object.assign(withTables(createOps(tx)), {
|
|
3615
|
-
asService
|
|
5317
|
+
client: Object.assign(withTables(createOps(tx, userContext)), {
|
|
5318
|
+
asService,
|
|
5319
|
+
atomic: atomic(identity.role, "user")
|
|
3616
5320
|
}),
|
|
3617
5321
|
async commit() {
|
|
3618
|
-
|
|
3619
|
-
|
|
5322
|
+
requestClosed = true;
|
|
5323
|
+
try {
|
|
5324
|
+
await tx.commit();
|
|
5325
|
+
await serviceTx?.commit();
|
|
5326
|
+
trace?.complete("committed");
|
|
5327
|
+
} catch (error) {
|
|
5328
|
+
trace?.failed();
|
|
5329
|
+
throw error;
|
|
5330
|
+
}
|
|
3620
5331
|
},
|
|
3621
5332
|
async rollback(reason2) {
|
|
5333
|
+
requestClosed = true;
|
|
3622
5334
|
await tx.rollback(reason2);
|
|
3623
5335
|
await serviceTx?.rollback(reason2);
|
|
5336
|
+
trace?.complete("failed");
|
|
3624
5337
|
}
|
|
3625
5338
|
};
|
|
3626
5339
|
}
|
|
@@ -3630,8 +5343,13 @@ var Args = class Args2 {
|
|
|
3630
5343
|
__name(this, "Args");
|
|
3631
5344
|
}
|
|
3632
5345
|
values = [];
|
|
3633
|
-
|
|
5346
|
+
columns = [];
|
|
5347
|
+
bind(value, column, table) {
|
|
3634
5348
|
this.values.push(value);
|
|
5349
|
+
this.columns.push({
|
|
5350
|
+
column,
|
|
5351
|
+
table
|
|
5352
|
+
});
|
|
3635
5353
|
return `$${this.values.length}`;
|
|
3636
5354
|
}
|
|
3637
5355
|
};
|
|
@@ -3717,7 +5435,7 @@ function planWhere(table, where, args, results) {
|
|
|
3717
5435
|
}
|
|
3718
5436
|
const resolved = resolveRefsDeep(where, results);
|
|
3719
5437
|
const known = schemaColumns(table);
|
|
3720
|
-
const bare = compileWhereBare(table, known, resolved, (v) => args.bind(v), "transaction plan");
|
|
5438
|
+
const bare = compileWhereBare(table, known, resolved, (v, column, from) => args.bind(v, column, from), "transaction plan");
|
|
3721
5439
|
return {
|
|
3722
5440
|
sql: bare === "" ? "" : ` AND ${bare}`,
|
|
3723
5441
|
resolved
|
|
@@ -3726,16 +5444,16 @@ function planWhere(table, where, args, results) {
|
|
|
3726
5444
|
__name(planWhere, "planWhere");
|
|
3727
5445
|
function bindMaybeVector(args, column, vectorCols, value, transforms) {
|
|
3728
5446
|
if (column !== void 0 && vectorCols?.has(column) && Array.isArray(value)) {
|
|
3729
|
-
return args.bind(toVectorLiteral(value));
|
|
5447
|
+
return args.bind(toVectorLiteral(value), column);
|
|
3730
5448
|
}
|
|
3731
5449
|
const t = column !== void 0 ? transforms?.get(column) : void 0;
|
|
3732
5450
|
if (t?.toDb !== void 0 && value !== null && value !== void 0) {
|
|
3733
|
-
return args.bind(t.toDb(value));
|
|
5451
|
+
return args.bind(t.toDb(value), column);
|
|
3734
5452
|
}
|
|
3735
|
-
return args.bind(value);
|
|
5453
|
+
return args.bind(value, column);
|
|
3736
5454
|
}
|
|
3737
5455
|
__name(bindMaybeVector, "bindMaybeVector");
|
|
3738
|
-
|
|
5456
|
+
function compilePlanOp(op, results) {
|
|
3739
5457
|
const opVectorCols = vectorColumnsOf(currentSchema, op.table);
|
|
3740
5458
|
const opTransforms = transformsOf(currentSchema, op.table);
|
|
3741
5459
|
const args = new Args();
|
|
@@ -3745,7 +5463,7 @@ async function runPlanOp(sp, op, results) {
|
|
|
3745
5463
|
case "insert": {
|
|
3746
5464
|
const cols = Object.keys(op.values ?? {});
|
|
3747
5465
|
const rendered = cols.map((c) => renderValue(op.values[c], c, args, results, opVectorCols, opTransforms));
|
|
3748
|
-
sql = cols.length ? `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES (${rendered.join(", ")}) RETURNING
|
|
5466
|
+
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)}`;
|
|
3749
5467
|
break;
|
|
3750
5468
|
}
|
|
3751
5469
|
case "upsert": {
|
|
@@ -3758,17 +5476,17 @@ async function runPlanOp(sp, op, results) {
|
|
|
3758
5476
|
});
|
|
3759
5477
|
}
|
|
3760
5478
|
const rendered = cols.map((c) => renderValue(op.values[c], c, args, results, opVectorCols, opTransforms));
|
|
3761
|
-
sql = `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES (${rendered.join(", ")}) ${onConflictTail(cols, conflict, "update")} RETURNING
|
|
5479
|
+
sql = `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES (${rendered.join(", ")}) ${onConflictTail(cols, conflict, "update")} RETURNING ${rowProjection(op.table)}`;
|
|
3762
5480
|
break;
|
|
3763
5481
|
}
|
|
3764
5482
|
case "insertMany": {
|
|
3765
5483
|
const rows = op.rows ?? [];
|
|
3766
|
-
if (rows.length === 0 || !rows[0]) return
|
|
5484
|
+
if (rows.length === 0 || !rows[0]) return null;
|
|
3767
5485
|
const cols = Object.keys(rows[0]);
|
|
3768
5486
|
const tuples = rows.map((r) => `(${cols.map((c) => renderValue(r[c], c, args, results, opVectorCols, opTransforms)).join(", ")})`);
|
|
3769
5487
|
const conflictCols = op.onConflict ?? [];
|
|
3770
5488
|
const tail = op.action !== void 0 && conflictCols.length > 0 ? ` ${onConflictTail(cols, conflictCols, op.action)}` : "";
|
|
3771
|
-
sql = `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES ${tuples.join(", ")}${tail} RETURNING
|
|
5489
|
+
sql = `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES ${tuples.join(", ")}${tail} RETURNING ${rowProjection(op.table)}`;
|
|
3772
5490
|
break;
|
|
3773
5491
|
}
|
|
3774
5492
|
case "update": {
|
|
@@ -3784,12 +5502,12 @@ async function runPlanOp(sp, op, results) {
|
|
|
3784
5502
|
const assignments = cols.map((c) => `${quoteIdent(c)} = ${renderValue(op.set[c], c, args, results, opVectorCols, opTransforms)}`);
|
|
3785
5503
|
const planned = planWhere(op.table, op.where, args, results);
|
|
3786
5504
|
const planPk = primaryKeyOf(op.table);
|
|
3787
|
-
sql = isSinglePkEquality(planned.resolved, planPk) || planPk === null ? `UPDATE ${table} AS t SET ${assignments.join(", ")} WHERE true${planned.sql} RETURNING t
|
|
5505
|
+
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")}`;
|
|
3788
5506
|
break;
|
|
3789
5507
|
}
|
|
3790
5508
|
case "delete": {
|
|
3791
5509
|
assertNotAppendOnly("deleteWhere", op.table);
|
|
3792
|
-
sql = `DELETE FROM ${table} AS t WHERE true${renderWhere(op.table, op.where, args, results)} RETURNING t
|
|
5510
|
+
sql = `DELETE FROM ${table} AS t WHERE true${renderWhere(op.table, op.where, args, results)} RETURNING ${rowProjection(op.table, "t")}`;
|
|
3793
5511
|
break;
|
|
3794
5512
|
}
|
|
3795
5513
|
case "select": {
|
|
@@ -3802,15 +5520,157 @@ async function runPlanOp(sp, op, results) {
|
|
|
3802
5520
|
limit = ` LIMIT ${n}`;
|
|
3803
5521
|
}
|
|
3804
5522
|
const lock = op.lock === "update" ? " FOR UPDATE" : "";
|
|
3805
|
-
sql = `SELECT t
|
|
5523
|
+
sql = `SELECT ${rowProjection(op.table, "t")} FROM ${table} t WHERE true${renderWhere(op.table, op.where, args, results)}${limit}${lock}`;
|
|
3806
5524
|
break;
|
|
3807
5525
|
}
|
|
3808
5526
|
default:
|
|
3809
5527
|
throw new Error(`unknown operation "${String(op.op)}" in a transaction plan`);
|
|
3810
5528
|
}
|
|
3811
|
-
return
|
|
5529
|
+
return {
|
|
5530
|
+
sql,
|
|
5531
|
+
params: args.values,
|
|
5532
|
+
columns: args.columns
|
|
5533
|
+
};
|
|
5534
|
+
}
|
|
5535
|
+
__name(compilePlanOp, "compilePlanOp");
|
|
5536
|
+
async function runPlanOp(sp, op, results) {
|
|
5537
|
+
const statement = compilePlanOp(op, results);
|
|
5538
|
+
return statement ? await sp.unsafe(statement.sql, statement.params) : [];
|
|
3812
5539
|
}
|
|
3813
5540
|
__name(runPlanOp, "runPlanOp");
|
|
5541
|
+
function compileCommand(plan) {
|
|
5542
|
+
const symbols = /* @__PURE__ */ new Map();
|
|
5543
|
+
const results = [];
|
|
5544
|
+
const steps = [];
|
|
5545
|
+
const pipeline = [];
|
|
5546
|
+
let hasRefs = false;
|
|
5547
|
+
const prefix = `command:${crypto.randomUUID()}:`;
|
|
5548
|
+
const containsFragment = /* @__PURE__ */ __name((v) => isSqlFragment(v) || v !== null && typeof v === "object" && Object.values(v).some(containsFragment), "containsFragment");
|
|
5549
|
+
const hasNullableFilterRef = /* @__PURE__ */ __name((v) => {
|
|
5550
|
+
if (isRef(v)) {
|
|
5551
|
+
const source = plan.ops[v.$ref.op];
|
|
5552
|
+
return source !== void 0 && pageSchema(source.table).columns[v.$ref.field]?.nullable !== false;
|
|
5553
|
+
}
|
|
5554
|
+
return v !== null && typeof v === "object" && Object.values(v).some(hasNullableFilterRef);
|
|
5555
|
+
}, "hasNullableFilterRef");
|
|
5556
|
+
for (const op of plan.ops) {
|
|
5557
|
+
if (!schemaColumns(op.table)?.size) return {
|
|
5558
|
+
reason: `table ${op.table} has no installed declaration`
|
|
5559
|
+
};
|
|
5560
|
+
if (transformsOf(currentSchema, op.table).size || vectorColumnsOf(currentSchema, op.table).size) return {
|
|
5561
|
+
reason: `${op.table} uses JavaScript codecs/transforms or vector values`
|
|
5562
|
+
};
|
|
5563
|
+
if (containsFragment(op)) return {
|
|
5564
|
+
reason: "SQL fragments require the sequential path"
|
|
5565
|
+
};
|
|
5566
|
+
if (hasNullableFilterRef(op.where)) return {
|
|
5567
|
+
reason: "nullable references in filters require the sequential path"
|
|
5568
|
+
};
|
|
5569
|
+
}
|
|
5570
|
+
for (const [index, op] of plan.ops.entries()) {
|
|
5571
|
+
const statement = compilePlanOp(op, results);
|
|
5572
|
+
if (statement === null) {
|
|
5573
|
+
steps.push({
|
|
5574
|
+
empty: true,
|
|
5575
|
+
guard: op.guard
|
|
5576
|
+
});
|
|
5577
|
+
pipeline.push({
|
|
5578
|
+
sql: "SELECT palbase_runtime.command_rows_v1('[]'::jsonb,$1::jsonb) AS rows",
|
|
5579
|
+
params: [
|
|
5580
|
+
op.guard ?? null
|
|
5581
|
+
]
|
|
5582
|
+
});
|
|
5583
|
+
} else {
|
|
5584
|
+
if (statement.params.some((v) => v === void 0 || typeof v === "symbol" || typeof v === "function" || typeof v === "number" && (!Number.isInteger(v) || v < -2147483648 || v > 2147483647))) {
|
|
5585
|
+
return {
|
|
5586
|
+
reason: "non-integer/wide numeric or non-JSON parameters require the sequential driver path; use exact decimal strings"
|
|
5587
|
+
};
|
|
5588
|
+
}
|
|
5589
|
+
for (const [i, value] of statement.params.entries()) {
|
|
5590
|
+
const target = statement.columns[i];
|
|
5591
|
+
if (target.column === void 0 || columnTypeOf(target.table ?? op.table, target.column) !== "jsonb") continue;
|
|
5592
|
+
const source = typeof value === "string" ? symbols.get(value) : void 0;
|
|
5593
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint" || source && "ref" in source && ![
|
|
5594
|
+
"text",
|
|
5595
|
+
"uuid",
|
|
5596
|
+
"timestamp",
|
|
5597
|
+
"enum"
|
|
5598
|
+
].includes(columnTypeOf(plan.ops[source.ref.op].table, source.ref.field) ?? "")) {
|
|
5599
|
+
return {
|
|
5600
|
+
reason: "JSONB scalar/ref binding requires the sequential driver path"
|
|
5601
|
+
};
|
|
5602
|
+
}
|
|
5603
|
+
}
|
|
5604
|
+
const wide = [
|
|
5605
|
+
...schemaColumns(op.table)
|
|
5606
|
+
].filter((c) => [
|
|
5607
|
+
"bigint",
|
|
5608
|
+
"numeric"
|
|
5609
|
+
].includes(columnTypeOf(op.table, c) ?? ""));
|
|
5610
|
+
const exact = wide.length ? ` || jsonb_build_object(${wide.map((c) => `${quoteLiteral(c)}, r.${quoteIdent(c)}::text`).join(", ")})` : "";
|
|
5611
|
+
const sql = `WITH command_rows AS (${statement.sql}) SELECT coalesce(jsonb_agg(to_jsonb(r)${exact}), '[]'::jsonb) FROM command_rows r`;
|
|
5612
|
+
pipeline.push({
|
|
5613
|
+
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`,
|
|
5614
|
+
params: [
|
|
5615
|
+
...statement.params,
|
|
5616
|
+
op.guard ?? null
|
|
5617
|
+
]
|
|
5618
|
+
});
|
|
5619
|
+
const bindings = statement.params.map((value, i) => {
|
|
5620
|
+
const target = statement.columns[i];
|
|
5621
|
+
const json = target.column !== void 0 && columnTypeOf(target.table ?? op.table, target.column) === "jsonb";
|
|
5622
|
+
if (typeof value === "string" && symbols.has(value)) {
|
|
5623
|
+
hasRefs = true;
|
|
5624
|
+
return {
|
|
5625
|
+
...symbols.get(value),
|
|
5626
|
+
json
|
|
5627
|
+
};
|
|
5628
|
+
}
|
|
5629
|
+
if (typeof value === "bigint") return {
|
|
5630
|
+
value: value.toString(),
|
|
5631
|
+
json
|
|
5632
|
+
};
|
|
5633
|
+
if (value instanceof Date) return {
|
|
5634
|
+
value: value.toISOString(),
|
|
5635
|
+
json
|
|
5636
|
+
};
|
|
5637
|
+
return {
|
|
5638
|
+
value,
|
|
5639
|
+
json
|
|
5640
|
+
};
|
|
5641
|
+
});
|
|
5642
|
+
steps.push({
|
|
5643
|
+
chunks: parameterChunks(sql),
|
|
5644
|
+
bindings,
|
|
5645
|
+
guard: op.guard
|
|
5646
|
+
});
|
|
5647
|
+
}
|
|
5648
|
+
const row = {};
|
|
5649
|
+
for (const field of schemaColumns(op.table)) {
|
|
5650
|
+
const token = `${prefix}${index}:${field}`;
|
|
5651
|
+
symbols.set(token, {
|
|
5652
|
+
ref: {
|
|
5653
|
+
op: index,
|
|
5654
|
+
field
|
|
5655
|
+
}
|
|
5656
|
+
});
|
|
5657
|
+
row[field] = token;
|
|
5658
|
+
}
|
|
5659
|
+
results.push({
|
|
5660
|
+
rows: [
|
|
5661
|
+
row
|
|
5662
|
+
],
|
|
5663
|
+
rows_affected: 1
|
|
5664
|
+
});
|
|
5665
|
+
}
|
|
5666
|
+
return {
|
|
5667
|
+
steps,
|
|
5668
|
+
...hasRefs ? {} : {
|
|
5669
|
+
pipeline
|
|
5670
|
+
}
|
|
5671
|
+
};
|
|
5672
|
+
}
|
|
5673
|
+
__name(compileCommand, "compileCommand");
|
|
3814
5674
|
function assertGuard(op, result) {
|
|
3815
5675
|
const guard = op.guard;
|
|
3816
5676
|
if (!guard) return;
|
|
@@ -3824,6 +5684,33 @@ function assertGuard(op, result) {
|
|
|
3824
5684
|
}
|
|
3825
5685
|
__name(assertGuard, "assertGuard");
|
|
3826
5686
|
|
|
5687
|
+
// src/engine/cancellation.ts
|
|
5688
|
+
function withQueryCancellation(application, control) {
|
|
5689
|
+
if (application === control) throw new Error("Query cancellation requires a separate control pool");
|
|
5690
|
+
const close = /* @__PURE__ */ __name(async (driver) => {
|
|
5691
|
+
const d = driver;
|
|
5692
|
+
if (d.close) await d.close();
|
|
5693
|
+
else if (d.end) await d.end();
|
|
5694
|
+
}, "close");
|
|
5695
|
+
return {
|
|
5696
|
+
unsafe: /* @__PURE__ */ __name((sql, params) => application.unsafe(sql, params), "unsafe"),
|
|
5697
|
+
begin: /* @__PURE__ */ __name((fn) => application.begin(fn), "begin"),
|
|
5698
|
+
async cancelBackend(pid, startedAt) {
|
|
5699
|
+
await control.unsafe(`SELECT pg_cancel_backend(pid) FROM pg_stat_activity
|
|
5700
|
+
WHERE pid=$1::integer AND backend_start=$2::timestamptz
|
|
5701
|
+
AND usename=session_user AND datname=current_database() AND backend_type='client backend'`, [
|
|
5702
|
+
pid,
|
|
5703
|
+
startedAt
|
|
5704
|
+
]);
|
|
5705
|
+
},
|
|
5706
|
+
async close() {
|
|
5707
|
+
await close(application);
|
|
5708
|
+
await close(control);
|
|
5709
|
+
}
|
|
5710
|
+
};
|
|
5711
|
+
}
|
|
5712
|
+
__name(withQueryCancellation, "withQueryCancellation");
|
|
5713
|
+
|
|
3827
5714
|
// src/decorators/registry.ts
|
|
3828
5715
|
var ROUTES = /* @__PURE__ */ Symbol.for("palbase.backend.routes");
|
|
3829
5716
|
var RETURN_BUFFER = /* @__PURE__ */ Symbol.for("palbase.backend.returnBuffer");
|
|
@@ -4765,10 +6652,13 @@ async function defaultSqlDriver(config) {
|
|
|
4765
6652
|
if (!g.Bun?.SQL) {
|
|
4766
6653
|
throw new BootRefused([], "boot refused: no SQL driver. Running outside Bun means the driver must be supplied \u2014 pass `sql` to createApp().");
|
|
4767
6654
|
}
|
|
4768
|
-
return new g.Bun.SQL({
|
|
6655
|
+
return withQueryCancellation(new g.Bun.SQL({
|
|
4769
6656
|
url: config.databaseUrl,
|
|
4770
6657
|
max: config.poolMax
|
|
4771
|
-
})
|
|
6658
|
+
}), new g.Bun.SQL({
|
|
6659
|
+
url: config.databaseUrl,
|
|
6660
|
+
max: 2
|
|
6661
|
+
}));
|
|
4772
6662
|
}
|
|
4773
6663
|
__name(defaultSqlDriver, "defaultSqlDriver");
|
|
4774
6664
|
var AUTHOR_ANSWERED = /* @__PURE__ */ new Set([
|
|
@@ -4785,13 +6675,25 @@ async function refuseUnlessAuthorized(client, spec, requestId, rolesOfCaller) {
|
|
|
4785
6675
|
spec.permission
|
|
4786
6676
|
]);
|
|
4787
6677
|
if (row?.ok !== true) {
|
|
4788
|
-
return envelope(
|
|
6678
|
+
return envelope(
|
|
6679
|
+
"forbidden",
|
|
6680
|
+
`This endpoint requires the "${spec.permission}" permission`,
|
|
6681
|
+
403,
|
|
6682
|
+
requestId,
|
|
6683
|
+
// FR-008 / D-7: reddin ÇARESİ gövdede — istemci `required`'ı görünce ilgili
|
|
6684
|
+
// nesneyi yeniden çeker (Entra CAE deseni); cümle değişmez.
|
|
6685
|
+
{
|
|
6686
|
+
required: spec.permission
|
|
6687
|
+
}
|
|
6688
|
+
);
|
|
4789
6689
|
}
|
|
4790
6690
|
}
|
|
4791
6691
|
if (spec.role !== void 0) {
|
|
4792
6692
|
const held = await rolesOfCaller();
|
|
4793
6693
|
if (!held.includes(spec.role)) {
|
|
4794
|
-
return envelope("forbidden", `This endpoint requires the "${spec.role}" role`, 403, requestId
|
|
6694
|
+
return envelope("forbidden", `This endpoint requires the "${spec.role}" role`, 403, requestId, {
|
|
6695
|
+
required_role: spec.role
|
|
6696
|
+
});
|
|
4795
6697
|
}
|
|
4796
6698
|
}
|
|
4797
6699
|
return null;
|
|
@@ -4827,6 +6729,11 @@ async function createApp(opts) {
|
|
|
4827
6729
|
const sql = opts.sql ?? await defaultSqlDriver(config);
|
|
4828
6730
|
await sql.unsafe("select 1");
|
|
4829
6731
|
setSchema(opts.schemas ?? []);
|
|
6732
|
+
setCursorKey(config.cursorSecret || config.serviceRoleKey || null);
|
|
6733
|
+
if (config.enableCommands) await installCommandExecutor(sql, [
|
|
6734
|
+
config.dbRole,
|
|
6735
|
+
config.dbServiceRole
|
|
6736
|
+
]);
|
|
4830
6737
|
setSecretReader(opts.secretReader ?? null);
|
|
4831
6738
|
const auth = new AuthVerifier({
|
|
4832
6739
|
jwksUrl: config.authJwksUrl,
|
|
@@ -4872,6 +6779,9 @@ async function createApp(opts) {
|
|
|
4872
6779
|
role: config.dbRole,
|
|
4873
6780
|
serviceRole: config.dbServiceRole,
|
|
4874
6781
|
claimsJson: "{}"
|
|
6782
|
+
}, config.databaseDiagnostics === void 0 ? void 0 : {
|
|
6783
|
+
...config.databaseDiagnostics,
|
|
6784
|
+
endpoint: "scheduled job"
|
|
4875
6785
|
});
|
|
4876
6786
|
try {
|
|
4877
6787
|
const out = await runWithRuntime(buildServices(db), fn);
|
|
@@ -4901,6 +6811,10 @@ async function createApp(opts) {
|
|
|
4901
6811
|
role: config.dbRole,
|
|
4902
6812
|
serviceRole: config.dbServiceRole,
|
|
4903
6813
|
claimsJson: JSON.stringify(callerClaims ?? {})
|
|
6814
|
+
}, config.databaseDiagnostics === void 0 ? void 0 : {
|
|
6815
|
+
...config.databaseDiagnostics,
|
|
6816
|
+
endpoint: "upload authorization",
|
|
6817
|
+
requestId
|
|
4904
6818
|
});
|
|
4905
6819
|
try {
|
|
4906
6820
|
const refusal = await refuseUnlessAuthorized(uploadDb.client, spec, requestId, makeRoleResolver(uploadDb.client));
|
|
@@ -4948,6 +6862,11 @@ async function createApp(opts) {
|
|
|
4948
6862
|
role: config.dbRole,
|
|
4949
6863
|
serviceRole: config.dbServiceRole,
|
|
4950
6864
|
claimsJson: JSON.stringify(claims ?? {})
|
|
6865
|
+
}, config.databaseDiagnostics === void 0 && meta.options.databaseBudget === void 0 ? void 0 : {
|
|
6866
|
+
...config.databaseDiagnostics,
|
|
6867
|
+
...meta.options.databaseBudget,
|
|
6868
|
+
endpoint: hit.entry.id,
|
|
6869
|
+
requestId
|
|
4951
6870
|
});
|
|
4952
6871
|
const rolesOfCaller = makeRoleResolver(db.client);
|
|
4953
6872
|
let dispatched = false;
|
|
@@ -5373,6 +7292,7 @@ __name(closeDriver, "closeDriver");
|
|
|
5373
7292
|
0 && (module.exports = {
|
|
5374
7293
|
AuthVerifier,
|
|
5375
7294
|
BootRefused,
|
|
7295
|
+
COMMAND_EXECUTOR_DDL,
|
|
5376
7296
|
DECLARATION_REFUSAL,
|
|
5377
7297
|
DeclarationRefused,
|
|
5378
7298
|
RateLimiter,
|
|
@@ -5383,6 +7303,7 @@ __name(closeDriver, "closeDriver");
|
|
|
5383
7303
|
createRequestDatabase,
|
|
5384
7304
|
effectiveAuth,
|
|
5385
7305
|
hostAllowed,
|
|
7306
|
+
installCommandExecutor,
|
|
5386
7307
|
installEgressFence,
|
|
5387
7308
|
isDeclarationRefused,
|
|
5388
7309
|
loadConfig,
|
|
@@ -5390,6 +7311,8 @@ __name(closeDriver, "closeDriver");
|
|
|
5390
7311
|
matchRoute,
|
|
5391
7312
|
quoteIdent,
|
|
5392
7313
|
scrubSecrets,
|
|
7314
|
+
setCursorKey,
|
|
7315
|
+
withQueryCancellation,
|
|
5393
7316
|
withTables
|
|
5394
7317
|
});
|
|
5395
7318
|
//# sourceMappingURL=index.cjs.map
|