@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
|
@@ -352,10 +352,10 @@ function encodeFilterValue(value, column) {
|
|
|
352
352
|
__name(encodeFilterValue, "encodeFilterValue");
|
|
353
353
|
function encodeFilterMap(map) {
|
|
354
354
|
const out = {};
|
|
355
|
-
for (const
|
|
356
|
-
const value = map[
|
|
355
|
+
for (const key2 of Object.keys(map).sort()) {
|
|
356
|
+
const value = map[key2];
|
|
357
357
|
if (value === void 0) continue;
|
|
358
|
-
out[
|
|
358
|
+
out[key2] = encodeFilterValue(value, key2);
|
|
359
359
|
}
|
|
360
360
|
return out;
|
|
361
361
|
}
|
|
@@ -377,12 +377,12 @@ function assertNoNestedHandles(value, column) {
|
|
|
377
377
|
__name(assertNoNestedHandles, "assertNoNestedHandles");
|
|
378
378
|
function encodeMap(map, allowColumnExpr) {
|
|
379
379
|
const out = {};
|
|
380
|
-
for (const
|
|
381
|
-
const value = map[
|
|
380
|
+
for (const key2 of Object.keys(map).sort()) {
|
|
381
|
+
const value = map[key2];
|
|
382
382
|
if (value === void 0) {
|
|
383
|
-
throw new TxPlanError(`${
|
|
383
|
+
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.`);
|
|
384
384
|
}
|
|
385
|
-
out[
|
|
385
|
+
out[key2] = encodeValue(value, key2, allowColumnExpr);
|
|
386
386
|
}
|
|
387
387
|
return out;
|
|
388
388
|
}
|
|
@@ -614,7 +614,7 @@ function materializeResult(value, results) {
|
|
|
614
614
|
if (Array.isArray(value)) return value.map((item) => materializeResult(item, results));
|
|
615
615
|
if (isPlainObject(value)) {
|
|
616
616
|
const out = {};
|
|
617
|
-
for (const [
|
|
617
|
+
for (const [key2, item] of Object.entries(value)) out[key2] = materializeResult(item, results);
|
|
618
618
|
return out;
|
|
619
619
|
}
|
|
620
620
|
return value;
|
|
@@ -663,162 +663,71 @@ function translateRejection(err, builder) {
|
|
|
663
663
|
}
|
|
664
664
|
__name(translateRejection, "translateRejection");
|
|
665
665
|
|
|
666
|
-
// src/
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
if (typeof err !== "object" || err === null) return false;
|
|
680
|
-
const e = err;
|
|
681
|
-
return e[HTTP_ERROR_BRAND] === true && typeof e.status === "number" && typeof e.error === "string" && typeof e.errorDescription === "string";
|
|
682
|
-
}
|
|
683
|
-
__name(isHttpError, "isHttpError");
|
|
684
|
-
var HttpError = class extends Error {
|
|
685
|
-
static {
|
|
686
|
-
__name(this, "HttpError");
|
|
687
|
-
}
|
|
688
|
-
status;
|
|
689
|
-
error;
|
|
690
|
-
errorDescription;
|
|
691
|
-
data;
|
|
692
|
-
/** See {@link HTTP_ERROR_BRAND} — how the engine recognises this across SDK copies. */
|
|
693
|
-
[HTTP_ERROR_BRAND] = true;
|
|
694
|
-
constructor(status, error, errorDescription, data) {
|
|
695
|
-
super(errorDescription);
|
|
696
|
-
this.name = "HttpError";
|
|
697
|
-
this.status = status;
|
|
698
|
-
this.error = error;
|
|
699
|
-
this.errorDescription = errorDescription;
|
|
700
|
-
if (data !== void 0) {
|
|
701
|
-
this.data = data;
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
/**
|
|
705
|
-
* Serialize to the standard Palbase error response format.
|
|
706
|
-
* The `requestId` is injected by the runtime layer from the request context.
|
|
707
|
-
* When called without arguments (e.g. JSON.stringify), request_id is omitted.
|
|
708
|
-
* When `data` is set, it is appended as a strict-superset field.
|
|
709
|
-
*/
|
|
710
|
-
toJSON(requestId) {
|
|
711
|
-
const result = {
|
|
712
|
-
error: this.error,
|
|
713
|
-
error_description: this.errorDescription,
|
|
714
|
-
status: this.status
|
|
715
|
-
};
|
|
716
|
-
if (requestId) {
|
|
717
|
-
result.request_id = requestId;
|
|
718
|
-
}
|
|
719
|
-
if (this.data !== void 0) {
|
|
720
|
-
result.data = this.data;
|
|
721
|
-
}
|
|
722
|
-
return result;
|
|
723
|
-
}
|
|
724
|
-
};
|
|
725
|
-
var NamedHttpError = class NamedHttpError2 extends HttpError {
|
|
726
|
-
static {
|
|
727
|
-
__name(this, "NamedHttpError");
|
|
728
|
-
}
|
|
729
|
-
constructor(status, defaultCode, name, message, code, data) {
|
|
730
|
-
super(status, code ?? defaultCode, message ?? defaultMessage(name), data);
|
|
731
|
-
this.name = name;
|
|
666
|
+
// src/db/workload.ts
|
|
667
|
+
function validateWorkload(value) {
|
|
668
|
+
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");
|
|
669
|
+
for (const name of [
|
|
670
|
+
"concurrency",
|
|
671
|
+
"tenantConcurrency",
|
|
672
|
+
"queueLimit",
|
|
673
|
+
"tenantQueueLimit",
|
|
674
|
+
"queueTimeoutMs"
|
|
675
|
+
]) {
|
|
676
|
+
const n = value[name], min = name.endsWith("Limit") ? 0 : 1;
|
|
677
|
+
const max = name === "queueTimeoutMs" ? 6e4 : name.endsWith("Limit") ? 1e4 : 1e3;
|
|
678
|
+
if (!Number.isInteger(n) || n < min || n > max) throw new Error(`workload ${name} must be an integer between ${min} and ${max}`);
|
|
732
679
|
}
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
const spaced = name.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
|
|
736
|
-
return spaced.charAt(0).toUpperCase() + spaced.slice(1).toLowerCase();
|
|
680
|
+
if (value.tenantConcurrency > value.concurrency || value.tenantQueueLimit > value.queueLimit) throw new Error("workload tenant limits cannot exceed global limits");
|
|
681
|
+
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");
|
|
737
682
|
}
|
|
738
|
-
__name(
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
683
|
+
__name(validateWorkload, "validateWorkload");
|
|
684
|
+
|
|
685
|
+
// src/db/transaction-options.ts
|
|
686
|
+
function retryBudget(value) {
|
|
687
|
+
const n = value ?? 0;
|
|
688
|
+
if (!Number.isInteger(n) || n < 0 || n > 10) {
|
|
689
|
+
throw new Error("transaction retry must be an integer between 0 and 10");
|
|
690
|
+
}
|
|
691
|
+
return n;
|
|
692
|
+
}
|
|
693
|
+
__name(retryBudget, "retryBudget");
|
|
694
|
+
function validateAtomicOptions(options) {
|
|
695
|
+
retryBudget(options.retry);
|
|
696
|
+
if (options.isolation !== void 0 && ![
|
|
697
|
+
"read committed",
|
|
698
|
+
"repeatable read",
|
|
699
|
+
"serializable"
|
|
700
|
+
].includes(options.isolation)) {
|
|
701
|
+
throw new Error("transaction isolation must be read committed, repeatable read or serializable");
|
|
702
|
+
}
|
|
703
|
+
if (options.readOnly !== void 0 && typeof options.readOnly !== "boolean") {
|
|
704
|
+
throw new Error("transaction readOnly must be a boolean");
|
|
705
|
+
}
|
|
706
|
+
for (const name of [
|
|
707
|
+
"lockTimeoutMs",
|
|
708
|
+
"statementTimeoutMs",
|
|
709
|
+
"timeoutMs"
|
|
710
|
+
]) {
|
|
711
|
+
const n = options[name];
|
|
712
|
+
if (n !== void 0 && (!Number.isInteger(n) || n < 1 || n > 2147483647)) {
|
|
713
|
+
throw new Error(`transaction ${name} must be a positive integer in milliseconds`);
|
|
714
|
+
}
|
|
750
715
|
}
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
*
|
|
755
|
-
* Use this instead of `instanceof`. Measured on a live stack: a controller
|
|
756
|
-
* bundle INLINES its own copy of `@palbase/backend`, and the engine that
|
|
757
|
-
* raises this error is the runtime's copy. Two copies, two class identities,
|
|
758
|
-
* and `e instanceof UniqueViolation` is false in the one place a caller
|
|
759
|
-
* writes it — a check that reads as correct and silently never matches.
|
|
760
|
-
*/
|
|
761
|
-
static is(e) {
|
|
762
|
-
return typeof e === "object" && e !== null && e.name === "UniqueViolation" && typeof e.constraint === "string";
|
|
716
|
+
if (options.workload !== void 0) validateWorkload(options.workload);
|
|
717
|
+
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))) {
|
|
718
|
+
throw new Error("conflictKeys requires a workload and at most 64 nonempty keys of at most 256 characters");
|
|
763
719
|
}
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
constraint;
|
|
767
|
-
constructor(constraint, message, code, data) {
|
|
768
|
-
super(message ?? "Unique constraint violated", code ?? "unique_violation", data);
|
|
769
|
-
this.name = "UniqueViolation";
|
|
770
|
-
this.constraint = constraint;
|
|
720
|
+
if (options.signal !== void 0 && (typeof options.signal.aborted !== "boolean" || typeof options.signal.addEventListener !== "function")) {
|
|
721
|
+
throw new Error("transaction signal must be an AbortSignal");
|
|
771
722
|
}
|
|
772
|
-
};
|
|
773
|
-
function isRetryable(e) {
|
|
774
|
-
return typeof e === "object" && e !== null && e.retryable === true && typeof e.sqlstate === "string";
|
|
775
723
|
}
|
|
776
|
-
__name(
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
}
|
|
781
|
-
retryable = true;
|
|
782
|
-
sqlstate;
|
|
783
|
-
static is(e) {
|
|
784
|
-
return typeof e === "object" && e !== null && e.name === "SerializationFailure";
|
|
785
|
-
}
|
|
786
|
-
constructor(sqlstate = "40001", message, data) {
|
|
787
|
-
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);
|
|
788
|
-
this.name = "SerializationFailure";
|
|
789
|
-
this.sqlstate = sqlstate;
|
|
790
|
-
}
|
|
791
|
-
};
|
|
792
|
-
var DeadlockDetected = class extends Conflict {
|
|
793
|
-
static {
|
|
794
|
-
__name(this, "DeadlockDetected");
|
|
795
|
-
}
|
|
796
|
-
retryable = true;
|
|
797
|
-
sqlstate;
|
|
798
|
-
static is(e) {
|
|
799
|
-
return typeof e === "object" && e !== null && e.name === "DeadlockDetected";
|
|
800
|
-
}
|
|
801
|
-
constructor(sqlstate = "40P01", message, data) {
|
|
802
|
-
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);
|
|
803
|
-
this.name = "DeadlockDetected";
|
|
804
|
-
this.sqlstate = sqlstate;
|
|
805
|
-
}
|
|
806
|
-
};
|
|
807
|
-
|
|
808
|
-
// src/db/typed-db.ts
|
|
809
|
-
async function withRetry(fn, opts = {}) {
|
|
810
|
-
const budget = opts.retry ?? 0;
|
|
811
|
-
let attempt = 0;
|
|
812
|
-
for (; ; ) {
|
|
813
|
-
try {
|
|
814
|
-
return await fn();
|
|
815
|
-
} catch (e) {
|
|
816
|
-
if (attempt >= budget || !isRetryable(e)) throw e;
|
|
817
|
-
attempt += 1;
|
|
818
|
-
}
|
|
724
|
+
__name(validateAtomicOptions, "validateAtomicOptions");
|
|
725
|
+
function assertPlanRetry(options) {
|
|
726
|
+
if (retryBudget(options?.retry) !== 0) {
|
|
727
|
+
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.");
|
|
819
728
|
}
|
|
820
729
|
}
|
|
821
|
-
__name(
|
|
730
|
+
__name(assertPlanRetry, "assertPlanRetry");
|
|
822
731
|
|
|
823
732
|
// src/db/schema-json.ts
|
|
824
733
|
function qualifiedTableKey(schemaName, tableName) {
|
|
@@ -915,10 +824,10 @@ async function __runStartHooks() {
|
|
|
915
824
|
};
|
|
916
825
|
}
|
|
917
826
|
__name(__runStartHooks, "__runStartHooks");
|
|
918
|
-
function makeServiceProxy(
|
|
827
|
+
function makeServiceProxy(key2) {
|
|
919
828
|
const handler = {
|
|
920
829
|
get(_target, prop, receiver) {
|
|
921
|
-
const client = __getRuntime()[
|
|
830
|
+
const client = __getRuntime()[key2];
|
|
922
831
|
const value = Reflect.get(client, prop, receiver);
|
|
923
832
|
return typeof value === "function" ? value.bind(client) : value;
|
|
924
833
|
}
|
|
@@ -941,6 +850,14 @@ function makeTableProxy(ops, prefix) {
|
|
|
941
850
|
update: /* @__PURE__ */ __name((q) => ops().update(name, q.where.id, q.set), "update"),
|
|
942
851
|
delete: /* @__PURE__ */ __name((id) => ops().delete(name, id), "delete"),
|
|
943
852
|
findById: /* @__PURE__ */ __name((id) => ops().findById(name, id), "findById"),
|
|
853
|
+
findUnique: /* @__PURE__ */ __name((q) => ops().findUnique(name, q.where, {
|
|
854
|
+
select: q.select,
|
|
855
|
+
with: q.with
|
|
856
|
+
}), "findUnique"),
|
|
857
|
+
page: /* @__PURE__ */ __name((q) => {
|
|
858
|
+
const { where, ...opts } = q ?? {};
|
|
859
|
+
return ops().page(name, where, opts);
|
|
860
|
+
}, "page"),
|
|
944
861
|
findMany: /* @__PURE__ */ __name((q) => {
|
|
945
862
|
const { where, ...opts } = q ?? {};
|
|
946
863
|
return ops().findMany(name, where, opts);
|
|
@@ -978,10 +895,16 @@ function makeTypedSurface(raw) {
|
|
|
978
895
|
const reco = raw;
|
|
979
896
|
const ops = {
|
|
980
897
|
$query: /* @__PURE__ */ __name((sql, params) => raw.query(sql, params), "$query"),
|
|
898
|
+
$diagnostics: /* @__PURE__ */ __name(() => raw.diagnostics(), "$diagnostics"),
|
|
899
|
+
$readQuery: /* @__PURE__ */ __name((query) => raw.readQuery(query), "$readQuery"),
|
|
900
|
+
$compileQuery: /* @__PURE__ */ __name((query) => raw.compileQuery(query), "$compileQuery"),
|
|
901
|
+
$explainQuery: /* @__PURE__ */ __name((query) => raw.explainQuery(query), "$explainQuery"),
|
|
981
902
|
$insert: /* @__PURE__ */ __name((table, data) => raw.insert(table, data), "$insert"),
|
|
982
903
|
$update: /* @__PURE__ */ __name((table, id, data) => raw.update(table, id, data), "$update"),
|
|
983
904
|
$delete: /* @__PURE__ */ __name((table, id) => raw.delete(table, id), "$delete"),
|
|
984
905
|
$findById: /* @__PURE__ */ __name((table, id) => raw.findById(table, id), "$findById"),
|
|
906
|
+
$findUnique: /* @__PURE__ */ __name((table, where, opts) => raw.findUnique(table, where, opts), "$findUnique"),
|
|
907
|
+
$page: /* @__PURE__ */ __name((table, query, opts) => raw.page(table, query, opts), "$page"),
|
|
985
908
|
$findMany: /* @__PURE__ */ __name((table, query, opts) => raw.findMany(table, query, opts), "$findMany"),
|
|
986
909
|
$put: /* @__PURE__ */ __name((table, data, opts) => raw.put(table, data, opts), "$put"),
|
|
987
910
|
// `opts` İLETİLİR. Düşürüldüğü sürece `Database.$updateMany(t, w, s,
|
|
@@ -998,20 +921,55 @@ function makeTypedSurface(raw) {
|
|
|
998
921
|
$facets: /* @__PURE__ */ __name((table, params) => reco.facets(table, params), "$facets"),
|
|
999
922
|
$claim: /* @__PURE__ */ __name((table, unique, extra) => reco.claim(table, unique, extra), "$claim"),
|
|
1000
923
|
$lockRows: /* @__PURE__ */ __name((table, ids) => reco.lockRows(table, ids), "$lockRows"),
|
|
1001
|
-
$advisoryXactLock: /* @__PURE__ */ __name((
|
|
924
|
+
$advisoryXactLock: /* @__PURE__ */ __name((key2) => reco.advisoryXactLock(key2), "$advisoryXactLock"),
|
|
1002
925
|
$aggregate: /* @__PURE__ */ __name((table, q) => raw.aggregate(table, q), "$aggregate"),
|
|
1003
926
|
$insertMany: /* @__PURE__ */ __name((table, rows, opts) => raw.insertMany(table, rows, opts), "$insertMany"),
|
|
1004
927
|
$supersede: /* @__PURE__ */ __name((table, id, row) => raw.supersede(table, id, row), "$supersede")
|
|
1005
928
|
};
|
|
1006
929
|
const base = Object.assign(ops, {
|
|
930
|
+
$atomic(fn, options) {
|
|
931
|
+
if (typeof raw.atomic !== "function") {
|
|
932
|
+
throw new Error("This Database handle cannot open a root transaction; nested $atomic is not supported");
|
|
933
|
+
}
|
|
934
|
+
const parent = __requestALS.getStore();
|
|
935
|
+
let runtime2;
|
|
936
|
+
try {
|
|
937
|
+
runtime2 = __getRuntime();
|
|
938
|
+
} catch {
|
|
939
|
+
}
|
|
940
|
+
return raw.atomic(async (tx) => {
|
|
941
|
+
const typed = makeTypedSurface(tx);
|
|
942
|
+
const invoke = /* @__PURE__ */ __name(() => fn(typed), "invoke");
|
|
943
|
+
if (!runtime2) return invoke();
|
|
944
|
+
const refuse = /* @__PURE__ */ __name(() => {
|
|
945
|
+
throw new Error("Database.$atomic owns one transaction and identity; nested roots and $asService are not allowed");
|
|
946
|
+
}, "refuse");
|
|
947
|
+
const ambient = Object.assign({}, tx, {
|
|
948
|
+
atomic: refuse,
|
|
949
|
+
asService: refuse
|
|
950
|
+
});
|
|
951
|
+
return __requestALS.run({
|
|
952
|
+
...parent,
|
|
953
|
+
runtime: {
|
|
954
|
+
...runtime2,
|
|
955
|
+
Database: ambient
|
|
956
|
+
}
|
|
957
|
+
}, invoke);
|
|
958
|
+
}, options);
|
|
959
|
+
},
|
|
1007
960
|
// Both surfaces get it: a savepoint on the service transaction is as useful
|
|
1008
961
|
// as one on the request's, and each is bound to its own connection.
|
|
1009
962
|
$attempt: /* @__PURE__ */ __name((fn) => raw.attempt(fn), "$attempt"),
|
|
1010
963
|
$transaction(fn, opts) {
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
964
|
+
assertPlanRetry(opts);
|
|
965
|
+
const builder = new TxPlanBuilder();
|
|
966
|
+
return runTxPlan(raw, makeTxPlanHandle(builder), builder, fn);
|
|
967
|
+
},
|
|
968
|
+
$command(fn, options) {
|
|
969
|
+
const builder = new TxPlanBuilder();
|
|
970
|
+
return runTxPlan({
|
|
971
|
+
txPlan: /* @__PURE__ */ __name((plan) => raw.command(plan, options), "txPlan")
|
|
972
|
+
}, makeTxPlanHandle(builder), builder, fn);
|
|
1015
973
|
}
|
|
1016
974
|
});
|
|
1017
975
|
return new Proxy(base, {
|
|
@@ -1112,8 +1070,8 @@ var Flags = Object.assign({
|
|
|
1112
1070
|
get(flagName, defaultOrContext, maybeContext) {
|
|
1113
1071
|
return rawFlags.get(flagName, defaultOrContext, maybeContext);
|
|
1114
1072
|
},
|
|
1115
|
-
setOverride(
|
|
1116
|
-
return rawFlags.setOverride(
|
|
1073
|
+
setOverride(key2, value) {
|
|
1074
|
+
return rawFlags.setOverride(key2, value);
|
|
1117
1075
|
}
|
|
1118
1076
|
}, {
|
|
1119
1077
|
/**
|
|
@@ -1141,6 +1099,175 @@ var Flags = Object.assign({
|
|
|
1141
1099
|
});
|
|
1142
1100
|
var Realtime = makeServiceProxy("Realtime");
|
|
1143
1101
|
|
|
1102
|
+
// src/db/sqlstate.ts
|
|
1103
|
+
var SQLSTATE = Object.freeze({
|
|
1104
|
+
serializationFailure: "40001",
|
|
1105
|
+
deadlockDetected: "40P01",
|
|
1106
|
+
commandGuardFailed: "PBC01"
|
|
1107
|
+
});
|
|
1108
|
+
function isTransactionRollbackState(state) {
|
|
1109
|
+
return state === SQLSTATE.serializationFailure || state === SQLSTATE.deadlockDetected;
|
|
1110
|
+
}
|
|
1111
|
+
__name(isTransactionRollbackState, "isTransactionRollbackState");
|
|
1112
|
+
|
|
1113
|
+
// src/errors.ts
|
|
1114
|
+
var HTTP_ERROR_BRAND = /* @__PURE__ */ Symbol.for("palbase.backend.httpError");
|
|
1115
|
+
var ENGINE_RAISED = /* @__PURE__ */ Symbol.for("palbase.backend.engineRaised");
|
|
1116
|
+
function markEngineRaised(e) {
|
|
1117
|
+
e[ENGINE_RAISED] = true;
|
|
1118
|
+
return e;
|
|
1119
|
+
}
|
|
1120
|
+
__name(markEngineRaised, "markEngineRaised");
|
|
1121
|
+
function isEngineRaised(e) {
|
|
1122
|
+
return typeof e === "object" && e !== null && e[ENGINE_RAISED] === true;
|
|
1123
|
+
}
|
|
1124
|
+
__name(isEngineRaised, "isEngineRaised");
|
|
1125
|
+
function isHttpError(err) {
|
|
1126
|
+
if (typeof err !== "object" || err === null) return false;
|
|
1127
|
+
const e = err;
|
|
1128
|
+
return e[HTTP_ERROR_BRAND] === true && typeof e.status === "number" && typeof e.error === "string" && typeof e.errorDescription === "string";
|
|
1129
|
+
}
|
|
1130
|
+
__name(isHttpError, "isHttpError");
|
|
1131
|
+
var HttpError = class extends Error {
|
|
1132
|
+
static {
|
|
1133
|
+
__name(this, "HttpError");
|
|
1134
|
+
}
|
|
1135
|
+
status;
|
|
1136
|
+
error;
|
|
1137
|
+
errorDescription;
|
|
1138
|
+
data;
|
|
1139
|
+
/** See {@link HTTP_ERROR_BRAND} — how the engine recognises this across SDK copies. */
|
|
1140
|
+
[HTTP_ERROR_BRAND] = true;
|
|
1141
|
+
constructor(status, error, errorDescription, data) {
|
|
1142
|
+
super(errorDescription);
|
|
1143
|
+
this.name = "HttpError";
|
|
1144
|
+
this.status = status;
|
|
1145
|
+
this.error = error;
|
|
1146
|
+
this.errorDescription = errorDescription;
|
|
1147
|
+
if (data !== void 0) {
|
|
1148
|
+
this.data = data;
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
/**
|
|
1152
|
+
* Serialize to the standard Palbase error response format.
|
|
1153
|
+
* The `requestId` is injected by the runtime layer from the request context.
|
|
1154
|
+
* When called without arguments (e.g. JSON.stringify), request_id is omitted.
|
|
1155
|
+
* When `data` is set, it is appended as a strict-superset field.
|
|
1156
|
+
*/
|
|
1157
|
+
toJSON(requestId) {
|
|
1158
|
+
const result = {
|
|
1159
|
+
error: this.error,
|
|
1160
|
+
error_description: this.errorDescription,
|
|
1161
|
+
status: this.status
|
|
1162
|
+
};
|
|
1163
|
+
if (requestId) {
|
|
1164
|
+
result.request_id = requestId;
|
|
1165
|
+
}
|
|
1166
|
+
if (this.data !== void 0) {
|
|
1167
|
+
result.data = this.data;
|
|
1168
|
+
}
|
|
1169
|
+
return result;
|
|
1170
|
+
}
|
|
1171
|
+
};
|
|
1172
|
+
var NamedHttpError = class NamedHttpError2 extends HttpError {
|
|
1173
|
+
static {
|
|
1174
|
+
__name(this, "NamedHttpError");
|
|
1175
|
+
}
|
|
1176
|
+
constructor(status, defaultCode, name, message, code, data) {
|
|
1177
|
+
super(status, code ?? defaultCode, message ?? defaultMessage(name), data);
|
|
1178
|
+
this.name = name;
|
|
1179
|
+
}
|
|
1180
|
+
};
|
|
1181
|
+
function defaultMessage(name) {
|
|
1182
|
+
const spaced = name.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
|
|
1183
|
+
return spaced.charAt(0).toUpperCase() + spaced.slice(1).toLowerCase();
|
|
1184
|
+
}
|
|
1185
|
+
__name(defaultMessage, "defaultMessage");
|
|
1186
|
+
var BadRequest = class extends NamedHttpError {
|
|
1187
|
+
static {
|
|
1188
|
+
__name(this, "BadRequest");
|
|
1189
|
+
}
|
|
1190
|
+
constructor(data, message) {
|
|
1191
|
+
super(400, "bad_request", "BadRequest", message, void 0, data);
|
|
1192
|
+
}
|
|
1193
|
+
};
|
|
1194
|
+
var Conflict = class extends NamedHttpError {
|
|
1195
|
+
static {
|
|
1196
|
+
__name(this, "Conflict");
|
|
1197
|
+
}
|
|
1198
|
+
constructor(message, code, data) {
|
|
1199
|
+
super(409, "conflict", "Conflict", message, code, data);
|
|
1200
|
+
}
|
|
1201
|
+
};
|
|
1202
|
+
var UniqueViolation = class extends Conflict {
|
|
1203
|
+
static {
|
|
1204
|
+
__name(this, "UniqueViolation");
|
|
1205
|
+
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Whether `e` is a unique violation — REGARDLESS of which copy of this SDK
|
|
1208
|
+
* constructed it.
|
|
1209
|
+
*
|
|
1210
|
+
* Use this instead of `instanceof`. Measured on a live stack: a controller
|
|
1211
|
+
* bundle INLINES its own copy of `@palbase/backend`, and the engine that
|
|
1212
|
+
* raises this error is the runtime's copy. Two copies, two class identities,
|
|
1213
|
+
* and `e instanceof UniqueViolation` is false in the one place a caller
|
|
1214
|
+
* writes it — a check that reads as correct and silently never matches.
|
|
1215
|
+
*/
|
|
1216
|
+
static is(e) {
|
|
1217
|
+
return typeof e === "object" && e !== null && e.name === "UniqueViolation" && typeof e.constraint === "string";
|
|
1218
|
+
}
|
|
1219
|
+
/** The unique constraint the statement violated, as Postgres named it.
|
|
1220
|
+
* `""` when the driver did not say which — see `engine/db.ts`. */
|
|
1221
|
+
constraint;
|
|
1222
|
+
constructor(constraint, message, code, data) {
|
|
1223
|
+
super(message ?? "Unique constraint violated", code ?? "unique_violation", data);
|
|
1224
|
+
this.name = "UniqueViolation";
|
|
1225
|
+
this.constraint = constraint;
|
|
1226
|
+
}
|
|
1227
|
+
};
|
|
1228
|
+
function isRetryable(e) {
|
|
1229
|
+
return typeof e === "object" && e !== null && e.retryable === true && isTransactionRollbackState(e.sqlstate);
|
|
1230
|
+
}
|
|
1231
|
+
__name(isRetryable, "isRetryable");
|
|
1232
|
+
var SerializationFailure = class extends Conflict {
|
|
1233
|
+
static {
|
|
1234
|
+
__name(this, "SerializationFailure");
|
|
1235
|
+
}
|
|
1236
|
+
retryable = true;
|
|
1237
|
+
sqlstate;
|
|
1238
|
+
static is(e) {
|
|
1239
|
+
return typeof e === "object" && e !== null && e.name === "SerializationFailure";
|
|
1240
|
+
}
|
|
1241
|
+
constructor(sqlstate = SQLSTATE.serializationFailure, message, data) {
|
|
1242
|
+
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);
|
|
1243
|
+
this.name = "SerializationFailure";
|
|
1244
|
+
this.sqlstate = sqlstate;
|
|
1245
|
+
}
|
|
1246
|
+
};
|
|
1247
|
+
var DeadlockDetected = class extends Conflict {
|
|
1248
|
+
static {
|
|
1249
|
+
__name(this, "DeadlockDetected");
|
|
1250
|
+
}
|
|
1251
|
+
retryable = true;
|
|
1252
|
+
sqlstate;
|
|
1253
|
+
static is(e) {
|
|
1254
|
+
return typeof e === "object" && e !== null && e.name === "DeadlockDetected";
|
|
1255
|
+
}
|
|
1256
|
+
constructor(sqlstate = SQLSTATE.deadlockDetected, message, data) {
|
|
1257
|
+
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);
|
|
1258
|
+
this.name = "DeadlockDetected";
|
|
1259
|
+
this.sqlstate = sqlstate;
|
|
1260
|
+
}
|
|
1261
|
+
};
|
|
1262
|
+
var TooManyRequests = class extends NamedHttpError {
|
|
1263
|
+
static {
|
|
1264
|
+
__name(this, "TooManyRequests");
|
|
1265
|
+
}
|
|
1266
|
+
constructor(data, message) {
|
|
1267
|
+
super(429, "too_many_requests", "TooManyRequests", message, void 0, data);
|
|
1268
|
+
}
|
|
1269
|
+
};
|
|
1270
|
+
|
|
1144
1271
|
// src/engine/config.ts
|
|
1145
1272
|
var BootRefused = class extends Error {
|
|
1146
1273
|
static {
|
|
@@ -1191,6 +1318,8 @@ ${detail}`);
|
|
|
1191
1318
|
uploadSecret: env.PALBASE_UPLOAD_SECRET ?? "",
|
|
1192
1319
|
anonKey: env.PALBASE_ANON_KEY ?? "",
|
|
1193
1320
|
serviceRoleKey: env.PALBASE_SERVICE_ROLE_KEY ?? "",
|
|
1321
|
+
cursorSecret: env.PALBASE_CURSOR_SECRET,
|
|
1322
|
+
enableCommands: env.PALBASE_DB_COMMANDS === "true",
|
|
1194
1323
|
realtimeSecret: env.REALTIME_INGESTION_SECRET ?? "",
|
|
1195
1324
|
port,
|
|
1196
1325
|
dbRole: env.DB_ROLE ?? "backend_authenticated",
|
|
@@ -1199,7 +1328,14 @@ ${detail}`);
|
|
|
1199
1328
|
// (`roleBackendServiceRole = "backend_service_role"`, NOLOGIN BYPASSRLS),
|
|
1200
1329
|
// and the live database agrees (pg_roles.rolbypassrls = true).
|
|
1201
1330
|
dbServiceRole: env.DB_SERVICE_ROLE ?? "backend_service_role",
|
|
1202
|
-
poolMax
|
|
1331
|
+
poolMax,
|
|
1332
|
+
...env.PALBASE_DB_DIAGNOSTICS === "true" ? {
|
|
1333
|
+
databaseDiagnostics: {
|
|
1334
|
+
onComplete: /* @__PURE__ */ __name((diagnostics) => {
|
|
1335
|
+
console.log("palbase.database", JSON.stringify(diagnostics));
|
|
1336
|
+
}, "onComplete")
|
|
1337
|
+
}
|
|
1338
|
+
} : {}
|
|
1203
1339
|
};
|
|
1204
1340
|
}
|
|
1205
1341
|
__name(loadConfig, "loadConfig");
|
|
@@ -1311,14 +1447,14 @@ var AuthVerifier = class {
|
|
|
1311
1447
|
return null;
|
|
1312
1448
|
}
|
|
1313
1449
|
if (header.alg !== "ES256" || !header.kid) return null;
|
|
1314
|
-
const
|
|
1315
|
-
if (!
|
|
1450
|
+
const key2 = await this.key(header.kid);
|
|
1451
|
+
if (!key2) return null;
|
|
1316
1452
|
let ok = false;
|
|
1317
1453
|
try {
|
|
1318
1454
|
ok = await crypto.subtle.verify({
|
|
1319
1455
|
name: "ECDSA",
|
|
1320
1456
|
hash: "SHA-256"
|
|
1321
|
-
},
|
|
1457
|
+
}, key2, b64urlToBytes(sig), new TextEncoder().encode(`${h}.${p}`));
|
|
1322
1458
|
} catch {
|
|
1323
1459
|
return null;
|
|
1324
1460
|
}
|
|
@@ -1384,12 +1520,12 @@ var RateLimiter = class {
|
|
|
1384
1520
|
* wait (never 0 — a caller told to wait 0 comes straight back to the same
|
|
1385
1521
|
* refusal).
|
|
1386
1522
|
*/
|
|
1387
|
-
check(rule,
|
|
1523
|
+
check(rule, key2, now) {
|
|
1388
1524
|
if (!rule || !(rule.max > 0) || !(rule.window > 0)) return null;
|
|
1389
|
-
const bucket = this.buckets.get(
|
|
1525
|
+
const bucket = this.buckets.get(key2);
|
|
1390
1526
|
if (!bucket || now >= bucket.resetAt) {
|
|
1391
1527
|
if (this.buckets.size >= this.maxKeys) this.evict(now);
|
|
1392
|
-
this.buckets.set(
|
|
1528
|
+
this.buckets.set(key2, {
|
|
1393
1529
|
count: 1,
|
|
1394
1530
|
resetAt: now + rule.window * 1e3
|
|
1395
1531
|
});
|
|
@@ -1432,11 +1568,11 @@ function makeMemoryCache(opts = {}) {
|
|
|
1432
1568
|
const now = opts.now ?? (() => Date.now());
|
|
1433
1569
|
const store = /* @__PURE__ */ new Map();
|
|
1434
1570
|
const inflight = /* @__PURE__ */ new Map();
|
|
1435
|
-
const live = /* @__PURE__ */ __name((
|
|
1436
|
-
const e = store.get(
|
|
1571
|
+
const live = /* @__PURE__ */ __name((key2) => {
|
|
1572
|
+
const e = store.get(key2);
|
|
1437
1573
|
if (!e) return void 0;
|
|
1438
1574
|
if (e.expiresAt !== 0 && e.expiresAt <= now()) {
|
|
1439
|
-
store.delete(
|
|
1575
|
+
store.delete(key2);
|
|
1440
1576
|
return void 0;
|
|
1441
1577
|
}
|
|
1442
1578
|
return e;
|
|
@@ -1459,51 +1595,830 @@ function makeMemoryCache(opts = {}) {
|
|
|
1459
1595
|
if (victim) store.delete(victim[0]);
|
|
1460
1596
|
}
|
|
1461
1597
|
}, "evict");
|
|
1462
|
-
const set = /* @__PURE__ */ __name(async (
|
|
1463
|
-
if (store.size >= maxEntries && !store.has(
|
|
1464
|
-
store.set(
|
|
1598
|
+
const set = /* @__PURE__ */ __name(async (key2, value, ttl) => {
|
|
1599
|
+
if (store.size >= maxEntries && !store.has(key2)) evict();
|
|
1600
|
+
store.set(key2, {
|
|
1465
1601
|
value,
|
|
1466
1602
|
expiresAt: ttl && ttl > 0 ? now() + ttl * 1e3 : 0
|
|
1467
1603
|
});
|
|
1468
1604
|
}, "set");
|
|
1469
1605
|
return {
|
|
1470
|
-
async get(
|
|
1471
|
-
const e = live(
|
|
1606
|
+
async get(key2) {
|
|
1607
|
+
const e = live(key2);
|
|
1472
1608
|
return e ? e.value : null;
|
|
1473
1609
|
},
|
|
1474
1610
|
set,
|
|
1475
|
-
async del(
|
|
1476
|
-
store.delete(
|
|
1611
|
+
async del(key2) {
|
|
1612
|
+
store.delete(key2);
|
|
1477
1613
|
},
|
|
1478
|
-
async incr(
|
|
1479
|
-
const e = live(
|
|
1614
|
+
async incr(key2) {
|
|
1615
|
+
const e = live(key2);
|
|
1480
1616
|
const next = (typeof e?.value === "number" ? e.value : 0) + 1;
|
|
1481
|
-
store.set(
|
|
1617
|
+
store.set(key2, {
|
|
1482
1618
|
value: next,
|
|
1483
1619
|
expiresAt: e?.expiresAt ?? 0
|
|
1484
1620
|
});
|
|
1485
1621
|
return next;
|
|
1486
1622
|
},
|
|
1487
|
-
async getOrSet(
|
|
1488
|
-
const hit = live(
|
|
1623
|
+
async getOrSet(key2, ttl, fn) {
|
|
1624
|
+
const hit = live(key2);
|
|
1489
1625
|
if (hit) return hit.value;
|
|
1490
|
-
const running = inflight.get(
|
|
1626
|
+
const running = inflight.get(key2);
|
|
1491
1627
|
if (running) return running;
|
|
1492
1628
|
const fill = (async () => {
|
|
1493
1629
|
try {
|
|
1494
1630
|
const value = await fn();
|
|
1495
|
-
await set(
|
|
1631
|
+
await set(key2, value, ttl);
|
|
1496
1632
|
return value;
|
|
1497
1633
|
} finally {
|
|
1498
|
-
inflight.delete(
|
|
1634
|
+
inflight.delete(key2);
|
|
1499
1635
|
}
|
|
1500
1636
|
})();
|
|
1501
|
-
inflight.set(
|
|
1637
|
+
inflight.set(key2, fill);
|
|
1502
1638
|
return fill;
|
|
1503
1639
|
}
|
|
1504
|
-
};
|
|
1505
|
-
}
|
|
1506
|
-
__name(makeMemoryCache, "makeMemoryCache");
|
|
1640
|
+
};
|
|
1641
|
+
}
|
|
1642
|
+
__name(makeMemoryCache, "makeMemoryCache");
|
|
1643
|
+
|
|
1644
|
+
// src/engine/db.ts
|
|
1645
|
+
var import_node_async_hooks2 = require("async_hooks");
|
|
1646
|
+
|
|
1647
|
+
// src/engine/schema-cache.ts
|
|
1648
|
+
function memoTable(cache, schema, table, compute) {
|
|
1649
|
+
if (!schema.tables || !Object.hasOwn(schema.tables, table)) return compute();
|
|
1650
|
+
let tables = cache.get(schema);
|
|
1651
|
+
if (!tables) {
|
|
1652
|
+
tables = /* @__PURE__ */ new Map();
|
|
1653
|
+
cache.set(schema, tables);
|
|
1654
|
+
}
|
|
1655
|
+
if (!tables.has(table)) tables.set(table, compute());
|
|
1656
|
+
return tables.get(table);
|
|
1657
|
+
}
|
|
1658
|
+
__name(memoTable, "memoTable");
|
|
1659
|
+
function snapshotDeclaration(value, seen = /* @__PURE__ */ new WeakMap()) {
|
|
1660
|
+
if (value === null || typeof value !== "object") return value;
|
|
1661
|
+
const previous = seen.get(value);
|
|
1662
|
+
if (previous) return previous;
|
|
1663
|
+
if (value instanceof Date) return new Date(value.getTime());
|
|
1664
|
+
const result = Array.isArray(value) ? [] : Object.create(Object.getPrototypeOf(value));
|
|
1665
|
+
seen.set(value, result);
|
|
1666
|
+
for (const key2 of Reflect.ownKeys(value)) {
|
|
1667
|
+
if (Array.isArray(value) && key2 === "length") continue;
|
|
1668
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key2);
|
|
1669
|
+
if ("value" in descriptor) descriptor.value = snapshotDeclaration(descriptor.value, seen);
|
|
1670
|
+
Object.defineProperty(result, key2, descriptor);
|
|
1671
|
+
}
|
|
1672
|
+
return Object.freeze(result);
|
|
1673
|
+
}
|
|
1674
|
+
__name(snapshotDeclaration, "snapshotDeclaration");
|
|
1675
|
+
|
|
1676
|
+
// src/engine/cursor.ts
|
|
1677
|
+
var import_node_crypto = require("crypto");
|
|
1678
|
+
var key = null;
|
|
1679
|
+
var invalidPage = /* @__PURE__ */ __name((message, field = "page") => new BadRequest({
|
|
1680
|
+
fields: [
|
|
1681
|
+
{
|
|
1682
|
+
field,
|
|
1683
|
+
message
|
|
1684
|
+
}
|
|
1685
|
+
]
|
|
1686
|
+
}, message), "invalidPage");
|
|
1687
|
+
function setCursorKey(secret) {
|
|
1688
|
+
key = secret ? (0, import_node_crypto.createHash)("sha256").update("palbase:cursor:v1\0").update(secret).digest() : null;
|
|
1689
|
+
}
|
|
1690
|
+
__name(setCursorKey, "setCursorKey");
|
|
1691
|
+
function cursorKey() {
|
|
1692
|
+
if (!key) throw new Error("Database.page needs PALBASE_CURSOR_SECRET or PALBASE_SERVICE_ROLE_KEY shared by all replicas");
|
|
1693
|
+
return key;
|
|
1694
|
+
}
|
|
1695
|
+
__name(cursorKey, "cursorKey");
|
|
1696
|
+
function fingerprint(value) {
|
|
1697
|
+
const stable = /* @__PURE__ */ __name((v) => {
|
|
1698
|
+
if (typeof v === "bigint") return {
|
|
1699
|
+
$bigint: v.toString()
|
|
1700
|
+
};
|
|
1701
|
+
if (v instanceof Date) return {
|
|
1702
|
+
$date: v.toISOString()
|
|
1703
|
+
};
|
|
1704
|
+
if (Array.isArray(v)) return v.map(stable);
|
|
1705
|
+
if (v && typeof v === "object") {
|
|
1706
|
+
return Object.fromEntries(Object.keys(v).sort().map((k) => [
|
|
1707
|
+
k,
|
|
1708
|
+
stable(v[k])
|
|
1709
|
+
]));
|
|
1710
|
+
}
|
|
1711
|
+
return v;
|
|
1712
|
+
}, "stable");
|
|
1713
|
+
return (0, import_node_crypto.createHash)("sha256").update(JSON.stringify(stable(value))).digest("base64url");
|
|
1714
|
+
}
|
|
1715
|
+
__name(fingerprint, "fingerprint");
|
|
1716
|
+
function cursorIdentity(role, claimsJson) {
|
|
1717
|
+
const claims = JSON.parse(claimsJson);
|
|
1718
|
+
const { iat, exp, nbf, jti, ...authorization } = claims;
|
|
1719
|
+
return fingerprint({
|
|
1720
|
+
role,
|
|
1721
|
+
authorization
|
|
1722
|
+
});
|
|
1723
|
+
}
|
|
1724
|
+
__name(cursorIdentity, "cursorIdentity");
|
|
1725
|
+
function encodeCursor(secret, scope, values) {
|
|
1726
|
+
const nonce = (0, import_node_crypto.randomBytes)(12);
|
|
1727
|
+
const cipher = (0, import_node_crypto.createCipheriv)("aes-256-gcm", secret, nonce);
|
|
1728
|
+
cipher.setAAD(Buffer.from(scope));
|
|
1729
|
+
const body = Buffer.concat([
|
|
1730
|
+
cipher.update(JSON.stringify(values), "utf8"),
|
|
1731
|
+
cipher.final()
|
|
1732
|
+
]);
|
|
1733
|
+
const token = `p1.${Buffer.concat([
|
|
1734
|
+
nonce,
|
|
1735
|
+
cipher.getAuthTag(),
|
|
1736
|
+
body
|
|
1737
|
+
]).toString("base64url")}`;
|
|
1738
|
+
if (token.length > 16384) throw invalidPage("page cursor values are too large; choose shorter ordering columns");
|
|
1739
|
+
return token;
|
|
1740
|
+
}
|
|
1741
|
+
__name(encodeCursor, "encodeCursor");
|
|
1742
|
+
function decodeCursor(secret, scope, token, count) {
|
|
1743
|
+
try {
|
|
1744
|
+
if (typeof token !== "string" || token.length > 16384 || !/^p1\.[\w-]+$/.test(token)) throw new Error();
|
|
1745
|
+
const data = Buffer.from(token.slice(3), "base64url");
|
|
1746
|
+
if (data.length < 30 || data.toString("base64url") !== token.slice(3)) throw new Error();
|
|
1747
|
+
const cipher = (0, import_node_crypto.createDecipheriv)("aes-256-gcm", secret, data.subarray(0, 12));
|
|
1748
|
+
cipher.setAAD(Buffer.from(scope));
|
|
1749
|
+
cipher.setAuthTag(data.subarray(12, 28));
|
|
1750
|
+
const values = JSON.parse(Buffer.concat([
|
|
1751
|
+
cipher.update(data.subarray(28)),
|
|
1752
|
+
cipher.final()
|
|
1753
|
+
]).toString("utf8"));
|
|
1754
|
+
if (!Array.isArray(values) || values.length !== count || values.some((v) => v !== null && typeof v !== "string")) throw new Error();
|
|
1755
|
+
return values;
|
|
1756
|
+
} catch {
|
|
1757
|
+
throw invalidPage("Invalid cursor for this query or identity; restart from the first page", "cursor");
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
__name(decodeCursor, "decodeCursor");
|
|
1761
|
+
function pageOrder(columns, primaryKey, opts) {
|
|
1762
|
+
const raw = opts;
|
|
1763
|
+
if ("limit" in raw || "offset" in raw) throw invalidPage("page uses first/after or last/before, not limit/offset");
|
|
1764
|
+
const backward = opts.last !== void 0 || opts.before !== void 0;
|
|
1765
|
+
if (backward && (opts.first !== void 0 || opts.after !== void 0)) throw invalidPage("Use first/after or last/before in one page request");
|
|
1766
|
+
const size = (backward ? opts.last : opts.first) ?? 50;
|
|
1767
|
+
if (!Number.isInteger(size) || size < 1 || size > 1e3) throw invalidPage("page size must be an integer between 1 and 1000");
|
|
1768
|
+
if (primaryKey.length === 0) throw new Error("page requires a declared primary key for a unique, stable ordering");
|
|
1769
|
+
const input = opts.orderBy === void 0 ? [] : Array.isArray(opts.orderBy) ? opts.orderBy : [
|
|
1770
|
+
opts.orderBy
|
|
1771
|
+
];
|
|
1772
|
+
if (input.length > 16) throw invalidPage("page supports at most 16 ordering columns");
|
|
1773
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1774
|
+
const order = [];
|
|
1775
|
+
const add = /* @__PURE__ */ __name((o) => {
|
|
1776
|
+
const def = columns[o?.column];
|
|
1777
|
+
if (!Object.hasOwn(columns, o?.column) || !def) throw invalidPage(`page order column ${o?.column} is not declared`);
|
|
1778
|
+
if (!(/* @__PURE__ */ new Set([
|
|
1779
|
+
"uuid",
|
|
1780
|
+
"text",
|
|
1781
|
+
"integer",
|
|
1782
|
+
"bigint",
|
|
1783
|
+
"boolean",
|
|
1784
|
+
"timestamp",
|
|
1785
|
+
"numeric",
|
|
1786
|
+
"enum"
|
|
1787
|
+
])).has(def.type ?? "")) {
|
|
1788
|
+
throw invalidPage(`page cannot order ${o.column}: ${def.type ?? "unknown"} is not a supported scalar column`);
|
|
1789
|
+
}
|
|
1790
|
+
if (seen.has(o.column)) throw invalidPage(`page order column ${o.column} is repeated`);
|
|
1791
|
+
if (o.direction !== void 0 && o.direction !== "asc" && o.direction !== "desc") throw invalidPage("page direction must be asc or desc");
|
|
1792
|
+
if (o.nulls !== void 0 && o.nulls !== "first" && o.nulls !== "last") throw invalidPage("page nulls must be first or last");
|
|
1793
|
+
const direction = o.direction ?? "asc";
|
|
1794
|
+
seen.add(o.column);
|
|
1795
|
+
order.push({
|
|
1796
|
+
column: o.column,
|
|
1797
|
+
direction,
|
|
1798
|
+
nulls: o.nulls ?? (direction === "asc" ? "last" : "first"),
|
|
1799
|
+
nullable: def.nullable !== false
|
|
1800
|
+
});
|
|
1801
|
+
}, "add");
|
|
1802
|
+
for (const o of input) add(o);
|
|
1803
|
+
for (const column of primaryKey) if (!seen.has(column)) add({
|
|
1804
|
+
column,
|
|
1805
|
+
direction: order.at(-1)?.direction ?? "asc"
|
|
1806
|
+
});
|
|
1807
|
+
return {
|
|
1808
|
+
order,
|
|
1809
|
+
size,
|
|
1810
|
+
backward,
|
|
1811
|
+
token: backward ? opts.before : opts.after
|
|
1812
|
+
};
|
|
1813
|
+
}
|
|
1814
|
+
__name(pageOrder, "pageOrder");
|
|
1815
|
+
function pageBoundary(order, values, backward, bind, column, inclusive = false) {
|
|
1816
|
+
const refs = values.map((v) => bind(v));
|
|
1817
|
+
if (order.every((o) => !o.nullable && o.direction === order[0].direction) && values.every((v) => v !== null)) {
|
|
1818
|
+
const greater = order[0].direction === "asc" !== backward;
|
|
1819
|
+
return `(${order.map((o) => column(o.column)).join(", ")}) ${greater ? ">" : "<"}${inclusive ? "=" : ""} (${refs.join(", ")})`;
|
|
1820
|
+
}
|
|
1821
|
+
const parts = [];
|
|
1822
|
+
const prefix = [];
|
|
1823
|
+
for (let i = 0; i < order.length; i++) {
|
|
1824
|
+
const o = order[i], c = column(o.column), r = refs[i];
|
|
1825
|
+
const nullsLast = o.nulls === "last" !== backward;
|
|
1826
|
+
const cmp = o.direction === "asc" !== backward ? ">" : "<";
|
|
1827
|
+
const tail = values[i] === null ? nullsLast ? "false" : `${c} IS NOT NULL` : `(${c} ${cmp} ${r}${nullsLast ? ` OR ${c} IS NULL` : ""})`;
|
|
1828
|
+
parts.push(`(${[
|
|
1829
|
+
...prefix,
|
|
1830
|
+
tail
|
|
1831
|
+
].join(" AND ")})`);
|
|
1832
|
+
prefix.push(`${c} IS NOT DISTINCT FROM ${r}`);
|
|
1833
|
+
}
|
|
1834
|
+
if (inclusive) parts.push(`(${prefix.join(" AND ")})`);
|
|
1835
|
+
return `(${parts.join(" OR ")})`;
|
|
1836
|
+
}
|
|
1837
|
+
__name(pageBoundary, "pageBoundary");
|
|
1838
|
+
|
|
1839
|
+
// src/db/bulk.ts
|
|
1840
|
+
function validateInsertManyOptions(opts) {
|
|
1841
|
+
if (!opts) return;
|
|
1842
|
+
if (opts.returning !== void 0 && typeof opts.returning !== "boolean") throw new Error("insertMany returning must be a boolean");
|
|
1843
|
+
if (opts.action !== void 0 && opts.action !== "ignore" && opts.action !== "update") throw new Error("insertMany action must be ignore or update");
|
|
1844
|
+
if (opts.onConflict !== void 0 && (!Array.isArray(opts.onConflict) || opts.onConflict.some((c) => typeof c !== "string" || c.length === 0))) {
|
|
1845
|
+
throw new Error("insertMany onConflict must be an array of column names");
|
|
1846
|
+
}
|
|
1847
|
+
if (opts.action !== void 0 && !opts.onConflict?.length) throw new Error("insertMany action requires nonempty onConflict columns");
|
|
1848
|
+
}
|
|
1849
|
+
__name(validateInsertManyOptions, "validateInsertManyOptions");
|
|
1850
|
+
|
|
1851
|
+
// src/db/query-contract.ts
|
|
1852
|
+
function queryColumnContract(column) {
|
|
1853
|
+
const def = column && typeof column === "object" && "_def" in column ? column._def : column;
|
|
1854
|
+
if (!def?.type) throw new Error("Query contract requires declared column types");
|
|
1855
|
+
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");
|
|
1856
|
+
return {
|
|
1857
|
+
type: def.type,
|
|
1858
|
+
nullable: def.nullable === true,
|
|
1859
|
+
...def.codec === void 0 ? {} : {
|
|
1860
|
+
codec: def.codec
|
|
1861
|
+
},
|
|
1862
|
+
...def.enumValues === void 0 ? {} : {
|
|
1863
|
+
enumValues: [
|
|
1864
|
+
...def.enumValues
|
|
1865
|
+
]
|
|
1866
|
+
},
|
|
1867
|
+
...def.dimensions === void 0 ? {} : {
|
|
1868
|
+
dimensions: def.dimensions
|
|
1869
|
+
}
|
|
1870
|
+
};
|
|
1871
|
+
}
|
|
1872
|
+
__name(queryColumnContract, "queryColumnContract");
|
|
1873
|
+
function stableContract(value) {
|
|
1874
|
+
if (Array.isArray(value)) return `[${value.map(stableContract).join(",")}]`;
|
|
1875
|
+
if (value && typeof value === "object") return `{${Object.keys(value).sort().map((key2) => `${JSON.stringify(key2)}:${stableContract(value[key2])}`).join(",")}}`;
|
|
1876
|
+
return JSON.stringify(value) ?? "null";
|
|
1877
|
+
}
|
|
1878
|
+
__name(stableContract, "stableContract");
|
|
1879
|
+
function assertReadContract(query, columns) {
|
|
1880
|
+
if (!query.options.select.length || new Set(query.options.select).size !== query.options.select.length) {
|
|
1881
|
+
throw new Error(`Query ${query.name}: select must contain distinct, explicit columns`);
|
|
1882
|
+
}
|
|
1883
|
+
for (const name of query.options.select) {
|
|
1884
|
+
if (!columns || !Object.hasOwn(columns, name) || !Object.hasOwn(query.columns, name) || stableContract(queryColumnContract(columns[name])) !== stableContract(query.columns[name])) {
|
|
1885
|
+
throw new Error(`Query ${query.name}: schema drift at ${query.table}.${name}; regenerate and review its response contract`);
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
__name(assertReadContract, "assertReadContract");
|
|
1890
|
+
|
|
1891
|
+
// src/db/unique.ts
|
|
1892
|
+
function uniqueKeysOf(table) {
|
|
1893
|
+
const columns = table.columns ?? {};
|
|
1894
|
+
const inlinePrimary = [], keys = [];
|
|
1895
|
+
for (const [name, value] of Object.entries(columns)) {
|
|
1896
|
+
const col = value && typeof value === "object" && "_def" in value ? value._def : value;
|
|
1897
|
+
if (col?.primaryKey) inlinePrimary.push(name);
|
|
1898
|
+
if (col?.unique) keys.push([
|
|
1899
|
+
name
|
|
1900
|
+
]);
|
|
1901
|
+
}
|
|
1902
|
+
const primary = table.primaryKey ?? inlinePrimary;
|
|
1903
|
+
if (primary.length) keys.unshift([
|
|
1904
|
+
...primary
|
|
1905
|
+
]);
|
|
1906
|
+
for (const key2 of table.unique ?? []) keys.push([
|
|
1907
|
+
...key2.columns
|
|
1908
|
+
]);
|
|
1909
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1910
|
+
return keys.filter((key2) => {
|
|
1911
|
+
if (!key2.length || new Set(key2).size !== key2.length || key2.some((col) => !Object.hasOwn(columns, col))) {
|
|
1912
|
+
throw new Error("Unique lookup requires a nonempty declared key of distinct, known columns");
|
|
1913
|
+
}
|
|
1914
|
+
const identity = JSON.stringify([
|
|
1915
|
+
...key2
|
|
1916
|
+
].sort());
|
|
1917
|
+
if (seen.has(identity)) return false;
|
|
1918
|
+
seen.add(identity);
|
|
1919
|
+
return true;
|
|
1920
|
+
});
|
|
1921
|
+
}
|
|
1922
|
+
__name(uniqueKeysOf, "uniqueKeysOf");
|
|
1923
|
+
function assertUniqueLookup(table, keys, where) {
|
|
1924
|
+
if (!where || typeof where !== "object" || Array.isArray(where)) throw new Error(`findUnique(${table}): where must contain one declared unique key`);
|
|
1925
|
+
const columns = Object.keys(where);
|
|
1926
|
+
if (!keys.some((key2) => key2.length === columns.length && key2.every((col) => Object.hasOwn(where, col)))) {
|
|
1927
|
+
throw new Error(`findUnique(${table}): supply exactly one declared unique key (${keys.map((key2) => key2.join(" + ")).join("; ") || "none declared"}); use findMany for other filters`);
|
|
1928
|
+
}
|
|
1929
|
+
for (const [name, value] of Object.entries(where)) {
|
|
1930
|
+
if (value === null || value === void 0 || typeof value === "object" && !(value instanceof Date) || typeof value === "function" || typeof value === "symbol") {
|
|
1931
|
+
throw new Error(`findUnique(${table}): ${name} requires a non-null scalar equality value`);
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
__name(assertUniqueLookup, "assertUniqueLookup");
|
|
1936
|
+
|
|
1937
|
+
// src/engine/commands.ts
|
|
1938
|
+
var installed = /* @__PURE__ */ new WeakSet();
|
|
1939
|
+
var hasCommandExecutor = /* @__PURE__ */ __name((sql) => installed.has(sql), "hasCommandExecutor");
|
|
1940
|
+
var COMMAND_EXECUTOR_DDL = `
|
|
1941
|
+
CREATE SCHEMA IF NOT EXISTS palbase_runtime;
|
|
1942
|
+
CREATE OR REPLACE FUNCTION palbase_runtime.command_v1(steps jsonb) RETURNS jsonb
|
|
1943
|
+
LANGUAGE plpgsql VOLATILE SECURITY INVOKER SET search_path = pg_catalog, public AS $pb_command$
|
|
1944
|
+
DECLARE
|
|
1945
|
+
results jsonb := '[]'::jsonb;
|
|
1946
|
+
step jsonb; chunk jsonb; binding jsonb; source jsonb; rows jsonb;
|
|
1947
|
+
statement text; rendered text; detail text; guard jsonb;
|
|
1948
|
+
index integer := 0; affected integer; passed boolean;
|
|
1949
|
+
BEGIN
|
|
1950
|
+
FOR step IN SELECT value FROM jsonb_array_elements(steps) LOOP
|
|
1951
|
+
IF step->>'empty' = 'true' THEN rows := '[]'::jsonb;
|
|
1952
|
+
ELSE
|
|
1953
|
+
statement := '';
|
|
1954
|
+
FOR chunk IN SELECT value FROM jsonb_array_elements(step->'chunks') LOOP
|
|
1955
|
+
IF jsonb_typeof(chunk) = 'string' THEN statement := statement || (chunk #>> '{}');
|
|
1956
|
+
ELSE
|
|
1957
|
+
binding := step->'bindings'->((chunk->>'bind')::integer);
|
|
1958
|
+
IF binding ? 'ref' THEN
|
|
1959
|
+
source := results->((binding->'ref'->>'op')::integer)->'rows'->0;
|
|
1960
|
+
IF source IS NULL OR NOT source ? (binding->'ref'->>'field') THEN
|
|
1961
|
+
RAISE EXCEPTION 'Command reference has no source row/column; use expectOne before reading it'
|
|
1962
|
+
USING ERRCODE = 'PBC02', DETAIL = jsonb_build_object('error_code','tx_ref_unresolved','op',index)::text;
|
|
1963
|
+
END IF;
|
|
1964
|
+
source := source->(binding->'ref'->>'field');
|
|
1965
|
+
ELSE source := binding->'value'; END IF;
|
|
1966
|
+
rendered := CASE WHEN source IS NULL OR source = 'null'::jsonb THEN NULL
|
|
1967
|
+
WHEN binding->>'json' = 'true' THEN source::text
|
|
1968
|
+
WHEN jsonb_typeof(source) = 'string' THEN source #>> '{}' ELSE source::text END;
|
|
1969
|
+
-- PostgreSQL quotes the value itself. Values reach this function as
|
|
1970
|
+
-- one bound JSON parameter, never as application-generated SQL text.
|
|
1971
|
+
statement := statement || quote_nullable(rendered);
|
|
1972
|
+
END IF;
|
|
1973
|
+
END LOOP;
|
|
1974
|
+
EXECUTE statement INTO rows;
|
|
1975
|
+
END IF;
|
|
1976
|
+
affected := jsonb_array_length(rows);
|
|
1977
|
+
guard := step->'guard';
|
|
1978
|
+
IF guard IS NOT NULL AND guard <> 'null'::jsonb THEN
|
|
1979
|
+
passed := CASE guard->>'kind'
|
|
1980
|
+
WHEN 'one' THEN affected = 1 WHEN 'none' THEN affected = 0
|
|
1981
|
+
WHEN 'atLeast' THEN affected >= (guard->>'n')::integer
|
|
1982
|
+
WHEN 'atMost' THEN affected <= (guard->>'n')::integer ELSE false END;
|
|
1983
|
+
IF NOT passed THEN
|
|
1984
|
+
RAISE EXCEPTION 'Command expectation failed'
|
|
1985
|
+
USING ERRCODE = 'PBC01', DETAIL = jsonb_build_object('error_code','tx_guard_failed','slot',guard->'slot','op',index)::text;
|
|
1986
|
+
END IF;
|
|
1987
|
+
END IF;
|
|
1988
|
+
results := results || jsonb_build_array(jsonb_build_object('rows',rows,'rows_affected',affected));
|
|
1989
|
+
index := index + 1;
|
|
1990
|
+
END LOOP;
|
|
1991
|
+
RETURN jsonb_build_object('results',results);
|
|
1992
|
+
EXCEPTION WHEN SQLSTATE 'PBC01' OR SQLSTATE 'PBC02' THEN
|
|
1993
|
+
GET STACKED DIAGNOSTICS detail = PG_EXCEPTION_DETAIL;
|
|
1994
|
+
RETURN jsonb_build_object('error',detail::jsonb);
|
|
1995
|
+
END
|
|
1996
|
+
$pb_command$;
|
|
1997
|
+
REVOKE ALL ON FUNCTION palbase_runtime.command_v1(jsonb) FROM PUBLIC;
|
|
1998
|
+
CREATE OR REPLACE FUNCTION palbase_runtime.command_rows_v1(rows jsonb, guard jsonb) RETURNS jsonb
|
|
1999
|
+
LANGUAGE plpgsql VOLATILE SECURITY INVOKER SET search_path = pg_catalog, public AS $pb_rows$
|
|
2000
|
+
DECLARE affected integer := jsonb_array_length(rows); passed boolean;
|
|
2001
|
+
BEGIN
|
|
2002
|
+
IF guard IS NOT NULL AND guard <> 'null'::jsonb THEN
|
|
2003
|
+
passed := CASE guard->>'kind'
|
|
2004
|
+
WHEN 'one' THEN affected = 1 WHEN 'none' THEN affected = 0
|
|
2005
|
+
WHEN 'atLeast' THEN affected >= (guard->>'n')::integer
|
|
2006
|
+
WHEN 'atMost' THEN affected <= (guard->>'n')::integer ELSE false END;
|
|
2007
|
+
IF NOT passed THEN
|
|
2008
|
+
RAISE EXCEPTION 'Command expectation failed'
|
|
2009
|
+
USING ERRCODE = 'PBC01', DETAIL = jsonb_build_object('error_code','tx_guard_failed','slot',guard->'slot')::text;
|
|
2010
|
+
END IF;
|
|
2011
|
+
END IF;
|
|
2012
|
+
RETURN rows;
|
|
2013
|
+
END
|
|
2014
|
+
$pb_rows$;
|
|
2015
|
+
REVOKE ALL ON FUNCTION palbase_runtime.command_rows_v1(jsonb,jsonb) FROM PUBLIC;
|
|
2016
|
+
`;
|
|
2017
|
+
async function installCommandExecutor(sql, roles) {
|
|
2018
|
+
const quote = /* @__PURE__ */ __name((s) => `"${s.replace(/"/g, '""')}"`, "quote");
|
|
2019
|
+
if (roles.length === 0) throw new Error("Command executor needs explicit runtime roles");
|
|
2020
|
+
await sql.begin(async (tx) => {
|
|
2021
|
+
await tx.unsafe("SELECT pg_advisory_xact_lock(196754721, 1)");
|
|
2022
|
+
await tx.unsafe(COMMAND_EXECUTOR_DDL);
|
|
2023
|
+
const list = [
|
|
2024
|
+
...new Set(roles)
|
|
2025
|
+
].map(quote).join(", ");
|
|
2026
|
+
await tx.unsafe(`GRANT USAGE ON SCHEMA palbase_runtime TO ${list}`);
|
|
2027
|
+
await tx.unsafe(`GRANT EXECUTE ON FUNCTION palbase_runtime.command_v1(jsonb) TO ${list}`);
|
|
2028
|
+
await tx.unsafe(`GRANT EXECUTE ON FUNCTION palbase_runtime.command_rows_v1(jsonb,jsonb) TO ${list}`);
|
|
2029
|
+
});
|
|
2030
|
+
installed.add(sql);
|
|
2031
|
+
}
|
|
2032
|
+
__name(installCommandExecutor, "installCommandExecutor");
|
|
2033
|
+
function parameterChunks(sql) {
|
|
2034
|
+
const chunks = [];
|
|
2035
|
+
let start = 0, quote = null;
|
|
2036
|
+
for (let i = 0; i < sql.length; i++) {
|
|
2037
|
+
const c = sql[i];
|
|
2038
|
+
if (quote) {
|
|
2039
|
+
if (c === quote) {
|
|
2040
|
+
if (sql[i + 1] === quote) i++;
|
|
2041
|
+
else quote = null;
|
|
2042
|
+
}
|
|
2043
|
+
continue;
|
|
2044
|
+
}
|
|
2045
|
+
if (c === "'" || c === '"') {
|
|
2046
|
+
quote = c;
|
|
2047
|
+
continue;
|
|
2048
|
+
}
|
|
2049
|
+
if (c !== "$") continue;
|
|
2050
|
+
const parameter = /^\$(\d+)\b/.exec(sql.slice(i));
|
|
2051
|
+
if (!parameter) continue;
|
|
2052
|
+
chunks.push(sql.slice(start, i), {
|
|
2053
|
+
bind: Number(parameter[1]) - 1
|
|
2054
|
+
});
|
|
2055
|
+
i += parameter[0].length - 1;
|
|
2056
|
+
start = i + 1;
|
|
2057
|
+
}
|
|
2058
|
+
chunks.push(sql.slice(start));
|
|
2059
|
+
return chunks;
|
|
2060
|
+
}
|
|
2061
|
+
__name(parameterChunks, "parameterChunks");
|
|
2062
|
+
|
|
2063
|
+
// src/engine/admission.ts
|
|
2064
|
+
var WorkloadAdmission = class {
|
|
2065
|
+
static {
|
|
2066
|
+
__name(this, "WorkloadAdmission");
|
|
2067
|
+
}
|
|
2068
|
+
definition;
|
|
2069
|
+
pending = [];
|
|
2070
|
+
active = 0;
|
|
2071
|
+
activeKeys = /* @__PURE__ */ new Set();
|
|
2072
|
+
tenants = /* @__PURE__ */ new Map();
|
|
2073
|
+
draining = false;
|
|
2074
|
+
constructor(definition) {
|
|
2075
|
+
this.definition = definition;
|
|
2076
|
+
validateWorkload(definition);
|
|
2077
|
+
}
|
|
2078
|
+
get snapshot() {
|
|
2079
|
+
return {
|
|
2080
|
+
active: this.active,
|
|
2081
|
+
queued: this.pending.length,
|
|
2082
|
+
tenants: this.tenants.size,
|
|
2083
|
+
keys: this.activeKeys.size
|
|
2084
|
+
};
|
|
2085
|
+
}
|
|
2086
|
+
acquire(tenant, keys, signal) {
|
|
2087
|
+
if (signal?.aborted) return Promise.reject(signal.reason ?? new Error("Database admission cancelled"));
|
|
2088
|
+
const normalized = [
|
|
2089
|
+
...new Set(keys)
|
|
2090
|
+
].sort();
|
|
2091
|
+
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"));
|
|
2092
|
+
const scoped = normalized;
|
|
2093
|
+
const globalFull = this.pending.length >= this.definition.queueLimit;
|
|
2094
|
+
const tenantFull = this.pending.filter((w) => w.tenant === tenant).length >= this.definition.tenantQueueLimit;
|
|
2095
|
+
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)));
|
|
2096
|
+
if (!canStart && (globalFull || tenantFull)) {
|
|
2097
|
+
return Promise.reject(new TooManyRequests({
|
|
2098
|
+
retryAfter: 1
|
|
2099
|
+
}, `Database workload ${this.definition.name}: ${tenantFull ? "tenant" : "global"} queue is full`));
|
|
2100
|
+
}
|
|
2101
|
+
return new Promise((grant, reject) => {
|
|
2102
|
+
const waiter = {
|
|
2103
|
+
tenant,
|
|
2104
|
+
keys: scoped,
|
|
2105
|
+
entered: performance.now(),
|
|
2106
|
+
grant,
|
|
2107
|
+
reject,
|
|
2108
|
+
cleanup: /* @__PURE__ */ __name(() => {
|
|
2109
|
+
}, "cleanup")
|
|
2110
|
+
};
|
|
2111
|
+
const remove = /* @__PURE__ */ __name((reason2) => {
|
|
2112
|
+
const index = this.pending.indexOf(waiter);
|
|
2113
|
+
if (index === -1) return;
|
|
2114
|
+
this.pending.splice(index, 1);
|
|
2115
|
+
waiter.cleanup();
|
|
2116
|
+
reject(reason2);
|
|
2117
|
+
this.drain();
|
|
2118
|
+
}, "remove");
|
|
2119
|
+
const abort = /* @__PURE__ */ __name(() => remove(signal?.reason ?? new Error("Database admission cancelled")), "abort");
|
|
2120
|
+
const timer = setTimeout(() => remove(new TooManyRequests({
|
|
2121
|
+
retryAfter: 1
|
|
2122
|
+
}, `Database workload ${this.definition.name}: queue deadline exceeded`)), this.definition.queueTimeoutMs);
|
|
2123
|
+
waiter.cleanup = () => {
|
|
2124
|
+
clearTimeout(timer);
|
|
2125
|
+
signal?.removeEventListener("abort", abort);
|
|
2126
|
+
};
|
|
2127
|
+
signal?.addEventListener("abort", abort, {
|
|
2128
|
+
once: true
|
|
2129
|
+
});
|
|
2130
|
+
this.pending.push(waiter);
|
|
2131
|
+
if (signal?.aborted) abort();
|
|
2132
|
+
else this.drain();
|
|
2133
|
+
});
|
|
2134
|
+
}
|
|
2135
|
+
drain() {
|
|
2136
|
+
if (this.draining) return;
|
|
2137
|
+
this.draining = true;
|
|
2138
|
+
try {
|
|
2139
|
+
const blockedKeys = /* @__PURE__ */ new Set();
|
|
2140
|
+
for (let index = 0; index < this.pending.length && this.active < this.definition.concurrency; ) {
|
|
2141
|
+
const waiter = this.pending[index];
|
|
2142
|
+
if ((this.tenants.get(waiter.tenant) ?? 0) >= this.definition.tenantConcurrency || waiter.keys.some((key2) => this.activeKeys.has(key2) || blockedKeys.has(key2))) {
|
|
2143
|
+
for (const key2 of waiter.keys) blockedKeys.add(key2);
|
|
2144
|
+
index++;
|
|
2145
|
+
continue;
|
|
2146
|
+
}
|
|
2147
|
+
this.pending.splice(index, 1);
|
|
2148
|
+
waiter.cleanup();
|
|
2149
|
+
this.active++;
|
|
2150
|
+
this.tenants.set(waiter.tenant, (this.tenants.get(waiter.tenant) ?? 0) + 1);
|
|
2151
|
+
for (const key2 of waiter.keys) this.activeKeys.add(key2);
|
|
2152
|
+
let released = false;
|
|
2153
|
+
waiter.grant({
|
|
2154
|
+
queueMs: performance.now() - waiter.entered,
|
|
2155
|
+
release: /* @__PURE__ */ __name(() => {
|
|
2156
|
+
if (released) return;
|
|
2157
|
+
released = true;
|
|
2158
|
+
this.active--;
|
|
2159
|
+
const left = this.tenants.get(waiter.tenant) - 1;
|
|
2160
|
+
if (left === 0) this.tenants.delete(waiter.tenant);
|
|
2161
|
+
else this.tenants.set(waiter.tenant, left);
|
|
2162
|
+
for (const key2 of waiter.keys) this.activeKeys.delete(key2);
|
|
2163
|
+
this.drain();
|
|
2164
|
+
}, "release")
|
|
2165
|
+
});
|
|
2166
|
+
}
|
|
2167
|
+
} finally {
|
|
2168
|
+
this.draining = false;
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
};
|
|
2172
|
+
var registries = /* @__PURE__ */ new WeakMap();
|
|
2173
|
+
function workloadAdmission(owner, definition) {
|
|
2174
|
+
validateWorkload(definition);
|
|
2175
|
+
let registry2 = registries.get(owner);
|
|
2176
|
+
if (!registry2) {
|
|
2177
|
+
registry2 = /* @__PURE__ */ new Map();
|
|
2178
|
+
registries.set(owner, registry2);
|
|
2179
|
+
}
|
|
2180
|
+
const existing = registry2.get(definition.name);
|
|
2181
|
+
if (existing) {
|
|
2182
|
+
const d = existing.definition;
|
|
2183
|
+
if (Object.keys(d).some((k) => d[k] !== definition[k])) throw new Error(`Workload ${definition.name} was already registered with different limits`);
|
|
2184
|
+
return existing;
|
|
2185
|
+
}
|
|
2186
|
+
if (registry2.size >= 64) throw new Error("At most 64 named database workloads may be registered per engine; declare and reuse them at module scope");
|
|
2187
|
+
const admission = new WorkloadAdmission(Object.freeze({
|
|
2188
|
+
...definition
|
|
2189
|
+
}));
|
|
2190
|
+
registry2.set(definition.name, admission);
|
|
2191
|
+
return admission;
|
|
2192
|
+
}
|
|
2193
|
+
__name(workloadAdmission, "workloadAdmission");
|
|
2194
|
+
function workloadTenant(definition, claimsJson) {
|
|
2195
|
+
const claims = JSON.parse(claimsJson);
|
|
2196
|
+
const value = claims[definition.tenantClaim] ?? claims.sub;
|
|
2197
|
+
return typeof value === "string" && value.length > 0 ? value : "__anonymous__";
|
|
2198
|
+
}
|
|
2199
|
+
__name(workloadTenant, "workloadTenant");
|
|
2200
|
+
|
|
2201
|
+
// src/engine/transaction-control.ts
|
|
2202
|
+
var TransactionControl = class {
|
|
2203
|
+
static {
|
|
2204
|
+
__name(this, "TransactionControl");
|
|
2205
|
+
}
|
|
2206
|
+
controller = new AbortController();
|
|
2207
|
+
active = /* @__PURE__ */ new Set();
|
|
2208
|
+
cancelBackend;
|
|
2209
|
+
cancellation = Promise.resolve();
|
|
2210
|
+
timer;
|
|
2211
|
+
unlink;
|
|
2212
|
+
rejection;
|
|
2213
|
+
deadline;
|
|
2214
|
+
get signal() {
|
|
2215
|
+
return this.controller.signal;
|
|
2216
|
+
}
|
|
2217
|
+
constructor(timeoutMs, external) {
|
|
2218
|
+
this.deadline = timeoutMs === void 0 ? void 0 : performance.now() + timeoutMs;
|
|
2219
|
+
this.rejection = new Promise((_, reject) => {
|
|
2220
|
+
this.signal.addEventListener("abort", () => {
|
|
2221
|
+
reject(this.signal.reason);
|
|
2222
|
+
if (this.active.size && this.cancelBackend) {
|
|
2223
|
+
this.cancellation = this.cancelBackend().catch(() => void 0);
|
|
2224
|
+
} else {
|
|
2225
|
+
for (const query of this.active) {
|
|
2226
|
+
try {
|
|
2227
|
+
query.cancel?.();
|
|
2228
|
+
} catch {
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
}, {
|
|
2233
|
+
once: true
|
|
2234
|
+
});
|
|
2235
|
+
});
|
|
2236
|
+
void this.rejection.catch(() => void 0);
|
|
2237
|
+
const abort = /* @__PURE__ */ __name(() => this.controller.abort(external?.reason ?? new DOMException("Database transaction cancelled", "AbortError")), "abort");
|
|
2238
|
+
external?.addEventListener("abort", abort, {
|
|
2239
|
+
once: true
|
|
2240
|
+
});
|
|
2241
|
+
this.unlink = () => external?.removeEventListener("abort", abort);
|
|
2242
|
+
if (external?.aborted) abort();
|
|
2243
|
+
if (timeoutMs !== void 0) this.timer = setTimeout(() => this.controller.abort(new HttpError(503, "database_deadline_exceeded", "Database transaction exceeded its deadline")), timeoutMs);
|
|
2244
|
+
}
|
|
2245
|
+
check() {
|
|
2246
|
+
if (this.deadline !== void 0 && performance.now() >= this.deadline && !this.signal.aborted) {
|
|
2247
|
+
this.controller.abort(new HttpError(503, "database_deadline_exceeded", "Database transaction exceeded its deadline"));
|
|
2248
|
+
}
|
|
2249
|
+
this.signal.throwIfAborted();
|
|
2250
|
+
}
|
|
2251
|
+
remainingMs() {
|
|
2252
|
+
this.check();
|
|
2253
|
+
return this.deadline === void 0 ? void 0 : Math.max(1, Math.ceil(this.deadline - performance.now()));
|
|
2254
|
+
}
|
|
2255
|
+
race(work) {
|
|
2256
|
+
return Promise.race([
|
|
2257
|
+
work,
|
|
2258
|
+
this.rejection
|
|
2259
|
+
]);
|
|
2260
|
+
}
|
|
2261
|
+
bindBackend(cancel) {
|
|
2262
|
+
this.cancelBackend = cancel;
|
|
2263
|
+
}
|
|
2264
|
+
/** Keep the application's connection parked until the cancellation request
|
|
2265
|
+
* and all statements settle; a late cancel must not hit its next borrower. */
|
|
2266
|
+
async drainCancellation() {
|
|
2267
|
+
await this.cancellation;
|
|
2268
|
+
await Promise.allSettled([
|
|
2269
|
+
...this.active
|
|
2270
|
+
]);
|
|
2271
|
+
}
|
|
2272
|
+
async watch(query) {
|
|
2273
|
+
this.active.add(query);
|
|
2274
|
+
try {
|
|
2275
|
+
if (this.signal.aborted) query.cancel?.();
|
|
2276
|
+
return await query;
|
|
2277
|
+
} finally {
|
|
2278
|
+
this.active.delete(query);
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
dispose() {
|
|
2282
|
+
if (this.timer !== void 0) clearTimeout(this.timer);
|
|
2283
|
+
this.unlink();
|
|
2284
|
+
}
|
|
2285
|
+
};
|
|
2286
|
+
|
|
2287
|
+
// src/engine/diagnostics.ts
|
|
2288
|
+
var import_node_crypto2 = require("crypto");
|
|
2289
|
+
var DatabaseTrace = class {
|
|
2290
|
+
static {
|
|
2291
|
+
__name(this, "DatabaseTrace");
|
|
2292
|
+
}
|
|
2293
|
+
options;
|
|
2294
|
+
start = performance.now();
|
|
2295
|
+
ended;
|
|
2296
|
+
failure;
|
|
2297
|
+
state;
|
|
2298
|
+
constructor(options) {
|
|
2299
|
+
this.options = options;
|
|
2300
|
+
for (const key2 of [
|
|
2301
|
+
"maxQueries",
|
|
2302
|
+
"maxRows",
|
|
2303
|
+
"maxBytes"
|
|
2304
|
+
]) {
|
|
2305
|
+
const value = options[key2];
|
|
2306
|
+
if (value !== void 0 && (!Number.isSafeInteger(value) || value < 0)) throw new Error(`database ${key2} must be a nonnegative safe integer`);
|
|
2307
|
+
}
|
|
2308
|
+
this.state = {
|
|
2309
|
+
endpoint: options.endpoint,
|
|
2310
|
+
requestId: options.requestId,
|
|
2311
|
+
outcome: "active",
|
|
2312
|
+
elapsedMs: 0,
|
|
2313
|
+
queries: 0,
|
|
2314
|
+
rows: 0,
|
|
2315
|
+
bytes: 0,
|
|
2316
|
+
queueMs: 0,
|
|
2317
|
+
poolMs: 0,
|
|
2318
|
+
setupMs: 0,
|
|
2319
|
+
sqlMs: 0,
|
|
2320
|
+
commitMs: 0,
|
|
2321
|
+
rollbackMs: 0,
|
|
2322
|
+
commits: 0,
|
|
2323
|
+
commitFailures: 0,
|
|
2324
|
+
rollbackRequests: 0,
|
|
2325
|
+
retries: 0,
|
|
2326
|
+
statements: []
|
|
2327
|
+
};
|
|
2328
|
+
}
|
|
2329
|
+
check() {
|
|
2330
|
+
if (this.failure) throw this.failure;
|
|
2331
|
+
}
|
|
2332
|
+
time(key2, started) {
|
|
2333
|
+
this.state[key2] += performance.now() - started;
|
|
2334
|
+
}
|
|
2335
|
+
retry() {
|
|
2336
|
+
this.state.retries++;
|
|
2337
|
+
}
|
|
2338
|
+
committed() {
|
|
2339
|
+
this.state.commits++;
|
|
2340
|
+
}
|
|
2341
|
+
commitFailed() {
|
|
2342
|
+
this.state.commitFailures++;
|
|
2343
|
+
}
|
|
2344
|
+
failed() {
|
|
2345
|
+
this.state.outcome = "failed";
|
|
2346
|
+
}
|
|
2347
|
+
rolledBack() {
|
|
2348
|
+
this.state.rollbackRequests++;
|
|
2349
|
+
}
|
|
2350
|
+
refuse(kind) {
|
|
2351
|
+
this.failure ??= new HttpError(500, "database_budget_exceeded", `Database ${kind} budget exceeded${this.options.endpoint ? ` in ${this.options.endpoint}` : ""}`);
|
|
2352
|
+
throw this.failure;
|
|
2353
|
+
}
|
|
2354
|
+
emit(callback, event) {
|
|
2355
|
+
try {
|
|
2356
|
+
const result = callback?.(event);
|
|
2357
|
+
if (result) void Promise.resolve(result).catch(() => void 0);
|
|
2358
|
+
} catch {
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
async query(sql, name, run) {
|
|
2362
|
+
this.check();
|
|
2363
|
+
if (this.options.maxQueries !== void 0 && this.state.queries >= this.options.maxQueries) this.refuse("query count");
|
|
2364
|
+
const ordinal = ++this.state.queries;
|
|
2365
|
+
const event = {
|
|
2366
|
+
ordinal,
|
|
2367
|
+
fingerprint: (0, import_node_crypto2.createHash)("sha256").update(sql).digest("hex").slice(0, 24),
|
|
2368
|
+
...name === void 0 ? {} : {
|
|
2369
|
+
name: name.slice(0, 128)
|
|
2370
|
+
},
|
|
2371
|
+
...this.options.includeSql ? {
|
|
2372
|
+
sql: sql.slice(0, 4096)
|
|
2373
|
+
} : {},
|
|
2374
|
+
durationMs: 0,
|
|
2375
|
+
rows: 0,
|
|
2376
|
+
bytes: 0
|
|
2377
|
+
};
|
|
2378
|
+
if (this.options.captureSource) event.source = new Error().stack?.split("\n").slice(1, 9).join("\n").slice(0, 2500);
|
|
2379
|
+
const started = performance.now();
|
|
2380
|
+
let measured = false;
|
|
2381
|
+
try {
|
|
2382
|
+
const result = await run();
|
|
2383
|
+
event.durationMs = performance.now() - started;
|
|
2384
|
+
measured = true;
|
|
2385
|
+
if (Array.isArray(result)) {
|
|
2386
|
+
event.rows = result.length;
|
|
2387
|
+
event.bytes = Buffer.byteLength(JSON.stringify(result, (_, value) => typeof value === "bigint" ? value.toString() : value));
|
|
2388
|
+
}
|
|
2389
|
+
this.state.rows += event.rows;
|
|
2390
|
+
this.state.bytes += event.bytes;
|
|
2391
|
+
if (this.options.maxRows !== void 0 && this.state.rows > this.options.maxRows) this.refuse("returned rows");
|
|
2392
|
+
if (this.options.maxBytes !== void 0 && this.state.bytes > this.options.maxBytes) this.refuse("result bytes");
|
|
2393
|
+
return result;
|
|
2394
|
+
} catch (error) {
|
|
2395
|
+
const e = error;
|
|
2396
|
+
event.errorCode = String(e?.sqlstate ?? e?.errno ?? e?.code ?? "query_failed").slice(0, 80);
|
|
2397
|
+
throw error;
|
|
2398
|
+
} finally {
|
|
2399
|
+
if (!measured) event.durationMs = performance.now() - started;
|
|
2400
|
+
this.state.sqlMs += event.durationMs;
|
|
2401
|
+
const frozen = Object.freeze(event);
|
|
2402
|
+
if (this.state.statements.length < 200) this.state.statements.push(frozen);
|
|
2403
|
+
this.emit(this.options.onQuery, frozen);
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
get snapshot() {
|
|
2407
|
+
return Object.freeze({
|
|
2408
|
+
...this.state,
|
|
2409
|
+
elapsedMs: (this.ended ?? performance.now()) - this.start,
|
|
2410
|
+
statements: Object.freeze([
|
|
2411
|
+
...this.state.statements
|
|
2412
|
+
].sort((a, b) => a.ordinal - b.ordinal))
|
|
2413
|
+
});
|
|
2414
|
+
}
|
|
2415
|
+
complete(outcome) {
|
|
2416
|
+
if (this.ended !== void 0) return;
|
|
2417
|
+
this.ended = performance.now();
|
|
2418
|
+
this.state.outcome = outcome;
|
|
2419
|
+
this.emit(this.options.onComplete, this.snapshot);
|
|
2420
|
+
}
|
|
2421
|
+
};
|
|
1507
2422
|
|
|
1508
2423
|
// src/db/columns.ts
|
|
1509
2424
|
var CODECS = {
|
|
@@ -1562,9 +2477,9 @@ function buildRelations(schemas) {
|
|
|
1562
2477
|
const taken = /* @__PURE__ */ new Map();
|
|
1563
2478
|
for (const schema of schemas) {
|
|
1564
2479
|
for (const t of Object.values(schema.tables)) {
|
|
1565
|
-
const
|
|
1566
|
-
out.set(
|
|
1567
|
-
taken.set(
|
|
2480
|
+
const key2 = qualifiedTableKey(schema.name, t.name);
|
|
2481
|
+
out.set(key2, []);
|
|
2482
|
+
taken.set(key2, /* @__PURE__ */ new Map());
|
|
1568
2483
|
}
|
|
1569
2484
|
}
|
|
1570
2485
|
const claim = /* @__PURE__ */ __name((tableKey, name, origin, edge) => {
|
|
@@ -1628,10 +2543,10 @@ function quoteIdent(name) {
|
|
|
1628
2543
|
return `"${name.replace(/"/g, '""')}"`;
|
|
1629
2544
|
}
|
|
1630
2545
|
__name(quoteIdent, "quoteIdent");
|
|
1631
|
-
function quoteTable(
|
|
1632
|
-
const dot =
|
|
1633
|
-
if (dot === -1) return quoteIdent(
|
|
1634
|
-
return `${quoteIdent(
|
|
2546
|
+
function quoteTable(key2) {
|
|
2547
|
+
const dot = key2.indexOf(".");
|
|
2548
|
+
if (dot === -1) return quoteIdent(key2);
|
|
2549
|
+
return `${quoteIdent(key2.slice(0, dot))}.${quoteIdent(key2.slice(dot + 1))}`;
|
|
1635
2550
|
}
|
|
1636
2551
|
__name(quoteTable, "quoteTable");
|
|
1637
2552
|
function encodePgArray(value) {
|
|
@@ -1644,9 +2559,10 @@ function encodePgArray(value) {
|
|
|
1644
2559
|
}
|
|
1645
2560
|
__name(encodePgArray, "encodePgArray");
|
|
1646
2561
|
var BIND_SQL = "select set_config('role',$1,true), set_config('search_path','public',true), set_config('request.jwt.claims',$2,true)";
|
|
2562
|
+
var atomicScope = new import_node_async_hooks2.AsyncLocalStorage();
|
|
1647
2563
|
function createLazyTransaction(sql, role, claimsJson, options = {}) {
|
|
1648
2564
|
const { lockTimeout } = options;
|
|
1649
|
-
|
|
2565
|
+
let bindSql = lockTimeout ? `${BIND_SQL}, set_config('lock_timeout',$3,true)` : BIND_SQL;
|
|
1650
2566
|
const bindParams = lockTimeout ? [
|
|
1651
2567
|
role,
|
|
1652
2568
|
claimsJson,
|
|
@@ -1655,25 +2571,118 @@ function createLazyTransaction(sql, role, claimsJson, options = {}) {
|
|
|
1655
2571
|
role,
|
|
1656
2572
|
claimsJson
|
|
1657
2573
|
];
|
|
2574
|
+
if (options.statementTimeout !== void 0) {
|
|
2575
|
+
bindParams.push(options.statementTimeout);
|
|
2576
|
+
bindSql += `, set_config('statement_timeout',$${bindParams.length},true)`;
|
|
2577
|
+
}
|
|
1658
2578
|
let opening = null;
|
|
1659
2579
|
let release = null;
|
|
1660
2580
|
let fail = null;
|
|
1661
2581
|
let settled = null;
|
|
1662
|
-
|
|
2582
|
+
let closed = false;
|
|
2583
|
+
const assertAccess = /* @__PURE__ */ __name(() => {
|
|
2584
|
+
if (closed) throw new Error("Database transaction has settled; its handle cannot be used again");
|
|
2585
|
+
options.control?.check();
|
|
2586
|
+
options.trace?.check();
|
|
2587
|
+
if (atomicScope.getStore() !== options.scope) {
|
|
2588
|
+
throw new Error("Database.$atomic cannot use a handle from another transaction or identity; use its tx argument");
|
|
2589
|
+
}
|
|
2590
|
+
}, "assertAccess");
|
|
2591
|
+
let savepointALS;
|
|
2592
|
+
let activeSavepoint;
|
|
2593
|
+
const pendingSavepoints = /* @__PURE__ */ new Set();
|
|
2594
|
+
const scoped = /* @__PURE__ */ __name((tx, checkParent = assertAccess, owner, children = /* @__PURE__ */ new Set()) => {
|
|
2595
|
+
const access = /* @__PURE__ */ __name(() => {
|
|
2596
|
+
checkParent();
|
|
2597
|
+
if (activeSavepoint !== owner || savepointALS?.getStore() !== owner) {
|
|
2598
|
+
throw new Error("Database connection belongs to another active savepoint; use its tx argument and await it before using the parent handle");
|
|
2599
|
+
}
|
|
2600
|
+
}, "access");
|
|
2601
|
+
return {
|
|
2602
|
+
async unsafe(text, params, metadata) {
|
|
2603
|
+
access();
|
|
2604
|
+
const run = /* @__PURE__ */ __name(() => {
|
|
2605
|
+
const query = tx.unsafe(text, params);
|
|
2606
|
+
return options.control ? options.control.watch(query) : query;
|
|
2607
|
+
}, "run");
|
|
2608
|
+
return options.trace ? options.trace.query(text, metadata?.name, run) : run();
|
|
2609
|
+
},
|
|
2610
|
+
async savepoint(fn) {
|
|
2611
|
+
access();
|
|
2612
|
+
const token = /* @__PURE__ */ Symbol("savepoint"), descendants = /* @__PURE__ */ new Set();
|
|
2613
|
+
savepointALS ??= new import_node_async_hooks2.AsyncLocalStorage();
|
|
2614
|
+
activeSavepoint = token;
|
|
2615
|
+
const work = (async () => {
|
|
2616
|
+
try {
|
|
2617
|
+
return await tx.savepoint(async (sp) => {
|
|
2618
|
+
let ended = false;
|
|
2619
|
+
const check = /* @__PURE__ */ __name(() => {
|
|
2620
|
+
checkParent();
|
|
2621
|
+
if (ended) throw new Error("Database savepoint has settled; its handle cannot be used again");
|
|
2622
|
+
}, "check");
|
|
2623
|
+
try {
|
|
2624
|
+
const callback = savepointALS.run(token, () => fn(scoped(sp, check, token, descendants)));
|
|
2625
|
+
return await (options.control ? options.control.race(callback) : callback);
|
|
2626
|
+
} finally {
|
|
2627
|
+
if (descendants.size) await Promise.allSettled([
|
|
2628
|
+
...descendants
|
|
2629
|
+
]);
|
|
2630
|
+
ended = true;
|
|
2631
|
+
}
|
|
2632
|
+
});
|
|
2633
|
+
} finally {
|
|
2634
|
+
activeSavepoint = owner;
|
|
2635
|
+
}
|
|
2636
|
+
})();
|
|
2637
|
+
pendingSavepoints.add(work);
|
|
2638
|
+
children.add(work);
|
|
2639
|
+
try {
|
|
2640
|
+
return await work;
|
|
2641
|
+
} finally {
|
|
2642
|
+
pendingSavepoints.delete(work);
|
|
2643
|
+
children.delete(work);
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
};
|
|
2647
|
+
}, "scoped");
|
|
2648
|
+
const ensure = /* @__PURE__ */ __name(async () => {
|
|
2649
|
+
assertAccess();
|
|
1663
2650
|
if (opening) return opening;
|
|
1664
2651
|
opening = new Promise((resolveTx2, rejectTx) => {
|
|
1665
2652
|
const parked = new Promise((res, rej) => {
|
|
1666
2653
|
release = res;
|
|
1667
2654
|
fail = rej;
|
|
1668
2655
|
});
|
|
2656
|
+
void parked.catch(() => void 0);
|
|
2657
|
+
const openingAt = options.trace ? performance.now() : 0;
|
|
1669
2658
|
settled = sql.begin(async (tx) => {
|
|
1670
|
-
|
|
1671
|
-
|
|
2659
|
+
options.trace?.time("poolMs", openingAt);
|
|
2660
|
+
const setupAt = options.trace ? performance.now() : 0;
|
|
2661
|
+
options.control?.check();
|
|
2662
|
+
if (options.isolation !== void 0 || options.readOnly !== void 0) {
|
|
2663
|
+
const isolation = options.isolation ?? "read committed";
|
|
2664
|
+
validateAtomicOptions({
|
|
2665
|
+
isolation,
|
|
2666
|
+
readOnly: options.readOnly
|
|
2667
|
+
});
|
|
2668
|
+
await tx.unsafe(`SET TRANSACTION ISOLATION LEVEL ${isolation.toUpperCase()}${options.readOnly === void 0 ? "" : options.readOnly ? " READ ONLY" : " READ WRITE"}`);
|
|
2669
|
+
}
|
|
2670
|
+
if (options.control && sql.cancelBackend) {
|
|
2671
|
+
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()");
|
|
2672
|
+
if (!Number.isInteger(ticket[0]?.pid) || typeof ticket[0]?.started !== "string") throw new Error("Database cancellation cannot identify its own backend");
|
|
2673
|
+
options.control.bindBackend(() => sql.cancelBackend(ticket[0].pid, ticket[0].started));
|
|
2674
|
+
options.control.check();
|
|
2675
|
+
}
|
|
2676
|
+
const binding = tx.unsafe(bindSql, bindParams);
|
|
2677
|
+
await (options.control ? options.control.watch(binding) : binding);
|
|
2678
|
+
options.trace?.time("setupMs", setupAt);
|
|
2679
|
+
resolveTx2(scoped(tx));
|
|
1672
2680
|
await parked;
|
|
1673
2681
|
}).catch((e) => {
|
|
1674
2682
|
rejectTx(e);
|
|
1675
2683
|
throw e;
|
|
1676
2684
|
});
|
|
2685
|
+
void settled.catch(() => void 0);
|
|
1677
2686
|
});
|
|
1678
2687
|
return opening;
|
|
1679
2688
|
}, "ensure");
|
|
@@ -1683,14 +2692,37 @@ function createLazyTransaction(sql, role, claimsJson, options = {}) {
|
|
|
1683
2692
|
return opening !== null;
|
|
1684
2693
|
},
|
|
1685
2694
|
async commit() {
|
|
2695
|
+
if (pendingSavepoints.size) await Promise.allSettled([
|
|
2696
|
+
...pendingSavepoints
|
|
2697
|
+
]);
|
|
2698
|
+
options.control?.check();
|
|
2699
|
+
options.trace?.check();
|
|
2700
|
+
closed = true;
|
|
1686
2701
|
if (!opening) return;
|
|
2702
|
+
const started = options.trace ? performance.now() : 0;
|
|
1687
2703
|
release();
|
|
1688
|
-
|
|
2704
|
+
try {
|
|
2705
|
+
await settled;
|
|
2706
|
+
options.trace?.committed();
|
|
2707
|
+
} catch (error) {
|
|
2708
|
+
options.trace?.commitFailed();
|
|
2709
|
+
throw error;
|
|
2710
|
+
} finally {
|
|
2711
|
+
options.trace?.time("commitMs", started);
|
|
2712
|
+
}
|
|
1689
2713
|
},
|
|
1690
2714
|
async rollback(reason2) {
|
|
2715
|
+
closed = true;
|
|
1691
2716
|
if (!opening) return;
|
|
2717
|
+
await options.control?.drainCancellation();
|
|
2718
|
+
if (pendingSavepoints.size) await Promise.allSettled([
|
|
2719
|
+
...pendingSavepoints
|
|
2720
|
+
]);
|
|
2721
|
+
const started = options.trace ? performance.now() : 0;
|
|
1692
2722
|
fail(reason2);
|
|
1693
2723
|
await settled?.catch(() => void 0);
|
|
2724
|
+
options.trace?.time("rollbackMs", started);
|
|
2725
|
+
options.trace?.rolledBack();
|
|
1694
2726
|
}
|
|
1695
2727
|
};
|
|
1696
2728
|
}
|
|
@@ -1705,7 +2737,7 @@ __name(asWireValue, "asWireValue");
|
|
|
1705
2737
|
function asWireRow(row) {
|
|
1706
2738
|
if (row === null || typeof row !== "object") return row;
|
|
1707
2739
|
const out = {};
|
|
1708
|
-
for (const [
|
|
2740
|
+
for (const [key2, value] of Object.entries(row)) out[key2] = asWireValue(value);
|
|
1709
2741
|
return out;
|
|
1710
2742
|
}
|
|
1711
2743
|
__name(asWireRow, "asWireRow");
|
|
@@ -1717,7 +2749,7 @@ function toVectorLiteral(v) {
|
|
|
1717
2749
|
return `[${v.join(",")}]`;
|
|
1718
2750
|
}
|
|
1719
2751
|
__name(toVectorLiteral, "toVectorLiteral");
|
|
1720
|
-
function
|
|
2752
|
+
function computeVectorColumns(schema, table) {
|
|
1721
2753
|
const out = /* @__PURE__ */ new Set();
|
|
1722
2754
|
const def = tableDefOf(table, schema);
|
|
1723
2755
|
if (def) {
|
|
@@ -1728,9 +2760,9 @@ function vectorColumnsOf(schema, table) {
|
|
|
1728
2760
|
}
|
|
1729
2761
|
return out;
|
|
1730
2762
|
}
|
|
1731
|
-
__name(
|
|
1732
|
-
function tableDefOf(
|
|
1733
|
-
return schema.tables?.[
|
|
2763
|
+
__name(computeVectorColumns, "computeVectorColumns");
|
|
2764
|
+
function tableDefOf(key2, schema = currentSchema) {
|
|
2765
|
+
return schema.tables?.[key2] ?? null;
|
|
1734
2766
|
}
|
|
1735
2767
|
__name(tableDefOf, "tableDefOf");
|
|
1736
2768
|
function reviveVectors(row, vectorCols) {
|
|
@@ -1743,7 +2775,7 @@ function reviveVectors(row, vectorCols) {
|
|
|
1743
2775
|
return row;
|
|
1744
2776
|
}
|
|
1745
2777
|
__name(reviveVectors, "reviveVectors");
|
|
1746
|
-
function
|
|
2778
|
+
function computeTransforms(schema, table) {
|
|
1747
2779
|
const out = /* @__PURE__ */ new Map();
|
|
1748
2780
|
const def = tableDefOf(table, schema);
|
|
1749
2781
|
if (def) {
|
|
@@ -1766,7 +2798,7 @@ function transformsOf(schema, table) {
|
|
|
1766
2798
|
}
|
|
1767
2799
|
return out;
|
|
1768
2800
|
}
|
|
1769
|
-
__name(
|
|
2801
|
+
__name(computeTransforms, "computeTransforms");
|
|
1770
2802
|
function applyFromDb(row, transforms) {
|
|
1771
2803
|
if (transforms.size === 0) return row;
|
|
1772
2804
|
for (const [col, t] of transforms) {
|
|
@@ -1780,13 +2812,27 @@ function applyFromDb(row, transforms) {
|
|
|
1780
2812
|
__name(applyFromDb, "applyFromDb");
|
|
1781
2813
|
function asTableRow(table, row) {
|
|
1782
2814
|
const revived = reviveVectors(asWireRow(row), vectorColumnsOf(currentSchema, table));
|
|
1783
|
-
return applyFromDb(revived, transformsOf(currentSchema, table));
|
|
2815
|
+
return foldCan(table, applyFromDb(revived, transformsOf(currentSchema, table)), false);
|
|
1784
2816
|
}
|
|
1785
2817
|
__name(asTableRow, "asTableRow");
|
|
1786
|
-
function asTableRows(table, rows) {
|
|
2818
|
+
function asTableRows(table, rows, withSpec, partial = false) {
|
|
1787
2819
|
const vectorCols = vectorColumnsOf(currentSchema, table);
|
|
1788
2820
|
const transforms = transformsOf(currentSchema, table);
|
|
1789
|
-
|
|
2821
|
+
const result = rows.map((row) => applyFromDb(reviveVectors(asWireRow(row), vectorCols), transforms));
|
|
2822
|
+
if (withSpec) for (const rel of currentRelations.get(table) ?? []) {
|
|
2823
|
+
const spec = withSpec[rel.name];
|
|
2824
|
+
if (!spec) continue;
|
|
2825
|
+
const nested = typeof spec === "object" ? spec.with : void 0;
|
|
2826
|
+
for (const row of result) {
|
|
2827
|
+
const value = row[rel.name];
|
|
2828
|
+
if (Array.isArray(value)) row[rel.name] = asTableRows(rel.to, value, nested);
|
|
2829
|
+
else if (value && typeof value === "object") row[rel.name] = asTableRows(rel.to, [
|
|
2830
|
+
value
|
|
2831
|
+
], nested)[0];
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
for (const row of result) foldCan(table, row, partial);
|
|
2835
|
+
return result;
|
|
1790
2836
|
}
|
|
1791
2837
|
__name(asTableRows, "asTableRows");
|
|
1792
2838
|
function asBindParams(table, cols, data, caller) {
|
|
@@ -2029,7 +3075,7 @@ function excludeSql(pk, ids, add) {
|
|
|
2029
3075
|
return ` AND t.${quoteIdent(pk)}::text <> ALL(ARRAY[${ids.map((x) => add(x)).join(", ")}])`;
|
|
2030
3076
|
}
|
|
2031
3077
|
__name(excludeSql, "excludeSql");
|
|
2032
|
-
function
|
|
3078
|
+
function computePrimaryKey(table) {
|
|
2033
3079
|
const t = tableDefOf(table);
|
|
2034
3080
|
const columns = t?.columns ?? {};
|
|
2035
3081
|
const cols = Object.keys(columns);
|
|
@@ -2049,7 +3095,7 @@ function primaryKeyOf(table) {
|
|
|
2049
3095
|
const pks = cols.filter((c) => pkOf(columns[c]));
|
|
2050
3096
|
return pks.length === 1 ? pks[0] : null;
|
|
2051
3097
|
}
|
|
2052
|
-
__name(
|
|
3098
|
+
__name(computePrimaryKey, "computePrimaryKey");
|
|
2053
3099
|
function isSinglePkEquality(where, pk) {
|
|
2054
3100
|
if (pk === null) return false;
|
|
2055
3101
|
const keys = Object.keys(where);
|
|
@@ -2064,12 +3110,270 @@ function assertNotAppendOnly(caller, table) {
|
|
|
2064
3110
|
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).`);
|
|
2065
3111
|
}
|
|
2066
3112
|
__name(assertNotAppendOnly, "assertNotAppendOnly");
|
|
2067
|
-
function
|
|
3113
|
+
function computeSchemaColumns(table) {
|
|
2068
3114
|
const t = tableDefOf(table);
|
|
2069
3115
|
const cols = t?.columns ? Object.keys(t.columns) : [];
|
|
2070
3116
|
return cols.length > 0 ? new Set(cols) : null;
|
|
2071
3117
|
}
|
|
2072
|
-
__name(
|
|
3118
|
+
__name(computeSchemaColumns, "computeSchemaColumns");
|
|
3119
|
+
function timestampResult(expr) {
|
|
3120
|
+
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`;
|
|
3121
|
+
}
|
|
3122
|
+
__name(timestampResult, "timestampResult");
|
|
3123
|
+
function columnResult(table, column, alias) {
|
|
3124
|
+
const expr = `${alias ? `${quoteIdent(alias)}.` : ""}${quoteIdent(column)}`;
|
|
3125
|
+
return columnTypeOf(table, column) === "timestamp" ? timestampResult(expr) : expr;
|
|
3126
|
+
}
|
|
3127
|
+
__name(columnResult, "columnResult");
|
|
3128
|
+
function projectedColumn(table, column, alias) {
|
|
3129
|
+
const expr = columnResult(table, column, alias);
|
|
3130
|
+
return columnTypeOf(table, column) === "timestamp" ? `${expr} AS ${quoteIdent(column)}` : expr;
|
|
3131
|
+
}
|
|
3132
|
+
__name(projectedColumn, "projectedColumn");
|
|
3133
|
+
var CAN_PREFIX = "can:";
|
|
3134
|
+
var canMemo = /* @__PURE__ */ new WeakMap();
|
|
3135
|
+
function canRoleGate(roles) {
|
|
3136
|
+
if (roles.length === 0) return null;
|
|
3137
|
+
const names = roles.flatMap((r) => [
|
|
3138
|
+
r,
|
|
3139
|
+
`backend_${r}`
|
|
3140
|
+
]).map((r) => `'${r.replace(/'/g, "''")}'`);
|
|
3141
|
+
return `(current_user = ANY (ARRAY[${names.join(", ")}]))`;
|
|
3142
|
+
}
|
|
3143
|
+
__name(canRoleGate, "canRoleGate");
|
|
3144
|
+
function canEntries(def, alias) {
|
|
3145
|
+
const policies = def.policies ?? [];
|
|
3146
|
+
const dropped = [];
|
|
3147
|
+
const restrictive = [];
|
|
3148
|
+
for (const p of policies) {
|
|
3149
|
+
if (p.permissive !== false || p.command !== "update" && p.command !== "all") continue;
|
|
3150
|
+
const r = renderCanUsing(p, alias, def.name);
|
|
3151
|
+
if (r === null) {
|
|
3152
|
+
return {
|
|
3153
|
+
entries: [],
|
|
3154
|
+
dropped: policies.filter((q) => q.command === "update" && q.permissive !== false).map((q) => ({
|
|
3155
|
+
name: q.name,
|
|
3156
|
+
reason: `restrictive policy "${p.name}" cannot be evaluated as a column`
|
|
3157
|
+
}))
|
|
3158
|
+
};
|
|
3159
|
+
}
|
|
3160
|
+
const rGate = canRoleGate(p.roles ?? []);
|
|
3161
|
+
restrictive.push(rGate === null ? r : `((NOT ${rGate}) OR ${r})`);
|
|
3162
|
+
}
|
|
3163
|
+
const entries = [];
|
|
3164
|
+
for (const p of policies) {
|
|
3165
|
+
if (p.command !== "update" || p.permissive === false) continue;
|
|
3166
|
+
if (Buffer.byteLength(CAN_PREFIX + p.name, "utf8") > 63) {
|
|
3167
|
+
dropped.push({
|
|
3168
|
+
name: p.name,
|
|
3169
|
+
reason: "its name is longer than 59 bytes and Postgres would truncate the column alias"
|
|
3170
|
+
});
|
|
3171
|
+
continue;
|
|
3172
|
+
}
|
|
3173
|
+
const using = renderCanUsing(p, alias, def.name);
|
|
3174
|
+
if (using === null) {
|
|
3175
|
+
dropped.push({
|
|
3176
|
+
name: p.name,
|
|
3177
|
+
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)"
|
|
3178
|
+
});
|
|
3179
|
+
continue;
|
|
3180
|
+
}
|
|
3181
|
+
const gate = canRoleGate(p.roles ?? []);
|
|
3182
|
+
const parts = [
|
|
3183
|
+
...gate === null ? [] : [
|
|
3184
|
+
gate
|
|
3185
|
+
],
|
|
3186
|
+
using,
|
|
3187
|
+
...restrictive
|
|
3188
|
+
];
|
|
3189
|
+
entries.push({
|
|
3190
|
+
name: p.name,
|
|
3191
|
+
using: parts.length === 1 ? using : `(${parts.join(" AND ")})`
|
|
3192
|
+
});
|
|
3193
|
+
}
|
|
3194
|
+
return {
|
|
3195
|
+
entries,
|
|
3196
|
+
dropped
|
|
3197
|
+
};
|
|
3198
|
+
}
|
|
3199
|
+
__name(canEntries, "canEntries");
|
|
3200
|
+
var CanRenderError = class CanRenderError2 extends Error {
|
|
3201
|
+
static {
|
|
3202
|
+
__name(this, "CanRenderError");
|
|
3203
|
+
}
|
|
3204
|
+
};
|
|
3205
|
+
var ENGINE_ALIASES = /^(t|locked|w\d+|wj\d+|x|q|g|p|upd)$/;
|
|
3206
|
+
function renderCanUsing(p, qualifier, tableName) {
|
|
3207
|
+
if (p.using === null || p.using === void 0) return null;
|
|
3208
|
+
if (typeof p.using === "string") {
|
|
3209
|
+
const withoutLiterals = p.using.replace(/'(?:[^']|'')*'/g, "''");
|
|
3210
|
+
if (/--|\/\*/.test(withoutLiterals)) return null;
|
|
3211
|
+
if (withoutLiterals.replace(/''/g, "").includes("'")) return null;
|
|
3212
|
+
let depth = 0;
|
|
3213
|
+
for (const ch of withoutLiterals) {
|
|
3214
|
+
if (ch === "(") depth++;
|
|
3215
|
+
else if (ch === ")" && --depth < 0) return null;
|
|
3216
|
+
}
|
|
3217
|
+
if (depth !== 0) return null;
|
|
3218
|
+
const tbl = tableName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3219
|
+
const qualified = new RegExp(`(^|[^A-Za-z0-9_])(?:"?[A-Za-z_][A-Za-z0-9_]*"?\\s*\\.\\s*)?"?${tbl}"?\\s*\\.`);
|
|
3220
|
+
if (qualified.test(withoutLiterals)) return null;
|
|
3221
|
+
const calls = withoutLiterals.match(/auth\s*\.\s*"?has_permission"?\s*\(/gi) ?? [];
|
|
3222
|
+
const wrapped = withoutLiterals.match(/\(\s*select\s*[(\s]*auth\s*\.\s*"?has_permission"?\s*\(/gi) ?? [];
|
|
3223
|
+
if (calls.length !== wrapped.length) return null;
|
|
3224
|
+
return `(${p.using})`;
|
|
3225
|
+
}
|
|
3226
|
+
try {
|
|
3227
|
+
const rendered = canExprSql(p.using, qualifier);
|
|
3228
|
+
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";
|
|
3229
|
+
return selfParenthesized ? rendered : `(${rendered})`;
|
|
3230
|
+
} catch (err) {
|
|
3231
|
+
if (err instanceof CanRenderError) return null;
|
|
3232
|
+
throw err;
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
__name(renderCanUsing, "renderCanUsing");
|
|
3236
|
+
var CAN_BINOPS = {
|
|
3237
|
+
eq: "=",
|
|
3238
|
+
neq: "<>",
|
|
3239
|
+
gt: ">",
|
|
3240
|
+
gte: ">=",
|
|
3241
|
+
lt: "<",
|
|
3242
|
+
lte: "<="
|
|
3243
|
+
};
|
|
3244
|
+
function canLiteral(v) {
|
|
3245
|
+
if (v === null) return "NULL";
|
|
3246
|
+
if (typeof v === "boolean") return v ? "TRUE" : "FALSE";
|
|
3247
|
+
if (typeof v === "number") {
|
|
3248
|
+
if (!Number.isFinite(v)) throw new CanRenderError("can: non-finite literal");
|
|
3249
|
+
return String(v);
|
|
3250
|
+
}
|
|
3251
|
+
return `'${v.replace(/'/g, "''")}'`;
|
|
3252
|
+
}
|
|
3253
|
+
__name(canLiteral, "canLiteral");
|
|
3254
|
+
function canExprSql(e, qualifier) {
|
|
3255
|
+
switch (e.kind) {
|
|
3256
|
+
case "col":
|
|
3257
|
+
return e.alias !== void 0 && e.alias !== "" ? `${quoteIdent(e.alias)}.${quoteIdent(e.name)}` : `${qualifier}.${quoteIdent(e.name)}`;
|
|
3258
|
+
case "authUid":
|
|
3259
|
+
return "(SELECT auth.uid())";
|
|
3260
|
+
case "lit":
|
|
3261
|
+
return canLiteral(e.value);
|
|
3262
|
+
case "binop": {
|
|
3263
|
+
const op = CAN_BINOPS[e.op];
|
|
3264
|
+
if (op === void 0) throw new CanRenderError(`can: unknown operator ${String(e.op)}`);
|
|
3265
|
+
return `(${canExprSql(e.left, qualifier)} ${op} ${canExprSql(e.right, qualifier)})`;
|
|
3266
|
+
}
|
|
3267
|
+
case "and":
|
|
3268
|
+
return e.parts.length === 0 ? "(true)" : `(${e.parts.map((p) => canExprSql(p, qualifier)).join(" AND ")})`;
|
|
3269
|
+
case "or":
|
|
3270
|
+
return e.parts.length === 0 ? "(false)" : `(${e.parts.map((p) => canExprSql(p, qualifier)).join(" OR ")})`;
|
|
3271
|
+
case "not":
|
|
3272
|
+
return `(NOT ${canExprSql(e.expr, qualifier)})`;
|
|
3273
|
+
case "isNull":
|
|
3274
|
+
return `(${canExprSql(e.expr, qualifier)} IS ${e.negated ? "NOT " : ""}NULL)`;
|
|
3275
|
+
case "exists":
|
|
3276
|
+
if (e.alias === "" || ENGINE_ALIASES.test(e.alias)) {
|
|
3277
|
+
throw new CanRenderError(`can: exists alias "${e.alias}" collides with the engine's own aliases \u2014 the correlation would silently collapse`);
|
|
3278
|
+
}
|
|
3279
|
+
return `EXISTS (SELECT 1 FROM ${quoteTable(e.table)} ${quoteIdent(e.alias)} WHERE ${canExprSql(e.predicate, qualifier)})`;
|
|
3280
|
+
default:
|
|
3281
|
+
throw new CanRenderError(`can: unknown policy expression kind ${String(e.kind)}`);
|
|
3282
|
+
}
|
|
3283
|
+
}
|
|
3284
|
+
__name(canExprSql, "canExprSql");
|
|
3285
|
+
function canEntriesOf(table, alias) {
|
|
3286
|
+
const byAlias = memoTable(canMemo, currentSchema, table, () => /* @__PURE__ */ new Map());
|
|
3287
|
+
const hit = byAlias.get(alias);
|
|
3288
|
+
if (hit !== void 0) return hit;
|
|
3289
|
+
const def = tableDefOf(table);
|
|
3290
|
+
const warnOnce = /* @__PURE__ */ __name((msg) => {
|
|
3291
|
+
if (byAlias.has("__warned")) return;
|
|
3292
|
+
byAlias.set("__warned", []);
|
|
3293
|
+
console.warn(msg);
|
|
3294
|
+
}, "warnOnce");
|
|
3295
|
+
if (def === null || def.policies === void 0) {
|
|
3296
|
+
byAlias.set(alias, []);
|
|
3297
|
+
return [];
|
|
3298
|
+
}
|
|
3299
|
+
if (hasCanColumn(table)) {
|
|
3300
|
+
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`);
|
|
3301
|
+
byAlias.set(alias, []);
|
|
3302
|
+
return [];
|
|
3303
|
+
}
|
|
3304
|
+
const { entries, dropped } = canEntries(def, alias === "" ? quoteTable(table) : alias);
|
|
3305
|
+
if (dropped.length > 0) {
|
|
3306
|
+
warnOnce(dropped.map((d) => `can(${table}): policy "${d.name}" is not exposed in can \u2014 ${d.reason}; the database still enforces it`).join("\n"));
|
|
3307
|
+
}
|
|
3308
|
+
byAlias.set(alias, entries);
|
|
3309
|
+
return entries;
|
|
3310
|
+
}
|
|
3311
|
+
__name(canEntriesOf, "canEntriesOf");
|
|
3312
|
+
function canOuter(table) {
|
|
3313
|
+
const cols = canProjection(table, "q");
|
|
3314
|
+
return cols.length === 0 ? "" : `, ${cols.join(", ")}`;
|
|
3315
|
+
}
|
|
3316
|
+
__name(canOuter, "canOuter");
|
|
3317
|
+
function canProjection(table, alias) {
|
|
3318
|
+
return canEntriesOf(table, alias).map((e) => `${e.using} AS ${quoteIdent(CAN_PREFIX + e.name)}`);
|
|
3319
|
+
}
|
|
3320
|
+
__name(canProjection, "canProjection");
|
|
3321
|
+
function canJsonPairs(table, alias) {
|
|
3322
|
+
return canEntriesOf(table, alias).map((e) => `${quoteLiteral(CAN_PREFIX + e.name)}, ${e.using}`);
|
|
3323
|
+
}
|
|
3324
|
+
__name(canJsonPairs, "canJsonPairs");
|
|
3325
|
+
function foldCan(table, row, partial) {
|
|
3326
|
+
let can = null;
|
|
3327
|
+
for (const key2 of Object.keys(row)) {
|
|
3328
|
+
if (!key2.startsWith(CAN_PREFIX)) continue;
|
|
3329
|
+
can ??= [];
|
|
3330
|
+
if (row[key2] === true) can.push(key2.slice(CAN_PREFIX.length));
|
|
3331
|
+
delete row[key2];
|
|
3332
|
+
}
|
|
3333
|
+
if (hasCanColumn(table)) return row;
|
|
3334
|
+
if (can !== null) row.can = can;
|
|
3335
|
+
else if (!partial && tableDefOf(table) !== null) {
|
|
3336
|
+
if (canEntriesOf(table, "").length > 0) {
|
|
3337
|
+
const flags = memoTable(canMemo, currentSchema, table, () => /* @__PURE__ */ new Map());
|
|
3338
|
+
if (!flags.has("__missing_warned")) {
|
|
3339
|
+
flags.set("__missing_warned", []);
|
|
3340
|
+
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: []`);
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3343
|
+
row.can = [];
|
|
3344
|
+
}
|
|
3345
|
+
return row;
|
|
3346
|
+
}
|
|
3347
|
+
__name(foldCan, "foldCan");
|
|
3348
|
+
function hasCanColumn(table) {
|
|
3349
|
+
const cols = tableDefOf(table)?.columns;
|
|
3350
|
+
if (cols !== void 0 && Object.prototype.hasOwnProperty.call(cols, "can")) return true;
|
|
3351
|
+
return (currentRelations.get(table) ?? []).some((rel) => rel.name === "can");
|
|
3352
|
+
}
|
|
3353
|
+
__name(hasCanColumn, "hasCanColumn");
|
|
3354
|
+
function rowProjection(table, alias, wildcard = alias ? `${alias}.*` : "*") {
|
|
3355
|
+
const cols = schemaColumns(table);
|
|
3356
|
+
let base = wildcard;
|
|
3357
|
+
if (cols && memoTable(timestampMemo, currentSchema, table, () => [
|
|
3358
|
+
...cols
|
|
3359
|
+
].some((c) => columnTypeOf(table, c) === "timestamp"))) {
|
|
3360
|
+
const projections = memoTable(projectionMemo, currentSchema, table, () => /* @__PURE__ */ new Map());
|
|
3361
|
+
const key2 = alias ?? "";
|
|
3362
|
+
let sql = projections.get(key2);
|
|
3363
|
+
if (sql === void 0) {
|
|
3364
|
+
sql = [
|
|
3365
|
+
...cols
|
|
3366
|
+
].map((c) => projectedColumn(table, c, alias)).join(", ");
|
|
3367
|
+
projections.set(key2, sql);
|
|
3368
|
+
}
|
|
3369
|
+
base = sql;
|
|
3370
|
+
}
|
|
3371
|
+
const can = canProjection(table, alias ?? "");
|
|
3372
|
+
if (can.length === 0) return base;
|
|
3373
|
+
const rows = base === "*" && alias !== void 0 ? `${alias}.*` : base;
|
|
3374
|
+
return `${rows}, ${can.join(", ")}`;
|
|
3375
|
+
}
|
|
3376
|
+
__name(rowProjection, "rowProjection");
|
|
2073
3377
|
function colRefSql(table, known, ref, caller, alias = "t") {
|
|
2074
3378
|
const name = ref.$col;
|
|
2075
3379
|
const shapeOK = /^[A-Za-z_][A-Za-z0-9_]*$/.test(name);
|
|
@@ -2083,13 +3387,13 @@ function selectClause(table, known, select) {
|
|
|
2083
3387
|
if (select !== void 0 && select.length === 0) {
|
|
2084
3388
|
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.`);
|
|
2085
3389
|
}
|
|
2086
|
-
if (select === void 0) return "*";
|
|
3390
|
+
if (select === void 0) return rowProjection(table, "t", "*");
|
|
2087
3391
|
return select.map((col) => {
|
|
2088
3392
|
const shapeOK = /^[A-Za-z_][A-Za-z0-9_]*$/.test(col);
|
|
2089
3393
|
if (known !== null ? !known.has(col) : !shapeOK) {
|
|
2090
3394
|
throw new Error(`findMany(${table}): select kolonu "${col}" tabloda yok`);
|
|
2091
3395
|
}
|
|
2092
|
-
return `t.${quoteIdent(col)}`;
|
|
3396
|
+
return columnTypeOf(table, col) === "timestamp" ? projectedColumn(table, col, "t") : `t.${quoteIdent(col)}`;
|
|
2093
3397
|
}).join(", ");
|
|
2094
3398
|
}
|
|
2095
3399
|
__name(selectClause, "selectClause");
|
|
@@ -2159,7 +3463,7 @@ function compileWhereBare(table, colSet, where, add, caller = "search", alias =
|
|
|
2159
3463
|
const transforms = transformsOf(currentSchema, table);
|
|
2160
3464
|
const bindFor = /* @__PURE__ */ __name((col) => {
|
|
2161
3465
|
const t = transforms.get(col);
|
|
2162
|
-
return t?.toDb === void 0
|
|
3466
|
+
return (v) => add(t?.toDb === void 0 || v === null || v === void 0 ? v : t.toDb(v), col, table);
|
|
2163
3467
|
}, "bindFor");
|
|
2164
3468
|
assertUsableFilter(caller, table, where);
|
|
2165
3469
|
if (isSqlFragment(where)) {
|
|
@@ -2190,8 +3494,7 @@ function compileWhereBare(table, colSet, where, add, caller = "search", alias =
|
|
|
2190
3494
|
const known = rels.map((r) => r.name).sort();
|
|
2191
3495
|
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.`));
|
|
2192
3496
|
}
|
|
2193
|
-
const ourCol =
|
|
2194
|
-
const theirCol = rel.kind === "many" ? rel.via : primaryKeyOf(rel.to);
|
|
3497
|
+
const [ourCol, theirCol] = relationColumns(table, rel);
|
|
2195
3498
|
if (ourCol === null || theirCol === null) {
|
|
2196
3499
|
const missing = ourCol === null ? table : rel.to;
|
|
2197
3500
|
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.`);
|
|
@@ -2329,8 +3632,8 @@ async function embedQuery(embed, text) {
|
|
|
2329
3632
|
if (secretReader === null) {
|
|
2330
3633
|
throw new Error(`query embed: secret reader ba\u011Flanmam\u0131\u015F \u2014 ${embed.apiKeyName} okunam\u0131yor`);
|
|
2331
3634
|
}
|
|
2332
|
-
const
|
|
2333
|
-
if (
|
|
3635
|
+
const key2 = await secretReader(embed.apiKeyName);
|
|
3636
|
+
if (key2 === null || key2 === "") {
|
|
2334
3637
|
throw new Error(`query embed: vault'ta ${embed.apiKeyName} yok (FR-021/FR-025)`);
|
|
2335
3638
|
}
|
|
2336
3639
|
const url = (embed.baseURL ?? "https://api.openai.com/v1").replace(/\/$/, "") + "/embeddings";
|
|
@@ -2341,7 +3644,7 @@ async function embedQuery(embed, text) {
|
|
|
2341
3644
|
method: "POST",
|
|
2342
3645
|
headers: {
|
|
2343
3646
|
"Content-Type": "application/json",
|
|
2344
|
-
Authorization: `Bearer ${
|
|
3647
|
+
Authorization: `Bearer ${key2}`
|
|
2345
3648
|
},
|
|
2346
3649
|
body: JSON.stringify({
|
|
2347
3650
|
model: embed.model,
|
|
@@ -2399,14 +3702,14 @@ function buildValueTuple(table, cols, data, add, caller) {
|
|
|
2399
3702
|
}).join(", ");
|
|
2400
3703
|
}
|
|
2401
3704
|
__name(buildValueTuple, "buildValueTuple");
|
|
2402
|
-
function
|
|
3705
|
+
function computeColumnType(table, col) {
|
|
2403
3706
|
const def = tableDefOf(table);
|
|
2404
3707
|
const c = def?.columns?.[col];
|
|
2405
3708
|
if (c === void 0 || c === null) return null;
|
|
2406
3709
|
const d = typeof c === "object" && "_def" in c ? c._def : c;
|
|
2407
3710
|
return d !== null && typeof d === "object" && typeof d.type === "string" ? d.type : null;
|
|
2408
3711
|
}
|
|
2409
|
-
__name(
|
|
3712
|
+
__name(computeColumnType, "computeColumnType");
|
|
2410
3713
|
var NUMERIC_PG_TYPES = /* @__PURE__ */ new Set([
|
|
2411
3714
|
"integer",
|
|
2412
3715
|
"bigint",
|
|
@@ -2416,23 +3719,44 @@ function aggregateExpr(table, fn, col, alias) {
|
|
|
2416
3719
|
const q = `t.${quoteIdent(col)}`;
|
|
2417
3720
|
const t = columnTypeOf(table, col);
|
|
2418
3721
|
if (fn === "sum" || fn === "avg") return `${fn}(${q})::text AS ${quoteIdent(alias)}`;
|
|
3722
|
+
if (t === "timestamp") return `${timestampResult(`${fn}(${q})`)} AS ${quoteIdent(alias)}`;
|
|
2419
3723
|
return t !== null && NUMERIC_PG_TYPES.has(t) && t !== "integer" ? `${fn}(${q})::text AS ${quoteIdent(alias)}` : `${fn}(${q}) AS ${quoteIdent(alias)}`;
|
|
2420
3724
|
}
|
|
2421
3725
|
__name(aggregateExpr, "aggregateExpr");
|
|
2422
|
-
function jsonObjectFor(table, cols, alias) {
|
|
3726
|
+
function jsonObjectFor(table, cols, alias, extra = []) {
|
|
2423
3727
|
const pairs = cols.map((c) => {
|
|
2424
3728
|
const t = columnTypeOf(table, c);
|
|
2425
|
-
const expr = t !== null && (t === "numeric" || t === "bigint") ? `${alias}.${quoteIdent(c)}::text` : `${alias}.${quoteIdent(c)}`;
|
|
3729
|
+
const expr = t !== null && (t === "numeric" || t === "bigint") ? `${alias}.${quoteIdent(c)}::text` : t === "timestamp" ? timestampResult(`${alias}.${quoteIdent(c)}`) : `${alias}.${quoteIdent(c)}`;
|
|
2426
3730
|
return `${quoteLiteral(c)}, ${expr}`;
|
|
2427
3731
|
});
|
|
2428
|
-
return `json_build_object(${
|
|
3732
|
+
return `json_build_object(${[
|
|
3733
|
+
...pairs,
|
|
3734
|
+
...extra
|
|
3735
|
+
].join(", ")})`;
|
|
2429
3736
|
}
|
|
2430
3737
|
__name(jsonObjectFor, "jsonObjectFor");
|
|
2431
|
-
function
|
|
3738
|
+
function relationColumns(table, relation) {
|
|
3739
|
+
const child = relation.kind === "many" ? relation.to : table;
|
|
3740
|
+
const value = tableDefOf(child)?.columns?.[relation.via];
|
|
3741
|
+
const column = value && typeof value === "object" && "_def" in value ? value._def : value;
|
|
3742
|
+
const target = column?.references?.column ?? primaryKeyOf(relation.kind === "many" ? table : relation.to);
|
|
3743
|
+
return relation.kind === "many" ? [
|
|
3744
|
+
target,
|
|
3745
|
+
relation.via
|
|
3746
|
+
] : [
|
|
3747
|
+
relation.via,
|
|
3748
|
+
target
|
|
3749
|
+
];
|
|
3750
|
+
}
|
|
3751
|
+
__name(relationColumns, "relationColumns");
|
|
3752
|
+
function withClauses(table, spec, add, caller, parentAlias = "t", depth = 0, counter = {
|
|
3753
|
+
value: 0
|
|
3754
|
+
}) {
|
|
3755
|
+
if (depth >= 3 && Object.values(spec).some(Boolean)) throw new Error(`${caller}(${table}): with supports at most three relation levels`);
|
|
2432
3756
|
const rels = currentRelations.get(table) ?? [];
|
|
2433
3757
|
const select = [];
|
|
2434
3758
|
const joins = [];
|
|
2435
|
-
|
|
3759
|
+
const jsonPairs = [];
|
|
2436
3760
|
for (const [name, opt] of Object.entries(spec)) {
|
|
2437
3761
|
if (opt === false || opt === void 0) continue;
|
|
2438
3762
|
const rel = rels.find((r) => r.name === name);
|
|
@@ -2440,13 +3764,13 @@ function withClauses(table, spec, add, caller) {
|
|
|
2440
3764
|
const known = rels.map((r) => r.name).sort();
|
|
2441
3765
|
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.`);
|
|
2442
3766
|
}
|
|
2443
|
-
const ourCol =
|
|
2444
|
-
const theirCol = rel.kind === "many" ? rel.via : primaryKeyOf(rel.to);
|
|
3767
|
+
const [ourCol, theirCol] = relationColumns(table, rel);
|
|
2445
3768
|
if (ourCol === null || theirCol === null) {
|
|
2446
3769
|
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.`);
|
|
2447
3770
|
}
|
|
2448
3771
|
const o = typeof opt === "object" && opt !== null ? opt : {};
|
|
2449
|
-
const
|
|
3772
|
+
const i = ++counter.value;
|
|
3773
|
+
const childAlias = `w${i}`;
|
|
2450
3774
|
const childKnown = schemaColumns(rel.to);
|
|
2451
3775
|
const childCols = Array.isArray(o["select"]) && o["select"].length > 0 ? o["select"] : childKnown !== null ? [
|
|
2452
3776
|
...childKnown
|
|
@@ -2459,28 +3783,103 @@ function withClauses(table, spec, add, caller) {
|
|
|
2459
3783
|
throw new Error(`${caller}(${table}): with.${name}.select kolonu "${c}" "${rel.to}" tablosunda yok`);
|
|
2460
3784
|
}
|
|
2461
3785
|
}
|
|
2462
|
-
const
|
|
3786
|
+
const nestedSpec = o["with"];
|
|
3787
|
+
if (nestedSpec !== void 0 && (nestedSpec === null || typeof nestedSpec !== "object" || Array.isArray(nestedSpec))) {
|
|
3788
|
+
throw new Error(`${caller}(${table}): with.${name}.with must be a relation object`);
|
|
3789
|
+
}
|
|
3790
|
+
const nested = nestedSpec === void 0 ? {
|
|
3791
|
+
joins: [],
|
|
3792
|
+
jsonPairs: []
|
|
3793
|
+
} : withClauses(rel.to, nestedSpec, add, caller, childAlias, depth + 1, counter);
|
|
3794
|
+
const nestedJoins = nested.joins.length ? ` ${nested.joins.join(" ")}` : "";
|
|
3795
|
+
const obj = jsonObjectFor(rel.to, childCols, childAlias, [
|
|
3796
|
+
...nested.jsonPairs,
|
|
3797
|
+
...canJsonPairs(rel.to, childAlias)
|
|
3798
|
+
]);
|
|
2463
3799
|
const innerWhere = o["where"] !== void 0 && o["where"] !== null ? compileWhereBare(rel.to, childKnown, o["where"], add, caller, childAlias, i) : "";
|
|
2464
|
-
const link = `${childAlias}.${quoteIdent(theirCol)} =
|
|
3800
|
+
const link = `${childAlias}.${quoteIdent(theirCol)} = ${parentAlias}.${quoteIdent(ourCol)}`;
|
|
2465
3801
|
const cond = innerWhere === "" ? link : `${link} AND ${innerWhere}`;
|
|
2466
|
-
const
|
|
3802
|
+
const requestedLimit = o["limit"];
|
|
3803
|
+
limitClause(requestedLimit);
|
|
3804
|
+
const relationLimit = rel.kind === "many" ? requestedLimit : Math.min(1, requestedLimit ?? 1);
|
|
3805
|
+
const inner = orderClauseFor(rel.to, childKnown, o["orderBy"], childAlias) + limitClause(relationLimit);
|
|
2467
3806
|
const outAlias = `wj${i}`;
|
|
2468
3807
|
if (rel.kind === "many") {
|
|
2469
|
-
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`);
|
|
3808
|
+
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`);
|
|
2470
3809
|
} else {
|
|
2471
|
-
joins.push(`LEFT JOIN LATERAL (SELECT ${obj} AS d FROM ${quoteTable(rel.to)} ${childAlias} WHERE ${cond}${inner}
|
|
3810
|
+
joins.push(`LEFT JOIN LATERAL (SELECT ${obj} AS d FROM ${quoteTable(rel.to)} ${childAlias}${nestedJoins} WHERE ${cond}${inner}) ${quoteIdent(outAlias)} ON true`);
|
|
2472
3811
|
}
|
|
2473
3812
|
select.push(`${quoteIdent(outAlias)}."d" AS ${quoteIdent(name)}`);
|
|
3813
|
+
jsonPairs.push(`${quoteLiteral(name)}, ${quoteIdent(outAlias)}."d"`);
|
|
2474
3814
|
}
|
|
2475
3815
|
return {
|
|
2476
3816
|
select,
|
|
2477
|
-
joins
|
|
3817
|
+
joins,
|
|
3818
|
+
jsonPairs
|
|
2478
3819
|
};
|
|
2479
3820
|
}
|
|
2480
3821
|
__name(withClauses, "withClauses");
|
|
2481
|
-
function
|
|
3822
|
+
function compileFindMany(table, query = {}, opts = {}) {
|
|
3823
|
+
const params = [];
|
|
3824
|
+
const add = /* @__PURE__ */ __name((v) => {
|
|
3825
|
+
params.push(v);
|
|
3826
|
+
return `$${params.length}`;
|
|
3827
|
+
}, "add");
|
|
3828
|
+
const known = schemaColumns(table);
|
|
3829
|
+
const whereSql = compileWhere(table, known, query, add, "findMany");
|
|
3830
|
+
const order = orderClause(table, known, opts.orderBy);
|
|
3831
|
+
const limit = limitClause(opts.limit);
|
|
3832
|
+
const offset = offsetClause(opts.offset, opts.limit);
|
|
3833
|
+
const cols = selectClause(table, known, opts.select);
|
|
3834
|
+
const w = opts.with !== void 0 && Object.keys(opts.with).length > 0 ? withClauses(table, opts.with, add, "findMany") : {
|
|
3835
|
+
select: [],
|
|
3836
|
+
joins: []
|
|
3837
|
+
};
|
|
3838
|
+
const selectList = w.select.length > 0 ? `${cols === "*" ? "t.*" : cols}, ${w.select.join(", ")}` : cols;
|
|
3839
|
+
const joinSql = w.joins.length > 0 ? ` ${w.joins.join(" ")}` : "";
|
|
3840
|
+
const sql = `SELECT ${selectList} FROM ${quoteTable(table)} t${joinSql} WHERE true${whereSql}${order}${limit}${offset}`;
|
|
3841
|
+
return {
|
|
3842
|
+
sql,
|
|
3843
|
+
params
|
|
3844
|
+
};
|
|
3845
|
+
}
|
|
3846
|
+
__name(compileFindMany, "compileFindMany");
|
|
3847
|
+
var uniqueKeysCache = /* @__PURE__ */ new WeakMap();
|
|
3848
|
+
var pageSchemaCache = /* @__PURE__ */ new WeakMap();
|
|
3849
|
+
function pageSchema(table) {
|
|
3850
|
+
return memoTable(pageSchemaCache, currentSchema, table, () => {
|
|
3851
|
+
const def = tableDefOf(table);
|
|
3852
|
+
if (!def?.columns) throw new Error(`page(${table}) requires an installed table declaration`);
|
|
3853
|
+
const columns = /* @__PURE__ */ Object.create(null);
|
|
3854
|
+
const inlineKeys = [];
|
|
3855
|
+
for (const [name, column] of Object.entries(def.columns)) {
|
|
3856
|
+
const c = column;
|
|
3857
|
+
const d = c._def ?? c;
|
|
3858
|
+
columns[name] = {
|
|
3859
|
+
type: d.type,
|
|
3860
|
+
nullable: d.nullable
|
|
3861
|
+
};
|
|
3862
|
+
if (d.primaryKey) inlineKeys.push(name);
|
|
3863
|
+
}
|
|
3864
|
+
const primaryKey = def.primaryKey ?? inlineKeys;
|
|
3865
|
+
for (const name of primaryKey) if (columns[name]) columns[name].nullable = false;
|
|
3866
|
+
return {
|
|
3867
|
+
columns,
|
|
3868
|
+
primaryKey,
|
|
3869
|
+
stamp: fingerprint({
|
|
3870
|
+
columns,
|
|
3871
|
+
primaryKey
|
|
3872
|
+
})
|
|
3873
|
+
};
|
|
3874
|
+
});
|
|
3875
|
+
}
|
|
3876
|
+
__name(pageSchema, "pageSchema");
|
|
3877
|
+
function createOps(tx, context = {}) {
|
|
2482
3878
|
const at = /* @__PURE__ */ __name(() => resolveTx(tx), "at");
|
|
2483
3879
|
const ops = {
|
|
3880
|
+
diagnostics() {
|
|
3881
|
+
return context.trace?.snapshot ?? null;
|
|
3882
|
+
},
|
|
2484
3883
|
// Generic dönüş, çağıranın İDDİASIDIR (FR-021). Motor her zaman satır objeleri
|
|
2485
3884
|
// üretir; `T` onlara bir ad vermekten ibarettir ve verilmezse `unknown` kalır —
|
|
2486
3885
|
// yani "bilmiyorum", ki ham SQL için doğru cevap odur.
|
|
@@ -2494,7 +3893,7 @@ function createOps(tx) {
|
|
|
2494
3893
|
const params = [];
|
|
2495
3894
|
const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
|
|
2496
3895
|
const tuple = buildValueTuple(table, cols, data, add, "insert");
|
|
2497
|
-
const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING
|
|
3896
|
+
const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING ${rowProjection(table)}`;
|
|
2498
3897
|
const rows = await (await at()).unsafe(sql, params);
|
|
2499
3898
|
const inserted = rows[0];
|
|
2500
3899
|
if (!inserted) {
|
|
@@ -2516,8 +3915,9 @@ function createOps(tx) {
|
|
|
2516
3915
|
* bir satırın fazla kolonu SESSİZCE YAZILMAZDI. Eksik kolon için `null`
|
|
2517
3916
|
* yazın ya da varsayılanı istiyorsanız o satırları ayrı çağrıda ekleyin.
|
|
2518
3917
|
*/
|
|
2519
|
-
async
|
|
2520
|
-
|
|
3918
|
+
insertMany: /* @__PURE__ */ __name(async function(table, rows, opts) {
|
|
3919
|
+
validateInsertManyOptions(opts);
|
|
3920
|
+
if (rows.length === 0) return opts?.returning === false ? 0 : [];
|
|
2521
3921
|
const first = rows[0];
|
|
2522
3922
|
const cols = Object.keys(first);
|
|
2523
3923
|
if (cols.length === 0) throw new Error(`insertMany into ${table}: no columns given`);
|
|
@@ -2532,11 +3932,15 @@ function createOps(tx) {
|
|
|
2532
3932
|
const params = [];
|
|
2533
3933
|
const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
|
|
2534
3934
|
const tuples = rows.map((r) => `(${buildValueTuple(table, cols, r, add, "insertMany")})`);
|
|
2535
|
-
const tail = opts !== void 0 && opts.onConflict.length > 0 ? ` ${onConflictTail(cols, opts.onConflict, opts.action ?? "ignore")}` : "";
|
|
2536
|
-
const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES ${tuples.join(", ")}${tail} RETURNING
|
|
3935
|
+
const tail = opts?.onConflict !== void 0 && opts.onConflict.length > 0 ? ` ${onConflictTail(cols, opts.onConflict, opts.action ?? "ignore")}` : "";
|
|
3936
|
+
const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES ${tuples.join(", ")}${tail}${opts?.returning === false ? "" : ` RETURNING ${rowProjection(table)}`}`;
|
|
2537
3937
|
const out = await (await at()).unsafe(sql, params);
|
|
3938
|
+
if (opts?.returning === false) {
|
|
3939
|
+
if (!Number.isSafeInteger(out.count) || out.count < 0) throw new Error("insertMany returning:false requires a driver with an affected row count");
|
|
3940
|
+
return out.count;
|
|
3941
|
+
}
|
|
2538
3942
|
return out.map((r) => asTableRow(table, r));
|
|
2539
|
-
},
|
|
3943
|
+
}, "insertMany"),
|
|
2540
3944
|
/**
|
|
2541
3945
|
* Bir ADI kilitle — satırı değil (FR-054).
|
|
2542
3946
|
*
|
|
@@ -2550,9 +3954,9 @@ function createOps(tx) {
|
|
|
2550
3954
|
* Transaction kapsamlısı commit/rollback'te Postgres tarafından bırakılır,
|
|
2551
3955
|
* yani unutulması imkânsız.
|
|
2552
3956
|
*/
|
|
2553
|
-
async advisoryXactLock(
|
|
3957
|
+
async advisoryXactLock(key2) {
|
|
2554
3958
|
await (await at()).unsafe("select pg_advisory_xact_lock(hashtextextended($1, 0))", [
|
|
2555
|
-
|
|
3959
|
+
key2
|
|
2556
3960
|
]);
|
|
2557
3961
|
},
|
|
2558
3962
|
/**
|
|
@@ -2626,7 +4030,7 @@ function createOps(tx) {
|
|
|
2626
4030
|
const params = [];
|
|
2627
4031
|
const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
|
|
2628
4032
|
const tuple = buildValueTuple(table, cols, data, add, "claim");
|
|
2629
|
-
const rows = await sp.unsafe(`INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING
|
|
4033
|
+
const rows = await sp.unsafe(`INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING ${rowProjection(table)}`, params);
|
|
2630
4034
|
if (!rows[0]) {
|
|
2631
4035
|
throw new Error(`claim(${table}): INSERT sat\u0131r d\xF6nd\xFCrmedi \u2014 yazma reddedildi (b\xFCy\xFCk olas\u0131l\u0131kla bir RLS policy'si).`);
|
|
2632
4036
|
}
|
|
@@ -2643,7 +4047,7 @@ function createOps(tx) {
|
|
|
2643
4047
|
return `$${params.length}`;
|
|
2644
4048
|
}, "add");
|
|
2645
4049
|
const where = keyCols.map((c) => `t.${quoteIdent(c)} = ${add(unique[c])}`).join(" AND ");
|
|
2646
|
-
const found = await (await at()).unsafe(`SELECT * FROM ${quoteTable(table)} t WHERE ${where} LIMIT 1`, params);
|
|
4050
|
+
const found = await (await at()).unsafe(`SELECT ${rowProjection(table, "t", "*")} FROM ${quoteTable(table)} t WHERE ${where} LIMIT 1`, params);
|
|
2647
4051
|
if (!found[0]) {
|
|
2648
4052
|
const named = constraintOf(e);
|
|
2649
4053
|
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.`);
|
|
@@ -2680,13 +4084,13 @@ function createOps(tx) {
|
|
|
2680
4084
|
const bindData = [];
|
|
2681
4085
|
const addPut = /* @__PURE__ */ __name((v) => `$${bindData.push(v)}`, "addPut");
|
|
2682
4086
|
const ph = buildValueTuple(table, cols, data, addPut, "put");
|
|
2683
|
-
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
|
|
4087
|
+
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)}`;
|
|
2684
4088
|
const live = await at();
|
|
2685
4089
|
const first = await live.unsafe(insertSql, bindData);
|
|
2686
4090
|
if (first[0]) return asTableRow(table, first[0]);
|
|
2687
4091
|
const condSql = conflict.map((c, i) => `${quoteIdent(c)} = $${i + 1}`).join(" AND ");
|
|
2688
4092
|
const condBind = conflict.map((c) => data[c]);
|
|
2689
|
-
const cur = await live.unsafe(`SELECT
|
|
4093
|
+
const cur = await live.unsafe(`SELECT ${rowProjection(table)} FROM ${quoteTable(table)} WHERE ${condSql} AND "valid_to" IS NULL`, condBind);
|
|
2690
4094
|
const old = cur[0];
|
|
2691
4095
|
if (old === void 0) {
|
|
2692
4096
|
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)`);
|
|
@@ -2712,7 +4116,7 @@ function createOps(tx) {
|
|
|
2712
4116
|
const conflictSet = new Set(conflict);
|
|
2713
4117
|
const assignments = cols.filter((c) => !conflictSet.has(c)).map((c) => `${quoteIdent(c)} = EXCLUDED.${quoteIdent(c)}`);
|
|
2714
4118
|
const action = assignments.length ? `DO UPDATE SET ${assignments.join(", ")}` : `DO UPDATE SET ${quoteIdent(conflict[0])} = EXCLUDED.${quoteIdent(conflict[0])}`;
|
|
2715
|
-
const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${placeholders}) ON CONFLICT (${conflict.map(quoteIdent).join(", ")}) ${action} RETURNING
|
|
4119
|
+
const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${placeholders}) ON CONFLICT (${conflict.map(quoteIdent).join(", ")}) ${action} RETURNING ${rowProjection(table)}`;
|
|
2716
4120
|
const rows = await (await at()).unsafe(sql, params);
|
|
2717
4121
|
const row = rows[0];
|
|
2718
4122
|
if (!row) {
|
|
@@ -2730,7 +4134,7 @@ function createOps(tx) {
|
|
|
2730
4134
|
return `$${params.length}`;
|
|
2731
4135
|
}, "add");
|
|
2732
4136
|
const assignments = buildSetAssignments(table, cols, data, add, "update");
|
|
2733
|
-
const sql = `UPDATE ${quoteTable(table)} SET ${assignments} WHERE id = ${add(id)} RETURNING
|
|
4137
|
+
const sql = `UPDATE ${quoteTable(table)} SET ${assignments} WHERE id = ${add(id)} RETURNING ${rowProjection(table)}`;
|
|
2734
4138
|
const rows = await (await at()).unsafe(sql, params);
|
|
2735
4139
|
return rows[0] ? asTableRow(table, rows[0]) : null;
|
|
2736
4140
|
},
|
|
@@ -2782,10 +4186,10 @@ function createOps(tx) {
|
|
|
2782
4186
|
assertHasPredicate(whereSql, "updateMany", table);
|
|
2783
4187
|
const pk = primaryKeyOf(table);
|
|
2784
4188
|
const wantRows = opts?.returning !== false;
|
|
2785
|
-
const tail = wantRows ?
|
|
4189
|
+
const tail = wantRows ? `RETURNING ${rowProjection(table, "t", "*")}` : "RETURNING 1";
|
|
2786
4190
|
const simple = isSinglePkEquality(where, pk) || pk === null;
|
|
2787
|
-
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
|
|
2788
|
-
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) `;
|
|
4191
|
+
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");
|
|
4192
|
+
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) `;
|
|
2789
4193
|
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`;
|
|
2790
4194
|
const out = await (await at()).unsafe(sql, params);
|
|
2791
4195
|
if (!wantRows) return Number(out[0]?.["n"] ?? 0);
|
|
@@ -2869,7 +4273,7 @@ function createOps(tx) {
|
|
|
2869
4273
|
}
|
|
2870
4274
|
const groups = q.groupBy ?? [];
|
|
2871
4275
|
for (const col of groups) check(col, "groupBy");
|
|
2872
|
-
const groupSel = groups.map((c) => `t.${quoteIdent(c)} AS ${quoteIdent(`g__${c}`)}`);
|
|
4276
|
+
const groupSel = groups.map((c) => `${columnTypeOf(table, c) === "timestamp" ? columnResult(table, c, "t") : `t.${quoteIdent(c)}`} AS ${quoteIdent(`g__${c}`)}`);
|
|
2873
4277
|
const params = [];
|
|
2874
4278
|
const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
|
|
2875
4279
|
const whereSql = compileWhere(table, known, q.where ?? {}, add, "aggregate");
|
|
@@ -2903,32 +4307,114 @@ function createOps(tx) {
|
|
|
2903
4307
|
const rows = await (await at()).unsafe(sql, params);
|
|
2904
4308
|
return Number(rows[0]?.n ?? 0);
|
|
2905
4309
|
},
|
|
4310
|
+
async findUnique(table, where, opts = {}) {
|
|
4311
|
+
const keys = memoTable(uniqueKeysCache, currentSchema, table, () => uniqueKeysOf(tableDefOf(table) ?? {}));
|
|
4312
|
+
assertUniqueLookup(table, keys, where);
|
|
4313
|
+
const rows = await ops.findMany(table, where, {
|
|
4314
|
+
...opts,
|
|
4315
|
+
select: opts.select === void 0 ? void 0 : [
|
|
4316
|
+
...opts.select
|
|
4317
|
+
],
|
|
4318
|
+
limit: 2
|
|
4319
|
+
});
|
|
4320
|
+
if (rows.length > 1) throw new Error(`findUnique(${table}): deployed constraints disagree with the schema; multiple rows matched`);
|
|
4321
|
+
return rows[0] ?? null;
|
|
4322
|
+
},
|
|
2906
4323
|
async findById(table, id) {
|
|
2907
|
-
const rows = await (await at()).unsafe(`SELECT
|
|
4324
|
+
const rows = await (await at()).unsafe(`SELECT ${rowProjection(table)} FROM ${quoteTable(table)} WHERE id = $1`, [
|
|
2908
4325
|
id
|
|
2909
4326
|
]);
|
|
2910
4327
|
return rows[0] ? asTableRow(table, rows[0]) : null;
|
|
2911
4328
|
},
|
|
4329
|
+
compileQuery(query) {
|
|
4330
|
+
assertReadContract(query, tableDefOf(query.table)?.columns);
|
|
4331
|
+
return compileFindMany(query.table, query.where, query.options);
|
|
4332
|
+
},
|
|
4333
|
+
async readQuery(query) {
|
|
4334
|
+
const compiled = ops.compileQuery(query);
|
|
4335
|
+
return asTableRows(query.table, await (await at()).unsafe(compiled.sql, compiled.params, {
|
|
4336
|
+
name: query.name
|
|
4337
|
+
}), void 0, true);
|
|
4338
|
+
},
|
|
4339
|
+
async explainQuery(query) {
|
|
4340
|
+
const compiled = ops.compileQuery(query);
|
|
4341
|
+
return await (await at()).unsafe(`EXPLAIN (FORMAT JSON) ${compiled.sql}`, compiled.params, {
|
|
4342
|
+
name: `${query.name}.explain`
|
|
4343
|
+
});
|
|
4344
|
+
},
|
|
2912
4345
|
async findMany(table, query = {}, opts = {}) {
|
|
4346
|
+
const compiled = compileFindMany(table, query, opts);
|
|
4347
|
+
return asTableRows(table, await (await at()).unsafe(compiled.sql, compiled.params), opts.with, opts.select !== void 0);
|
|
4348
|
+
},
|
|
4349
|
+
async page(table, query = {}, opts = {}) {
|
|
4350
|
+
const secret = cursorKey();
|
|
4351
|
+
const schema = pageSchema(table);
|
|
4352
|
+
const { order, size, backward, token } = pageOrder(schema.columns, schema.primaryKey, opts);
|
|
2913
4353
|
const params = [];
|
|
2914
|
-
const add = /* @__PURE__ */ __name((v) => {
|
|
2915
|
-
params.push(v);
|
|
2916
|
-
return `$${params.length}`;
|
|
2917
|
-
}, "add");
|
|
4354
|
+
const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
|
|
2918
4355
|
const known = schemaColumns(table);
|
|
2919
|
-
const
|
|
2920
|
-
const
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
const
|
|
2924
|
-
|
|
4356
|
+
const where = compileWhere(table, known, query, add, "page");
|
|
4357
|
+
const cols = selectClause(table, known, opts.select === void 0 ? void 0 : [
|
|
4358
|
+
...opts.select
|
|
4359
|
+
]);
|
|
4360
|
+
const scope = fingerprint({
|
|
4361
|
+
table,
|
|
4362
|
+
schema: schema.stamp,
|
|
4363
|
+
order,
|
|
4364
|
+
where,
|
|
4365
|
+
params,
|
|
4366
|
+
select: opts.select,
|
|
4367
|
+
with: opts.with,
|
|
4368
|
+
identity: context.cursorScope ?? "direct"
|
|
4369
|
+
});
|
|
4370
|
+
const values = token === void 0 ? void 0 : decodeCursor(secret, scope, token, order.length);
|
|
4371
|
+
const column = /* @__PURE__ */ __name((name) => `t.${quoteIdent(name)}`, "column");
|
|
4372
|
+
const boundary = values ? ` AND ${pageBoundary(order, values, backward, add, column)}` : "";
|
|
4373
|
+
const opposite = values ? `EXISTS (SELECT 1 FROM ${quoteTable(table)} t WHERE true${where} AND ${pageBoundary(order, values, !backward, add, column, true)})` : "false";
|
|
4374
|
+
let prefix = "__pb_page_";
|
|
4375
|
+
const names = [
|
|
4376
|
+
...Object.keys(schema.columns),
|
|
4377
|
+
...Object.keys(opts.with ?? {})
|
|
4378
|
+
];
|
|
4379
|
+
while (names.some((n) => n.startsWith(prefix))) prefix += "_";
|
|
4380
|
+
const ordinal = `${prefix}ordinal`, other = `${prefix}opposite`;
|
|
4381
|
+
const hidden = order.map((o, i) => `${prefix}${i}`);
|
|
4382
|
+
const cursorCols = order.map((o, i) => `${columnTypeOf(table, o.column) === "timestamp" ? timestampResult(column(o.column)) : `${column(o.column)}::text`} AS ${quoteIdent(hidden[i])}`);
|
|
4383
|
+
const w = opts.with && Object.keys(opts.with).length ? withClauses(table, opts.with, add, "page") : {
|
|
2925
4384
|
select: [],
|
|
2926
4385
|
joins: []
|
|
2927
4386
|
};
|
|
2928
|
-
const
|
|
2929
|
-
const
|
|
2930
|
-
const
|
|
2931
|
-
|
|
4387
|
+
const nativeOrder = order.map((o) => `${column(o.column)} ${o.direction === "asc" !== backward ? "ASC" : "DESC"} NULLS ${o.nulls === "first" !== backward ? "FIRST" : "LAST"}`).join(", ");
|
|
4388
|
+
const visible = cols === "*" ? "t.*" : cols;
|
|
4389
|
+
const inner = `SELECT ${[
|
|
4390
|
+
visible,
|
|
4391
|
+
...w.select,
|
|
4392
|
+
...cursorCols,
|
|
4393
|
+
`row_number() OVER (ORDER BY ${nativeOrder}) AS ${quoteIdent(ordinal)}`
|
|
4394
|
+
].join(", ")} FROM ${quoteTable(table)} t ${w.joins.join(" ")} WHERE true${where}${boundary} ORDER BY ${nativeOrder} LIMIT ${size + 1}`;
|
|
4395
|
+
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)}`;
|
|
4396
|
+
const result = await (await at()).unsafe(sql, params);
|
|
4397
|
+
const hasOpposite = result[0]?.[other] === true;
|
|
4398
|
+
let rows = result.filter((row) => row[ordinal] !== null && row[ordinal] !== void 0);
|
|
4399
|
+
const hasMore = rows.length > size;
|
|
4400
|
+
rows = rows.slice(0, size);
|
|
4401
|
+
if (backward) rows.reverse();
|
|
4402
|
+
const cursor = /* @__PURE__ */ __name((row) => row ? encodeCursor(secret, scope, hidden.map((name) => row[name] === null ? null : String(row[name]))) : null, "cursor");
|
|
4403
|
+
const pageInfo = {
|
|
4404
|
+
startCursor: cursor(rows[0]),
|
|
4405
|
+
endCursor: cursor(rows.at(-1)),
|
|
4406
|
+
hasNextPage: backward ? hasOpposite : hasMore,
|
|
4407
|
+
hasPreviousPage: backward ? hasMore : hasOpposite
|
|
4408
|
+
};
|
|
4409
|
+
for (const row of rows) for (const name of [
|
|
4410
|
+
...hidden,
|
|
4411
|
+
ordinal,
|
|
4412
|
+
other
|
|
4413
|
+
]) delete row[name];
|
|
4414
|
+
return {
|
|
4415
|
+
rows: asTableRows(table, rows, opts.with, opts.select !== void 0),
|
|
4416
|
+
pageInfo
|
|
4417
|
+
};
|
|
2932
4418
|
},
|
|
2933
4419
|
/**
|
|
2934
4420
|
* Tek-SQL hibrit arama (FR-014): iki kol CTE + FULL OUTER JOIN + RRF
|
|
@@ -3033,7 +4519,9 @@ function createOps(tx) {
|
|
|
3033
4519
|
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}`;
|
|
3034
4520
|
}
|
|
3035
4521
|
const msC = params.minScore === void 0 ? "" : addC(params.minScore);
|
|
3036
|
-
const colListC =
|
|
4522
|
+
const colListC = [
|
|
4523
|
+
...cfg.cols.map((c) => columnTypeOf(table, c) === "timestamp" ? projectedColumn(table, c, "t") : `t.${quoteIdent(c)}`)
|
|
4524
|
+
].join(", ");
|
|
3037
4525
|
const scoreC = scoreMul === "" ? "g._score" : `(g._score)${scoreMul}`;
|
|
3038
4526
|
const assembleC = /* @__PURE__ */ __name((kwIn, withKwn) => {
|
|
3039
4527
|
const kwnCol = withKwn ? `, (SELECT count(*) FROM kw)::int AS _kwn` : "";
|
|
@@ -3046,7 +4534,7 @@ function createOps(tx) {
|
|
|
3046
4534
|
fusedSrc = `WITH kw AS (${kwIn}), fused AS (SELECT kw.pid, kw.chunk_seq, (1.0/(${K2} + kw.r))::float8 AS cscore FROM kw)`;
|
|
3047
4535
|
}
|
|
3048
4536
|
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`;
|
|
3049
|
-
return msC === "" ? `SELECT
|
|
4537
|
+
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}`;
|
|
3050
4538
|
}, "assembleC");
|
|
3051
4539
|
let rowsC = await liveC.unsafe(assembleC(kwC, kwC !== ""), bindC);
|
|
3052
4540
|
if (wantTextC && kwC !== "" && (rowsC.length === 0 || rowsC[0]._kwn === 0)) {
|
|
@@ -3115,26 +4603,26 @@ function createOps(tx) {
|
|
|
3115
4603
|
qpRef = qp;
|
|
3116
4604
|
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}`;
|
|
3117
4605
|
}
|
|
3118
|
-
const colList =
|
|
4606
|
+
const colList = [
|
|
4607
|
+
...cfg.cols.map((c) => columnTypeOf(table, c) === "timestamp" ? projectedColumn(table, c, "t") : `t.${quoteIdent(c)}`)
|
|
4608
|
+
].join(", ");
|
|
3119
4609
|
const msP = params.minScore === void 0 ? "" : add(params.minScore);
|
|
3120
4610
|
const hlCol = params.highlight === true && wantText ? `, ts_headline('simple', ${ftsExprOf(cfg.ftsCols)}, websearch_to_tsquery('simple', ${qpRef})) AS _highlight` : "";
|
|
3121
4611
|
const assemble = /* @__PURE__ */ __name((kwIn, withKwn) => {
|
|
3122
4612
|
const kwnCol = withKwn ? `, (SELECT count(*) FROM kw)::int AS _kwn` : "";
|
|
3123
4613
|
let inner;
|
|
3124
|
-
let orderScore;
|
|
3125
4614
|
if (semSql !== "" && kwIn !== "") {
|
|
3126
4615
|
const score = scoreMul === "" ? "fused._score" : `(fused._score)${scoreMul}`;
|
|
3127
4616
|
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`;
|
|
3128
|
-
orderScore = scoreMul === "" ? "fused._score" : "_score";
|
|
3129
4617
|
} else {
|
|
3130
4618
|
const single = semSql !== "" ? `sem AS (${semSql})` : `kw AS (${kwIn})`;
|
|
3131
4619
|
const alias = semSql !== "" ? "sem" : "kw";
|
|
3132
4620
|
const base = `(1.0/(${K} + ${alias}.r))::float8`;
|
|
3133
4621
|
const score = scoreMul === "" ? base : `(${base})${scoreMul}`;
|
|
3134
4622
|
inner = `WITH ${single} SELECT ${colList}, ${score} AS _score${hlCol}${semSql !== "" ? "" : kwnCol} FROM ${alias} JOIN ${quoteTable(table)} t ON t.${quoteIdent(cfg.pk)} = ${alias}.id`;
|
|
3135
|
-
orderScore = "_score";
|
|
3136
4623
|
}
|
|
3137
|
-
|
|
4624
|
+
const ms = msP === "" ? "" : ` WHERE q._score >= ${msP}`;
|
|
4625
|
+
return `SELECT q.*${canOuter(table)} FROM (${inner}) q${ms} ORDER BY q._score DESC, q.${quoteIdent(cfg.pk)} LIMIT ${limit}`;
|
|
3138
4626
|
}, "assemble");
|
|
3139
4627
|
let rows = await live.unsafe(assemble(kwSql, kwSql !== ""), bind);
|
|
3140
4628
|
if (wantText && kwSql !== "" && (rows.length === 0 || rows[0]._kwn === 0)) {
|
|
@@ -3323,7 +4811,7 @@ function createOps(tx) {
|
|
|
3323
4811
|
const params = [];
|
|
3324
4812
|
const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
|
|
3325
4813
|
const tuple = buildValueTuple(table, cols, data, add, "supersede");
|
|
3326
|
-
const rows = await sp.unsafe(`INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING
|
|
4814
|
+
const rows = await sp.unsafe(`INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING ${rowProjection(table)}`, params);
|
|
3327
4815
|
if (!rows[0]) {
|
|
3328
4816
|
throw new Error(`supersede(${table}): INSERT sat\u0131r d\xF6nd\xFCrmedi \u2014 yazma reddedildi (b\xFCy\xFCk olas\u0131l\u0131kla bir RLS policy'si)`);
|
|
3329
4817
|
}
|
|
@@ -3333,7 +4821,7 @@ function createOps(tx) {
|
|
|
3333
4821
|
/** A real SAVEPOINT inside the request's transaction. */
|
|
3334
4822
|
async transaction(cb) {
|
|
3335
4823
|
const live = await at();
|
|
3336
|
-
return live.savepoint(async (sp) => cb(withTables(createOps(sp), currentSchema)));
|
|
4824
|
+
return live.savepoint(async (sp) => cb(withTables(createOps(sp, context), currentSchema)));
|
|
3337
4825
|
},
|
|
3338
4826
|
/**
|
|
3339
4827
|
* Run `fn` against a handle bound to a SAVEPOINT, so a failure inside it
|
|
@@ -3354,7 +4842,7 @@ function createOps(tx) {
|
|
|
3354
4842
|
*/
|
|
3355
4843
|
async attempt(fn) {
|
|
3356
4844
|
const live = await at();
|
|
3357
|
-
return live.savepoint(async (sp) => fn(withTables(createOps(sp), currentSchema)));
|
|
4845
|
+
return live.savepoint(async (sp) => fn(withTables(createOps(sp, context), currentSchema)));
|
|
3358
4846
|
},
|
|
3359
4847
|
/**
|
|
3360
4848
|
* Execute a whole transaction plan — what `Database.$transaction(fn)` builds.
|
|
@@ -3374,6 +4862,62 @@ function createOps(tx) {
|
|
|
3374
4862
|
* "transport.txPlan is not a function" while every test that covered
|
|
3375
4863
|
* transactions passed against a mock that did implement it.
|
|
3376
4864
|
*/
|
|
4865
|
+
async command(plan, options = {}) {
|
|
4866
|
+
if (options.mode !== void 0 && ![
|
|
4867
|
+
"auto",
|
|
4868
|
+
"compiled",
|
|
4869
|
+
"sequential"
|
|
4870
|
+
].includes(options.mode)) throw new Error("command mode must be auto, compiled or sequential");
|
|
4871
|
+
if (options.mode === "sequential") return ops.txPlan(plan);
|
|
4872
|
+
const compiled = context.commands ? compileCommand(plan) : {
|
|
4873
|
+
reason: "the PostgreSQL command executor is not installed"
|
|
4874
|
+
};
|
|
4875
|
+
if ("reason" in compiled) {
|
|
4876
|
+
if (options.mode === "compiled") throw new Error(`Command cannot compile: ${compiled.reason}. Use auto/sequential mode or installCommandExecutor at boot.`);
|
|
4877
|
+
return ops.txPlan(plan);
|
|
4878
|
+
}
|
|
4879
|
+
if (compiled.steps.length === 0) return {
|
|
4880
|
+
results: []
|
|
4881
|
+
};
|
|
4882
|
+
if (compiled.pipeline) {
|
|
4883
|
+
return (await at()).savepoint(async (sp) => {
|
|
4884
|
+
const settled = await Promise.allSettled(compiled.pipeline.map((statement) => sp.unsafe(statement.sql, statement.params)));
|
|
4885
|
+
const failed = settled.find((result) => result.status === "rejected");
|
|
4886
|
+
if (failed?.status === "rejected") {
|
|
4887
|
+
const error = failed.reason;
|
|
4888
|
+
if (sqlstateOf(error) === SQLSTATE.commandGuardFailed && typeof error.detail === "string") {
|
|
4889
|
+
const detail = JSON.parse(error.detail);
|
|
4890
|
+
if (detail.error_code === "tx_guard_failed" && Number.isInteger(detail.slot)) {
|
|
4891
|
+
throw Object.assign(new Error("Command expectation failed"), detail);
|
|
4892
|
+
}
|
|
4893
|
+
}
|
|
4894
|
+
throw error;
|
|
4895
|
+
}
|
|
4896
|
+
return {
|
|
4897
|
+
results: settled.map((result, i) => {
|
|
4898
|
+
const rows2 = result.value[0].rows;
|
|
4899
|
+
return {
|
|
4900
|
+
rows: asTableRows(plan.ops[i].table, rows2),
|
|
4901
|
+
rows_affected: rows2.length
|
|
4902
|
+
};
|
|
4903
|
+
})
|
|
4904
|
+
};
|
|
4905
|
+
});
|
|
4906
|
+
}
|
|
4907
|
+
const rows = await (await at()).savepoint((sp) => sp.unsafe("SELECT palbase_runtime.command_v1($1::jsonb -> 'steps') AS result", [
|
|
4908
|
+
{
|
|
4909
|
+
steps: compiled.steps
|
|
4910
|
+
}
|
|
4911
|
+
]));
|
|
4912
|
+
const response = rows[0].result;
|
|
4913
|
+
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);
|
|
4914
|
+
return {
|
|
4915
|
+
results: response.results.map((result, i) => ({
|
|
4916
|
+
...result,
|
|
4917
|
+
rows: asTableRows(plan.ops[i].table, result.rows)
|
|
4918
|
+
}))
|
|
4919
|
+
};
|
|
4920
|
+
},
|
|
3377
4921
|
async txPlan(plan) {
|
|
3378
4922
|
const live = await at();
|
|
3379
4923
|
return live.savepoint(async (sp) => {
|
|
@@ -3397,27 +4941,55 @@ function createOps(tx) {
|
|
|
3397
4941
|
}
|
|
3398
4942
|
__name(createOps, "createOps");
|
|
3399
4943
|
var currentSchema = {};
|
|
4944
|
+
var columnsMemo = /* @__PURE__ */ new WeakMap();
|
|
4945
|
+
var primaryMemo = /* @__PURE__ */ new WeakMap();
|
|
4946
|
+
var vectorMemo = /* @__PURE__ */ new WeakMap();
|
|
4947
|
+
var transformMemo = /* @__PURE__ */ new WeakMap();
|
|
4948
|
+
var typeMemo = /* @__PURE__ */ new WeakMap();
|
|
4949
|
+
var timestampMemo = /* @__PURE__ */ new WeakMap();
|
|
4950
|
+
var projectionMemo = /* @__PURE__ */ new WeakMap();
|
|
4951
|
+
function schemaColumns(table) {
|
|
4952
|
+
return memoTable(columnsMemo, currentSchema, table, () => computeSchemaColumns(table));
|
|
4953
|
+
}
|
|
4954
|
+
__name(schemaColumns, "schemaColumns");
|
|
4955
|
+
function primaryKeyOf(table) {
|
|
4956
|
+
return memoTable(primaryMemo, currentSchema, table, () => computePrimaryKey(table));
|
|
4957
|
+
}
|
|
4958
|
+
__name(primaryKeyOf, "primaryKeyOf");
|
|
4959
|
+
function vectorColumnsOf(schema, table) {
|
|
4960
|
+
return memoTable(vectorMemo, schema, table, () => computeVectorColumns(schema, table));
|
|
4961
|
+
}
|
|
4962
|
+
__name(vectorColumnsOf, "vectorColumnsOf");
|
|
4963
|
+
function transformsOf(schema, table) {
|
|
4964
|
+
return memoTable(transformMemo, schema, table, () => computeTransforms(schema, table));
|
|
4965
|
+
}
|
|
4966
|
+
__name(transformsOf, "transformsOf");
|
|
4967
|
+
function columnTypeOf(table, col) {
|
|
4968
|
+
const types = memoTable(typeMemo, currentSchema, table, () => new Map([
|
|
4969
|
+
...schemaColumns(table) ?? []
|
|
4970
|
+
].map((c) => [
|
|
4971
|
+
c,
|
|
4972
|
+
computeColumnType(table, c)
|
|
4973
|
+
])));
|
|
4974
|
+
return types.get(col) ?? null;
|
|
4975
|
+
}
|
|
4976
|
+
__name(columnTypeOf, "columnTypeOf");
|
|
3400
4977
|
var currentRelations = /* @__PURE__ */ new Map();
|
|
3401
4978
|
function setSchema(schemas) {
|
|
3402
|
-
|
|
3403
|
-
cachedTrgmSchema = null;
|
|
3404
|
-
const tables = {};
|
|
4979
|
+
const tables = /* @__PURE__ */ Object.create(null);
|
|
3405
4980
|
for (const entry of schemas) {
|
|
3406
4981
|
const mod = entry;
|
|
3407
4982
|
const def = (mod && "default" in mod ? mod.default : mod) ?? {};
|
|
3408
4983
|
const schemaName = def.name ?? "public";
|
|
3409
|
-
for (const [
|
|
3410
|
-
tables[qualifiedTableKey(schemaName, table.name ??
|
|
4984
|
+
for (const [key2, table] of Object.entries(def.tables ?? {})) {
|
|
4985
|
+
tables[qualifiedTableKey(schemaName, table.name ?? key2)] = table;
|
|
3411
4986
|
}
|
|
3412
4987
|
}
|
|
3413
|
-
|
|
3414
|
-
tables
|
|
3415
|
-
};
|
|
3416
|
-
currentRelations = buildRelations(schemas.map((entry) => {
|
|
4988
|
+
const relations = buildRelations(schemas.map((entry) => {
|
|
3417
4989
|
const mod = entry;
|
|
3418
4990
|
const def = (mod && "default" in mod ? mod.default : mod) ?? {};
|
|
3419
|
-
const tables2 =
|
|
3420
|
-
for (const [
|
|
4991
|
+
const tables2 = /* @__PURE__ */ Object.create(null);
|
|
4992
|
+
for (const [key2, t] of Object.entries(def.tables ?? {})) {
|
|
3421
4993
|
const table = t ?? {};
|
|
3422
4994
|
const columns = {};
|
|
3423
4995
|
for (const [cname, c] of Object.entries(table.columns ?? {})) {
|
|
@@ -3427,9 +4999,9 @@ function setSchema(schemas) {
|
|
|
3427
4999
|
_def: cd
|
|
3428
5000
|
};
|
|
3429
5001
|
}
|
|
3430
|
-
tables2[
|
|
5002
|
+
tables2[key2] = {
|
|
3431
5003
|
...table,
|
|
3432
|
-
name: table.name ??
|
|
5004
|
+
name: table.name ?? key2,
|
|
3433
5005
|
columns
|
|
3434
5006
|
};
|
|
3435
5007
|
}
|
|
@@ -3438,40 +5010,86 @@ function setSchema(schemas) {
|
|
|
3438
5010
|
tables: tables2
|
|
3439
5011
|
};
|
|
3440
5012
|
}));
|
|
5013
|
+
for (const key2 of Object.keys(tables)) tables[key2] = snapshotDeclaration(tables[key2]);
|
|
5014
|
+
currentSchema = Object.freeze({
|
|
5015
|
+
tables: Object.freeze(tables)
|
|
5016
|
+
});
|
|
5017
|
+
currentRelations = relations;
|
|
5018
|
+
cachedVectorSchema = null;
|
|
5019
|
+
cachedTrgmSchema = null;
|
|
3441
5020
|
}
|
|
3442
5021
|
__name(setSchema, "setSchema");
|
|
5022
|
+
var tableNamespaces = /* @__PURE__ */ new WeakMap();
|
|
3443
5023
|
function withTables(ops, schema = currentSchema) {
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
5024
|
+
let namespaces = tableNamespaces.get(schema);
|
|
5025
|
+
if (!namespaces) {
|
|
5026
|
+
namespaces = /* @__PURE__ */ new Map();
|
|
5027
|
+
for (const key2 of Object.keys(schema.tables ?? {})) {
|
|
5028
|
+
const dot = key2.indexOf(".");
|
|
5029
|
+
const name = dot === -1 ? "public" : key2.slice(0, dot);
|
|
5030
|
+
const short = dot === -1 ? key2 : key2.slice(dot + 1);
|
|
5031
|
+
if (!namespaces.has(name)) namespaces.set(name, /* @__PURE__ */ new Map());
|
|
5032
|
+
namespaces.get(name).set(short, key2);
|
|
5033
|
+
}
|
|
5034
|
+
if (Object.isFrozen(schema) && Object.isFrozen(schema.tables)) tableNamespaces.set(schema, namespaces);
|
|
5035
|
+
}
|
|
5036
|
+
const bind = /* @__PURE__ */ __name((key2) => ({
|
|
5037
|
+
insert: /* @__PURE__ */ __name((data) => ops.insert(key2, data), "insert"),
|
|
5038
|
+
update: /* @__PURE__ */ __name((id, data) => ops.update(key2, id, data), "update"),
|
|
5039
|
+
delete: /* @__PURE__ */ __name((id) => ops.delete(key2, id), "delete"),
|
|
5040
|
+
findById: /* @__PURE__ */ __name((id) => ops.findById(key2, id), "findById"),
|
|
5041
|
+
findUnique: /* @__PURE__ */ __name((where, opts) => ops.findUnique(key2, where, opts), "findUnique"),
|
|
5042
|
+
findMany: /* @__PURE__ */ __name((query, opts) => ops.findMany(key2, query ?? {}, opts), "findMany"),
|
|
5043
|
+
page: /* @__PURE__ */ __name((query, opts) => ops.page(key2, query ?? {}, opts), "page"),
|
|
5044
|
+
put: /* @__PURE__ */ __name((q) => ops.put(key2, q.data, {
|
|
3451
5045
|
onConflict: q.onConflict
|
|
3452
5046
|
}), "put")
|
|
3453
5047
|
}), "bind");
|
|
3454
|
-
const
|
|
3455
|
-
for (const key of Object.keys(schema.tables ?? {})) {
|
|
3456
|
-
if (!key.includes(".")) tables[key] = bind(key);
|
|
3457
|
-
}
|
|
5048
|
+
const surfaces = /* @__PURE__ */ new Map();
|
|
3458
5049
|
const schemaOf = /* @__PURE__ */ __name((name) => {
|
|
3459
|
-
const
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
5050
|
+
const cached = surfaces.get(name);
|
|
5051
|
+
if (cached) return cached;
|
|
5052
|
+
const names = namespaces.get(name) ?? /* @__PURE__ */ new Map();
|
|
5053
|
+
const target = /* @__PURE__ */ Object.create(null);
|
|
5054
|
+
const get = /* @__PURE__ */ __name((key2) => {
|
|
5055
|
+
if (!Object.hasOwn(target, key2) && names.has(key2)) {
|
|
5056
|
+
Object.defineProperty(target, key2, {
|
|
5057
|
+
value: bind(names.get(key2)),
|
|
5058
|
+
configurable: true,
|
|
5059
|
+
enumerable: true,
|
|
5060
|
+
writable: true
|
|
5061
|
+
});
|
|
3466
5062
|
}
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
5063
|
+
return Reflect.get(target, key2);
|
|
5064
|
+
}, "get");
|
|
5065
|
+
const tables = new Proxy(target, {
|
|
5066
|
+
get: /* @__PURE__ */ __name((t, key2, receiver) => typeof key2 === "string" ? get(key2) : Reflect.get(t, key2, receiver), "get"),
|
|
5067
|
+
has: /* @__PURE__ */ __name((t, key2) => typeof key2 === "string" && names.has(key2) || Reflect.has(t, key2), "has"),
|
|
5068
|
+
ownKeys: /* @__PURE__ */ __name((t) => [
|
|
5069
|
+
.../* @__PURE__ */ new Set([
|
|
5070
|
+
...names.keys(),
|
|
5071
|
+
...Reflect.ownKeys(t)
|
|
5072
|
+
])
|
|
5073
|
+
], "ownKeys"),
|
|
5074
|
+
getOwnPropertyDescriptor: /* @__PURE__ */ __name((t, key2) => Reflect.getOwnPropertyDescriptor(t, key2) ?? (typeof key2 === "string" && names.has(key2) ? {
|
|
5075
|
+
configurable: true,
|
|
5076
|
+
enumerable: true,
|
|
5077
|
+
get: /* @__PURE__ */ __name(() => get(key2), "get")
|
|
5078
|
+
} : void 0), "getOwnPropertyDescriptor"),
|
|
5079
|
+
preventExtensions: /* @__PURE__ */ __name((t) => {
|
|
5080
|
+
for (const key2 of names.keys()) get(key2);
|
|
5081
|
+
return Reflect.preventExtensions(t);
|
|
5082
|
+
}, "preventExtensions")
|
|
5083
|
+
});
|
|
5084
|
+
const surface = {
|
|
5085
|
+
tables
|
|
3470
5086
|
};
|
|
5087
|
+
if (namespaces.has(name) || name === "public") surfaces.set(name, surface);
|
|
5088
|
+
return surface;
|
|
3471
5089
|
}, "schemaOf");
|
|
3472
5090
|
const base = /* @__PURE__ */ Object.create(null);
|
|
3473
5091
|
return Object.assign(base, ops, {
|
|
3474
|
-
tables,
|
|
5092
|
+
tables: schemaOf("public").tables,
|
|
3475
5093
|
schema: schemaOf
|
|
3476
5094
|
});
|
|
3477
5095
|
}
|
|
@@ -3529,8 +5147,8 @@ function diagnosingDriver(sql, surface = "service") {
|
|
|
3529
5147
|
return markEngineRaised(new UniqueViolation(constraintOf(e)));
|
|
3530
5148
|
}
|
|
3531
5149
|
const state = sqlstateOf(e);
|
|
3532
|
-
if (state ===
|
|
3533
|
-
if (state ===
|
|
5150
|
+
if (state === SQLSTATE.serializationFailure) return markEngineRaised(new SerializationFailure(state));
|
|
5151
|
+
if (state === SQLSTATE.deadlockDetected) return markEngineRaised(new DeadlockDetected(state));
|
|
3534
5152
|
if (surface === "service" && isForeignKeyViolation(e)) {
|
|
3535
5153
|
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)})`);
|
|
3536
5154
|
}
|
|
@@ -3540,11 +5158,16 @@ function diagnosingDriver(sql, surface = "service") {
|
|
|
3540
5158
|
return e;
|
|
3541
5159
|
}, "explain");
|
|
3542
5160
|
const wrapTx = /* @__PURE__ */ __name((tx) => ({
|
|
3543
|
-
|
|
5161
|
+
unsafe(text, params) {
|
|
3544
5162
|
try {
|
|
3545
|
-
|
|
5163
|
+
const query = tx.unsafe(text, params);
|
|
5164
|
+
const mapped = query.catch((e) => {
|
|
5165
|
+
throw explain(e);
|
|
5166
|
+
});
|
|
5167
|
+
if (query.cancel) mapped.cancel = () => query.cancel();
|
|
5168
|
+
return mapped;
|
|
3546
5169
|
} catch (e) {
|
|
3547
|
-
|
|
5170
|
+
return Promise.reject(explain(e));
|
|
3548
5171
|
}
|
|
3549
5172
|
},
|
|
3550
5173
|
savepoint(cb) {
|
|
@@ -3553,34 +5176,120 @@ function diagnosingDriver(sql, surface = "service") {
|
|
|
3553
5176
|
}), "wrapTx");
|
|
3554
5177
|
return {
|
|
3555
5178
|
unsafe: /* @__PURE__ */ __name((text, params) => sql.unsafe(text, params), "unsafe"),
|
|
3556
|
-
|
|
5179
|
+
cancelBackend: sql.cancelBackend?.bind(sql),
|
|
5180
|
+
begin: /* @__PURE__ */ __name((cb) => sql.begin((tx) => cb(wrapTx(tx))).catch((e) => {
|
|
5181
|
+
throw explain(e);
|
|
5182
|
+
}), "begin")
|
|
3557
5183
|
};
|
|
3558
5184
|
}
|
|
3559
5185
|
__name(diagnosingDriver, "diagnosingDriver");
|
|
3560
|
-
function createRequestDatabase(sql, identity) {
|
|
3561
|
-
const
|
|
5186
|
+
function createRequestDatabase(sql, identity, diagnostics) {
|
|
5187
|
+
const trace = diagnostics === void 0 ? void 0 : new DatabaseTrace(diagnostics);
|
|
5188
|
+
let requestClosed = false;
|
|
5189
|
+
const contextFor = /* @__PURE__ */ __name((role) => {
|
|
5190
|
+
let scope;
|
|
5191
|
+
return {
|
|
5192
|
+
trace,
|
|
5193
|
+
get cursorScope() {
|
|
5194
|
+
return scope ??= cursorIdentity(role, identity.claimsJson);
|
|
5195
|
+
},
|
|
5196
|
+
get commands() {
|
|
5197
|
+
return hasCommandExecutor(sql);
|
|
5198
|
+
}
|
|
5199
|
+
};
|
|
5200
|
+
}, "contextFor");
|
|
5201
|
+
const userContext = contextFor(identity.role);
|
|
5202
|
+
const serviceContext = contextFor(identity.serviceRole);
|
|
5203
|
+
const atomic = /* @__PURE__ */ __name((role, surface) => async (fn, options = {}) => {
|
|
5204
|
+
validateAtomicOptions(options);
|
|
5205
|
+
if (requestClosed) throw new Error("Database request has settled; it cannot open another transaction");
|
|
5206
|
+
if (atomicScope.getStore() !== void 0) throw new Error("Nested Database.$atomic is not supported; use tx.$attempt for a savepoint");
|
|
5207
|
+
const retries = retryBudget(options.retry);
|
|
5208
|
+
const control = options.timeoutMs !== void 0 || options.signal !== void 0 ? new TransactionControl(options.timeoutMs, options.signal) : void 0;
|
|
5209
|
+
let lease;
|
|
5210
|
+
try {
|
|
5211
|
+
control?.check();
|
|
5212
|
+
if (options.workload) {
|
|
5213
|
+
const queuedAt = trace ? performance.now() : 0;
|
|
5214
|
+
try {
|
|
5215
|
+
lease = await workloadAdmission(sql, options.workload).acquire(workloadTenant(options.workload, identity.claimsJson), options.conflictKeys ?? [], control?.signal);
|
|
5216
|
+
} finally {
|
|
5217
|
+
trace?.time("queueMs", queuedAt);
|
|
5218
|
+
}
|
|
5219
|
+
}
|
|
5220
|
+
for (let attempt = 0; ; attempt++) {
|
|
5221
|
+
control?.check();
|
|
5222
|
+
const timeouts = [
|
|
5223
|
+
options.statementTimeoutMs,
|
|
5224
|
+
control?.remainingMs()
|
|
5225
|
+
].filter((n) => n !== void 0);
|
|
5226
|
+
const scope = /* @__PURE__ */ Symbol("atomic transaction");
|
|
5227
|
+
const root = createLazyTransaction(diagnosingDriver(sql, surface), role, identity.claimsJson, {
|
|
5228
|
+
scope,
|
|
5229
|
+
isolation: options.isolation,
|
|
5230
|
+
readOnly: options.readOnly,
|
|
5231
|
+
control,
|
|
5232
|
+
trace,
|
|
5233
|
+
lockTimeout: `${options.lockTimeoutMs ?? 5e3}ms`,
|
|
5234
|
+
statementTimeout: timeouts.length === 0 ? void 0 : `${Math.min(...timeouts)}ms`
|
|
5235
|
+
});
|
|
5236
|
+
try {
|
|
5237
|
+
const callback = atomicScope.run(scope, () => fn(withTables(createOps(root, surface === "user" ? userContext : serviceContext))));
|
|
5238
|
+
const value = await (control ? control.race(callback) : callback);
|
|
5239
|
+
control?.check();
|
|
5240
|
+
await root.commit();
|
|
5241
|
+
return value;
|
|
5242
|
+
} catch (error) {
|
|
5243
|
+
await root.rollback(error);
|
|
5244
|
+
if (attempt >= retries || !isRetryable(error)) throw error;
|
|
5245
|
+
trace?.retry();
|
|
5246
|
+
const delay = new Promise((resolve2) => setTimeout(resolve2, Math.min(100, 2 ** attempt)));
|
|
5247
|
+
await (control ? control.race(delay) : delay);
|
|
5248
|
+
}
|
|
5249
|
+
}
|
|
5250
|
+
} finally {
|
|
5251
|
+
control?.dispose();
|
|
5252
|
+
lease?.release();
|
|
5253
|
+
}
|
|
5254
|
+
}, "atomic");
|
|
5255
|
+
const tx = createLazyTransaction(diagnosingDriver(sql, "user"), identity.role, identity.claimsJson, {
|
|
5256
|
+
trace
|
|
5257
|
+
});
|
|
3562
5258
|
let serviceTx = null;
|
|
3563
5259
|
let serviceClient = null;
|
|
3564
5260
|
const asService = /* @__PURE__ */ __name(() => {
|
|
3565
5261
|
if (serviceClient === null) {
|
|
3566
5262
|
serviceTx = createLazyTransaction(diagnosingDriver(sql, "service"), identity.serviceRole, identity.claimsJson, {
|
|
3567
|
-
lockTimeout: SERVICE_LOCK_TIMEOUT
|
|
5263
|
+
lockTimeout: SERVICE_LOCK_TIMEOUT,
|
|
5264
|
+
trace
|
|
5265
|
+
});
|
|
5266
|
+
serviceClient = Object.assign(withTables(createOps(serviceTx, serviceContext)), {
|
|
5267
|
+
atomic: atomic(identity.serviceRole, "service")
|
|
3568
5268
|
});
|
|
3569
|
-
serviceClient = withTables(createOps(serviceTx));
|
|
3570
5269
|
}
|
|
3571
5270
|
return serviceClient;
|
|
3572
5271
|
}, "asService");
|
|
3573
5272
|
return {
|
|
3574
|
-
client: Object.assign(withTables(createOps(tx)), {
|
|
3575
|
-
asService
|
|
5273
|
+
client: Object.assign(withTables(createOps(tx, userContext)), {
|
|
5274
|
+
asService,
|
|
5275
|
+
atomic: atomic(identity.role, "user")
|
|
3576
5276
|
}),
|
|
3577
5277
|
async commit() {
|
|
3578
|
-
|
|
3579
|
-
|
|
5278
|
+
requestClosed = true;
|
|
5279
|
+
try {
|
|
5280
|
+
await tx.commit();
|
|
5281
|
+
await serviceTx?.commit();
|
|
5282
|
+
trace?.complete("committed");
|
|
5283
|
+
} catch (error) {
|
|
5284
|
+
trace?.failed();
|
|
5285
|
+
throw error;
|
|
5286
|
+
}
|
|
3580
5287
|
},
|
|
3581
5288
|
async rollback(reason2) {
|
|
5289
|
+
requestClosed = true;
|
|
3582
5290
|
await tx.rollback(reason2);
|
|
3583
5291
|
await serviceTx?.rollback(reason2);
|
|
5292
|
+
trace?.complete("failed");
|
|
3584
5293
|
}
|
|
3585
5294
|
};
|
|
3586
5295
|
}
|
|
@@ -3590,8 +5299,13 @@ var Args = class Args2 {
|
|
|
3590
5299
|
__name(this, "Args");
|
|
3591
5300
|
}
|
|
3592
5301
|
values = [];
|
|
3593
|
-
|
|
5302
|
+
columns = [];
|
|
5303
|
+
bind(value, column, table) {
|
|
3594
5304
|
this.values.push(value);
|
|
5305
|
+
this.columns.push({
|
|
5306
|
+
column,
|
|
5307
|
+
table
|
|
5308
|
+
});
|
|
3595
5309
|
return `$${this.values.length}`;
|
|
3596
5310
|
}
|
|
3597
5311
|
};
|
|
@@ -3677,7 +5391,7 @@ function planWhere(table, where, args, results) {
|
|
|
3677
5391
|
}
|
|
3678
5392
|
const resolved = resolveRefsDeep(where, results);
|
|
3679
5393
|
const known = schemaColumns(table);
|
|
3680
|
-
const bare = compileWhereBare(table, known, resolved, (v) => args.bind(v), "transaction plan");
|
|
5394
|
+
const bare = compileWhereBare(table, known, resolved, (v, column, from) => args.bind(v, column, from), "transaction plan");
|
|
3681
5395
|
return {
|
|
3682
5396
|
sql: bare === "" ? "" : ` AND ${bare}`,
|
|
3683
5397
|
resolved
|
|
@@ -3686,16 +5400,16 @@ function planWhere(table, where, args, results) {
|
|
|
3686
5400
|
__name(planWhere, "planWhere");
|
|
3687
5401
|
function bindMaybeVector(args, column, vectorCols, value, transforms) {
|
|
3688
5402
|
if (column !== void 0 && vectorCols?.has(column) && Array.isArray(value)) {
|
|
3689
|
-
return args.bind(toVectorLiteral(value));
|
|
5403
|
+
return args.bind(toVectorLiteral(value), column);
|
|
3690
5404
|
}
|
|
3691
5405
|
const t = column !== void 0 ? transforms?.get(column) : void 0;
|
|
3692
5406
|
if (t?.toDb !== void 0 && value !== null && value !== void 0) {
|
|
3693
|
-
return args.bind(t.toDb(value));
|
|
5407
|
+
return args.bind(t.toDb(value), column);
|
|
3694
5408
|
}
|
|
3695
|
-
return args.bind(value);
|
|
5409
|
+
return args.bind(value, column);
|
|
3696
5410
|
}
|
|
3697
5411
|
__name(bindMaybeVector, "bindMaybeVector");
|
|
3698
|
-
|
|
5412
|
+
function compilePlanOp(op, results) {
|
|
3699
5413
|
const opVectorCols = vectorColumnsOf(currentSchema, op.table);
|
|
3700
5414
|
const opTransforms = transformsOf(currentSchema, op.table);
|
|
3701
5415
|
const args = new Args();
|
|
@@ -3705,7 +5419,7 @@ async function runPlanOp(sp, op, results) {
|
|
|
3705
5419
|
case "insert": {
|
|
3706
5420
|
const cols = Object.keys(op.values ?? {});
|
|
3707
5421
|
const rendered = cols.map((c) => renderValue(op.values[c], c, args, results, opVectorCols, opTransforms));
|
|
3708
|
-
sql = cols.length ? `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES (${rendered.join(", ")}) RETURNING
|
|
5422
|
+
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)}`;
|
|
3709
5423
|
break;
|
|
3710
5424
|
}
|
|
3711
5425
|
case "upsert": {
|
|
@@ -3718,17 +5432,17 @@ async function runPlanOp(sp, op, results) {
|
|
|
3718
5432
|
});
|
|
3719
5433
|
}
|
|
3720
5434
|
const rendered = cols.map((c) => renderValue(op.values[c], c, args, results, opVectorCols, opTransforms));
|
|
3721
|
-
sql = `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES (${rendered.join(", ")}) ${onConflictTail(cols, conflict, "update")} RETURNING
|
|
5435
|
+
sql = `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES (${rendered.join(", ")}) ${onConflictTail(cols, conflict, "update")} RETURNING ${rowProjection(op.table)}`;
|
|
3722
5436
|
break;
|
|
3723
5437
|
}
|
|
3724
5438
|
case "insertMany": {
|
|
3725
5439
|
const rows = op.rows ?? [];
|
|
3726
|
-
if (rows.length === 0 || !rows[0]) return
|
|
5440
|
+
if (rows.length === 0 || !rows[0]) return null;
|
|
3727
5441
|
const cols = Object.keys(rows[0]);
|
|
3728
5442
|
const tuples = rows.map((r) => `(${cols.map((c) => renderValue(r[c], c, args, results, opVectorCols, opTransforms)).join(", ")})`);
|
|
3729
5443
|
const conflictCols = op.onConflict ?? [];
|
|
3730
5444
|
const tail = op.action !== void 0 && conflictCols.length > 0 ? ` ${onConflictTail(cols, conflictCols, op.action)}` : "";
|
|
3731
|
-
sql = `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES ${tuples.join(", ")}${tail} RETURNING
|
|
5445
|
+
sql = `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES ${tuples.join(", ")}${tail} RETURNING ${rowProjection(op.table)}`;
|
|
3732
5446
|
break;
|
|
3733
5447
|
}
|
|
3734
5448
|
case "update": {
|
|
@@ -3744,12 +5458,12 @@ async function runPlanOp(sp, op, results) {
|
|
|
3744
5458
|
const assignments = cols.map((c) => `${quoteIdent(c)} = ${renderValue(op.set[c], c, args, results, opVectorCols, opTransforms)}`);
|
|
3745
5459
|
const planned = planWhere(op.table, op.where, args, results);
|
|
3746
5460
|
const planPk = primaryKeyOf(op.table);
|
|
3747
|
-
sql = isSinglePkEquality(planned.resolved, planPk) || planPk === null ? `UPDATE ${table} AS t SET ${assignments.join(", ")} WHERE true${planned.sql} RETURNING t
|
|
5461
|
+
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")}`;
|
|
3748
5462
|
break;
|
|
3749
5463
|
}
|
|
3750
5464
|
case "delete": {
|
|
3751
5465
|
assertNotAppendOnly("deleteWhere", op.table);
|
|
3752
|
-
sql = `DELETE FROM ${table} AS t WHERE true${renderWhere(op.table, op.where, args, results)} RETURNING t
|
|
5466
|
+
sql = `DELETE FROM ${table} AS t WHERE true${renderWhere(op.table, op.where, args, results)} RETURNING ${rowProjection(op.table, "t")}`;
|
|
3753
5467
|
break;
|
|
3754
5468
|
}
|
|
3755
5469
|
case "select": {
|
|
@@ -3762,15 +5476,157 @@ async function runPlanOp(sp, op, results) {
|
|
|
3762
5476
|
limit = ` LIMIT ${n}`;
|
|
3763
5477
|
}
|
|
3764
5478
|
const lock = op.lock === "update" ? " FOR UPDATE" : "";
|
|
3765
|
-
sql = `SELECT t
|
|
5479
|
+
sql = `SELECT ${rowProjection(op.table, "t")} FROM ${table} t WHERE true${renderWhere(op.table, op.where, args, results)}${limit}${lock}`;
|
|
3766
5480
|
break;
|
|
3767
5481
|
}
|
|
3768
5482
|
default:
|
|
3769
5483
|
throw new Error(`unknown operation "${String(op.op)}" in a transaction plan`);
|
|
3770
5484
|
}
|
|
3771
|
-
return
|
|
5485
|
+
return {
|
|
5486
|
+
sql,
|
|
5487
|
+
params: args.values,
|
|
5488
|
+
columns: args.columns
|
|
5489
|
+
};
|
|
5490
|
+
}
|
|
5491
|
+
__name(compilePlanOp, "compilePlanOp");
|
|
5492
|
+
async function runPlanOp(sp, op, results) {
|
|
5493
|
+
const statement = compilePlanOp(op, results);
|
|
5494
|
+
return statement ? await sp.unsafe(statement.sql, statement.params) : [];
|
|
3772
5495
|
}
|
|
3773
5496
|
__name(runPlanOp, "runPlanOp");
|
|
5497
|
+
function compileCommand(plan) {
|
|
5498
|
+
const symbols = /* @__PURE__ */ new Map();
|
|
5499
|
+
const results = [];
|
|
5500
|
+
const steps = [];
|
|
5501
|
+
const pipeline = [];
|
|
5502
|
+
let hasRefs = false;
|
|
5503
|
+
const prefix = `command:${crypto.randomUUID()}:`;
|
|
5504
|
+
const containsFragment = /* @__PURE__ */ __name((v) => isSqlFragment(v) || v !== null && typeof v === "object" && Object.values(v).some(containsFragment), "containsFragment");
|
|
5505
|
+
const hasNullableFilterRef = /* @__PURE__ */ __name((v) => {
|
|
5506
|
+
if (isRef(v)) {
|
|
5507
|
+
const source = plan.ops[v.$ref.op];
|
|
5508
|
+
return source !== void 0 && pageSchema(source.table).columns[v.$ref.field]?.nullable !== false;
|
|
5509
|
+
}
|
|
5510
|
+
return v !== null && typeof v === "object" && Object.values(v).some(hasNullableFilterRef);
|
|
5511
|
+
}, "hasNullableFilterRef");
|
|
5512
|
+
for (const op of plan.ops) {
|
|
5513
|
+
if (!schemaColumns(op.table)?.size) return {
|
|
5514
|
+
reason: `table ${op.table} has no installed declaration`
|
|
5515
|
+
};
|
|
5516
|
+
if (transformsOf(currentSchema, op.table).size || vectorColumnsOf(currentSchema, op.table).size) return {
|
|
5517
|
+
reason: `${op.table} uses JavaScript codecs/transforms or vector values`
|
|
5518
|
+
};
|
|
5519
|
+
if (containsFragment(op)) return {
|
|
5520
|
+
reason: "SQL fragments require the sequential path"
|
|
5521
|
+
};
|
|
5522
|
+
if (hasNullableFilterRef(op.where)) return {
|
|
5523
|
+
reason: "nullable references in filters require the sequential path"
|
|
5524
|
+
};
|
|
5525
|
+
}
|
|
5526
|
+
for (const [index, op] of plan.ops.entries()) {
|
|
5527
|
+
const statement = compilePlanOp(op, results);
|
|
5528
|
+
if (statement === null) {
|
|
5529
|
+
steps.push({
|
|
5530
|
+
empty: true,
|
|
5531
|
+
guard: op.guard
|
|
5532
|
+
});
|
|
5533
|
+
pipeline.push({
|
|
5534
|
+
sql: "SELECT palbase_runtime.command_rows_v1('[]'::jsonb,$1::jsonb) AS rows",
|
|
5535
|
+
params: [
|
|
5536
|
+
op.guard ?? null
|
|
5537
|
+
]
|
|
5538
|
+
});
|
|
5539
|
+
} else {
|
|
5540
|
+
if (statement.params.some((v) => v === void 0 || typeof v === "symbol" || typeof v === "function" || typeof v === "number" && (!Number.isInteger(v) || v < -2147483648 || v > 2147483647))) {
|
|
5541
|
+
return {
|
|
5542
|
+
reason: "non-integer/wide numeric or non-JSON parameters require the sequential driver path; use exact decimal strings"
|
|
5543
|
+
};
|
|
5544
|
+
}
|
|
5545
|
+
for (const [i, value] of statement.params.entries()) {
|
|
5546
|
+
const target = statement.columns[i];
|
|
5547
|
+
if (target.column === void 0 || columnTypeOf(target.table ?? op.table, target.column) !== "jsonb") continue;
|
|
5548
|
+
const source = typeof value === "string" ? symbols.get(value) : void 0;
|
|
5549
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint" || source && "ref" in source && ![
|
|
5550
|
+
"text",
|
|
5551
|
+
"uuid",
|
|
5552
|
+
"timestamp",
|
|
5553
|
+
"enum"
|
|
5554
|
+
].includes(columnTypeOf(plan.ops[source.ref.op].table, source.ref.field) ?? "")) {
|
|
5555
|
+
return {
|
|
5556
|
+
reason: "JSONB scalar/ref binding requires the sequential driver path"
|
|
5557
|
+
};
|
|
5558
|
+
}
|
|
5559
|
+
}
|
|
5560
|
+
const wide = [
|
|
5561
|
+
...schemaColumns(op.table)
|
|
5562
|
+
].filter((c) => [
|
|
5563
|
+
"bigint",
|
|
5564
|
+
"numeric"
|
|
5565
|
+
].includes(columnTypeOf(op.table, c) ?? ""));
|
|
5566
|
+
const exact = wide.length ? ` || jsonb_build_object(${wide.map((c) => `${quoteLiteral(c)}, r.${quoteIdent(c)}::text`).join(", ")})` : "";
|
|
5567
|
+
const sql = `WITH command_rows AS (${statement.sql}) SELECT coalesce(jsonb_agg(to_jsonb(r)${exact}), '[]'::jsonb) FROM command_rows r`;
|
|
5568
|
+
pipeline.push({
|
|
5569
|
+
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`,
|
|
5570
|
+
params: [
|
|
5571
|
+
...statement.params,
|
|
5572
|
+
op.guard ?? null
|
|
5573
|
+
]
|
|
5574
|
+
});
|
|
5575
|
+
const bindings = statement.params.map((value, i) => {
|
|
5576
|
+
const target = statement.columns[i];
|
|
5577
|
+
const json = target.column !== void 0 && columnTypeOf(target.table ?? op.table, target.column) === "jsonb";
|
|
5578
|
+
if (typeof value === "string" && symbols.has(value)) {
|
|
5579
|
+
hasRefs = true;
|
|
5580
|
+
return {
|
|
5581
|
+
...symbols.get(value),
|
|
5582
|
+
json
|
|
5583
|
+
};
|
|
5584
|
+
}
|
|
5585
|
+
if (typeof value === "bigint") return {
|
|
5586
|
+
value: value.toString(),
|
|
5587
|
+
json
|
|
5588
|
+
};
|
|
5589
|
+
if (value instanceof Date) return {
|
|
5590
|
+
value: value.toISOString(),
|
|
5591
|
+
json
|
|
5592
|
+
};
|
|
5593
|
+
return {
|
|
5594
|
+
value,
|
|
5595
|
+
json
|
|
5596
|
+
};
|
|
5597
|
+
});
|
|
5598
|
+
steps.push({
|
|
5599
|
+
chunks: parameterChunks(sql),
|
|
5600
|
+
bindings,
|
|
5601
|
+
guard: op.guard
|
|
5602
|
+
});
|
|
5603
|
+
}
|
|
5604
|
+
const row = {};
|
|
5605
|
+
for (const field of schemaColumns(op.table)) {
|
|
5606
|
+
const token = `${prefix}${index}:${field}`;
|
|
5607
|
+
symbols.set(token, {
|
|
5608
|
+
ref: {
|
|
5609
|
+
op: index,
|
|
5610
|
+
field
|
|
5611
|
+
}
|
|
5612
|
+
});
|
|
5613
|
+
row[field] = token;
|
|
5614
|
+
}
|
|
5615
|
+
results.push({
|
|
5616
|
+
rows: [
|
|
5617
|
+
row
|
|
5618
|
+
],
|
|
5619
|
+
rows_affected: 1
|
|
5620
|
+
});
|
|
5621
|
+
}
|
|
5622
|
+
return {
|
|
5623
|
+
steps,
|
|
5624
|
+
...hasRefs ? {} : {
|
|
5625
|
+
pipeline
|
|
5626
|
+
}
|
|
5627
|
+
};
|
|
5628
|
+
}
|
|
5629
|
+
__name(compileCommand, "compileCommand");
|
|
3774
5630
|
function assertGuard(op, result) {
|
|
3775
5631
|
const guard = op.guard;
|
|
3776
5632
|
if (!guard) return;
|
|
@@ -3784,6 +5640,33 @@ function assertGuard(op, result) {
|
|
|
3784
5640
|
}
|
|
3785
5641
|
__name(assertGuard, "assertGuard");
|
|
3786
5642
|
|
|
5643
|
+
// src/engine/cancellation.ts
|
|
5644
|
+
function withQueryCancellation(application, control) {
|
|
5645
|
+
if (application === control) throw new Error("Query cancellation requires a separate control pool");
|
|
5646
|
+
const close = /* @__PURE__ */ __name(async (driver) => {
|
|
5647
|
+
const d = driver;
|
|
5648
|
+
if (d.close) await d.close();
|
|
5649
|
+
else if (d.end) await d.end();
|
|
5650
|
+
}, "close");
|
|
5651
|
+
return {
|
|
5652
|
+
unsafe: /* @__PURE__ */ __name((sql, params) => application.unsafe(sql, params), "unsafe"),
|
|
5653
|
+
begin: /* @__PURE__ */ __name((fn) => application.begin(fn), "begin"),
|
|
5654
|
+
async cancelBackend(pid, startedAt) {
|
|
5655
|
+
await control.unsafe(`SELECT pg_cancel_backend(pid) FROM pg_stat_activity
|
|
5656
|
+
WHERE pid=$1::integer AND backend_start=$2::timestamptz
|
|
5657
|
+
AND usename=session_user AND datname=current_database() AND backend_type='client backend'`, [
|
|
5658
|
+
pid,
|
|
5659
|
+
startedAt
|
|
5660
|
+
]);
|
|
5661
|
+
},
|
|
5662
|
+
async close() {
|
|
5663
|
+
await close(application);
|
|
5664
|
+
await close(control);
|
|
5665
|
+
}
|
|
5666
|
+
};
|
|
5667
|
+
}
|
|
5668
|
+
__name(withQueryCancellation, "withQueryCancellation");
|
|
5669
|
+
|
|
3787
5670
|
// src/decorators/registry.ts
|
|
3788
5671
|
var ROUTES = /* @__PURE__ */ Symbol.for("palbase.backend.routes");
|
|
3789
5672
|
var RETURN_BUFFER = /* @__PURE__ */ Symbol.for("palbase.backend.returnBuffer");
|
|
@@ -4671,10 +6554,13 @@ async function defaultSqlDriver(config) {
|
|
|
4671
6554
|
if (!g.Bun?.SQL) {
|
|
4672
6555
|
throw new BootRefused([], "boot refused: no SQL driver. Running outside Bun means the driver must be supplied \u2014 pass `sql` to createApp().");
|
|
4673
6556
|
}
|
|
4674
|
-
return new g.Bun.SQL({
|
|
6557
|
+
return withQueryCancellation(new g.Bun.SQL({
|
|
4675
6558
|
url: config.databaseUrl,
|
|
4676
6559
|
max: config.poolMax
|
|
4677
|
-
})
|
|
6560
|
+
}), new g.Bun.SQL({
|
|
6561
|
+
url: config.databaseUrl,
|
|
6562
|
+
max: 2
|
|
6563
|
+
}));
|
|
4678
6564
|
}
|
|
4679
6565
|
__name(defaultSqlDriver, "defaultSqlDriver");
|
|
4680
6566
|
var AUTHOR_ANSWERED = /* @__PURE__ */ new Set([
|
|
@@ -4691,13 +6577,25 @@ async function refuseUnlessAuthorized(client, spec, requestId, rolesOfCaller) {
|
|
|
4691
6577
|
spec.permission
|
|
4692
6578
|
]);
|
|
4693
6579
|
if (row?.ok !== true) {
|
|
4694
|
-
return envelope(
|
|
6580
|
+
return envelope(
|
|
6581
|
+
"forbidden",
|
|
6582
|
+
`This endpoint requires the "${spec.permission}" permission`,
|
|
6583
|
+
403,
|
|
6584
|
+
requestId,
|
|
6585
|
+
// FR-008 / D-7: reddin ÇARESİ gövdede — istemci `required`'ı görünce ilgili
|
|
6586
|
+
// nesneyi yeniden çeker (Entra CAE deseni); cümle değişmez.
|
|
6587
|
+
{
|
|
6588
|
+
required: spec.permission
|
|
6589
|
+
}
|
|
6590
|
+
);
|
|
4695
6591
|
}
|
|
4696
6592
|
}
|
|
4697
6593
|
if (spec.role !== void 0) {
|
|
4698
6594
|
const held = await rolesOfCaller();
|
|
4699
6595
|
if (!held.includes(spec.role)) {
|
|
4700
|
-
return envelope("forbidden", `This endpoint requires the "${spec.role}" role`, 403, requestId
|
|
6596
|
+
return envelope("forbidden", `This endpoint requires the "${spec.role}" role`, 403, requestId, {
|
|
6597
|
+
required_role: spec.role
|
|
6598
|
+
});
|
|
4701
6599
|
}
|
|
4702
6600
|
}
|
|
4703
6601
|
return null;
|
|
@@ -4733,6 +6631,11 @@ async function createApp(opts) {
|
|
|
4733
6631
|
const sql = opts.sql ?? await defaultSqlDriver(config);
|
|
4734
6632
|
await sql.unsafe("select 1");
|
|
4735
6633
|
setSchema(opts.schemas ?? []);
|
|
6634
|
+
setCursorKey(config.cursorSecret || config.serviceRoleKey || null);
|
|
6635
|
+
if (config.enableCommands) await installCommandExecutor(sql, [
|
|
6636
|
+
config.dbRole,
|
|
6637
|
+
config.dbServiceRole
|
|
6638
|
+
]);
|
|
4736
6639
|
setSecretReader(opts.secretReader ?? null);
|
|
4737
6640
|
const auth = new AuthVerifier({
|
|
4738
6641
|
jwksUrl: config.authJwksUrl,
|
|
@@ -4778,6 +6681,9 @@ async function createApp(opts) {
|
|
|
4778
6681
|
role: config.dbRole,
|
|
4779
6682
|
serviceRole: config.dbServiceRole,
|
|
4780
6683
|
claimsJson: "{}"
|
|
6684
|
+
}, config.databaseDiagnostics === void 0 ? void 0 : {
|
|
6685
|
+
...config.databaseDiagnostics,
|
|
6686
|
+
endpoint: "scheduled job"
|
|
4781
6687
|
});
|
|
4782
6688
|
try {
|
|
4783
6689
|
const out = await runWithRuntime(buildServices(db), fn);
|
|
@@ -4807,6 +6713,10 @@ async function createApp(opts) {
|
|
|
4807
6713
|
role: config.dbRole,
|
|
4808
6714
|
serviceRole: config.dbServiceRole,
|
|
4809
6715
|
claimsJson: JSON.stringify(callerClaims ?? {})
|
|
6716
|
+
}, config.databaseDiagnostics === void 0 ? void 0 : {
|
|
6717
|
+
...config.databaseDiagnostics,
|
|
6718
|
+
endpoint: "upload authorization",
|
|
6719
|
+
requestId
|
|
4810
6720
|
});
|
|
4811
6721
|
try {
|
|
4812
6722
|
const refusal = await refuseUnlessAuthorized(uploadDb.client, spec, requestId, makeRoleResolver(uploadDb.client));
|
|
@@ -4854,6 +6764,11 @@ async function createApp(opts) {
|
|
|
4854
6764
|
role: config.dbRole,
|
|
4855
6765
|
serviceRole: config.dbServiceRole,
|
|
4856
6766
|
claimsJson: JSON.stringify(claims ?? {})
|
|
6767
|
+
}, config.databaseDiagnostics === void 0 && meta.options.databaseBudget === void 0 ? void 0 : {
|
|
6768
|
+
...config.databaseDiagnostics,
|
|
6769
|
+
...meta.options.databaseBudget,
|
|
6770
|
+
endpoint: hit.entry.id,
|
|
6771
|
+
requestId
|
|
4857
6772
|
});
|
|
4858
6773
|
const rolesOfCaller = makeRoleResolver(db.client);
|
|
4859
6774
|
let dispatched = false;
|
|
@@ -5306,13 +7221,13 @@ async function loadDotEnv(root) {
|
|
|
5306
7221
|
if (!line || line.startsWith("#")) continue;
|
|
5307
7222
|
const eq = line.indexOf("=");
|
|
5308
7223
|
if (eq < 1) continue;
|
|
5309
|
-
const
|
|
5310
|
-
if (process.env[
|
|
7224
|
+
const key2 = line.slice(0, eq).trim();
|
|
7225
|
+
if (process.env[key2] !== void 0) continue;
|
|
5311
7226
|
let value = line.slice(eq + 1).trim();
|
|
5312
7227
|
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
5313
7228
|
value = value.slice(1, -1);
|
|
5314
7229
|
}
|
|
5315
|
-
process.env[
|
|
7230
|
+
process.env[key2] = value;
|
|
5316
7231
|
}
|
|
5317
7232
|
}
|
|
5318
7233
|
__name(loadDotEnv, "loadDotEnv");
|