@palbase/backend 32.0.0 → 33.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/palbase-backend.cjs +79 -26
- package/dist/bin/palbase-backend.cjs.map +1 -1
- package/dist/bin/palbase-backend.js +5 -5
- package/dist/{chunk-IXAX5CON.js → chunk-C4ZA5AT2.js} +1 -1
- package/dist/{chunk-IXAX5CON.js.map → chunk-C4ZA5AT2.js.map} +1 -1
- package/dist/{chunk-AZJIKCOR.js → chunk-DCDHAKF3.js} +39 -23
- package/dist/{chunk-AZJIKCOR.js.map → chunk-DCDHAKF3.js.map} +1 -1
- package/dist/{chunk-Z4CZRMNF.js → chunk-GOPZPM2A.js} +2 -2
- package/dist/{chunk-5C5UCILO.js → chunk-N54QZER3.js} +3 -3
- package/dist/{chunk-XEGZ3S2Q.js → chunk-QMFOL2K6.js} +32 -7
- package/dist/chunk-QMFOL2K6.js.map +1 -0
- package/dist/{chunk-KGP6ALIU.js → chunk-XOX6RFPZ.js} +30 -8
- package/dist/chunk-XOX6RFPZ.js.map +1 -0
- package/dist/db/index.cjs +14 -4
- package/dist/db/index.cjs.map +1 -1
- package/dist/db/index.d.cts +1 -1
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.js +3 -3
- package/dist/engine/index.cjs +79 -26
- package/dist/engine/index.cjs.map +1 -1
- package/dist/engine/index.d.cts +3 -3
- package/dist/engine/index.d.ts +3 -3
- package/dist/engine/index.js +5 -5
- package/dist/{index-DgYkdNT9.d.cts → index-BWgnGj68.d.cts} +68 -20
- package/dist/{index-BILC9WwS.d.ts → index-Bi74dcOu.d.ts} +2 -2
- package/dist/{index-BgWnP07a.d.ts → index-Dg10RnZO.d.ts} +68 -20
- package/dist/{index-CWGiJ2Up.d.cts → index-g6iQyYci.d.cts} +2 -2
- package/dist/index.cjs +59 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +5 -5
- package/dist/openapi/index.d.cts +2 -2
- package/dist/openapi/index.d.ts +2 -2
- package/dist/{registry-BcRYIQ-R.d.cts → registry-6VT5RPeO.d.cts} +1 -1
- package/dist/{registry-ClzjxIWy.d.ts → registry-B-sxJJN0.d.ts} +1 -1
- package/dist/test/index.cjs +35 -8
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.d.cts +1 -1
- package/dist/test/index.d.ts +1 -1
- package/dist/test/index.js +7 -5
- package/dist/test/index.js.map +1 -1
- package/docs/README.md +1 -1
- package/docs/auth.md +102 -20
- package/docs/llms-full.txt +129 -21
- package/docs/schema.md +26 -0
- package/package.json +1 -1
- package/template/db/public.ts +23 -0
- package/template/package.json +1 -1
- package/dist/chunk-KGP6ALIU.js.map +0 -1
- package/dist/chunk-XEGZ3S2Q.js.map +0 -1
- /package/dist/{chunk-Z4CZRMNF.js.map → chunk-GOPZPM2A.js.map} +0 -0
- /package/dist/{chunk-5C5UCILO.js.map → chunk-N54QZER3.js.map} +0 -0
|
@@ -839,9 +839,29 @@ function __runWithRuntime(services, fn) {
|
|
|
839
839
|
}, fn);
|
|
840
840
|
}
|
|
841
841
|
__name(__runWithRuntime, "__runWithRuntime");
|
|
842
|
+
var REQUIRED_SERVICES = [
|
|
843
|
+
"Database",
|
|
844
|
+
"Auth",
|
|
845
|
+
"Secrets",
|
|
846
|
+
"Documents",
|
|
847
|
+
"Storage",
|
|
848
|
+
"Cache",
|
|
849
|
+
"Log",
|
|
850
|
+
"Notifications",
|
|
851
|
+
"Flags",
|
|
852
|
+
"Realtime"
|
|
853
|
+
];
|
|
854
|
+
function refuseIncompleteBox(services) {
|
|
855
|
+
const missing = REQUIRED_SERVICES.filter((k) => services[k] === void 0);
|
|
856
|
+
if (missing.length > 0) {
|
|
857
|
+
throw new Error(`the request scope is missing ${missing.join(", ")} \u2014 this box was most likely written by a DIFFERENT @palbase/backend major sharing the same process (the request store is a process-wide well-known Symbol, deliberately, so two bundles of ONE version can share it). Align the versions, or pass every service when building the scope yourself.`);
|
|
858
|
+
}
|
|
859
|
+
return services;
|
|
860
|
+
}
|
|
861
|
+
__name(refuseIncompleteBox, "refuseIncompleteBox");
|
|
842
862
|
function __getRuntime() {
|
|
843
863
|
const scoped = __requestALS.getStore();
|
|
844
|
-
if (scoped) return scoped.runtime;
|
|
864
|
+
if (scoped) return refuseIncompleteBox(scoped.runtime);
|
|
845
865
|
if (runtime === null) {
|
|
846
866
|
throw new Error("Palbase services accessed outside a request scope. The Database/Documents/\u2026 singletons are only available inside an endpoint handler (or after the runtime has called __runWithRuntime / __setRuntime).");
|
|
847
867
|
}
|
|
@@ -964,7 +984,12 @@ function makeTypedSurface(raw) {
|
|
|
964
984
|
$findById: /* @__PURE__ */ __name((table, id) => raw.findById(table, id), "$findById"),
|
|
965
985
|
$findMany: /* @__PURE__ */ __name((table, query, opts) => raw.findMany(table, query, opts), "$findMany"),
|
|
966
986
|
$put: /* @__PURE__ */ __name((table, data, opts) => raw.put(table, data, opts), "$put"),
|
|
967
|
-
|
|
987
|
+
// `opts` İLETİLİR. Düşürüldüğü sürece `Database.$updateMany(t, w, s,
|
|
988
|
+
// { returning: false })` sessizce SATIRLARI döndürüyordu, sayıyı değil —
|
|
989
|
+
// ve `if (n === 0) throw new Conflict(...)` guard'ı HİÇ çalışmıyordu,
|
|
990
|
+
// çünkü `[] === 0` yanlıştır. Komşu forwarder'lar (`$findMany`, `$put`)
|
|
991
|
+
// kendi opsiyonlarını zaten iletiyordu; bu biri unutulmuştu.
|
|
992
|
+
$updateMany: /* @__PURE__ */ __name((table, where, set, opts) => raw.updateMany(table, where, set, opts), "$updateMany"),
|
|
968
993
|
$deleteMany: /* @__PURE__ */ __name((table, where) => raw.deleteMany(table, where), "$deleteMany"),
|
|
969
994
|
$count: /* @__PURE__ */ __name((table, where) => raw.count(table, where), "$count"),
|
|
970
995
|
$search: /* @__PURE__ */ __name((table, params) => raw.search(table, params), "$search"),
|
|
@@ -1095,13 +1120,13 @@ var Flags = Object.assign({
|
|
|
1095
1120
|
* Lazily resolve the runtime's cross-user sibling on each call. We do NOT
|
|
1096
1121
|
* cache it: `rawFlags.asService()` reads the CURRENT request scope through
|
|
1097
1122
|
* the runtime proxy, so caching would leak one request's sibling into
|
|
1098
|
-
* another concurrent request. Mirrors `Database
|
|
1123
|
+
* another concurrent request. Mirrors `Database.$asService()`.
|
|
1099
1124
|
*/
|
|
1100
1125
|
$asService() {
|
|
1101
1126
|
return rawFlags.asService();
|
|
1102
1127
|
},
|
|
1103
1128
|
/**
|
|
1104
|
-
* The name this member carried before
|
|
1129
|
+
* The name this member carried before 32.0.0.
|
|
1105
1130
|
*
|
|
1106
1131
|
* It does NOT work silently. A retired member that quietly keeps returning
|
|
1107
1132
|
* is how a rename becomes a mystery: the old call site goes on compiling,
|
|
@@ -1111,7 +1136,7 @@ var Flags = Object.assign({
|
|
|
1111
1136
|
* Database.$asService()" while the promise went unkept.
|
|
1112
1137
|
*/
|
|
1113
1138
|
asService() {
|
|
1114
|
-
throw new Error("Flags.asService() was renamed to Flags.$asService() in
|
|
1139
|
+
throw new Error("Flags.asService() was renamed to Flags.$asService() in 32.0.0 \u2014 system members carry the `$` prefix, like Database.$asService().");
|
|
1115
1140
|
}
|
|
1116
1141
|
});
|
|
1117
1142
|
var Realtime = makeServiceProxy("Realtime");
|
|
@@ -1486,15 +1511,27 @@ var CODECS = {
|
|
|
1486
1511
|
// not representable and the author wants `asDecimal()` instead.
|
|
1487
1512
|
number: {
|
|
1488
1513
|
fromDb: /* @__PURE__ */ __name((v) => Number(v), "fromDb"),
|
|
1489
|
-
|
|
1514
|
+
// YAZMA YOLUNDA SESSİZ BOZULMA YOK. `toDb(9007199254740993)` bir zamanlar
|
|
1515
|
+
// "9007199254740992" yazıyordu: uygulamanın tuttuğundan BAŞKA bir sayı
|
|
1516
|
+
// kolona gidiyordu ve hiçbir şey söylemiyordu. `asNumber()`'ın JSDoc'u
|
|
1517
|
+
// "2^53'ün altında güvenli" diyordu ama bunu uygulayan bir şey yoktu.
|
|
1518
|
+
toDb: /* @__PURE__ */ __name((v) => {
|
|
1519
|
+
if (typeof v === "number" && !Number.isSafeInteger(v)) {
|
|
1520
|
+
throw new Error(`asNumber() cannot write ${v}: it is outside the safe integer range and would be stored as a different number. Declare .asDecimal() and hand this column a string.`);
|
|
1521
|
+
}
|
|
1522
|
+
return String(v);
|
|
1523
|
+
}, "toDb"),
|
|
1490
1524
|
tsType: "number"
|
|
1491
1525
|
},
|
|
1492
1526
|
// Exact-precision column → the string Postgres sent. It exists so the
|
|
1493
1527
|
// DECLARATION is explicit: "this column is a decimal I handle as text",
|
|
1494
1528
|
// rather than the ABSENCE of a declaration meaning the same thing by default.
|
|
1529
|
+
// NULL güvencesi ÇİFTİN KENDİSİNDE. Motor NULL'ı zaten atlıyor (üç ayrı
|
|
1530
|
+
// yerde), ama bir değişmez, onu koruyan üç `if`'in DIŞINDA yazılmalı: biri
|
|
1531
|
+
// düşerse `String(null)` kolonu "null" dizesine çevirirdi.
|
|
1495
1532
|
decimal: {
|
|
1496
|
-
fromDb: /* @__PURE__ */ __name((v) => String(v), "fromDb"),
|
|
1497
|
-
toDb: /* @__PURE__ */ __name((v) => String(v), "toDb"),
|
|
1533
|
+
fromDb: /* @__PURE__ */ __name((v) => v === null || v === void 0 ? v : String(v), "fromDb"),
|
|
1534
|
+
toDb: /* @__PURE__ */ __name((v) => v === null || v === void 0 ? v : String(v), "toDb"),
|
|
1498
1535
|
tsType: "string"
|
|
1499
1536
|
}
|
|
1500
1537
|
};
|
|
@@ -4766,23 +4803,21 @@ async function createApp(opts) {
|
|
|
4766
4803
|
if (spec.required && !callerClaims) {
|
|
4767
4804
|
return envelope("unauthorized", "A valid access token is required", 401, requestId);
|
|
4768
4805
|
}
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
if (refusal) {
|
|
4778
|
-
await uploadDb.commit();
|
|
4779
|
-
return refusal;
|
|
4780
|
-
}
|
|
4806
|
+
const uploadDb = createRequestDatabase(sql, {
|
|
4807
|
+
role: config.dbRole,
|
|
4808
|
+
serviceRole: config.dbServiceRole,
|
|
4809
|
+
claimsJson: JSON.stringify(callerClaims ?? {})
|
|
4810
|
+
});
|
|
4811
|
+
try {
|
|
4812
|
+
const refusal = await refuseUnlessAuthorized(uploadDb.client, spec, requestId, makeRoleResolver(uploadDb.client));
|
|
4813
|
+
if (refusal) {
|
|
4781
4814
|
await uploadDb.commit();
|
|
4782
|
-
|
|
4783
|
-
await uploadDb.rollback(err);
|
|
4784
|
-
throw err;
|
|
4815
|
+
return refusal;
|
|
4785
4816
|
}
|
|
4817
|
+
await uploadDb.commit();
|
|
4818
|
+
} catch (err) {
|
|
4819
|
+
await uploadDb.rollback(err);
|
|
4820
|
+
throw err;
|
|
4786
4821
|
}
|
|
4787
4822
|
const grant = grantFor(target?.entry, {
|
|
4788
4823
|
userId: typeof callerClaims?.sub === "string" ? callerClaims.sub : null,
|
|
@@ -4821,11 +4856,18 @@ async function createApp(opts) {
|
|
|
4821
4856
|
claimsJson: JSON.stringify(claims ?? {})
|
|
4822
4857
|
});
|
|
4823
4858
|
const rolesOfCaller = makeRoleResolver(db.client);
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
if (
|
|
4859
|
+
let dispatched = false;
|
|
4860
|
+
const settleBeforeRefusal = /* @__PURE__ */ __name(async () => {
|
|
4861
|
+
if (dispatched) return;
|
|
4862
|
+
await db.rollback(new Error("request refused before dispatch"));
|
|
4863
|
+
}, "settleBeforeRefusal");
|
|
4864
|
+
const refusal = await refuseUnlessAuthorized(db.client, spec, requestId, rolesOfCaller);
|
|
4865
|
+
if (refusal) {
|
|
4866
|
+
await settleBeforeRefusal();
|
|
4867
|
+
return refusal;
|
|
4827
4868
|
}
|
|
4828
4869
|
if (claims && spec.verifiedEmail && claims.email_verified !== true) {
|
|
4870
|
+
await settleBeforeRefusal();
|
|
4829
4871
|
return envelope("email_not_verified", "A verified email address is required", 403, requestId);
|
|
4830
4872
|
}
|
|
4831
4873
|
let parsedBody;
|
|
@@ -4834,6 +4876,7 @@ async function createApp(opts) {
|
|
|
4834
4876
|
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
4835
4877
|
const declared = Number(req.headers.get("content-length") ?? "");
|
|
4836
4878
|
if (Number.isFinite(declared) && declared > MAX_BODY_BYTES) {
|
|
4879
|
+
await settleBeforeRefusal();
|
|
4837
4880
|
return new Response(JSON.stringify({
|
|
4838
4881
|
error: "payload_too_large",
|
|
4839
4882
|
error_description: `Request body exceeds ${MAX_BODY_BYTES} bytes`,
|
|
@@ -4846,6 +4889,7 @@ async function createApp(opts) {
|
|
|
4846
4889
|
}
|
|
4847
4890
|
const buf = await req.arrayBuffer().catch(() => null);
|
|
4848
4891
|
if (buf && buf.byteLength > MAX_BODY_BYTES) {
|
|
4892
|
+
await settleBeforeRefusal();
|
|
4849
4893
|
return new Response(JSON.stringify({
|
|
4850
4894
|
error: "payload_too_large",
|
|
4851
4895
|
error_description: `Request body exceeds ${MAX_BODY_BYTES} bytes`,
|
|
@@ -4881,6 +4925,7 @@ async function createApp(opts) {
|
|
|
4881
4925
|
...JSON_HEADERS
|
|
4882
4926
|
};
|
|
4883
4927
|
if (attest.nextChallenge) headers["x-palbase-attest-challenge"] = attest.nextChallenge;
|
|
4928
|
+
await settleBeforeRefusal();
|
|
4884
4929
|
return new Response(JSON.stringify({
|
|
4885
4930
|
error: attest.reason,
|
|
4886
4931
|
error_description: "This endpoint requires a verified app instance",
|
|
@@ -4895,6 +4940,7 @@ async function createApp(opts) {
|
|
|
4895
4940
|
attestChallengeOut = attest.nextChallenge;
|
|
4896
4941
|
const retryAfter = limiter.check(meta.options?.rateLimit, RateLimiter.key(hit.entry.id, userId, req.headers), Date.now());
|
|
4897
4942
|
if (retryAfter !== null) {
|
|
4943
|
+
await settleBeforeRefusal();
|
|
4898
4944
|
return new Response(JSON.stringify({
|
|
4899
4945
|
error: "too_many_requests",
|
|
4900
4946
|
error_description: "Rate limit exceeded for this endpoint",
|
|
@@ -4926,6 +4972,7 @@ async function createApp(opts) {
|
|
|
4926
4972
|
readParsedBody();
|
|
4927
4973
|
const r = p.schema.safeParse(parsedBody);
|
|
4928
4974
|
if (!r.success) {
|
|
4975
|
+
await settleBeforeRefusal();
|
|
4929
4976
|
return envelope("bad_request", "Request body failed validation", 400, requestId, {
|
|
4930
4977
|
data: {
|
|
4931
4978
|
fields: fieldErrors(r.error)
|
|
@@ -4938,6 +4985,7 @@ async function createApp(opts) {
|
|
|
4938
4985
|
case "query": {
|
|
4939
4986
|
const r = p.schema.safeParse(Object.fromEntries(url.searchParams));
|
|
4940
4987
|
if (!r.success) {
|
|
4988
|
+
await settleBeforeRefusal();
|
|
4941
4989
|
return envelope("bad_request", "Query parameters failed validation", 400, requestId, {
|
|
4942
4990
|
data: {
|
|
4943
4991
|
fields: fieldErrors(r.error)
|
|
@@ -4958,6 +5006,7 @@ async function createApp(opts) {
|
|
|
4958
5006
|
}
|
|
4959
5007
|
const r = p.schema.safeParse(headersFor(raw, p.schema));
|
|
4960
5008
|
if (!r.success) {
|
|
5009
|
+
await settleBeforeRefusal();
|
|
4961
5010
|
return envelope("bad_request", "Request headers failed validation", 400, requestId, {
|
|
4962
5011
|
data: {
|
|
4963
5012
|
fields: fieldErrors(r.error)
|
|
@@ -4990,17 +5039,20 @@ async function createApp(opts) {
|
|
|
4990
5039
|
break;
|
|
4991
5040
|
case "uploadedObject": {
|
|
4992
5041
|
if (uploadSecret === "" || !verifySignature(req.headers.get(SIGNATURE_HEADER) ?? "", uploadSecret)) {
|
|
5042
|
+
await settleBeforeRefusal();
|
|
4993
5043
|
return envelope("unauthorized", "This endpoint accepts uploads through storage, not directly", 401, requestId);
|
|
4994
5044
|
}
|
|
4995
5045
|
readParsedBody();
|
|
4996
5046
|
const envelopeIn = parsedBody;
|
|
4997
5047
|
if (!envelopeIn?.uploadedObject) {
|
|
5048
|
+
await settleBeforeRefusal();
|
|
4998
5049
|
return envelope("bad_request", "the completion call carried no uploaded object", 400, requestId);
|
|
4999
5050
|
}
|
|
5000
5051
|
const uploadId = envelopeIn.uploadedObject.uploadId;
|
|
5001
5052
|
if (typeof uploadId === "string" && uploadId !== "") {
|
|
5002
5053
|
const already = completions.recall(uploadId);
|
|
5003
5054
|
if (already) {
|
|
5055
|
+
await settleBeforeRefusal();
|
|
5004
5056
|
return new Response(already.body, {
|
|
5005
5057
|
status: already.status,
|
|
5006
5058
|
headers: already.contentType ? {
|
|
@@ -5050,6 +5102,7 @@ async function createApp(opts) {
|
|
|
5050
5102
|
args[p.index] = void 0;
|
|
5051
5103
|
}
|
|
5052
5104
|
}
|
|
5105
|
+
dispatched = true;
|
|
5053
5106
|
try {
|
|
5054
5107
|
const services = buildServices(db);
|
|
5055
5108
|
if (meta.options?.sseConfig !== void 0) {
|