tina4-nodejs 3.13.138 → 3.13.140
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/CLAUDE.md +2 -2
- package/package.json +1 -1
- package/packages/cli/dist/bin.js +683 -329
- package/packages/cli/src/commands/generate.ts +7 -13
- package/packages/core/dist/index.js +688 -329
- package/packages/core/src/api.ts +33 -4
- package/packages/core/src/auth.ts +73 -6
- package/packages/core/src/authGate.ts +27 -20
- package/packages/core/src/graphql.ts +92 -0
- package/packages/core/src/index.ts +1 -0
- package/packages/core/src/job.ts +8 -1
- package/packages/core/src/mcp.ts +3 -1
- package/packages/core/src/middleware.ts +4 -2
- package/packages/core/src/push.ts +16 -0
- package/packages/core/src/queue.ts +15 -2
- package/packages/core/src/queueBackends/liteBackend.ts +15 -0
- package/packages/core/src/queueBackends/mongoBackend.ts +40 -6
- package/packages/core/src/server.ts +6 -1
- package/packages/core/src/sessionHandlers/databaseHandler.ts +60 -3
- package/packages/core/src/sso.ts +12 -4
- package/packages/core/src/ssoIdentity.ts +19 -0
- package/packages/core/src/ssrf.ts +221 -0
- package/packages/core/src/testClient.ts +3 -0
- package/packages/core/src/websocket.ts +4 -2
- package/packages/frond/dist/index.js +6 -0
- package/packages/frond/src/engine.ts +9 -0
- package/packages/orm/dist/index.js +675 -320
- package/packages/orm/src/adapters/firebird.ts +14 -45
- package/packages/orm/src/adapters/mssql.ts +21 -58
- package/packages/orm/src/adapters/mysql.ts +10 -49
- package/packages/orm/src/adapters/odbc.ts +11 -42
- package/packages/orm/src/adapters/postgres.ts +20 -60
- package/packages/orm/src/adapters/sqlCrud.ts +178 -0
- package/packages/orm/src/adapters/sqlite.ts +46 -66
- package/packages/orm/src/index.ts +1 -1
- package/types/core/src/api.d.ts +6 -0
- package/types/core/src/auth.d.ts +30 -0
- package/types/core/src/graphql.d.ts +25 -0
- package/types/core/src/index.d.ts +1 -0
- package/types/core/src/job.d.ts +1 -0
- package/types/core/src/push.d.ts +5 -0
- package/types/core/src/queue.d.ts +7 -1
- package/types/core/src/queueBackends/liteBackend.d.ts +9 -0
- package/types/core/src/queueBackends/mongoBackend.d.ts +8 -1
- package/types/core/src/sessionHandlers/databaseHandler.d.ts +25 -1
- package/types/core/src/sso.d.ts +1 -0
- package/types/core/src/ssoIdentity.d.ts +10 -0
- package/types/core/src/ssrf.d.ts +19 -0
- package/types/orm/src/adapters/sqlCrud.d.ts +90 -0
- package/types/orm/src/adapters/sqlite.d.ts +17 -0
- package/types/orm/src/index.d.ts +1 -1
|
@@ -2612,6 +2612,8 @@ var auth_exports = {};
|
|
|
2612
2612
|
__export(auth_exports, {
|
|
2613
2613
|
Auth: () => Auth,
|
|
2614
2614
|
JWT_LEEWAY_SECONDS: () => JWT_LEEWAY_SECONDS,
|
|
2615
|
+
MIN_SECRET_BYTES: () => MIN_SECRET_BYTES,
|
|
2616
|
+
NON_IDENTITY_TOKEN_TYPES: () => NON_IDENTITY_TOKEN_TYPES,
|
|
2615
2617
|
algorithmAvailable: () => algorithmAvailable,
|
|
2616
2618
|
authMiddleware: () => authMiddleware,
|
|
2617
2619
|
authenticateRequest: () => authenticateRequest,
|
|
@@ -2621,7 +2623,10 @@ __export(auth_exports, {
|
|
|
2621
2623
|
getPayload: () => getPayload,
|
|
2622
2624
|
getToken: () => getToken,
|
|
2623
2625
|
hashPassword: () => hashPassword,
|
|
2626
|
+
insecureSecretMessage: () => insecureSecretMessage,
|
|
2627
|
+
isIdentityPayload: () => isIdentityPayload,
|
|
2624
2628
|
refreshToken: () => refreshToken,
|
|
2629
|
+
requireBootSecret: () => requireBootSecret,
|
|
2625
2630
|
resolveAlgorithm: () => resolveAlgorithm,
|
|
2626
2631
|
validToken: () => validToken,
|
|
2627
2632
|
validateApiKey: () => validateApiKey
|
|
@@ -2657,6 +2662,23 @@ async function _logWarning(message) {
|
|
|
2657
2662
|
function _warnBlankSecret() {
|
|
2658
2663
|
void _logWarning(BLANK_SECRET_WARNING);
|
|
2659
2664
|
}
|
|
2665
|
+
function isIdentityPayload(payload) {
|
|
2666
|
+
return payload != null && !NON_IDENTITY_TOKEN_TYPES.includes(payload.type);
|
|
2667
|
+
}
|
|
2668
|
+
function insecureSecretMessage(secret) {
|
|
2669
|
+
const length = Buffer.byteLength(secret ?? "", "utf8");
|
|
2670
|
+
if (length >= MIN_SECRET_BYTES) return null;
|
|
2671
|
+
const what = length === 0 ? "is not set" : `is ${length} bytes`;
|
|
2672
|
+
return `Auth: TINA4_SECRET ${what}; an HMAC JWT secret must be at least ${MIN_SECRET_BYTES} bytes. Generate one with \`openssl rand -hex 32\` and set TINA4_SECRET in your environment or .env.`;
|
|
2673
|
+
}
|
|
2674
|
+
function requireBootSecret() {
|
|
2675
|
+
const algorithm = (process.env.TINA4_JWT_ALGORITHM ?? "").trim() || "HS256";
|
|
2676
|
+
if (RSA_SIGN_ALGORITHMS.has(algorithm)) return;
|
|
2677
|
+
const secret = process.env.TINA4_SECRET ?? "";
|
|
2678
|
+
if (secret === "" && _isDev()) return;
|
|
2679
|
+
const message = insecureSecretMessage(secret);
|
|
2680
|
+
if (message) throw new Error(message);
|
|
2681
|
+
}
|
|
2660
2682
|
function ensureDevSecret(cwd) {
|
|
2661
2683
|
if (process.env.TINA4_SECRET) return null;
|
|
2662
2684
|
if (!_isDev() || _isCi() || _isProduction()) {
|
|
@@ -2787,6 +2809,13 @@ function validToken(token, secret, algorithm) {
|
|
|
2787
2809
|
const [h, p, sig] = parts;
|
|
2788
2810
|
const header = JSON.parse(base64urlDecode(h).toString());
|
|
2789
2811
|
if (header.alg !== resolvedAlgorithm) return null;
|
|
2812
|
+
if (HMAC_DIGESTS.has(resolvedAlgorithm)) {
|
|
2813
|
+
const weak = insecureSecretMessage(resolvedSecret);
|
|
2814
|
+
if (weak) {
|
|
2815
|
+
void _logWarning(weak);
|
|
2816
|
+
return null;
|
|
2817
|
+
}
|
|
2818
|
+
}
|
|
2790
2819
|
const signingInput = `${h}.${p}`;
|
|
2791
2820
|
if (!verifySignature(signingInput, sig, resolvedSecret, resolvedAlgorithm)) {
|
|
2792
2821
|
return null;
|
|
@@ -2818,6 +2847,8 @@ function getPayload(token) {
|
|
|
2818
2847
|
function sign(input, secret, algorithm) {
|
|
2819
2848
|
const digest = HMAC_DIGESTS.get(algorithm);
|
|
2820
2849
|
if (digest) {
|
|
2850
|
+
const weak = insecureSecretMessage(secret);
|
|
2851
|
+
if (weak) throw new Error(weak);
|
|
2821
2852
|
return base64urlEncode(createHmac(digest, secret).update(input).digest());
|
|
2822
2853
|
}
|
|
2823
2854
|
const rsaAlgorithm = RSA_SIGN_ALGORITHMS.get(algorithm);
|
|
@@ -2875,17 +2906,17 @@ function authMiddleware(secret, algorithm) {
|
|
|
2875
2906
|
return;
|
|
2876
2907
|
}
|
|
2877
2908
|
const token = authHeader.slice(7);
|
|
2878
|
-
|
|
2909
|
+
const payload = validToken(token, secret, algorithm);
|
|
2910
|
+
if (!isIdentityPayload(payload)) {
|
|
2879
2911
|
res({ error: "Unauthorized" }, 401);
|
|
2880
2912
|
return;
|
|
2881
2913
|
}
|
|
2882
|
-
req2.auth =
|
|
2914
|
+
req2.auth = payload;
|
|
2883
2915
|
next();
|
|
2884
2916
|
};
|
|
2885
2917
|
}
|
|
2886
2918
|
function refreshToken(token, expiresIn = 60) {
|
|
2887
|
-
|
|
2888
|
-
const payload = getPayload(token);
|
|
2919
|
+
const payload = validToken(token);
|
|
2889
2920
|
if (!payload) return null;
|
|
2890
2921
|
const { iat: _iat, exp: _exp, ...claims } = payload;
|
|
2891
2922
|
return getToken(claims, expiresIn);
|
|
@@ -2894,7 +2925,8 @@ function authenticateRequest(headers, secret, algorithm) {
|
|
|
2894
2925
|
const authHeader = headers.authorization ?? headers.Authorization ?? "";
|
|
2895
2926
|
if (!authHeader.startsWith("Bearer ")) return null;
|
|
2896
2927
|
const token = authHeader.slice(7);
|
|
2897
|
-
|
|
2928
|
+
const payload = validToken(token, secret, algorithm);
|
|
2929
|
+
if (payload) return isIdentityPayload(payload) ? payload : null;
|
|
2898
2930
|
if (validateApiKey(token)) {
|
|
2899
2931
|
return { _auth: "api_key" };
|
|
2900
2932
|
}
|
|
@@ -2908,12 +2940,14 @@ function validateApiKey(provided, expected) {
|
|
|
2908
2940
|
if (a.length !== b.length) return false;
|
|
2909
2941
|
return timingSafeEqual(a, b);
|
|
2910
2942
|
}
|
|
2911
|
-
var BLANK_SECRET_WARNING, HMAC_DIGESTS, RSA_SIGN_ALGORITHMS, KNOWN_ALGORITHMS, ALGORITHM_REMEDY, capabilityCache, JWT_LEEWAY_SECONDS, Auth;
|
|
2943
|
+
var BLANK_SECRET_WARNING, NON_IDENTITY_TOKEN_TYPES, MIN_SECRET_BYTES, HMAC_DIGESTS, RSA_SIGN_ALGORITHMS, KNOWN_ALGORITHMS, ALGORITHM_REMEDY, capabilityCache, JWT_LEEWAY_SECONDS, Auth;
|
|
2912
2944
|
var init_auth = __esm({
|
|
2913
2945
|
"../core/src/auth.ts"() {
|
|
2914
2946
|
"use strict";
|
|
2915
2947
|
init_dotenv();
|
|
2916
2948
|
BLANK_SECRET_WARNING = "Auth: TINA4_SECRET is not set \u2014 JWT signing is insecure. Set TINA4_SECRET to a random value (e.g. `openssl rand -hex 32`) in your environment or .env before serving traffic. For LOCAL DEV, set TINA4_DEBUG=true and a per-machine secret is generated automatically into .env.local (gitignored). Seeing this warning means the run was NOT detected as dev \u2014 typically a container or CI without TINA4_DEBUG set, or TINA4_ENV=production.";
|
|
2949
|
+
NON_IDENTITY_TOKEN_TYPES = ["form"];
|
|
2950
|
+
MIN_SECRET_BYTES = 32;
|
|
2917
2951
|
HMAC_DIGESTS = /* @__PURE__ */ new Map([
|
|
2918
2952
|
["HS256", "sha256"],
|
|
2919
2953
|
["HS384", "sha384"],
|
|
@@ -3296,9 +3330,9 @@ var init_cache = __esm({
|
|
|
3296
3330
|
return entry.value;
|
|
3297
3331
|
}
|
|
3298
3332
|
async set(key, value, ttl) {
|
|
3299
|
-
const
|
|
3333
|
+
const expiresAt2 = ttl > 0 ? Date.now() + ttl * 1e3 : 0;
|
|
3300
3334
|
this.store.delete(key);
|
|
3301
|
-
this.store.set(key, { value, expiresAt });
|
|
3335
|
+
this.store.set(key, { value, expiresAt: expiresAt2 });
|
|
3302
3336
|
while (this.store.size > this.maxEntries) {
|
|
3303
3337
|
const firstKey = this.store.keys().next().value;
|
|
3304
3338
|
if (firstKey !== void 0) this.store.delete(firstKey);
|
|
@@ -3731,8 +3765,8 @@ ${s}\r
|
|
|
3731
3765
|
const oldest = files.shift();
|
|
3732
3766
|
if (oldest) fs.unlinkSync(path.join(this.dir, oldest.name));
|
|
3733
3767
|
}
|
|
3734
|
-
const
|
|
3735
|
-
const entry = { key, value, expiresAt };
|
|
3768
|
+
const expiresAt2 = ttl > 0 ? Date.now() / 1e3 + ttl : 0;
|
|
3769
|
+
const entry = { key, value, expiresAt: expiresAt2 };
|
|
3736
3770
|
fs.writeFileSync(this.keyPath(key), JSON.stringify(entry));
|
|
3737
3771
|
} catch {
|
|
3738
3772
|
}
|
|
@@ -5479,8 +5513,8 @@ function capCache(cache, maxEntries) {
|
|
|
5479
5513
|
}
|
|
5480
5514
|
function sweepExpiredCache(cache) {
|
|
5481
5515
|
const now = Date.now();
|
|
5482
|
-
for (const [key, [,
|
|
5483
|
-
if (
|
|
5516
|
+
for (const [key, [, expiresAt2]] of cache) {
|
|
5517
|
+
if (expiresAt2 <= now) cache.delete(key);
|
|
5484
5518
|
}
|
|
5485
5519
|
}
|
|
5486
5520
|
function tokenize(source) {
|
|
@@ -5726,15 +5760,15 @@ function findOutsideQuotes(expr, needle) {
|
|
|
5726
5760
|
}
|
|
5727
5761
|
return -1;
|
|
5728
5762
|
}
|
|
5729
|
-
function splitOutsideQuotes(expr,
|
|
5730
|
-
if (!expr.includes(
|
|
5763
|
+
function splitOutsideQuotes(expr, sep9) {
|
|
5764
|
+
if (!expr.includes(sep9)) return [expr];
|
|
5731
5765
|
const parts = [];
|
|
5732
5766
|
let currentStart = 0;
|
|
5733
5767
|
let inQuote = null;
|
|
5734
5768
|
let depth = 0;
|
|
5735
5769
|
let bracketDepth = 0;
|
|
5736
5770
|
let i = 0;
|
|
5737
|
-
const sepLen =
|
|
5771
|
+
const sepLen = sep9.length;
|
|
5738
5772
|
const lastStart = expr.length - sepLen;
|
|
5739
5773
|
while (i <= lastStart) {
|
|
5740
5774
|
const ch = expr[i];
|
|
@@ -5755,7 +5789,7 @@ function splitOutsideQuotes(expr, sep8) {
|
|
|
5755
5789
|
else if (ch === ")") depth--;
|
|
5756
5790
|
else if (ch === "[") bracketDepth++;
|
|
5757
5791
|
else if (ch === "]") bracketDepth--;
|
|
5758
|
-
if (depth === 0 && bracketDepth === 0 && expr.startsWith(
|
|
5792
|
+
if (depth === 0 && bracketDepth === 0 && expr.startsWith(sep9, i)) {
|
|
5759
5793
|
parts.push(expr.slice(currentStart, i));
|
|
5760
5794
|
i += sepLen;
|
|
5761
5795
|
currentStart = i;
|
|
@@ -6452,6 +6486,12 @@ function setFormTokenSessionId(sessionId) {
|
|
|
6452
6486
|
}
|
|
6453
6487
|
function _buildFormTokenJwt(descriptor = "") {
|
|
6454
6488
|
const secret = process.env.TINA4_SECRET ?? "";
|
|
6489
|
+
const secretBytes = Buffer.byteLength(secret, "utf8");
|
|
6490
|
+
if (secretBytes < 32) {
|
|
6491
|
+
throw new Error(
|
|
6492
|
+
`Auth: TINA4_SECRET ${secretBytes === 0 ? "is not set" : `is ${secretBytes} bytes`}; an HMAC JWT secret must be at least 32 bytes. Generate one with \`openssl rand -hex 32\` and set TINA4_SECRET in your environment or .env.`
|
|
6493
|
+
);
|
|
6494
|
+
}
|
|
6455
6495
|
const ttlMinutes = parseInt(process.env.TINA4_TOKEN_LIMIT || "60", 10);
|
|
6456
6496
|
const header = { alg: "HS256", typ: "JWT" };
|
|
6457
6497
|
const now = Math.floor(Date.now() / 1e3);
|
|
@@ -6702,8 +6742,8 @@ var init_engine = __esm({
|
|
|
6702
6742
|
},
|
|
6703
6743
|
first: (v) => Array.isArray(v) ? v[0] ?? null : null,
|
|
6704
6744
|
last: (v) => Array.isArray(v) ? v[v.length - 1] ?? null : null,
|
|
6705
|
-
join: (v,
|
|
6706
|
-
split: (v,
|
|
6745
|
+
join: (v, sep9) => Array.isArray(v) ? v.map(String).join(sep9 !== void 0 ? String(sep9) : ", ") : String(v),
|
|
6746
|
+
split: (v, sep9) => String(v).split(sep9 !== void 0 ? String(sep9) : " "),
|
|
6707
6747
|
replace: (v, from, to) => {
|
|
6708
6748
|
const s = String(v);
|
|
6709
6749
|
if (from !== void 0 && typeof from === "object" && from !== null && !Array.isArray(from)) {
|
|
@@ -7993,8 +8033,8 @@ var init_engine = __esm({
|
|
|
7993
8033
|
sweepExpiredCache(this.fragmentCache);
|
|
7994
8034
|
const cached = this.fragmentCache.get(cacheKey);
|
|
7995
8035
|
if (cached) {
|
|
7996
|
-
const [htmlContent,
|
|
7997
|
-
if (Date.now() <
|
|
8036
|
+
const [htmlContent, expiresAt2] = cached;
|
|
8037
|
+
if (Date.now() < expiresAt2) {
|
|
7998
8038
|
let i2 = start2 + 1;
|
|
7999
8039
|
let depth = 0;
|
|
8000
8040
|
while (i2 < tokens.length) {
|
|
@@ -8787,7 +8827,7 @@ function csrfBearerIsValid(req2, secret) {
|
|
|
8787
8827
|
const authHeader = req2.headers.authorization ?? "";
|
|
8788
8828
|
if (!authHeader.startsWith("Bearer ")) return false;
|
|
8789
8829
|
const bearerToken = authHeader.slice(7).trim();
|
|
8790
|
-
return Boolean(bearerToken && validToken(bearerToken, secret));
|
|
8830
|
+
return Boolean(bearerToken && isIdentityPayload(validToken(bearerToken, secret)));
|
|
8791
8831
|
}
|
|
8792
8832
|
function csrfRequestToken(req2) {
|
|
8793
8833
|
const query = req2.query ?? {};
|
|
@@ -9924,6 +9964,22 @@ var init_transport = __esm({
|
|
|
9924
9964
|
}
|
|
9925
9965
|
});
|
|
9926
9966
|
|
|
9967
|
+
// ../core/src/ssoIdentity.ts
|
|
9968
|
+
function liveSessionIdentity(stored) {
|
|
9969
|
+
if (!stored || typeof stored !== "object") return null;
|
|
9970
|
+
const identity = stored.identity;
|
|
9971
|
+
if (!identity || typeof identity !== "object" || !identity.issuer || !identity.subject) return null;
|
|
9972
|
+
const expiresAt2 = stored.expires_at ?? 0;
|
|
9973
|
+
if (typeof expiresAt2 !== "number") return null;
|
|
9974
|
+
if (expiresAt2 > 0 && Math.floor(Date.now() / 1e3) >= expiresAt2) return null;
|
|
9975
|
+
return identity;
|
|
9976
|
+
}
|
|
9977
|
+
var init_ssoIdentity = __esm({
|
|
9978
|
+
"../core/src/ssoIdentity.ts"() {
|
|
9979
|
+
"use strict";
|
|
9980
|
+
}
|
|
9981
|
+
});
|
|
9982
|
+
|
|
9927
9983
|
// ../core/src/authGate.ts
|
|
9928
9984
|
function enforceRouteAuth(req2, res, match, isDevAdmin) {
|
|
9929
9985
|
if (!(match.secure === true && match.noAuth !== true && !isDevAdmin)) {
|
|
@@ -9931,38 +9987,50 @@ function enforceRouteAuth(req2, res, match, isDevAdmin) {
|
|
|
9931
9987
|
}
|
|
9932
9988
|
const authHeader = req2.headers.authorization ?? "";
|
|
9933
9989
|
const headerToken = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : "";
|
|
9934
|
-
let
|
|
9990
|
+
let payload = null;
|
|
9935
9991
|
let tokenSource = "";
|
|
9936
|
-
|
|
9937
|
-
|
|
9938
|
-
|
|
9992
|
+
let resolvedToken = "";
|
|
9993
|
+
if (headerToken) {
|
|
9994
|
+
const candidate = validToken(headerToken);
|
|
9995
|
+
if (isIdentityPayload(candidate)) {
|
|
9996
|
+
payload = candidate;
|
|
9997
|
+
tokenSource = "header";
|
|
9998
|
+
resolvedToken = headerToken;
|
|
9999
|
+
}
|
|
9939
10000
|
}
|
|
9940
|
-
if (!
|
|
10001
|
+
if (!payload) {
|
|
9941
10002
|
const bodyToken = req2.body?.formToken;
|
|
9942
|
-
if (bodyToken
|
|
9943
|
-
|
|
9944
|
-
|
|
10003
|
+
if (bodyToken) {
|
|
10004
|
+
const candidate = validToken(bodyToken);
|
|
10005
|
+
if (isIdentityPayload(candidate)) {
|
|
10006
|
+
payload = candidate;
|
|
10007
|
+
tokenSource = "body";
|
|
10008
|
+
resolvedToken = bodyToken;
|
|
10009
|
+
}
|
|
9945
10010
|
}
|
|
9946
10011
|
}
|
|
9947
|
-
if (!
|
|
9948
|
-
const
|
|
9949
|
-
|
|
9950
|
-
if (identity?.issuer && identity?.subject) {
|
|
10012
|
+
if (!payload) {
|
|
10013
|
+
const identity = liveSessionIdentity(req2.session?.get?.("_tina4_sso"));
|
|
10014
|
+
if (identity) {
|
|
9951
10015
|
req2.user = identity;
|
|
9952
10016
|
return rbacForbidden(match, identity, res);
|
|
9953
10017
|
}
|
|
9954
10018
|
const sessionToken = req2.session?.get?.("token");
|
|
9955
|
-
if (sessionToken
|
|
9956
|
-
|
|
9957
|
-
|
|
10019
|
+
if (sessionToken) {
|
|
10020
|
+
const candidate = validToken(sessionToken);
|
|
10021
|
+
if (isIdentityPayload(candidate)) {
|
|
10022
|
+
payload = candidate;
|
|
10023
|
+
tokenSource = "session";
|
|
10024
|
+
resolvedToken = sessionToken;
|
|
10025
|
+
}
|
|
9958
10026
|
}
|
|
9959
10027
|
}
|
|
9960
|
-
if (!
|
|
10028
|
+
if (!payload) {
|
|
9961
10029
|
res.raw.writeHead(401, { "Content-Type": "application/json" });
|
|
9962
10030
|
res.raw.end(JSON.stringify({ error: "Unauthorized" }));
|
|
9963
10031
|
return true;
|
|
9964
10032
|
}
|
|
9965
|
-
req2.user =
|
|
10033
|
+
req2.user = payload;
|
|
9966
10034
|
if (tokenSource === "body") {
|
|
9967
10035
|
const fresh = refreshToken(resolvedToken);
|
|
9968
10036
|
if (fresh) {
|
|
@@ -10012,6 +10080,7 @@ var init_authGate = __esm({
|
|
|
10012
10080
|
"../core/src/authGate.ts"() {
|
|
10013
10081
|
"use strict";
|
|
10014
10082
|
init_auth();
|
|
10083
|
+
init_ssoIdentity();
|
|
10015
10084
|
}
|
|
10016
10085
|
});
|
|
10017
10086
|
|
|
@@ -11085,8 +11154,8 @@ var init_mongoHandler = __esm({
|
|
|
11085
11154
|
if (!result || result === "__EMPTY__") return null;
|
|
11086
11155
|
try {
|
|
11087
11156
|
const doc = JSON.parse(result);
|
|
11088
|
-
const
|
|
11089
|
-
if (
|
|
11157
|
+
const expiresAt2 = Number(doc?.expires_at ?? 0);
|
|
11158
|
+
if (expiresAt2 > 0 && Date.now() / 1e3 > expiresAt2) {
|
|
11090
11159
|
this.destroy(sessionId);
|
|
11091
11160
|
return null;
|
|
11092
11161
|
}
|
|
@@ -11312,6 +11381,9 @@ parentPort.on("message", (message) => {
|
|
|
11312
11381
|
|
|
11313
11382
|
// ../core/src/sessionHandlers/databaseHandler.ts
|
|
11314
11383
|
import { DatabaseSync } from "node:sqlite";
|
|
11384
|
+
function sleepSync2(ms) {
|
|
11385
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
11386
|
+
}
|
|
11315
11387
|
function column(row, name) {
|
|
11316
11388
|
if (name in row) return row[name];
|
|
11317
11389
|
return row[name.toUpperCase()];
|
|
@@ -11320,7 +11392,7 @@ function schemeOf2(url) {
|
|
|
11320
11392
|
const match = url.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):/);
|
|
11321
11393
|
return match ? match[1].toLowerCase() : "unknown";
|
|
11322
11394
|
}
|
|
11323
|
-
var CREATE_TABLE, DatabaseSessionHandler;
|
|
11395
|
+
var CREATE_TABLE, CREATE_ATTEMPTS, CREATE_RETRY_DELAY_MS, DatabaseSessionHandler;
|
|
11324
11396
|
var init_databaseHandler = __esm({
|
|
11325
11397
|
"../core/src/sessionHandlers/databaseHandler.ts"() {
|
|
11326
11398
|
"use strict";
|
|
@@ -11400,6 +11472,8 @@ var init_databaseHandler = __esm({
|
|
|
11400
11472
|
END
|
|
11401
11473
|
`
|
|
11402
11474
|
};
|
|
11475
|
+
CREATE_ATTEMPTS = 5;
|
|
11476
|
+
CREATE_RETRY_DELAY_MS = 50;
|
|
11403
11477
|
DatabaseSessionHandler = class {
|
|
11404
11478
|
sqliteHandle = null;
|
|
11405
11479
|
/** Set for SQLite. Null when this handler talks to a networked engine. */
|
|
@@ -11440,6 +11514,7 @@ var init_databaseHandler = __esm({
|
|
|
11440
11514
|
get sqlite() {
|
|
11441
11515
|
if (this.sqliteHandle === null) {
|
|
11442
11516
|
this.sqliteHandle = new DatabaseSync(this.dbPath);
|
|
11517
|
+
this.sqliteHandle.exec("PRAGMA busy_timeout = 5000");
|
|
11443
11518
|
this.sqliteHandle.exec("PRAGMA journal_mode = WAL");
|
|
11444
11519
|
}
|
|
11445
11520
|
return this.sqliteHandle;
|
|
@@ -11527,15 +11602,55 @@ var init_databaseHandler = __esm({
|
|
|
11527
11602
|
sqlCommandSync(this.target, sql, params);
|
|
11528
11603
|
}
|
|
11529
11604
|
/**
|
|
11530
|
-
* Ensure the session table exists (called once on first use)
|
|
11605
|
+
* Ensure the session table exists (called once on first use) - safely when
|
|
11606
|
+
* several processes race to create it.
|
|
11607
|
+
*
|
|
11608
|
+
* Every per-engine statement above is check-then-act, so two processes can
|
|
11609
|
+
* both find the table absent and both CREATE; the loser errors, and every
|
|
11610
|
+
* engine spells that differently (a pg_type unique violation on PostgreSQL,
|
|
11611
|
+
* Msg 2714 on SQL Server, an RDB$RELATIONS unique violation on Firebird). So
|
|
11612
|
+
* a failure is judged by RE-CHECKING whether the table now exists, never by
|
|
11613
|
+
* parsing the message - the rescue the Firebird note above has always
|
|
11614
|
+
* assumed, and which was not here. MEASURED on the lab before it: six real
|
|
11615
|
+
* processes racing lost one to five of them on PostgreSQL, SQLite and
|
|
11616
|
+
* Firebird.
|
|
11617
|
+
*
|
|
11618
|
+
* The re-check is RETRIED because the loser can fail BEFORE the winner has
|
|
11619
|
+
* committed. On MySQL, CREATE TABLE IF NOT EXISTS takes a shared metadata
|
|
11620
|
+
* lock, checks, then upgrades it to exclusive; two upgrades deadlock, and when
|
|
11621
|
+
* MySQL cannot back the victim off silently it answers 1213 "Deadlock found"
|
|
11622
|
+
* while the winner's table is still invisible (tina4-php CI run 35972320442).
|
|
11623
|
+
* Bounded, so a genuine failure (no CREATE grant) still throws. Same attempts
|
|
11624
|
+
* and delays in all four frameworks.
|
|
11531
11625
|
*/
|
|
11532
11626
|
ensureTable() {
|
|
11533
11627
|
if (this.initialized) return;
|
|
11534
11628
|
const ddl = CREATE_TABLE[this.engine];
|
|
11535
|
-
|
|
11536
|
-
|
|
11629
|
+
for (let attempt = 1; ; attempt++) {
|
|
11630
|
+
try {
|
|
11631
|
+
if (this.target === null) this.sqlite.exec(ddl);
|
|
11632
|
+
else sqlCommandSync(this.target, ddl, []);
|
|
11633
|
+
break;
|
|
11634
|
+
} catch (error2) {
|
|
11635
|
+
if (this.tableExists()) break;
|
|
11636
|
+
if (attempt >= CREATE_ATTEMPTS) throw error2;
|
|
11637
|
+
sleepSync2(CREATE_RETRY_DELAY_MS * attempt);
|
|
11638
|
+
}
|
|
11639
|
+
}
|
|
11537
11640
|
this.initialized = true;
|
|
11538
11641
|
}
|
|
11642
|
+
/**
|
|
11643
|
+
* Whether tina4_session exists, asked the one way every engine answers alike:
|
|
11644
|
+
* a query against it that returns no rows succeeds only if it is there.
|
|
11645
|
+
*/
|
|
11646
|
+
tableExists() {
|
|
11647
|
+
try {
|
|
11648
|
+
this.query("SELECT 1 FROM tina4_session WHERE 1 = 0", []);
|
|
11649
|
+
return true;
|
|
11650
|
+
} catch {
|
|
11651
|
+
return false;
|
|
11652
|
+
}
|
|
11653
|
+
}
|
|
11539
11654
|
read(sessionId) {
|
|
11540
11655
|
this.ensureTable();
|
|
11541
11656
|
const rows = this.query(
|
|
@@ -11545,8 +11660,8 @@ var init_databaseHandler = __esm({
|
|
|
11545
11660
|
const row = rows[0];
|
|
11546
11661
|
if (!row) return null;
|
|
11547
11662
|
const now = Date.now() / 1e3;
|
|
11548
|
-
const
|
|
11549
|
-
if (
|
|
11663
|
+
const expiresAt2 = Number(column(row, "expires_at") ?? 0);
|
|
11664
|
+
if (expiresAt2 > 0 && expiresAt2 < now) {
|
|
11550
11665
|
this.destroy(sessionId);
|
|
11551
11666
|
return null;
|
|
11552
11667
|
}
|
|
@@ -11559,7 +11674,7 @@ var init_databaseHandler = __esm({
|
|
|
11559
11674
|
write(sessionId, data, ttl) {
|
|
11560
11675
|
this.ensureTable();
|
|
11561
11676
|
const json = JSON.stringify(data);
|
|
11562
|
-
const
|
|
11677
|
+
const expiresAt2 = ttl > 0 ? Date.now() / 1e3 + ttl : 0;
|
|
11563
11678
|
const existing = this.query(
|
|
11564
11679
|
"SELECT session_id FROM tina4_session WHERE session_id = ?",
|
|
11565
11680
|
[sessionId]
|
|
@@ -11567,12 +11682,12 @@ var init_databaseHandler = __esm({
|
|
|
11567
11682
|
if (existing.length > 0) {
|
|
11568
11683
|
this.exec(
|
|
11569
11684
|
"UPDATE tina4_session SET data = ?, expires_at = ? WHERE session_id = ?",
|
|
11570
|
-
[json,
|
|
11685
|
+
[json, expiresAt2, sessionId]
|
|
11571
11686
|
);
|
|
11572
11687
|
} else {
|
|
11573
11688
|
this.exec(
|
|
11574
11689
|
"INSERT INTO tina4_session (session_id, data, expires_at) VALUES (?, ?, ?)",
|
|
11575
|
-
[sessionId, json,
|
|
11690
|
+
[sessionId, json, expiresAt2]
|
|
11576
11691
|
);
|
|
11577
11692
|
}
|
|
11578
11693
|
}
|
|
@@ -13615,12 +13730,6 @@ function printResolution() {
|
|
|
13615
13730
|
lines.push("");
|
|
13616
13731
|
process.stderr.write(lines.join("\n"));
|
|
13617
13732
|
}
|
|
13618
|
-
function toPlural(name) {
|
|
13619
|
-
const lower = name.toLowerCase();
|
|
13620
|
-
if (lower.endsWith("s")) return lower;
|
|
13621
|
-
if (lower.endsWith("y") && !/[aeiou]y$/i.test(lower)) return lower.slice(0, -1) + "ies";
|
|
13622
|
-
return lower + "s";
|
|
13623
|
-
}
|
|
13624
13733
|
function toCamel(name) {
|
|
13625
13734
|
return name.charAt(0).toLowerCase() + name.slice(1);
|
|
13626
13735
|
}
|
|
@@ -14053,7 +14162,7 @@ ${aiFill(`delete_${singular}`, {
|
|
|
14053
14162
|
}
|
|
14054
14163
|
function generateCrud(name, flags) {
|
|
14055
14164
|
const table2 = resolveTable(name, flags, { announce: false });
|
|
14056
|
-
const routeName =
|
|
14165
|
+
const routeName = pluralizeReserved(toSnake(name));
|
|
14057
14166
|
const isPublic = Boolean(flags.public);
|
|
14058
14167
|
if (!__resolution.jsonMode) console.log(`
|
|
14059
14168
|
Generating CRUD for ${name}...
|
|
@@ -14307,7 +14416,7 @@ void test${titleName};
|
|
|
14307
14416
|
function generateForm(name, flags) {
|
|
14308
14417
|
const fields = fieldsOrDefault(flags.fields || "");
|
|
14309
14418
|
const table2 = resolveTable(name, flags, { announce: false });
|
|
14310
|
-
const routeName =
|
|
14419
|
+
const routeName = pluralizeReserved(toSnake(name));
|
|
14311
14420
|
const inputTypes = {
|
|
14312
14421
|
string: "text",
|
|
14313
14422
|
str: "text",
|
|
@@ -14373,7 +14482,7 @@ function generateForm(name, flags) {
|
|
|
14373
14482
|
function generateView(name, flags) {
|
|
14374
14483
|
const fields = fieldsOrDefault(flags.fields || "");
|
|
14375
14484
|
const table2 = resolveTable(name, flags, { announce: false });
|
|
14376
|
-
const routeName =
|
|
14485
|
+
const routeName = pluralizeReserved(toSnake(name));
|
|
14377
14486
|
const cols = fields.map(([f]) => f);
|
|
14378
14487
|
const dir = resolve6("src/templates/pages");
|
|
14379
14488
|
ensureDir(dir);
|
|
@@ -15385,7 +15494,7 @@ var init_generate = __esm({
|
|
|
15385
15494
|
crud: ({ name, table: table2 }) => [
|
|
15386
15495
|
`Apply the migration: npx tina4nodejs migrate`,
|
|
15387
15496
|
`Serve and try: npx tina4nodejs serve -> visit /swagger`,
|
|
15388
|
-
`Run the gate test: npx tsx tests/${
|
|
15497
|
+
`Run the gate test: npx tsx tests/${pluralizeReserved(toSnake(name))}.test.ts`,
|
|
15389
15498
|
`Change fields: edit src/models/${name}.ts then re-run generate crud`
|
|
15390
15499
|
],
|
|
15391
15500
|
migration: () => [
|
|
@@ -15403,10 +15512,10 @@ var init_generate = __esm({
|
|
|
15403
15512
|
],
|
|
15404
15513
|
form: ({ name, table: table2 }) => [
|
|
15405
15514
|
`Render from a route: res.render("forms/${table2}.twig", { item })`,
|
|
15406
|
-
`Add the POST route: npx tina4nodejs generate route ${
|
|
15515
|
+
`Add the POST route: npx tina4nodejs generate route ${pluralizeReserved(toSnake(name))} --model ${name}`
|
|
15407
15516
|
],
|
|
15408
|
-
view: ({ table: table2 }) => [
|
|
15409
|
-
`Wire routes to render list -> ${
|
|
15517
|
+
view: ({ name, table: table2 }) => [
|
|
15518
|
+
`Wire routes to render list -> ${pluralizeReserved(toSnake(name))}.twig, detail -> ${table2}.twig`,
|
|
15410
15519
|
`Customize the templates in src/templates/pages/`
|
|
15411
15520
|
],
|
|
15412
15521
|
auth: () => [
|
|
@@ -15567,7 +15676,7 @@ function envTruthy(val) {
|
|
|
15567
15676
|
return ["true", "1", "yes", "on"].includes(val.trim().toLowerCase());
|
|
15568
15677
|
}
|
|
15569
15678
|
function isLoopback(ip) {
|
|
15570
|
-
if (ip == null || ip === "") return
|
|
15679
|
+
if (ip == null || ip === "") return false;
|
|
15571
15680
|
let addr = ip.trim().toLowerCase();
|
|
15572
15681
|
if (addr.startsWith("::ffff:")) addr = addr.slice(7);
|
|
15573
15682
|
return addr === "::1" || addr === "localhost" || addr.startsWith("127.");
|
|
@@ -17158,6 +17267,7 @@ __export(graphql_exports, {
|
|
|
17158
17267
|
graphqlAutoSchemaEnabled: () => graphqlAutoSchemaEnabled,
|
|
17159
17268
|
graphqlEndpoint: () => graphqlEndpoint,
|
|
17160
17269
|
graphqlMaxDepth: () => graphqlMaxDepth,
|
|
17270
|
+
graphqlMaxNodes: () => graphqlMaxNodes,
|
|
17161
17271
|
tokenize: () => tokenize2
|
|
17162
17272
|
});
|
|
17163
17273
|
function tokenize2(source) {
|
|
@@ -17197,7 +17307,12 @@ function graphqlMaxDepth() {
|
|
|
17197
17307
|
const n = parseInt(raw, 10);
|
|
17198
17308
|
return Number.isNaN(n) ? 50 : n;
|
|
17199
17309
|
}
|
|
17200
|
-
|
|
17310
|
+
function graphqlMaxNodes() {
|
|
17311
|
+
const raw = (process.env.TINA4_GRAPHQL_MAX_NODES ?? "1000").trim();
|
|
17312
|
+
const n = parseInt(raw, 10);
|
|
17313
|
+
return Number.isNaN(n) ? 1e3 : n;
|
|
17314
|
+
}
|
|
17315
|
+
var TOKEN_PATTERNS, ParseError, Parser, ComplexityError, GraphQL, GraphQLType;
|
|
17201
17316
|
var init_graphql = __esm({
|
|
17202
17317
|
"../core/src/graphql.ts"() {
|
|
17203
17318
|
"use strict";
|
|
@@ -17236,9 +17351,16 @@ var init_graphql = __esm({
|
|
|
17236
17351
|
Parser = class {
|
|
17237
17352
|
tokens;
|
|
17238
17353
|
pos;
|
|
17354
|
+
// F2: bound the parser's own recursion. A deeply nested query
|
|
17355
|
+
// ("{a{a{a...}}}") otherwise recurses in parseSelectionSet until the call
|
|
17356
|
+
// stack overflows — BEFORE the execution-time depth guard can run. Reusing
|
|
17357
|
+
// TINA4_GRAPHQL_MAX_DEPTH keeps parse and execution on one bound.
|
|
17358
|
+
depth = 0;
|
|
17359
|
+
maxDepth;
|
|
17239
17360
|
constructor(tokens) {
|
|
17240
17361
|
this.tokens = tokens;
|
|
17241
17362
|
this.pos = 0;
|
|
17363
|
+
this.maxDepth = graphqlMaxDepth();
|
|
17242
17364
|
}
|
|
17243
17365
|
peek() {
|
|
17244
17366
|
return this.pos < this.tokens.length ? this.tokens[this.pos] : null;
|
|
@@ -17320,6 +17442,17 @@ var init_graphql = __esm({
|
|
|
17320
17442
|
};
|
|
17321
17443
|
}
|
|
17322
17444
|
parseSelectionSet() {
|
|
17445
|
+
this.depth++;
|
|
17446
|
+
if (this.maxDepth > 0 && this.depth > this.maxDepth) {
|
|
17447
|
+
throw new Error(`Query exceeds maximum depth of ${this.maxDepth}`);
|
|
17448
|
+
}
|
|
17449
|
+
try {
|
|
17450
|
+
return this.parseSelectionSetInner();
|
|
17451
|
+
} finally {
|
|
17452
|
+
this.depth--;
|
|
17453
|
+
}
|
|
17454
|
+
}
|
|
17455
|
+
parseSelectionSetInner() {
|
|
17323
17456
|
this.expect("LBRACE");
|
|
17324
17457
|
const selections = [];
|
|
17325
17458
|
while (!this.match("RBRACE")) {
|
|
@@ -17474,6 +17607,8 @@ var init_graphql = __esm({
|
|
|
17474
17607
|
return t;
|
|
17475
17608
|
}
|
|
17476
17609
|
};
|
|
17610
|
+
ComplexityError = class extends Error {
|
|
17611
|
+
};
|
|
17477
17612
|
GraphQL = class _GraphQL {
|
|
17478
17613
|
types = /* @__PURE__ */ new Map();
|
|
17479
17614
|
queries = /* @__PURE__ */ new Map();
|
|
@@ -17496,6 +17631,12 @@ var init_graphql = __esm({
|
|
|
17496
17631
|
* spread / inline fragment, so circular fragments are caught too.
|
|
17497
17632
|
*/
|
|
17498
17633
|
maxDepth = graphqlMaxDepth();
|
|
17634
|
+
/**
|
|
17635
|
+
* Maximum expanded selection nodes per query (from `TINA4_GRAPHQL_MAX_NODES`,
|
|
17636
|
+
* default 1000; `<= 0` disables). Bounds query WIDTH — fragment bombs and
|
|
17637
|
+
* alias explosions — which the depth guard does not. See {@link graphqlMaxNodes}.
|
|
17638
|
+
*/
|
|
17639
|
+
maxNodes = graphqlMaxNodes();
|
|
17499
17640
|
/**
|
|
17500
17641
|
* Decorator-style resolver registration.
|
|
17501
17642
|
*
|
|
@@ -17643,6 +17784,16 @@ var init_graphql = __esm({
|
|
|
17643
17784
|
return { data: null, errors: [{ message: "No operation found" }] };
|
|
17644
17785
|
}
|
|
17645
17786
|
const op = operations[0];
|
|
17787
|
+
if (this.maxNodes > 0) {
|
|
17788
|
+
try {
|
|
17789
|
+
this.assertComplexity(op.selections, fragments);
|
|
17790
|
+
} catch (e) {
|
|
17791
|
+
if (e instanceof ComplexityError) {
|
|
17792
|
+
return { data: null, errors: [{ message: e.message }] };
|
|
17793
|
+
}
|
|
17794
|
+
throw e;
|
|
17795
|
+
}
|
|
17796
|
+
}
|
|
17646
17797
|
const resolvers = op.operation === "query" ? this.queries : this.mutations;
|
|
17647
17798
|
for (const vdef of op.variables) {
|
|
17648
17799
|
if (!(vdef.name in vars) && vdef.default !== void 0) {
|
|
@@ -17668,6 +17819,36 @@ var init_graphql = __esm({
|
|
|
17668
17819
|
* instead of recursing until the interpreter stack overflows. Top-level
|
|
17669
17820
|
* starts at depth 1; `maxDepth <= 0` disables the guard.
|
|
17670
17821
|
*/
|
|
17822
|
+
/**
|
|
17823
|
+
* Count the expanded selection nodes, throwing {@link ComplexityError} past
|
|
17824
|
+
* `maxNodes` (F2). Fragment spreads are expanded (so a fragment bomb is
|
|
17825
|
+
* counted, not the small unexpanded document) and every field — including
|
|
17826
|
+
* each alias — counts once. The count is capped by `maxNodes` and the walk by
|
|
17827
|
+
* `maxDepth`, so a circular fragment trips the budget instead of looping and
|
|
17828
|
+
* the check itself can never cost more than the budget.
|
|
17829
|
+
*/
|
|
17830
|
+
assertComplexity(selections, fragments) {
|
|
17831
|
+
const depthCap = this.maxDepth > 0 ? this.maxDepth : 1e3;
|
|
17832
|
+
let count = 0;
|
|
17833
|
+
const walk2 = (sels, depth) => {
|
|
17834
|
+
if (depth > depthCap) return;
|
|
17835
|
+
for (const sel of sels) {
|
|
17836
|
+
count++;
|
|
17837
|
+
if (count > this.maxNodes) {
|
|
17838
|
+
throw new ComplexityError(`Query exceeds maximum complexity of ${this.maxNodes} nodes`);
|
|
17839
|
+
}
|
|
17840
|
+
if (sel.kind === "fragment_spread") {
|
|
17841
|
+
const frag = fragments.get(sel.name);
|
|
17842
|
+
if (frag) walk2(frag.selections, depth + 1);
|
|
17843
|
+
} else if (sel.kind === "inline_fragment") {
|
|
17844
|
+
walk2(sel.selections, depth + 1);
|
|
17845
|
+
} else if (sel.kind === "field" && sel.selections) {
|
|
17846
|
+
walk2(sel.selections, depth + 1);
|
|
17847
|
+
}
|
|
17848
|
+
}
|
|
17849
|
+
};
|
|
17850
|
+
walk2(selections, 1);
|
|
17851
|
+
}
|
|
17671
17852
|
async resolveSelectionsInto(selections, resolvers, parent, variables, context, fragments, target, depth) {
|
|
17672
17853
|
const errors = [];
|
|
17673
17854
|
if (this.maxDepth > 0 && depth > this.maxDepth) {
|
|
@@ -18870,7 +19051,8 @@ function wsAuthorized(route, headers, queryString = "", subprotocol = "") {
|
|
|
18870
19051
|
const token = wsToken(headers, queryString, subprotocol);
|
|
18871
19052
|
if (!token) return [null, false];
|
|
18872
19053
|
const payload = validToken(token);
|
|
18873
|
-
return [
|
|
19054
|
+
if (!isIdentityPayload(payload)) return [null, false];
|
|
19055
|
+
return [payload, true];
|
|
18874
19056
|
}
|
|
18875
19057
|
function offeredBearerSubprotocol(subprotocol) {
|
|
18876
19058
|
return subprotocol.split(",").some((p) => p.trim().toLowerCase() === "bearer");
|
|
@@ -20065,12 +20247,36 @@ var init_mongoBackend = __esm({
|
|
|
20065
20247
|
}
|
|
20066
20248
|
}
|
|
20067
20249
|
else if (operation === "size") {
|
|
20068
|
-
|
|
20069
|
-
|
|
20070
|
-
|
|
20071
|
-
|
|
20250
|
+
// ADR-0022 dec 7: size(status) must not answer a different question.
|
|
20251
|
+
// The dead aliases count the .dead_letter store (== deadLetters().length);
|
|
20252
|
+
// reserved/completed count by status; else pending. data = status.
|
|
20253
|
+
const status = data || "pending";
|
|
20254
|
+
let count: number;
|
|
20255
|
+
if (status === "dead" || status === "failed" || status === "dead_letter") {
|
|
20256
|
+
count = await col.countDocuments({ queue: queueName + ".dead_letter" });
|
|
20257
|
+
} else if (status === "reserved" || status === "completed") {
|
|
20258
|
+
count = await col.countDocuments({ queue: queueName, status });
|
|
20259
|
+
} else {
|
|
20260
|
+
count = await col.countDocuments({ queue: queueName, status: "pending" });
|
|
20261
|
+
}
|
|
20072
20262
|
process.stdout.write(String(count));
|
|
20073
20263
|
}
|
|
20264
|
+
else if (operation === "reject") {
|
|
20265
|
+
// Dead-letter NOW, no retry (ADR-0023). Floor attempts at maxRetries
|
|
20266
|
+
// so it reads as exhausted (parity with fail()'s dead-letter branch
|
|
20267
|
+
// and size("dead")). data = JSON { id, reason, maxRetries }.
|
|
20268
|
+
const info = JSON.parse(data);
|
|
20269
|
+
const doc = await col.findOne({ queue: queueName, id: info.id });
|
|
20270
|
+
if (doc) {
|
|
20271
|
+
const attempts = Math.max((doc.attempts || 0) + 1, info.maxRetries);
|
|
20272
|
+
await col.insertOne({
|
|
20273
|
+
...doc, _id: undefined, attempts,
|
|
20274
|
+
status: "dead", queue: queueName + ".dead_letter", error: info.reason,
|
|
20275
|
+
});
|
|
20276
|
+
await col.deleteOne({ _id: doc._id, queue: queueName });
|
|
20277
|
+
}
|
|
20278
|
+
process.stdout.write("__OK__");
|
|
20279
|
+
}
|
|
20074
20280
|
else if (operation === "clear") {
|
|
20075
20281
|
await col.deleteMany({ queue: queueName });
|
|
20076
20282
|
process.stdout.write("__CLEARED__");
|
|
@@ -20306,11 +20512,20 @@ var init_mongoBackend = __esm({
|
|
|
20306
20512
|
return null;
|
|
20307
20513
|
}
|
|
20308
20514
|
}
|
|
20309
|
-
size(queue) {
|
|
20310
|
-
const result = this.execSync("size", queue);
|
|
20515
|
+
size(queue, status2 = "pending") {
|
|
20516
|
+
const result = this.execSync("size", queue, status2);
|
|
20311
20517
|
const num = parseInt(result, 10);
|
|
20312
20518
|
return isNaN(num) ? 0 : num;
|
|
20313
20519
|
}
|
|
20520
|
+
/**
|
|
20521
|
+
* Dead-letter a job immediately — no retry (ADR-0023 reject()). Distinct from
|
|
20522
|
+
* fail(): fail() requeues until maxRetries is spent. attempts is floored at
|
|
20523
|
+
* maxRetries so deadLetters()/size("dead") (which filter/scope to the
|
|
20524
|
+
* dead-letter store) see it.
|
|
20525
|
+
*/
|
|
20526
|
+
reject(queue, id, reason, maxRetries) {
|
|
20527
|
+
this.execSync("reject", queue, JSON.stringify({ id, reason, maxRetries }));
|
|
20528
|
+
}
|
|
20314
20529
|
clear(queue) {
|
|
20315
20530
|
this.execSync("clear", queue);
|
|
20316
20531
|
}
|
|
@@ -20405,7 +20620,9 @@ function createJob(data, queue) {
|
|
|
20405
20620
|
queue._failJob(job.topic, job, reason, queue.getMaxRetries());
|
|
20406
20621
|
},
|
|
20407
20622
|
reject(reason = "") {
|
|
20408
|
-
job.
|
|
20623
|
+
job.status = "dead";
|
|
20624
|
+
job.error = reason;
|
|
20625
|
+
queue._rejectJob(job.topic, job, reason, queue.getMaxRetries());
|
|
20409
20626
|
},
|
|
20410
20627
|
retry(delaySeconds) {
|
|
20411
20628
|
queue._retryJob(job.topic, job, delaySeconds);
|
|
@@ -21014,6 +21231,20 @@ var init_liteBackend = __esm({
|
|
|
21014
21231
|
* dead-letter file -- two spellings of the same intent that previously
|
|
21015
21232
|
* diverged.
|
|
21016
21233
|
*/
|
|
21234
|
+
/**
|
|
21235
|
+
* Reject a job permanently — dead-letter it NOW, no retry (ADR-0023).
|
|
21236
|
+
*
|
|
21237
|
+
* Unlike failJob(), which requeues until maxRetries is spent, rejectJob()
|
|
21238
|
+
* moves a known-poison job straight to the dead-letter store. attempts is
|
|
21239
|
+
* floored at maxRetries so deadLetters() (attempts >= maxRetries) returns it
|
|
21240
|
+
* and size("dead") agrees.
|
|
21241
|
+
*/
|
|
21242
|
+
rejectJob(queue, job, reason, maxRetries) {
|
|
21243
|
+
this.clearReservation(queue, job.id);
|
|
21244
|
+
job.attempts = Math.max((job.attempts || 0) + 1, maxRetries);
|
|
21245
|
+
job.error = reason;
|
|
21246
|
+
this.deadLetter(queue, job, reason);
|
|
21247
|
+
}
|
|
21017
21248
|
retryJob(queue, job, delaySeconds) {
|
|
21018
21249
|
this.clearReservation(queue, job.id);
|
|
21019
21250
|
try {
|
|
@@ -21235,7 +21466,7 @@ var init_queue = __esm({
|
|
|
21235
21466
|
size(status2 = "pending") {
|
|
21236
21467
|
const q = this.topic;
|
|
21237
21468
|
if (this.externalBackend) {
|
|
21238
|
-
return this.externalBackend.size(q);
|
|
21469
|
+
return this.externalBackend.size(q, status2);
|
|
21239
21470
|
}
|
|
21240
21471
|
return this.liteBackend.size(q, status2);
|
|
21241
21472
|
}
|
|
@@ -21503,6 +21734,17 @@ var init_queue = __esm({
|
|
|
21503
21734
|
}
|
|
21504
21735
|
this.liteBackend.failJob(queue, job, error2, maxRetries, this._retryBackoff);
|
|
21505
21736
|
}
|
|
21737
|
+
/**
|
|
21738
|
+
* Dead-letter a job immediately — no retry (ADR-0023 reject()). Distinct from
|
|
21739
|
+
* _failJob, which retries until maxRetries is spent.
|
|
21740
|
+
*/
|
|
21741
|
+
_rejectJob(queue, job, reason, maxRetries) {
|
|
21742
|
+
if (this.externalBackend?.reject) {
|
|
21743
|
+
this.externalBackend.reject(queue, job.id, reason, maxRetries);
|
|
21744
|
+
return;
|
|
21745
|
+
}
|
|
21746
|
+
this.liteBackend.rejectJob(queue, job, reason, maxRetries);
|
|
21747
|
+
}
|
|
21506
21748
|
/**
|
|
21507
21749
|
* Re-queue a job back to the main queue directory with incremented attempts.
|
|
21508
21750
|
*/
|
|
@@ -27214,13 +27456,20 @@ var sso_exports = {};
|
|
|
27214
27456
|
__export(sso_exports, {
|
|
27215
27457
|
SSO: () => Sso,
|
|
27216
27458
|
Sso: () => Sso,
|
|
27217
|
-
SsoError: () => SsoError
|
|
27459
|
+
SsoError: () => SsoError,
|
|
27460
|
+
liveSessionIdentity: () => liveSessionIdentity
|
|
27218
27461
|
});
|
|
27219
27462
|
import { createHash as createHash9, randomBytes as randomBytes5, timingSafeEqual as timingSafeEqual3 } from "node:crypto";
|
|
27463
|
+
function expiresAt(tokens) {
|
|
27464
|
+
const lifetime = Number(tokens.expires_in ?? 0);
|
|
27465
|
+
return lifetime > 0 ? Math.floor(Date.now() / 1e3) + lifetime : 0;
|
|
27466
|
+
}
|
|
27220
27467
|
var SsoError, Sso;
|
|
27221
27468
|
var init_sso = __esm({
|
|
27222
27469
|
"../core/src/sso.ts"() {
|
|
27223
27470
|
"use strict";
|
|
27471
|
+
init_ssoIdentity();
|
|
27472
|
+
init_ssoIdentity();
|
|
27224
27473
|
SsoError = class extends Error {
|
|
27225
27474
|
};
|
|
27226
27475
|
Sso = class _Sso {
|
|
@@ -27426,13 +27675,12 @@ var init_sso = __esm({
|
|
|
27426
27675
|
access_token: tokens.access_token,
|
|
27427
27676
|
refresh_token: tokens.refresh_token,
|
|
27428
27677
|
id_token: tokens.id_token,
|
|
27429
|
-
expires_at:
|
|
27678
|
+
expires_at: expiresAt(tokens)
|
|
27430
27679
|
});
|
|
27431
27680
|
return { identity, return_to: _Sso.safeReturn(pending.return_to) };
|
|
27432
27681
|
}
|
|
27433
27682
|
identity(requestOrSession) {
|
|
27434
|
-
const
|
|
27435
|
-
const identity = stored?.identity ?? null;
|
|
27683
|
+
const identity = liveSessionIdentity(this.session(requestOrSession)?.get(_Sso.SESSION_KEY));
|
|
27436
27684
|
if (identity && requestOrSession?.session) requestOrSession.user = identity;
|
|
27437
27685
|
return identity;
|
|
27438
27686
|
}
|
|
@@ -27459,7 +27707,7 @@ var init_sso = __esm({
|
|
|
27459
27707
|
access_token: tokens.access_token,
|
|
27460
27708
|
refresh_token: tokens.refresh_token ?? stored.refresh_token,
|
|
27461
27709
|
id_token: tokens.id_token ?? stored.id_token,
|
|
27462
|
-
expires_at:
|
|
27710
|
+
expires_at: expiresAt(tokens)
|
|
27463
27711
|
});
|
|
27464
27712
|
return identity;
|
|
27465
27713
|
} catch (error2) {
|
|
@@ -27551,8 +27799,8 @@ function writeMcpDiscovery(projectRoot3, port) {
|
|
|
27551
27799
|
const lines = contents.split(/\r?\n/);
|
|
27552
27800
|
const already = lines.some((l) => l.trim() === GITIGNORE_LINE || l.trim() === ".tina4");
|
|
27553
27801
|
if (!already) {
|
|
27554
|
-
const
|
|
27555
|
-
fs9.writeFileSync(gitignorePath, `${contents}${
|
|
27802
|
+
const sep9 = contents.endsWith("\n") || contents === "" ? "" : "\n";
|
|
27803
|
+
fs9.writeFileSync(gitignorePath, `${contents}${sep9}${GITIGNORE_LINE}
|
|
27556
27804
|
`, "utf-8");
|
|
27557
27805
|
}
|
|
27558
27806
|
}
|
|
@@ -27601,12 +27849,12 @@ function bannerSurfaceLines(port, opts) {
|
|
|
27601
27849
|
];
|
|
27602
27850
|
}
|
|
27603
27851
|
function swaggerAdvertised() {
|
|
27604
|
-
const
|
|
27852
|
+
const TRUTHY3 = ["true", "1", "yes", "on"];
|
|
27605
27853
|
const raw = (process.env.TINA4_SWAGGER_ENABLED ?? "").trim().toLowerCase();
|
|
27606
27854
|
if (raw === "") {
|
|
27607
|
-
return
|
|
27855
|
+
return TRUTHY3.includes((process.env.TINA4_DEBUG ?? "").trim().toLowerCase());
|
|
27608
27856
|
}
|
|
27609
|
-
return
|
|
27857
|
+
return TRUTHY3.includes(raw);
|
|
27610
27858
|
}
|
|
27611
27859
|
async function autoMigrateOnStartup(migrationDir = "migrations", base = process.cwd()) {
|
|
27612
27860
|
const dir = resolve15(base, migrationDir);
|
|
@@ -28525,8 +28773,9 @@ function wireLocaleGlobal(frondEngine, base) {
|
|
|
28525
28773
|
async function startServer(config) {
|
|
28526
28774
|
loadEnv(".env.local");
|
|
28527
28775
|
loadEnv();
|
|
28528
|
-
const { ensureDevSecret: ensureDevSecret2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
|
|
28776
|
+
const { ensureDevSecret: ensureDevSecret2, requireBootSecret: requireBootSecret2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
|
|
28529
28777
|
ensureDevSecret2();
|
|
28778
|
+
requireBootSecret2();
|
|
28530
28779
|
_checkLegacyEnvVars();
|
|
28531
28780
|
const resolved = resolvePortAndHost(config);
|
|
28532
28781
|
const host = resolved.host;
|
|
@@ -30722,6 +30971,178 @@ var init_service = __esm({
|
|
|
30722
30971
|
}
|
|
30723
30972
|
});
|
|
30724
30973
|
|
|
30974
|
+
// ../core/src/ssrf.ts
|
|
30975
|
+
import { isIP } from "node:net";
|
|
30976
|
+
import { lookup } from "node:dns/promises";
|
|
30977
|
+
function v4ToBytes(ip) {
|
|
30978
|
+
const parts = ip.split(".");
|
|
30979
|
+
if (parts.length !== 4) return null;
|
|
30980
|
+
const bytes = new Uint8Array(4);
|
|
30981
|
+
for (let i = 0; i < 4; i++) {
|
|
30982
|
+
const n = Number(parts[i]);
|
|
30983
|
+
if (!Number.isInteger(n) || n < 0 || n > 255 || !/^\d+$/.test(parts[i])) return null;
|
|
30984
|
+
bytes[i] = n;
|
|
30985
|
+
}
|
|
30986
|
+
return bytes;
|
|
30987
|
+
}
|
|
30988
|
+
function v6ToBytes(input) {
|
|
30989
|
+
let ip = input.split("%")[0];
|
|
30990
|
+
const lastColon = ip.lastIndexOf(":");
|
|
30991
|
+
const tail = ip.slice(lastColon + 1);
|
|
30992
|
+
if (tail.includes(".")) {
|
|
30993
|
+
const v4 = v4ToBytes(tail);
|
|
30994
|
+
if (!v4) return null;
|
|
30995
|
+
const hextetA = (v4[0] << 8 | v4[1]).toString(16);
|
|
30996
|
+
const hextetB = (v4[2] << 8 | v4[3]).toString(16);
|
|
30997
|
+
ip = ip.slice(0, lastColon + 1) + hextetA + ":" + hextetB;
|
|
30998
|
+
}
|
|
30999
|
+
const halves = ip.split("::");
|
|
31000
|
+
if (halves.length > 2) return null;
|
|
31001
|
+
const head = halves[0] ? halves[0].split(":") : [];
|
|
31002
|
+
const back = halves.length === 2 ? halves[1] ? halves[1].split(":") : [] : [];
|
|
31003
|
+
let groups;
|
|
31004
|
+
if (halves.length === 1) {
|
|
31005
|
+
if (head.length !== 8) return null;
|
|
31006
|
+
groups = head;
|
|
31007
|
+
} else {
|
|
31008
|
+
const missing = 8 - (head.length + back.length);
|
|
31009
|
+
if (missing < 0) return null;
|
|
31010
|
+
groups = [...head, ...Array(missing).fill("0"), ...back];
|
|
31011
|
+
}
|
|
31012
|
+
if (groups.length !== 8) return null;
|
|
31013
|
+
const bytes = new Uint8Array(16);
|
|
31014
|
+
for (let i = 0; i < 8; i++) {
|
|
31015
|
+
if (!/^[0-9a-fA-F]{1,4}$/.test(groups[i])) return null;
|
|
31016
|
+
const g = parseInt(groups[i], 16);
|
|
31017
|
+
bytes[i * 2] = g >> 8 & 255;
|
|
31018
|
+
bytes[i * 2 + 1] = g & 255;
|
|
31019
|
+
}
|
|
31020
|
+
return bytes;
|
|
31021
|
+
}
|
|
31022
|
+
function ipToBytes(ip) {
|
|
31023
|
+
const cleaned = ip.split("%")[0];
|
|
31024
|
+
const family = isIP(cleaned);
|
|
31025
|
+
if (family === 4) return v4ToBytes(cleaned);
|
|
31026
|
+
if (family === 6) {
|
|
31027
|
+
const bytes = v6ToBytes(cleaned);
|
|
31028
|
+
if (!bytes) return null;
|
|
31029
|
+
const isMapped = bytes.subarray(0, 10).every((b) => b === 0) && bytes[10] === 255 && bytes[11] === 255;
|
|
31030
|
+
return isMapped ? bytes.subarray(12) : bytes;
|
|
31031
|
+
}
|
|
31032
|
+
return null;
|
|
31033
|
+
}
|
|
31034
|
+
function parseCidr(cidr) {
|
|
31035
|
+
const [netStr, bitsStr] = cidr.split("/");
|
|
31036
|
+
const net4 = ipToBytes(netStr);
|
|
31037
|
+
if (!net4) return null;
|
|
31038
|
+
return { net: net4, bits: Number(bitsStr) };
|
|
31039
|
+
}
|
|
31040
|
+
function inCidr(packed, cidr) {
|
|
31041
|
+
if (packed.length !== cidr.net.length) return false;
|
|
31042
|
+
const whole = Math.floor(cidr.bits / 8);
|
|
31043
|
+
const remainder = cidr.bits % 8;
|
|
31044
|
+
for (let i = 0; i < whole; i++) {
|
|
31045
|
+
if (packed[i] !== cidr.net[i]) return false;
|
|
31046
|
+
}
|
|
31047
|
+
if (remainder === 0) return true;
|
|
31048
|
+
const mask = 255 << 8 - remainder & 255;
|
|
31049
|
+
return (packed[whole] & mask) === (cidr.net[whole] & mask);
|
|
31050
|
+
}
|
|
31051
|
+
function allowPrivateRequests() {
|
|
31052
|
+
return TRUTHY2.has((process.env[ALLOW_PRIVATE_ENV] ?? "").trim().toLowerCase());
|
|
31053
|
+
}
|
|
31054
|
+
function isBlockedAddress(ip) {
|
|
31055
|
+
const packed = ipToBytes(ip);
|
|
31056
|
+
if (!packed) return true;
|
|
31057
|
+
return BLOCKED.some((cidr) => inCidr(packed, cidr));
|
|
31058
|
+
}
|
|
31059
|
+
function matchesAllowList(host, port, resolved, allowHosts) {
|
|
31060
|
+
const hostLower = host.toLowerCase();
|
|
31061
|
+
for (const raw of allowHosts ?? []) {
|
|
31062
|
+
const entry = String(raw).trim().toLowerCase();
|
|
31063
|
+
if (!entry) continue;
|
|
31064
|
+
if (entry === hostLower || entry === `${hostLower}:${port}`) return true;
|
|
31065
|
+
if (entry.includes("/")) {
|
|
31066
|
+
const cidr = parseCidr(entry);
|
|
31067
|
+
if (cidr && resolved.some((ip) => {
|
|
31068
|
+
const packed = ipToBytes(ip);
|
|
31069
|
+
return packed !== null && inCidr(packed, cidr);
|
|
31070
|
+
})) {
|
|
31071
|
+
return true;
|
|
31072
|
+
}
|
|
31073
|
+
}
|
|
31074
|
+
}
|
|
31075
|
+
return false;
|
|
31076
|
+
}
|
|
31077
|
+
function logBlock(host, ip) {
|
|
31078
|
+
void Promise.resolve().then(() => (init_logger(), logger_exports)).then((mod) => mod.Log?.warning?.(`SSRF guard blocked outbound request to ${host} (${ip})`)).catch(() => void 0);
|
|
31079
|
+
}
|
|
31080
|
+
async function guardUrl(url, allowHosts) {
|
|
31081
|
+
let parsed;
|
|
31082
|
+
try {
|
|
31083
|
+
parsed = new URL(url);
|
|
31084
|
+
} catch {
|
|
31085
|
+
throw new SsrfError(`Blocked request: '${url}' is not a valid URL`);
|
|
31086
|
+
}
|
|
31087
|
+
const scheme = parsed.protocol.replace(/:$/, "").toLowerCase();
|
|
31088
|
+
if (scheme !== "http" && scheme !== "https") {
|
|
31089
|
+
throw new SsrfError(`Blocked request: URL scheme '${scheme || "(none)"}' is not http or https`);
|
|
31090
|
+
}
|
|
31091
|
+
const host = parsed.hostname.replace(/^\[|\]$/g, "");
|
|
31092
|
+
if (!host) {
|
|
31093
|
+
throw new SsrfError("Blocked request: URL has no host");
|
|
31094
|
+
}
|
|
31095
|
+
const port = parsed.port ? Number(parsed.port) : scheme === "https" ? 443 : 80;
|
|
31096
|
+
if (allowPrivateRequests()) return;
|
|
31097
|
+
let resolved;
|
|
31098
|
+
try {
|
|
31099
|
+
const results = await lookup(host, { all: true });
|
|
31100
|
+
resolved = results.map((r) => r.address);
|
|
31101
|
+
} catch {
|
|
31102
|
+
throw new SsrfError(`Blocked request to ${host}: cannot resolve host`);
|
|
31103
|
+
}
|
|
31104
|
+
if (resolved.length === 0) {
|
|
31105
|
+
throw new SsrfError(`Blocked request to ${host}: cannot resolve host`);
|
|
31106
|
+
}
|
|
31107
|
+
if (matchesAllowList(host, port, resolved, allowHosts)) return;
|
|
31108
|
+
for (const ip of resolved) {
|
|
31109
|
+
if (isBlockedAddress(ip)) {
|
|
31110
|
+
logBlock(host, ip);
|
|
31111
|
+
throw new SsrfError(
|
|
31112
|
+
`Blocked request to private/internal address ${ip} (host ${host}): set ${ALLOW_PRIVATE_ENV}=true to allow, or pass an allow-list.`
|
|
31113
|
+
);
|
|
31114
|
+
}
|
|
31115
|
+
}
|
|
31116
|
+
}
|
|
31117
|
+
var ALLOW_PRIVATE_ENV, TRUTHY2, SsrfError, BLOCKED_CIDRS, BLOCKED;
|
|
31118
|
+
var init_ssrf = __esm({
|
|
31119
|
+
"../core/src/ssrf.ts"() {
|
|
31120
|
+
"use strict";
|
|
31121
|
+
ALLOW_PRIVATE_ENV = "TINA4_ALLOW_PRIVATE_REQUESTS";
|
|
31122
|
+
TRUTHY2 = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
|
|
31123
|
+
SsrfError = class extends Error {
|
|
31124
|
+
constructor(message) {
|
|
31125
|
+
super(message);
|
|
31126
|
+
this.name = "SsrfError";
|
|
31127
|
+
}
|
|
31128
|
+
};
|
|
31129
|
+
BLOCKED_CIDRS = [
|
|
31130
|
+
"0.0.0.0/8",
|
|
31131
|
+
"10.0.0.0/8",
|
|
31132
|
+
"127.0.0.0/8",
|
|
31133
|
+
"169.254.0.0/16",
|
|
31134
|
+
"172.16.0.0/12",
|
|
31135
|
+
"192.168.0.0/16",
|
|
31136
|
+
"100.64.0.0/10",
|
|
31137
|
+
"::1/128",
|
|
31138
|
+
"::/128",
|
|
31139
|
+
"fc00::/7",
|
|
31140
|
+
"fe80::/10"
|
|
31141
|
+
];
|
|
31142
|
+
BLOCKED = BLOCKED_CIDRS.map(parseCidr).filter((c) => c !== null);
|
|
31143
|
+
}
|
|
31144
|
+
});
|
|
31145
|
+
|
|
30725
31146
|
// ../core/src/api.ts
|
|
30726
31147
|
import http from "node:http";
|
|
30727
31148
|
import https from "node:https";
|
|
@@ -30878,6 +31299,7 @@ var init_api = __esm({
|
|
|
30878
31299
|
"../core/src/api.ts"() {
|
|
30879
31300
|
"use strict";
|
|
30880
31301
|
init_version();
|
|
31302
|
+
init_ssrf();
|
|
30881
31303
|
ApiStreamError = class extends Error {
|
|
30882
31304
|
constructor(message, status2 = null) {
|
|
30883
31305
|
super(message);
|
|
@@ -30932,6 +31354,7 @@ var init_api = __esm({
|
|
|
30932
31354
|
transportFn;
|
|
30933
31355
|
cookiesEnabled;
|
|
30934
31356
|
cookies;
|
|
31357
|
+
allowHosts;
|
|
30935
31358
|
/**
|
|
30936
31359
|
* Construct an Api client.
|
|
30937
31360
|
*
|
|
@@ -30971,6 +31394,7 @@ var init_api = __esm({
|
|
|
30971
31394
|
this.transportFn = void 0;
|
|
30972
31395
|
this.cookiesEnabled = false;
|
|
30973
31396
|
this.cookies = {};
|
|
31397
|
+
this.allowHosts = [];
|
|
30974
31398
|
if (typeof authHeaderOrOptions === "object" && authHeaderOrOptions !== null) {
|
|
30975
31399
|
const opts = authHeaderOrOptions;
|
|
30976
31400
|
this.authHeader = opts.authHeader ?? "";
|
|
@@ -30980,6 +31404,7 @@ var init_api = __esm({
|
|
|
30980
31404
|
this.retryBackoff = opts.retryBackoff ?? 0.5;
|
|
30981
31405
|
this.transportFn = opts.transport;
|
|
30982
31406
|
this.cookiesEnabled = opts.cookies ?? false;
|
|
31407
|
+
this.allowHosts = opts.allowHosts ?? [];
|
|
30983
31408
|
if (opts.bearerToken != null) {
|
|
30984
31409
|
this.setBearerToken(opts.bearerToken);
|
|
30985
31410
|
} else if (opts.username != null && opts.password != null) {
|
|
@@ -31293,7 +31718,12 @@ var init_api = __esm({
|
|
|
31293
31718
|
* body-phase timeout is applied by the caller (streamBytes) via
|
|
31294
31719
|
* `res.destroy()`.
|
|
31295
31720
|
*/
|
|
31296
|
-
openStreamRequest(method, url, headers, data, connectSec) {
|
|
31721
|
+
async openStreamRequest(method, url, headers, data, connectSec) {
|
|
31722
|
+
try {
|
|
31723
|
+
await guardUrl(url, this.allowHosts);
|
|
31724
|
+
} catch (err) {
|
|
31725
|
+
throw new ApiStreamError(err instanceof Error ? err.message : String(err), null);
|
|
31726
|
+
}
|
|
31297
31727
|
return new Promise((resolve21, reject) => {
|
|
31298
31728
|
let parsed;
|
|
31299
31729
|
try {
|
|
@@ -31352,10 +31782,11 @@ var init_api = __esm({
|
|
|
31352
31782
|
*/
|
|
31353
31783
|
buildRequest(method, contentType, body, extraHeaders, targetUrl) {
|
|
31354
31784
|
const headers = { "User-Agent": `Tina4/${TINA4_VERSION}`, ...this.headers };
|
|
31355
|
-
|
|
31785
|
+
const sameOriginAsBase = targetUrl === void 0 || sameOrigin(targetUrl, this.baseUrl);
|
|
31786
|
+
if (this.authHeader && sameOriginAsBase) {
|
|
31356
31787
|
headers["Authorization"] = this.authHeader;
|
|
31357
31788
|
}
|
|
31358
|
-
if (this.cookiesEnabled) {
|
|
31789
|
+
if (this.cookiesEnabled && sameOriginAsBase) {
|
|
31359
31790
|
const cookieHeader = this.cookieHeader();
|
|
31360
31791
|
if (cookieHeader) {
|
|
31361
31792
|
headers["Cookie"] = cookieHeader;
|
|
@@ -31456,7 +31887,12 @@ var init_api = __esm({
|
|
|
31456
31887
|
* bearer token / session cookie never leaks to a host you didn't
|
|
31457
31888
|
* authenticate to.
|
|
31458
31889
|
*/
|
|
31459
|
-
performRequest(method, url, headers, data, redirectsLeft) {
|
|
31890
|
+
async performRequest(method, url, headers, data, redirectsLeft) {
|
|
31891
|
+
try {
|
|
31892
|
+
await guardUrl(url, this.allowHosts);
|
|
31893
|
+
} catch (err) {
|
|
31894
|
+
return { kind: "error", error: err instanceof Error ? err.message : String(err) };
|
|
31895
|
+
}
|
|
31460
31896
|
return new Promise((resolve21) => {
|
|
31461
31897
|
let parsed;
|
|
31462
31898
|
try {
|
|
@@ -34196,6 +34632,7 @@ var CURVE, RECORD_SIZE, MAX_PAYLOAD, PushError, Push;
|
|
|
34196
34632
|
var init_push = __esm({
|
|
34197
34633
|
"../core/src/push.ts"() {
|
|
34198
34634
|
"use strict";
|
|
34635
|
+
init_ssrf();
|
|
34199
34636
|
CURVE = "prime256v1";
|
|
34200
34637
|
RECORD_SIZE = 4096;
|
|
34201
34638
|
MAX_PAYLOAD = RECORD_SIZE - 17;
|
|
@@ -34270,10 +34707,19 @@ var init_push = __esm({
|
|
|
34270
34707
|
return { rawPublic, rawPrivate };
|
|
34271
34708
|
}
|
|
34272
34709
|
async deliver(endpoint, config, keys, body) {
|
|
34710
|
+
try {
|
|
34711
|
+
await guardUrl(endpoint.toString(), this.options.allowHosts);
|
|
34712
|
+
} catch (error2) {
|
|
34713
|
+
if (error2 instanceof SsrfError) throw new PushError(error2.message);
|
|
34714
|
+
throw error2;
|
|
34715
|
+
}
|
|
34273
34716
|
let response;
|
|
34274
34717
|
try {
|
|
34275
34718
|
response = await fetch(endpoint, {
|
|
34276
34719
|
method: "POST",
|
|
34720
|
+
// A real push service answers the POST directly; a redirect from a push
|
|
34721
|
+
// endpoint is not followed to a private address (ADR-0084).
|
|
34722
|
+
redirect: "manual",
|
|
34277
34723
|
headers: {
|
|
34278
34724
|
Authorization: `vapid t=${vapidToken(endpoint.toString(), config.subject, keys.rawPrivate, keys.rawPublic)}, k=${config.publicKey}`,
|
|
34279
34725
|
"Content-Encoding": "aes128gcm",
|
|
@@ -36360,6 +36806,7 @@ var init_testClient = __esm({
|
|
|
36360
36806
|
reqHeaders["content-length"] = String(Buffer.byteLength(rawBody));
|
|
36361
36807
|
}
|
|
36362
36808
|
const socket = new Socket();
|
|
36809
|
+
Object.defineProperty(socket, "remoteAddress", { value: "127.0.0.1" });
|
|
36363
36810
|
const rawReq = new IncomingMessage2(socket);
|
|
36364
36811
|
rawReq.method = method.toUpperCase();
|
|
36365
36812
|
rawReq.url = path10;
|
|
@@ -36732,6 +37179,7 @@ var init_validator = __esm({
|
|
|
36732
37179
|
var src_exports2 = {};
|
|
36733
37180
|
__export(src_exports2, {
|
|
36734
37181
|
AI_TOOLS: () => AI_TOOLS,
|
|
37182
|
+
ALLOW_PRIVATE_ENV: () => ALLOW_PRIVATE_ENV,
|
|
36735
37183
|
APPLICATION_FORM: () => APPLICATION_FORM,
|
|
36736
37184
|
APPLICATION_JSON: () => APPLICATION_JSON,
|
|
36737
37185
|
APPLICATION_OCTET: () => APPLICATION_OCTET,
|
|
@@ -36839,6 +37287,7 @@ __export(src_exports2, {
|
|
|
36839
37287
|
Session: () => Session,
|
|
36840
37288
|
Sso: () => Sso,
|
|
36841
37289
|
SsoError: () => SsoError,
|
|
37290
|
+
SsrfError: () => SsrfError,
|
|
36842
37291
|
TAKEOVER_KILLED: () => TAKEOVER_KILLED,
|
|
36843
37292
|
TAKEOVER_NOTHING: () => TAKEOVER_NOTHING,
|
|
36844
37293
|
TAKEOVER_REFUSALS: () => TAKEOVER_REFUSALS,
|
|
@@ -36872,6 +37321,7 @@ __export(src_exports2, {
|
|
|
36872
37321
|
addHtmlHelpers: () => addHtmlHelpers,
|
|
36873
37322
|
algorithmAvailable: () => algorithmAvailable,
|
|
36874
37323
|
allEnv: () => allEnv,
|
|
37324
|
+
allowPrivateRequests: () => allowPrivateRequests,
|
|
36875
37325
|
any: () => any,
|
|
36876
37326
|
attachCsrfFromEnv: () => attachCsrfFromEnv,
|
|
36877
37327
|
authMiddleware: () => authMiddleware,
|
|
@@ -36937,6 +37387,7 @@ __export(src_exports2, {
|
|
|
36937
37387
|
graphqlAutoSchemaEnabled: () => graphqlAutoSchemaEnabled,
|
|
36938
37388
|
graphqlEndpoint: () => graphqlEndpoint,
|
|
36939
37389
|
graphqlMaxDepth: () => graphqlMaxDepth,
|
|
37390
|
+
guardUrl: () => guardUrl,
|
|
36940
37391
|
handle: () => handle,
|
|
36941
37392
|
handleFeedbackTurn: () => handleFeedbackTurn,
|
|
36942
37393
|
handleFeedbackWidgetJs: () => handleFeedbackWidgetJs,
|
|
@@ -36950,6 +37401,7 @@ __export(src_exports2, {
|
|
|
36950
37401
|
installAll: () => installAll,
|
|
36951
37402
|
installSelected: () => installSelected,
|
|
36952
37403
|
isBannerSuppressed: () => isBannerSuppressed,
|
|
37404
|
+
isBlockedAddress: () => isBlockedAddress,
|
|
36953
37405
|
isDebugMode: () => isDebugMode,
|
|
36954
37406
|
isDev: () => isDev,
|
|
36955
37407
|
isInstalled: () => isInstalled,
|
|
@@ -37069,6 +37521,7 @@ var init_src2 = __esm({
|
|
|
37069
37521
|
init_service();
|
|
37070
37522
|
init_cache();
|
|
37071
37523
|
init_api();
|
|
37524
|
+
init_ssrf();
|
|
37072
37525
|
init_context();
|
|
37073
37526
|
init_events();
|
|
37074
37527
|
init_devAdmin();
|
|
@@ -37675,14 +38128,72 @@ var init_cachedDatabase = __esm({
|
|
|
37675
38128
|
}
|
|
37676
38129
|
});
|
|
37677
38130
|
|
|
38131
|
+
// src/adapters/sqlCrud.ts
|
|
38132
|
+
function buildInsertRow(dialect, table2, data, suffix = "", options = {}) {
|
|
38133
|
+
const keys = Object.keys(data);
|
|
38134
|
+
const sql = buildInsert(dialect, table2, keys, suffix, options.startAt ?? 1);
|
|
38135
|
+
return { sql, values: Object.values(data) };
|
|
38136
|
+
}
|
|
38137
|
+
function buildInsertRows(dialect, table2, rows) {
|
|
38138
|
+
if (rows.length === 0) return null;
|
|
38139
|
+
const keys = Object.keys(rows[0]);
|
|
38140
|
+
const sql = buildInsert(dialect, table2, keys);
|
|
38141
|
+
const paramsList = rows.map((row) => keys.map((k) => row[k]));
|
|
38142
|
+
return { sql, paramsList };
|
|
38143
|
+
}
|
|
38144
|
+
function buildUpdate(dialect, table2, data, filter, params, options = {}) {
|
|
38145
|
+
const startAt = options.startAt ?? 1;
|
|
38146
|
+
const convertFragment = options.convertFragment ?? identityFragment;
|
|
38147
|
+
const dataKeys = Object.keys(data);
|
|
38148
|
+
const setClauses = buildSetClause(dialect, dataKeys, startAt);
|
|
38149
|
+
const whereStart = startAt + dataKeys.length;
|
|
38150
|
+
if (typeof filter === "string") {
|
|
38151
|
+
const where = filter ? ` WHERE ${convertFragment(filter, whereStart)}` : "";
|
|
38152
|
+
return {
|
|
38153
|
+
sql: `UPDATE ${dialect.quote(table2)} SET ${setClauses}${where}`,
|
|
38154
|
+
values: [...Object.values(data), ...params ?? []]
|
|
38155
|
+
};
|
|
38156
|
+
}
|
|
38157
|
+
const whereClauses = buildWhereClause(dialect, Object.keys(filter), whereStart);
|
|
38158
|
+
return {
|
|
38159
|
+
sql: `UPDATE ${dialect.quote(table2)} SET ${setClauses} WHERE ${whereClauses}`,
|
|
38160
|
+
values: [...Object.values(data), ...Object.values(filter)]
|
|
38161
|
+
};
|
|
38162
|
+
}
|
|
38163
|
+
function buildDelete(dialect, table2, filter, params, options = {}) {
|
|
38164
|
+
const startAt = options.startAt ?? 1;
|
|
38165
|
+
const convertFragment = options.convertFragment ?? identityFragment;
|
|
38166
|
+
if (typeof filter === "string") {
|
|
38167
|
+
const where = filter ? ` WHERE ${convertFragment(filter, startAt)}` : "";
|
|
38168
|
+
return {
|
|
38169
|
+
sql: `DELETE FROM ${dialect.quote(table2)}${where}`,
|
|
38170
|
+
values: params ?? []
|
|
38171
|
+
};
|
|
38172
|
+
}
|
|
38173
|
+
const whereClauses = buildWhereClause(dialect, Object.keys(filter), startAt);
|
|
38174
|
+
return {
|
|
38175
|
+
sql: `DELETE FROM ${dialect.quote(table2)} WHERE ${whereClauses}`,
|
|
38176
|
+
values: Object.values(filter)
|
|
38177
|
+
};
|
|
38178
|
+
}
|
|
38179
|
+
var identityFragment;
|
|
38180
|
+
var init_sqlCrud = __esm({
|
|
38181
|
+
"src/adapters/sqlCrud.ts"() {
|
|
38182
|
+
"use strict";
|
|
38183
|
+
init_sqlDialect();
|
|
38184
|
+
identityFragment = (fragment) => fragment;
|
|
38185
|
+
}
|
|
38186
|
+
});
|
|
38187
|
+
|
|
37678
38188
|
// src/adapters/sqlite.ts
|
|
37679
38189
|
var sqlite_exports = {};
|
|
37680
38190
|
__export(sqlite_exports, {
|
|
37681
|
-
SQLiteAdapter: () => SQLiteAdapter
|
|
38191
|
+
SQLiteAdapter: () => SQLiteAdapter,
|
|
38192
|
+
resolveSqlitePath: () => resolveSqlitePath
|
|
37682
38193
|
});
|
|
37683
38194
|
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
37684
38195
|
import { mkdirSync as mkdirSync18 } from "node:fs";
|
|
37685
|
-
import { dirname as dirname13, isAbsolute as isAbsolute5, join as join29, resolve as resolve18 } from "node:path";
|
|
38196
|
+
import { dirname as dirname13, isAbsolute as isAbsolute5, join as join29, resolve as resolve18, sep as sep7 } from "node:path";
|
|
37686
38197
|
function isIdentifier(str) {
|
|
37687
38198
|
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(str);
|
|
37688
38199
|
}
|
|
@@ -37713,17 +38224,20 @@ function sqliteVersionAtLeast(major, minor, patch2) {
|
|
|
37713
38224
|
}
|
|
37714
38225
|
function resolveSqlitePath(dbPath) {
|
|
37715
38226
|
if (dbPath === ":memory:") return dbPath;
|
|
37716
|
-
|
|
37717
|
-
if (
|
|
37718
|
-
|
|
37719
|
-
|
|
37720
|
-
|
|
37721
|
-
|
|
37722
|
-
|
|
37723
|
-
|
|
37724
|
-
|
|
37725
|
-
|
|
38227
|
+
const isWindowsAbs = /^[A-Za-z]:[\\/]/.test(dbPath);
|
|
38228
|
+
if (isAbsolute5(dbPath) || isWindowsAbs) {
|
|
38229
|
+
return dbPath;
|
|
38230
|
+
}
|
|
38231
|
+
const cwd = resolve18(process.cwd());
|
|
38232
|
+
const path10 = join29(cwd, dbPath);
|
|
38233
|
+
const parent = dirname13(path10);
|
|
38234
|
+
const absParent = resolve18(parent);
|
|
38235
|
+
if (absParent !== cwd && !absParent.startsWith(cwd + sep7)) {
|
|
38236
|
+
throw new Error(
|
|
38237
|
+
`SQLite path "${dbPath}" resolves outside the project directory: "${absParent}" is not within "${cwd}". Tina4 refuses to create directories outside the project (ADR-0086). Use an absolute path for a database that lives outside the project.`
|
|
38238
|
+
);
|
|
37726
38239
|
}
|
|
38240
|
+
mkdirSync18(parent, { recursive: true, mode: 509 });
|
|
37727
38241
|
return path10;
|
|
37728
38242
|
}
|
|
37729
38243
|
function fieldTypeToSQLite(def) {
|
|
@@ -37759,6 +38273,7 @@ var init_sqlite = __esm({
|
|
|
37759
38273
|
"src/adapters/sqlite.ts"() {
|
|
37760
38274
|
"use strict";
|
|
37761
38275
|
init_sqlDialect();
|
|
38276
|
+
init_sqlCrud();
|
|
37762
38277
|
init_sqlTranslator();
|
|
37763
38278
|
_sqliteVersionInfo = null;
|
|
37764
38279
|
SQLiteAdapter = class {
|
|
@@ -37879,16 +38394,12 @@ var init_sqlite = __esm({
|
|
|
37879
38394
|
}
|
|
37880
38395
|
insert(table2, data) {
|
|
37881
38396
|
if (Array.isArray(data)) {
|
|
37882
|
-
|
|
37883
|
-
|
|
37884
|
-
const
|
|
37885
|
-
const paramsList = data.map((row) => keys2.map((k) => row[k]));
|
|
37886
|
-
const result = this.executeMany(sql2, paramsList);
|
|
38397
|
+
const batch = buildInsertRows(ANSI_DIALECT, table2, data);
|
|
38398
|
+
if (!batch) return { success: true, affectedRows: 0 };
|
|
38399
|
+
const result = this.executeMany(batch.sql, batch.paramsList);
|
|
37887
38400
|
return { success: true, affectedRows: result.affectedRows, lastId: result.lastId };
|
|
37888
38401
|
}
|
|
37889
|
-
const
|
|
37890
|
-
const sql = buildInsert(ANSI_DIALECT, table2, keys);
|
|
37891
|
-
const values = Object.values(data);
|
|
38402
|
+
const { sql, values } = buildInsertRow(ANSI_DIALECT, table2, data);
|
|
37892
38403
|
try {
|
|
37893
38404
|
const result = this.db.prepare(sql).run(...toSqlParams(values));
|
|
37894
38405
|
this._lastInsertId = result.lastInsertRowid;
|
|
@@ -37898,21 +38409,7 @@ var init_sqlite = __esm({
|
|
|
37898
38409
|
}
|
|
37899
38410
|
}
|
|
37900
38411
|
update(table2, data, filter, params) {
|
|
37901
|
-
const
|
|
37902
|
-
if (typeof filter === "string") {
|
|
37903
|
-
const where = filter ? ` WHERE ${filter}` : "";
|
|
37904
|
-
const sql2 = `UPDATE ${ANSI_DIALECT.quote(table2)} SET ${setClauses}${where}`;
|
|
37905
|
-
const values2 = [...Object.values(data), ...params ?? []];
|
|
37906
|
-
try {
|
|
37907
|
-
const result = this.db.prepare(sql2).run(...toSqlParams(values2));
|
|
37908
|
-
return { success: true, affectedRows: Number(result.changes) };
|
|
37909
|
-
} catch (e) {
|
|
37910
|
-
return { success: false, affectedRows: 0, error: e.message };
|
|
37911
|
-
}
|
|
37912
|
-
}
|
|
37913
|
-
const whereClauses = buildWhereClause(ANSI_DIALECT, Object.keys(filter));
|
|
37914
|
-
const sql = `UPDATE ${ANSI_DIALECT.quote(table2)} SET ${setClauses} WHERE ${whereClauses}`;
|
|
37915
|
-
const values = [...Object.values(data), ...Object.values(filter)];
|
|
38412
|
+
const { sql, values } = buildUpdate(ANSI_DIALECT, table2, data, filter, params);
|
|
37916
38413
|
try {
|
|
37917
38414
|
const result = this.db.prepare(sql).run(...toSqlParams(values));
|
|
37918
38415
|
return { success: true, affectedRows: Number(result.changes) };
|
|
@@ -37929,18 +38426,7 @@ var init_sqlite = __esm({
|
|
|
37929
38426
|
}
|
|
37930
38427
|
return { success: true, affectedRows: totalAffected };
|
|
37931
38428
|
}
|
|
37932
|
-
|
|
37933
|
-
const sql2 = filter ? `DELETE FROM "${table2}" WHERE ${filter}` : `DELETE FROM "${table2}"`;
|
|
37934
|
-
try {
|
|
37935
|
-
const result = this.db.prepare(sql2).run(...toSqlParams(params ?? []));
|
|
37936
|
-
return { success: true, affectedRows: Number(result.changes) };
|
|
37937
|
-
} catch (e) {
|
|
37938
|
-
return { success: false, affectedRows: 0, error: e.message };
|
|
37939
|
-
}
|
|
37940
|
-
}
|
|
37941
|
-
const whereClauses = buildWhereClause(ANSI_DIALECT, Object.keys(filter));
|
|
37942
|
-
const sql = `DELETE FROM ${ANSI_DIALECT.quote(table2)} WHERE ${whereClauses}`;
|
|
37943
|
-
const values = Object.values(filter);
|
|
38429
|
+
const { sql, values } = buildDelete(ANSI_DIALECT, table2, filter, params);
|
|
37944
38430
|
try {
|
|
37945
38431
|
const result = this.db.prepare(sql).run(...toSqlParams(values));
|
|
37946
38432
|
return { success: true, affectedRows: Number(result.changes) };
|
|
@@ -38232,6 +38718,7 @@ var init_postgres = __esm({
|
|
|
38232
38718
|
"src/adapters/postgres.ts"() {
|
|
38233
38719
|
"use strict";
|
|
38234
38720
|
init_sqlDialect();
|
|
38721
|
+
init_sqlCrud();
|
|
38235
38722
|
init_sqlTranslator();
|
|
38236
38723
|
init_connectTimeout();
|
|
38237
38724
|
pg = null;
|
|
@@ -38433,21 +38920,17 @@ var init_postgres = __esm({
|
|
|
38433
38920
|
async insertAsync(table2, data) {
|
|
38434
38921
|
this.ensureConnected();
|
|
38435
38922
|
if (Array.isArray(data)) {
|
|
38436
|
-
|
|
38437
|
-
|
|
38438
|
-
const sql2 = buildInsert(ANSI_DIALECT, table2, keys2);
|
|
38439
|
-
const paramsList = data.map((row) => keys2.map((k) => row[k]));
|
|
38923
|
+
const batch = buildInsertRows(ANSI_DIALECT, table2, data);
|
|
38924
|
+
if (!batch) return { success: true, affectedRows: 0 };
|
|
38440
38925
|
try {
|
|
38441
|
-
const result = await this.executeManyAsync(
|
|
38926
|
+
const result = await this.executeManyAsync(batch.sql, batch.paramsList);
|
|
38442
38927
|
if (result.lastId !== void 0) this._lastInsertId = result.lastId;
|
|
38443
38928
|
return { success: true, affectedRows: result.totalAffected, lastId: result.lastId };
|
|
38444
38929
|
} catch (e) {
|
|
38445
38930
|
return { success: false, affectedRows: 0, error: e.message };
|
|
38446
38931
|
}
|
|
38447
38932
|
}
|
|
38448
|
-
const
|
|
38449
|
-
const sql = buildInsert(POSTGRES_DIALECT, table2, keys, " RETURNING *");
|
|
38450
|
-
const values = Object.values(data);
|
|
38933
|
+
const { sql, values } = buildInsertRow(POSTGRES_DIALECT, table2, data, " RETURNING *");
|
|
38451
38934
|
try {
|
|
38452
38935
|
const result = await this.client.query(sql, values);
|
|
38453
38936
|
const insertedRow = result.rows[0];
|
|
@@ -38467,26 +38950,10 @@ var init_postgres = __esm({
|
|
|
38467
38950
|
}
|
|
38468
38951
|
async updateAsync(table2, data, filter, params) {
|
|
38469
38952
|
this.ensureConnected();
|
|
38470
|
-
const
|
|
38471
|
-
|
|
38472
|
-
|
|
38473
|
-
|
|
38474
|
-
if (typeof filter === "string") {
|
|
38475
|
-
const where = filter ? ` WHERE ${this.convertPlaceholders(filter, paramIndex)}` : "";
|
|
38476
|
-
const sql2 = `UPDATE ${POSTGRES_DIALECT.quote(table2)} SET ${setClauses}${where}`;
|
|
38477
|
-
const values2 = [...Object.values(data), ...params ?? []];
|
|
38478
|
-
try {
|
|
38479
|
-
const result = await this.client.query(sql2, values2);
|
|
38480
|
-
return { success: true, affectedRows: result.rowCount ?? 0 };
|
|
38481
|
-
} catch (e) {
|
|
38482
|
-
return { success: false, affectedRows: 0, error: e.message };
|
|
38483
|
-
}
|
|
38484
|
-
}
|
|
38485
|
-
const filterKeys = Object.keys(filter);
|
|
38486
|
-
const whereClauses = buildWhereClause(POSTGRES_DIALECT, filterKeys, paramIndex);
|
|
38487
|
-
paramIndex += filterKeys.length;
|
|
38488
|
-
const sql = `UPDATE ${POSTGRES_DIALECT.quote(table2)} SET ${setClauses} WHERE ${whereClauses}`;
|
|
38489
|
-
const values = [...Object.values(data), ...Object.values(filter)];
|
|
38953
|
+
const { sql, values } = buildUpdate(POSTGRES_DIALECT, table2, data, filter, params, {
|
|
38954
|
+
startAt: 1,
|
|
38955
|
+
convertFragment: (fragment, startAt) => this.convertPlaceholders(fragment, startAt)
|
|
38956
|
+
});
|
|
38490
38957
|
try {
|
|
38491
38958
|
const result = await this.client.query(sql, values);
|
|
38492
38959
|
return { success: true, affectedRows: result.rowCount ?? 0 };
|
|
@@ -38499,21 +38966,10 @@ var init_postgres = __esm({
|
|
|
38499
38966
|
}
|
|
38500
38967
|
async deleteAsync(table2, filter, params) {
|
|
38501
38968
|
this.ensureConnected();
|
|
38502
|
-
|
|
38503
|
-
|
|
38504
|
-
|
|
38505
|
-
|
|
38506
|
-
return { success: true, affectedRows: result.rowCount ?? 0 };
|
|
38507
|
-
} catch (e) {
|
|
38508
|
-
return { success: false, affectedRows: 0, error: e.message };
|
|
38509
|
-
}
|
|
38510
|
-
}
|
|
38511
|
-
const filterKeys = Object.keys(filter);
|
|
38512
|
-
let paramIndex = 1;
|
|
38513
|
-
const whereClauses = buildWhereClause(POSTGRES_DIALECT, filterKeys, paramIndex);
|
|
38514
|
-
paramIndex += filterKeys.length;
|
|
38515
|
-
const sql = `DELETE FROM ${POSTGRES_DIALECT.quote(table2)} WHERE ${whereClauses}`;
|
|
38516
|
-
const values = Object.values(filter);
|
|
38969
|
+
const { sql, values } = buildDelete(POSTGRES_DIALECT, table2, filter, params, {
|
|
38970
|
+
startAt: 1,
|
|
38971
|
+
convertFragment: (fragment) => this.bindable(fragment, params)
|
|
38972
|
+
});
|
|
38517
38973
|
try {
|
|
38518
38974
|
const result = await this.client.query(sql, values);
|
|
38519
38975
|
return { success: true, affectedRows: result.rowCount ?? 0 };
|
|
@@ -38687,6 +39143,7 @@ var init_mysql = __esm({
|
|
|
38687
39143
|
"src/adapters/mysql.ts"() {
|
|
38688
39144
|
"use strict";
|
|
38689
39145
|
init_sqlDialect();
|
|
39146
|
+
init_sqlCrud();
|
|
38690
39147
|
init_sqlTranslator();
|
|
38691
39148
|
init_connectTimeout();
|
|
38692
39149
|
mysql2 = null;
|
|
@@ -38868,21 +39325,17 @@ var init_mysql = __esm({
|
|
|
38868
39325
|
async insertAsync(table2, data) {
|
|
38869
39326
|
this.ensureConnected();
|
|
38870
39327
|
if (Array.isArray(data)) {
|
|
38871
|
-
|
|
38872
|
-
|
|
38873
|
-
const sql2 = buildInsert(MYSQL_DIALECT, table2, keys2);
|
|
38874
|
-
const paramsList = data.map((row) => keys2.map((k) => row[k]));
|
|
39328
|
+
const batch = buildInsertRows(MYSQL_DIALECT, table2, data);
|
|
39329
|
+
if (!batch) return { success: true, affectedRows: 0 };
|
|
38875
39330
|
try {
|
|
38876
|
-
const result = await this.executeManyAsync(
|
|
39331
|
+
const result = await this.executeManyAsync(batch.sql, batch.paramsList);
|
|
38877
39332
|
if (result.lastId !== void 0) this._lastInsertId = result.lastId;
|
|
38878
39333
|
return { success: true, affectedRows: result.totalAffected, lastId: result.lastId };
|
|
38879
39334
|
} catch (e) {
|
|
38880
39335
|
return { success: false, affectedRows: 0, error: e.message };
|
|
38881
39336
|
}
|
|
38882
39337
|
}
|
|
38883
|
-
const
|
|
38884
|
-
const sql = buildInsert(MYSQL_DIALECT, table2, keys);
|
|
38885
|
-
const values = Object.values(data);
|
|
39338
|
+
const { sql, values } = buildInsertRow(MYSQL_DIALECT, table2, data);
|
|
38886
39339
|
try {
|
|
38887
39340
|
const result = await this.queryPromise(sql, values);
|
|
38888
39341
|
this._lastInsertId = result.insertId ?? null;
|
|
@@ -38900,21 +39353,7 @@ var init_mysql = __esm({
|
|
|
38900
39353
|
}
|
|
38901
39354
|
async updateAsync(table2, data, filter, params) {
|
|
38902
39355
|
this.ensureConnected();
|
|
38903
|
-
const
|
|
38904
|
-
if (typeof filter === "string") {
|
|
38905
|
-
const where = filter ? ` WHERE ${filter}` : "";
|
|
38906
|
-
const sql2 = `UPDATE ${MYSQL_DIALECT.quote(table2)} SET ${setClauses}${where}`;
|
|
38907
|
-
const values2 = [...Object.values(data), ...params ?? []];
|
|
38908
|
-
try {
|
|
38909
|
-
const result = await this.queryPromise(sql2, values2);
|
|
38910
|
-
return { success: true, affectedRows: result.affectedRows ?? 0 };
|
|
38911
|
-
} catch (e) {
|
|
38912
|
-
return { success: false, affectedRows: 0, error: e.message };
|
|
38913
|
-
}
|
|
38914
|
-
}
|
|
38915
|
-
const whereClauses = buildWhereClause(MYSQL_DIALECT, Object.keys(filter));
|
|
38916
|
-
const sql = `UPDATE ${MYSQL_DIALECT.quote(table2)} SET ${setClauses} WHERE ${whereClauses}`;
|
|
38917
|
-
const values = [...Object.values(data), ...Object.values(filter)];
|
|
39356
|
+
const { sql, values } = buildUpdate(MYSQL_DIALECT, table2, data, filter, params);
|
|
38918
39357
|
try {
|
|
38919
39358
|
const result = await this.queryPromise(sql, values);
|
|
38920
39359
|
return { success: true, affectedRows: result.affectedRows ?? 0 };
|
|
@@ -38927,18 +39366,7 @@ var init_mysql = __esm({
|
|
|
38927
39366
|
}
|
|
38928
39367
|
async deleteAsync(table2, filter, params) {
|
|
38929
39368
|
this.ensureConnected();
|
|
38930
|
-
|
|
38931
|
-
const sql2 = filter ? `DELETE FROM \`${table2}\` WHERE ${filter}` : `DELETE FROM \`${table2}\``;
|
|
38932
|
-
try {
|
|
38933
|
-
const result = await this.queryPromise(sql2, params ?? []);
|
|
38934
|
-
return { success: true, affectedRows: result.affectedRows ?? 0 };
|
|
38935
|
-
} catch (e) {
|
|
38936
|
-
return { success: false, affectedRows: 0, error: e.message };
|
|
38937
|
-
}
|
|
38938
|
-
}
|
|
38939
|
-
const whereClauses = buildWhereClause(MYSQL_DIALECT, Object.keys(filter));
|
|
38940
|
-
const sql = `DELETE FROM ${MYSQL_DIALECT.quote(table2)} WHERE ${whereClauses}`;
|
|
38941
|
-
const values = Object.values(filter);
|
|
39369
|
+
const { sql, values } = buildDelete(MYSQL_DIALECT, table2, filter, params);
|
|
38942
39370
|
try {
|
|
38943
39371
|
const result = await this.queryPromise(sql, values);
|
|
38944
39372
|
return { success: true, affectedRows: result.affectedRows ?? 0 };
|
|
@@ -39090,6 +39518,7 @@ var init_mssql = __esm({
|
|
|
39090
39518
|
"src/adapters/mssql.ts"() {
|
|
39091
39519
|
"use strict";
|
|
39092
39520
|
init_sqlDialect();
|
|
39521
|
+
init_sqlCrud();
|
|
39093
39522
|
init_sqlTranslator();
|
|
39094
39523
|
init_connectTimeout();
|
|
39095
39524
|
tedious = null;
|
|
@@ -39346,21 +39775,17 @@ var init_mssql = __esm({
|
|
|
39346
39775
|
async insertAsync(table2, data) {
|
|
39347
39776
|
this.ensureConnected();
|
|
39348
39777
|
if (Array.isArray(data)) {
|
|
39349
|
-
|
|
39350
|
-
|
|
39351
|
-
const sql2 = buildInsert({ quote: MSSQL_DIALECT.quote, marker: ANSI_DIALECT.marker }, table2, keys2);
|
|
39352
|
-
const paramsList = data.map((row) => keys2.map((k) => row[k]));
|
|
39778
|
+
const batch = buildInsertRows({ quote: MSSQL_DIALECT.quote, marker: ANSI_DIALECT.marker }, table2, data);
|
|
39779
|
+
if (!batch) return { success: true, affectedRows: 0 };
|
|
39353
39780
|
try {
|
|
39354
|
-
const result = await this.executeManyAsync(
|
|
39781
|
+
const result = await this.executeManyAsync(batch.sql, batch.paramsList);
|
|
39355
39782
|
if (result.lastId !== void 0) this._lastInsertId = result.lastId;
|
|
39356
39783
|
return { success: true, affectedRows: result.totalAffected, lastId: result.lastId };
|
|
39357
39784
|
} catch (e) {
|
|
39358
39785
|
return { success: false, affectedRows: 0, error: e.message };
|
|
39359
39786
|
}
|
|
39360
39787
|
}
|
|
39361
|
-
const
|
|
39362
|
-
const sql = buildInsert(MSSQL_DIALECT, table2, keys, "; SELECT SCOPE_IDENTITY() AS id", 0);
|
|
39363
|
-
const values = Object.values(data);
|
|
39788
|
+
const { sql, values } = buildInsertRow(MSSQL_DIALECT, table2, data, "; SELECT SCOPE_IDENTITY() AS id", { startAt: 0 });
|
|
39364
39789
|
try {
|
|
39365
39790
|
const result = await this.execSqlPromise(sql, values);
|
|
39366
39791
|
const id = result.rows[0]?.id ?? null;
|
|
@@ -39383,26 +39808,10 @@ var init_mssql = __esm({
|
|
|
39383
39808
|
}
|
|
39384
39809
|
async updateAsync(table2, data, filter, params) {
|
|
39385
39810
|
this.ensureConnected();
|
|
39386
|
-
const
|
|
39387
|
-
|
|
39388
|
-
|
|
39389
|
-
|
|
39390
|
-
if (typeof filter === "string") {
|
|
39391
|
-
const where = filter ? ` WHERE ${this.convertPlaceholders(filter, paramIndex)}` : "";
|
|
39392
|
-
const sql2 = `UPDATE ${MSSQL_DIALECT.quote(table2)} SET ${setClauses}${where}`;
|
|
39393
|
-
const values2 = [...Object.values(data), ...params ?? []];
|
|
39394
|
-
try {
|
|
39395
|
-
const result = await this.execSqlPromise(sql2, values2);
|
|
39396
|
-
return { success: true, affectedRows: result.rowCount };
|
|
39397
|
-
} catch (e) {
|
|
39398
|
-
return { success: false, affectedRows: 0, error: e.message };
|
|
39399
|
-
}
|
|
39400
|
-
}
|
|
39401
|
-
const filterKeys = Object.keys(filter);
|
|
39402
|
-
const whereClauses = buildWhereClause(MSSQL_DIALECT, filterKeys, paramIndex);
|
|
39403
|
-
paramIndex += filterKeys.length;
|
|
39404
|
-
const sql = `UPDATE ${MSSQL_DIALECT.quote(table2)} SET ${setClauses} WHERE ${whereClauses}`;
|
|
39405
|
-
const values = [...Object.values(data), ...Object.values(filter)];
|
|
39811
|
+
const { sql, values } = buildUpdate(MSSQL_DIALECT, table2, data, filter, params, {
|
|
39812
|
+
startAt: 0,
|
|
39813
|
+
convertFragment: (fragment, startAt) => this.convertPlaceholders(fragment, startAt)
|
|
39814
|
+
});
|
|
39406
39815
|
try {
|
|
39407
39816
|
const result = await this.execSqlPromise(sql, values);
|
|
39408
39817
|
return { success: true, affectedRows: result.rowCount };
|
|
@@ -39415,21 +39824,10 @@ var init_mssql = __esm({
|
|
|
39415
39824
|
}
|
|
39416
39825
|
async deleteAsync(table2, filter, params) {
|
|
39417
39826
|
this.ensureConnected();
|
|
39418
|
-
|
|
39419
|
-
|
|
39420
|
-
|
|
39421
|
-
|
|
39422
|
-
return { success: true, affectedRows: result.rowCount };
|
|
39423
|
-
} catch (e) {
|
|
39424
|
-
return { success: false, affectedRows: 0, error: e.message };
|
|
39425
|
-
}
|
|
39426
|
-
}
|
|
39427
|
-
const filterKeys = Object.keys(filter);
|
|
39428
|
-
let paramIndex = 0;
|
|
39429
|
-
const whereClauses = buildWhereClause(MSSQL_DIALECT, filterKeys, paramIndex);
|
|
39430
|
-
paramIndex += filterKeys.length;
|
|
39431
|
-
const sql = `DELETE FROM ${MSSQL_DIALECT.quote(table2)} WHERE ${whereClauses}`;
|
|
39432
|
-
const values = Object.values(filter);
|
|
39827
|
+
const { sql, values } = buildDelete(MSSQL_DIALECT, table2, filter, params, {
|
|
39828
|
+
startAt: 0,
|
|
39829
|
+
convertFragment: (fragment) => this.bindable(fragment, params)
|
|
39830
|
+
});
|
|
39433
39831
|
try {
|
|
39434
39832
|
const result = await this.execSqlPromise(sql, values);
|
|
39435
39833
|
return { success: true, affectedRows: result.rowCount };
|
|
@@ -39682,6 +40080,7 @@ var init_firebird = __esm({
|
|
|
39682
40080
|
"src/adapters/firebird.ts"() {
|
|
39683
40081
|
"use strict";
|
|
39684
40082
|
init_sqlDialect();
|
|
40083
|
+
init_sqlCrud();
|
|
39685
40084
|
init_sqlTranslator();
|
|
39686
40085
|
init_connectTimeout();
|
|
39687
40086
|
firebird = null;
|
|
@@ -40052,17 +40451,13 @@ var init_firebird = __esm({
|
|
|
40052
40451
|
async insertAsync(table2, data) {
|
|
40053
40452
|
this.ensureConnected();
|
|
40054
40453
|
if (Array.isArray(data)) {
|
|
40055
|
-
|
|
40056
|
-
|
|
40057
|
-
const
|
|
40058
|
-
const paramsList = data.map((row) => keys2.map((k) => row[k]));
|
|
40059
|
-
const result = await this.executeManyAsync(sql2, paramsList);
|
|
40454
|
+
const batch = buildInsertRows(FB_DIALECT, table2, data);
|
|
40455
|
+
if (!batch) return { success: true, affectedRows: 0 };
|
|
40456
|
+
const result = await this.executeManyAsync(batch.sql, batch.paramsList);
|
|
40060
40457
|
const lastId2 = await this.readGeneratorId(table2) ?? result.lastId;
|
|
40061
40458
|
return { success: true, affectedRows: result.totalAffected, lastId: lastId2 ?? void 0 };
|
|
40062
40459
|
}
|
|
40063
|
-
const
|
|
40064
|
-
const sql = buildInsert(FB_DIALECT, table2, keys);
|
|
40065
|
-
const values = Object.values(data);
|
|
40460
|
+
const { sql, values } = buildInsertRow(FB_DIALECT, table2, data);
|
|
40066
40461
|
await this.executePromise(sql, values);
|
|
40067
40462
|
const lastId = await this.readGeneratorId(table2);
|
|
40068
40463
|
return { success: true, affectedRows: 1, lastId: lastId ?? void 0 };
|
|
@@ -40072,18 +40467,7 @@ var init_firebird = __esm({
|
|
|
40072
40467
|
}
|
|
40073
40468
|
async updateAsync(table2, data, filter, params) {
|
|
40074
40469
|
this.ensureConnected();
|
|
40075
|
-
const
|
|
40076
|
-
if (typeof filter === "string") {
|
|
40077
|
-
const where = filter ? ` WHERE ${filter}` : "";
|
|
40078
|
-
const affected2 = await this.executeReturningCount(
|
|
40079
|
-
`UPDATE ${fbQuote(table2)} SET ${setClauses}${where}`,
|
|
40080
|
-
[...Object.values(data), ...params ?? []]
|
|
40081
|
-
);
|
|
40082
|
-
return { success: true, affectedRows: affected2 };
|
|
40083
|
-
}
|
|
40084
|
-
const whereClauses = buildWhereClause(FB_DIALECT, Object.keys(filter));
|
|
40085
|
-
const sql = `UPDATE ${FB_DIALECT.quote(table2)} SET ${setClauses} WHERE ${whereClauses}`;
|
|
40086
|
-
const values = [...Object.values(data), ...Object.values(filter)];
|
|
40470
|
+
const { sql, values } = buildUpdate(FB_DIALECT, table2, data, filter, params);
|
|
40087
40471
|
const affected = await this.executeReturningCount(sql, values);
|
|
40088
40472
|
return { success: true, affectedRows: affected };
|
|
40089
40473
|
}
|
|
@@ -40092,14 +40476,7 @@ var init_firebird = __esm({
|
|
|
40092
40476
|
}
|
|
40093
40477
|
async deleteAsync(table2, filter, params) {
|
|
40094
40478
|
this.ensureConnected();
|
|
40095
|
-
|
|
40096
|
-
const where = filter ? ` WHERE ${filter}` : "";
|
|
40097
|
-
const affected2 = await this.executeReturningCount(`DELETE FROM ${fbQuote(table2)}${where}`, params ?? []);
|
|
40098
|
-
return { success: true, affectedRows: affected2 };
|
|
40099
|
-
}
|
|
40100
|
-
const whereClauses = buildWhereClause(FB_DIALECT, Object.keys(filter));
|
|
40101
|
-
const sql = `DELETE FROM ${FB_DIALECT.quote(table2)} WHERE ${whereClauses}`;
|
|
40102
|
-
const values = Object.values(filter);
|
|
40479
|
+
const { sql, values } = buildDelete(FB_DIALECT, table2, filter, params);
|
|
40103
40480
|
const affected = await this.executeReturningCount(sql, values);
|
|
40104
40481
|
return { success: true, affectedRows: affected };
|
|
40105
40482
|
}
|
|
@@ -40931,6 +41308,7 @@ var init_odbc = __esm({
|
|
|
40931
41308
|
"src/adapters/odbc.ts"() {
|
|
40932
41309
|
"use strict";
|
|
40933
41310
|
init_sqlDialect();
|
|
41311
|
+
init_sqlCrud();
|
|
40934
41312
|
init_sqlTranslator();
|
|
40935
41313
|
init_connectTimeout();
|
|
40936
41314
|
odbcModule = null;
|
|
@@ -41130,16 +41508,12 @@ var init_odbc = __esm({
|
|
|
41130
41508
|
async insertAsync(table2, data) {
|
|
41131
41509
|
this.ensureConnected();
|
|
41132
41510
|
if (Array.isArray(data)) {
|
|
41133
|
-
|
|
41134
|
-
|
|
41135
|
-
const
|
|
41136
|
-
const paramsList = data.map((row) => keys2.map((k) => row[k]));
|
|
41137
|
-
const { totalAffected } = await this.executeManyAsync(sql2, paramsList);
|
|
41511
|
+
const batch = buildInsertRows(ANSI_DIALECT, table2, data);
|
|
41512
|
+
if (!batch) return { success: true, affectedRows: 0 };
|
|
41513
|
+
const { totalAffected } = await this.executeManyAsync(batch.sql, batch.paramsList);
|
|
41138
41514
|
return { success: true, affectedRows: totalAffected };
|
|
41139
41515
|
}
|
|
41140
|
-
const
|
|
41141
|
-
const sql = buildInsert(ANSI_DIALECT, table2, keys);
|
|
41142
|
-
const values = Object.values(data);
|
|
41516
|
+
const { sql, values } = buildInsertRow(ANSI_DIALECT, table2, data);
|
|
41143
41517
|
try {
|
|
41144
41518
|
const result = await this.connection.query(sql, values);
|
|
41145
41519
|
return { success: true, affectedRows: this.affectedCount(result), lastId: this._lastInsertId ?? void 0 };
|
|
@@ -41150,17 +41524,7 @@ var init_odbc = __esm({
|
|
|
41150
41524
|
/** Update rows in a table matching filter. */
|
|
41151
41525
|
async updateAsync(table2, data, filter, params) {
|
|
41152
41526
|
this.ensureConnected();
|
|
41153
|
-
const
|
|
41154
|
-
let whereSql;
|
|
41155
|
-
let values;
|
|
41156
|
-
if (typeof filter === "string") {
|
|
41157
|
-
whereSql = filter;
|
|
41158
|
-
values = [...Object.values(data), ...params ?? []];
|
|
41159
|
-
} else {
|
|
41160
|
-
whereSql = buildWhereClause(ANSI_DIALECT, Object.keys(filter));
|
|
41161
|
-
values = [...Object.values(data), ...Object.values(filter)];
|
|
41162
|
-
}
|
|
41163
|
-
const sql = `UPDATE ${ANSI_DIALECT.quote(table2)} SET ${setClauses} WHERE ${whereSql}`;
|
|
41527
|
+
const { sql, values } = buildUpdate(ANSI_DIALECT, table2, data, filter, params);
|
|
41164
41528
|
try {
|
|
41165
41529
|
const result = await this.connection.query(sql, values);
|
|
41166
41530
|
return { success: true, affectedRows: this.affectedCount(result) };
|
|
@@ -41179,18 +41543,7 @@ var init_odbc = __esm({
|
|
|
41179
41543
|
}
|
|
41180
41544
|
return { success: true, affectedRows: totalAffected };
|
|
41181
41545
|
}
|
|
41182
|
-
|
|
41183
|
-
const sql2 = filter ? `DELETE FROM "${table2}" WHERE ${filter}` : `DELETE FROM "${table2}"`;
|
|
41184
|
-
try {
|
|
41185
|
-
const result = await this.connection.query(sql2, params ?? []);
|
|
41186
|
-
return { success: true, affectedRows: this.affectedCount(result) };
|
|
41187
|
-
} catch (e) {
|
|
41188
|
-
return { success: false, affectedRows: 0, error: e.message };
|
|
41189
|
-
}
|
|
41190
|
-
}
|
|
41191
|
-
const whereClauses = buildWhereClause(ANSI_DIALECT, Object.keys(filter));
|
|
41192
|
-
const sql = `DELETE FROM ${ANSI_DIALECT.quote(table2)} WHERE ${whereClauses}`;
|
|
41193
|
-
const values = Object.values(filter);
|
|
41546
|
+
const { sql, values } = buildDelete(ANSI_DIALECT, table2, filter, params);
|
|
41194
41547
|
try {
|
|
41195
41548
|
const result = await this.connection.query(sql, values);
|
|
41196
41549
|
return { success: true, affectedRows: this.affectedCount(result) };
|
|
@@ -44910,11 +45263,11 @@ async function eagerLoadBelongsToRelation(instances, model, relatedClass, relati
|
|
|
44910
45263
|
const relatedPkColumn = relatedClass.getDbColumn(relatedPk);
|
|
44911
45264
|
const related = await eagerQueryRelated(relatedClass, relatedPkColumn, values);
|
|
44912
45265
|
if (nested.length > 0 && related.length > 0) await relatedClass._eagerLoad(related, nested);
|
|
44913
|
-
const
|
|
44914
|
-
for (const record of related)
|
|
45266
|
+
const lookup2 = {};
|
|
45267
|
+
for (const record of related) lookup2[_joinKey(record[relatedPk])] = record;
|
|
44915
45268
|
for (const instance of instances) {
|
|
44916
45269
|
const value = instance[fkProp];
|
|
44917
|
-
relationshipCache(instance)[relationName] = value !== void 0 && value !== null ?
|
|
45270
|
+
relationshipCache(instance)[relationName] = value !== void 0 && value !== null ? lookup2[_joinKey(value)] ?? null : null;
|
|
44918
45271
|
}
|
|
44919
45272
|
}
|
|
44920
45273
|
async function eagerLoadRelation(instances, model, relationName, nested) {
|
|
@@ -48070,7 +48423,7 @@ var init_attachment = __esm({
|
|
|
48070
48423
|
// src/realtime/storage.ts
|
|
48071
48424
|
import { randomBytes as randomBytes11 } from "node:crypto";
|
|
48072
48425
|
import { mkdirSync as mkdirSync21, readFileSync as readFileSync26, writeFileSync as writeFileSync18, unlinkSync as unlinkSync8, statSync as statSync19 } from "node:fs";
|
|
48073
|
-
import { resolve as resolve20, sep as
|
|
48426
|
+
import { resolve as resolve20, sep as sep8 } from "node:path";
|
|
48074
48427
|
import { createRequire as createRequire8 } from "node:module";
|
|
48075
48428
|
function loadAwsSdk(packageName) {
|
|
48076
48429
|
try {
|
|
@@ -48123,7 +48476,7 @@ var init_storage = __esm({
|
|
|
48123
48476
|
// Resolve inside the root and reject any traversal attempt.
|
|
48124
48477
|
pathFor(key) {
|
|
48125
48478
|
const target = resolve20(this.directory, key);
|
|
48126
|
-
if (target !== this.directory && !target.startsWith(this.directory +
|
|
48479
|
+
if (target !== this.directory && !target.startsWith(this.directory + sep8)) {
|
|
48127
48480
|
throw new Error(`unsafe storage key: ${JSON.stringify(key)}`);
|
|
48128
48481
|
}
|
|
48129
48482
|
return target;
|
|
@@ -48640,6 +48993,7 @@ __export(index_exports, {
|
|
|
48640
48993
|
resolveDbPool: () => resolveDbPool,
|
|
48641
48994
|
resolveFirebirdCharset: () => resolveFirebirdCharset,
|
|
48642
48995
|
resolveGraphConnectTimeout: () => resolveGraphConnectTimeout,
|
|
48996
|
+
resolveSqlitePath: () => resolveSqlitePath,
|
|
48643
48997
|
rollback: () => rollback,
|
|
48644
48998
|
seedModels: () => seedModels,
|
|
48645
48999
|
seedOrm: () => seedOrm,
|
|
@@ -48806,6 +49160,7 @@ export {
|
|
|
48806
49160
|
resolveDbPool,
|
|
48807
49161
|
resolveFirebirdCharset,
|
|
48808
49162
|
resolveGraphConnectTimeout,
|
|
49163
|
+
resolveSqlitePath,
|
|
48809
49164
|
rollback,
|
|
48810
49165
|
seedModels,
|
|
48811
49166
|
seedOrm,
|