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
package/packages/cli/dist/bin.js
CHANGED
|
@@ -951,6 +951,8 @@ var auth_exports = {};
|
|
|
951
951
|
__export(auth_exports, {
|
|
952
952
|
Auth: () => Auth,
|
|
953
953
|
JWT_LEEWAY_SECONDS: () => JWT_LEEWAY_SECONDS,
|
|
954
|
+
MIN_SECRET_BYTES: () => MIN_SECRET_BYTES,
|
|
955
|
+
NON_IDENTITY_TOKEN_TYPES: () => NON_IDENTITY_TOKEN_TYPES,
|
|
954
956
|
algorithmAvailable: () => algorithmAvailable,
|
|
955
957
|
authMiddleware: () => authMiddleware,
|
|
956
958
|
authenticateRequest: () => authenticateRequest,
|
|
@@ -960,7 +962,10 @@ __export(auth_exports, {
|
|
|
960
962
|
getPayload: () => getPayload,
|
|
961
963
|
getToken: () => getToken,
|
|
962
964
|
hashPassword: () => hashPassword,
|
|
965
|
+
insecureSecretMessage: () => insecureSecretMessage,
|
|
966
|
+
isIdentityPayload: () => isIdentityPayload,
|
|
963
967
|
refreshToken: () => refreshToken,
|
|
968
|
+
requireBootSecret: () => requireBootSecret,
|
|
964
969
|
resolveAlgorithm: () => resolveAlgorithm,
|
|
965
970
|
validToken: () => validToken,
|
|
966
971
|
validateApiKey: () => validateApiKey
|
|
@@ -996,6 +1001,23 @@ async function _logWarning(message) {
|
|
|
996
1001
|
function _warnBlankSecret() {
|
|
997
1002
|
void _logWarning(BLANK_SECRET_WARNING);
|
|
998
1003
|
}
|
|
1004
|
+
function isIdentityPayload(payload) {
|
|
1005
|
+
return payload != null && !NON_IDENTITY_TOKEN_TYPES.includes(payload.type);
|
|
1006
|
+
}
|
|
1007
|
+
function insecureSecretMessage(secret) {
|
|
1008
|
+
const length = Buffer.byteLength(secret ?? "", "utf8");
|
|
1009
|
+
if (length >= MIN_SECRET_BYTES) return null;
|
|
1010
|
+
const what = length === 0 ? "is not set" : `is ${length} bytes`;
|
|
1011
|
+
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.`;
|
|
1012
|
+
}
|
|
1013
|
+
function requireBootSecret() {
|
|
1014
|
+
const algorithm = (process.env.TINA4_JWT_ALGORITHM ?? "").trim() || "HS256";
|
|
1015
|
+
if (RSA_SIGN_ALGORITHMS.has(algorithm)) return;
|
|
1016
|
+
const secret = process.env.TINA4_SECRET ?? "";
|
|
1017
|
+
if (secret === "" && _isDev()) return;
|
|
1018
|
+
const message = insecureSecretMessage(secret);
|
|
1019
|
+
if (message) throw new Error(message);
|
|
1020
|
+
}
|
|
999
1021
|
function ensureDevSecret(cwd) {
|
|
1000
1022
|
if (process.env.TINA4_SECRET) return null;
|
|
1001
1023
|
if (!_isDev() || _isCi() || _isProduction()) {
|
|
@@ -1126,6 +1148,13 @@ function validToken(token, secret, algorithm) {
|
|
|
1126
1148
|
const [h, p, sig] = parts;
|
|
1127
1149
|
const header = JSON.parse(base64urlDecode(h).toString());
|
|
1128
1150
|
if (header.alg !== resolvedAlgorithm) return null;
|
|
1151
|
+
if (HMAC_DIGESTS.has(resolvedAlgorithm)) {
|
|
1152
|
+
const weak = insecureSecretMessage(resolvedSecret);
|
|
1153
|
+
if (weak) {
|
|
1154
|
+
void _logWarning(weak);
|
|
1155
|
+
return null;
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1129
1158
|
const signingInput = `${h}.${p}`;
|
|
1130
1159
|
if (!verifySignature(signingInput, sig, resolvedSecret, resolvedAlgorithm)) {
|
|
1131
1160
|
return null;
|
|
@@ -1157,6 +1186,8 @@ function getPayload(token) {
|
|
|
1157
1186
|
function sign(input, secret, algorithm) {
|
|
1158
1187
|
const digest = HMAC_DIGESTS.get(algorithm);
|
|
1159
1188
|
if (digest) {
|
|
1189
|
+
const weak = insecureSecretMessage(secret);
|
|
1190
|
+
if (weak) throw new Error(weak);
|
|
1160
1191
|
return base64urlEncode(createHmac(digest, secret).update(input).digest());
|
|
1161
1192
|
}
|
|
1162
1193
|
const rsaAlgorithm = RSA_SIGN_ALGORITHMS.get(algorithm);
|
|
@@ -1214,17 +1245,17 @@ function authMiddleware(secret, algorithm) {
|
|
|
1214
1245
|
return;
|
|
1215
1246
|
}
|
|
1216
1247
|
const token = authHeader.slice(7);
|
|
1217
|
-
|
|
1248
|
+
const payload = validToken(token, secret, algorithm);
|
|
1249
|
+
if (!isIdentityPayload(payload)) {
|
|
1218
1250
|
res({ error: "Unauthorized" }, 401);
|
|
1219
1251
|
return;
|
|
1220
1252
|
}
|
|
1221
|
-
req2.auth =
|
|
1253
|
+
req2.auth = payload;
|
|
1222
1254
|
next();
|
|
1223
1255
|
};
|
|
1224
1256
|
}
|
|
1225
1257
|
function refreshToken(token, expiresIn = 60) {
|
|
1226
|
-
|
|
1227
|
-
const payload = getPayload(token);
|
|
1258
|
+
const payload = validToken(token);
|
|
1228
1259
|
if (!payload) return null;
|
|
1229
1260
|
const { iat: _iat, exp: _exp, ...claims } = payload;
|
|
1230
1261
|
return getToken(claims, expiresIn);
|
|
@@ -1233,7 +1264,8 @@ function authenticateRequest(headers, secret, algorithm) {
|
|
|
1233
1264
|
const authHeader = headers.authorization ?? headers.Authorization ?? "";
|
|
1234
1265
|
if (!authHeader.startsWith("Bearer ")) return null;
|
|
1235
1266
|
const token = authHeader.slice(7);
|
|
1236
|
-
|
|
1267
|
+
const payload = validToken(token, secret, algorithm);
|
|
1268
|
+
if (payload) return isIdentityPayload(payload) ? payload : null;
|
|
1237
1269
|
if (validateApiKey(token)) {
|
|
1238
1270
|
return { _auth: "api_key" };
|
|
1239
1271
|
}
|
|
@@ -1247,12 +1279,14 @@ function validateApiKey(provided, expected) {
|
|
|
1247
1279
|
if (a.length !== b.length) return false;
|
|
1248
1280
|
return timingSafeEqual(a, b);
|
|
1249
1281
|
}
|
|
1250
|
-
var BLANK_SECRET_WARNING, HMAC_DIGESTS, RSA_SIGN_ALGORITHMS, KNOWN_ALGORITHMS, ALGORITHM_REMEDY, capabilityCache, JWT_LEEWAY_SECONDS, Auth;
|
|
1282
|
+
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;
|
|
1251
1283
|
var init_auth = __esm({
|
|
1252
1284
|
"../core/src/auth.ts"() {
|
|
1253
1285
|
"use strict";
|
|
1254
1286
|
init_dotenv();
|
|
1255
1287
|
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.";
|
|
1288
|
+
NON_IDENTITY_TOKEN_TYPES = ["form"];
|
|
1289
|
+
MIN_SECRET_BYTES = 32;
|
|
1256
1290
|
HMAC_DIGESTS = /* @__PURE__ */ new Map([
|
|
1257
1291
|
["HS256", "sha256"],
|
|
1258
1292
|
["HS384", "sha384"],
|
|
@@ -3541,14 +3575,72 @@ var init_cachedDatabase = __esm({
|
|
|
3541
3575
|
}
|
|
3542
3576
|
});
|
|
3543
3577
|
|
|
3578
|
+
// ../orm/src/adapters/sqlCrud.ts
|
|
3579
|
+
function buildInsertRow(dialect, table2, data, suffix = "", options = {}) {
|
|
3580
|
+
const keys = Object.keys(data);
|
|
3581
|
+
const sql = buildInsert(dialect, table2, keys, suffix, options.startAt ?? 1);
|
|
3582
|
+
return { sql, values: Object.values(data) };
|
|
3583
|
+
}
|
|
3584
|
+
function buildInsertRows(dialect, table2, rows) {
|
|
3585
|
+
if (rows.length === 0) return null;
|
|
3586
|
+
const keys = Object.keys(rows[0]);
|
|
3587
|
+
const sql = buildInsert(dialect, table2, keys);
|
|
3588
|
+
const paramsList = rows.map((row) => keys.map((k) => row[k]));
|
|
3589
|
+
return { sql, paramsList };
|
|
3590
|
+
}
|
|
3591
|
+
function buildUpdate(dialect, table2, data, filter, params, options = {}) {
|
|
3592
|
+
const startAt = options.startAt ?? 1;
|
|
3593
|
+
const convertFragment = options.convertFragment ?? identityFragment;
|
|
3594
|
+
const dataKeys = Object.keys(data);
|
|
3595
|
+
const setClauses = buildSetClause(dialect, dataKeys, startAt);
|
|
3596
|
+
const whereStart = startAt + dataKeys.length;
|
|
3597
|
+
if (typeof filter === "string") {
|
|
3598
|
+
const where = filter ? ` WHERE ${convertFragment(filter, whereStart)}` : "";
|
|
3599
|
+
return {
|
|
3600
|
+
sql: `UPDATE ${dialect.quote(table2)} SET ${setClauses}${where}`,
|
|
3601
|
+
values: [...Object.values(data), ...params ?? []]
|
|
3602
|
+
};
|
|
3603
|
+
}
|
|
3604
|
+
const whereClauses = buildWhereClause(dialect, Object.keys(filter), whereStart);
|
|
3605
|
+
return {
|
|
3606
|
+
sql: `UPDATE ${dialect.quote(table2)} SET ${setClauses} WHERE ${whereClauses}`,
|
|
3607
|
+
values: [...Object.values(data), ...Object.values(filter)]
|
|
3608
|
+
};
|
|
3609
|
+
}
|
|
3610
|
+
function buildDelete(dialect, table2, filter, params, options = {}) {
|
|
3611
|
+
const startAt = options.startAt ?? 1;
|
|
3612
|
+
const convertFragment = options.convertFragment ?? identityFragment;
|
|
3613
|
+
if (typeof filter === "string") {
|
|
3614
|
+
const where = filter ? ` WHERE ${convertFragment(filter, startAt)}` : "";
|
|
3615
|
+
return {
|
|
3616
|
+
sql: `DELETE FROM ${dialect.quote(table2)}${where}`,
|
|
3617
|
+
values: params ?? []
|
|
3618
|
+
};
|
|
3619
|
+
}
|
|
3620
|
+
const whereClauses = buildWhereClause(dialect, Object.keys(filter), startAt);
|
|
3621
|
+
return {
|
|
3622
|
+
sql: `DELETE FROM ${dialect.quote(table2)} WHERE ${whereClauses}`,
|
|
3623
|
+
values: Object.values(filter)
|
|
3624
|
+
};
|
|
3625
|
+
}
|
|
3626
|
+
var identityFragment;
|
|
3627
|
+
var init_sqlCrud = __esm({
|
|
3628
|
+
"../orm/src/adapters/sqlCrud.ts"() {
|
|
3629
|
+
"use strict";
|
|
3630
|
+
init_sqlDialect();
|
|
3631
|
+
identityFragment = (fragment) => fragment;
|
|
3632
|
+
}
|
|
3633
|
+
});
|
|
3634
|
+
|
|
3544
3635
|
// ../orm/src/adapters/sqlite.ts
|
|
3545
3636
|
var sqlite_exports = {};
|
|
3546
3637
|
__export(sqlite_exports, {
|
|
3547
|
-
SQLiteAdapter: () => SQLiteAdapter
|
|
3638
|
+
SQLiteAdapter: () => SQLiteAdapter,
|
|
3639
|
+
resolveSqlitePath: () => resolveSqlitePath
|
|
3548
3640
|
});
|
|
3549
3641
|
import { DatabaseSync } from "node:sqlite";
|
|
3550
3642
|
import { mkdirSync as mkdirSync3 } from "node:fs";
|
|
3551
|
-
import { dirname as dirname4, isAbsolute as isAbsolute2, join as join5, resolve as resolve3 } from "node:path";
|
|
3643
|
+
import { dirname as dirname4, isAbsolute as isAbsolute2, join as join5, resolve as resolve3, sep } from "node:path";
|
|
3552
3644
|
function isIdentifier(str) {
|
|
3553
3645
|
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(str);
|
|
3554
3646
|
}
|
|
@@ -3579,17 +3671,20 @@ function sqliteVersionAtLeast(major, minor, patch2) {
|
|
|
3579
3671
|
}
|
|
3580
3672
|
function resolveSqlitePath(dbPath) {
|
|
3581
3673
|
if (dbPath === ":memory:") return dbPath;
|
|
3582
|
-
|
|
3583
|
-
if (
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3674
|
+
const isWindowsAbs = /^[A-Za-z]:[\\/]/.test(dbPath);
|
|
3675
|
+
if (isAbsolute2(dbPath) || isWindowsAbs) {
|
|
3676
|
+
return dbPath;
|
|
3677
|
+
}
|
|
3678
|
+
const cwd = resolve3(process.cwd());
|
|
3679
|
+
const path10 = join5(cwd, dbPath);
|
|
3680
|
+
const parent = dirname4(path10);
|
|
3681
|
+
const absParent = resolve3(parent);
|
|
3682
|
+
if (absParent !== cwd && !absParent.startsWith(cwd + sep)) {
|
|
3683
|
+
throw new Error(
|
|
3684
|
+
`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.`
|
|
3685
|
+
);
|
|
3592
3686
|
}
|
|
3687
|
+
mkdirSync3(parent, { recursive: true, mode: 509 });
|
|
3593
3688
|
return path10;
|
|
3594
3689
|
}
|
|
3595
3690
|
function fieldTypeToSQLite(def) {
|
|
@@ -3625,6 +3720,7 @@ var init_sqlite = __esm({
|
|
|
3625
3720
|
"../orm/src/adapters/sqlite.ts"() {
|
|
3626
3721
|
"use strict";
|
|
3627
3722
|
init_sqlDialect();
|
|
3723
|
+
init_sqlCrud();
|
|
3628
3724
|
init_sqlTranslator();
|
|
3629
3725
|
_sqliteVersionInfo = null;
|
|
3630
3726
|
SQLiteAdapter = class {
|
|
@@ -3745,16 +3841,12 @@ var init_sqlite = __esm({
|
|
|
3745
3841
|
}
|
|
3746
3842
|
insert(table2, data) {
|
|
3747
3843
|
if (Array.isArray(data)) {
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
const
|
|
3751
|
-
const paramsList = data.map((row) => keys2.map((k) => row[k]));
|
|
3752
|
-
const result = this.executeMany(sql2, paramsList);
|
|
3844
|
+
const batch = buildInsertRows(ANSI_DIALECT, table2, data);
|
|
3845
|
+
if (!batch) return { success: true, affectedRows: 0 };
|
|
3846
|
+
const result = this.executeMany(batch.sql, batch.paramsList);
|
|
3753
3847
|
return { success: true, affectedRows: result.affectedRows, lastId: result.lastId };
|
|
3754
3848
|
}
|
|
3755
|
-
const
|
|
3756
|
-
const sql = buildInsert(ANSI_DIALECT, table2, keys);
|
|
3757
|
-
const values = Object.values(data);
|
|
3849
|
+
const { sql, values } = buildInsertRow(ANSI_DIALECT, table2, data);
|
|
3758
3850
|
try {
|
|
3759
3851
|
const result = this.db.prepare(sql).run(...toSqlParams(values));
|
|
3760
3852
|
this._lastInsertId = result.lastInsertRowid;
|
|
@@ -3764,21 +3856,7 @@ var init_sqlite = __esm({
|
|
|
3764
3856
|
}
|
|
3765
3857
|
}
|
|
3766
3858
|
update(table2, data, filter, params) {
|
|
3767
|
-
const
|
|
3768
|
-
if (typeof filter === "string") {
|
|
3769
|
-
const where = filter ? ` WHERE ${filter}` : "";
|
|
3770
|
-
const sql2 = `UPDATE ${ANSI_DIALECT.quote(table2)} SET ${setClauses}${where}`;
|
|
3771
|
-
const values2 = [...Object.values(data), ...params ?? []];
|
|
3772
|
-
try {
|
|
3773
|
-
const result = this.db.prepare(sql2).run(...toSqlParams(values2));
|
|
3774
|
-
return { success: true, affectedRows: Number(result.changes) };
|
|
3775
|
-
} catch (e) {
|
|
3776
|
-
return { success: false, affectedRows: 0, error: e.message };
|
|
3777
|
-
}
|
|
3778
|
-
}
|
|
3779
|
-
const whereClauses = buildWhereClause(ANSI_DIALECT, Object.keys(filter));
|
|
3780
|
-
const sql = `UPDATE ${ANSI_DIALECT.quote(table2)} SET ${setClauses} WHERE ${whereClauses}`;
|
|
3781
|
-
const values = [...Object.values(data), ...Object.values(filter)];
|
|
3859
|
+
const { sql, values } = buildUpdate(ANSI_DIALECT, table2, data, filter, params);
|
|
3782
3860
|
try {
|
|
3783
3861
|
const result = this.db.prepare(sql).run(...toSqlParams(values));
|
|
3784
3862
|
return { success: true, affectedRows: Number(result.changes) };
|
|
@@ -3795,18 +3873,7 @@ var init_sqlite = __esm({
|
|
|
3795
3873
|
}
|
|
3796
3874
|
return { success: true, affectedRows: totalAffected };
|
|
3797
3875
|
}
|
|
3798
|
-
|
|
3799
|
-
const sql2 = filter ? `DELETE FROM "${table2}" WHERE ${filter}` : `DELETE FROM "${table2}"`;
|
|
3800
|
-
try {
|
|
3801
|
-
const result = this.db.prepare(sql2).run(...toSqlParams(params ?? []));
|
|
3802
|
-
return { success: true, affectedRows: Number(result.changes) };
|
|
3803
|
-
} catch (e) {
|
|
3804
|
-
return { success: false, affectedRows: 0, error: e.message };
|
|
3805
|
-
}
|
|
3806
|
-
}
|
|
3807
|
-
const whereClauses = buildWhereClause(ANSI_DIALECT, Object.keys(filter));
|
|
3808
|
-
const sql = `DELETE FROM ${ANSI_DIALECT.quote(table2)} WHERE ${whereClauses}`;
|
|
3809
|
-
const values = Object.values(filter);
|
|
3876
|
+
const { sql, values } = buildDelete(ANSI_DIALECT, table2, filter, params);
|
|
3810
3877
|
try {
|
|
3811
3878
|
const result = this.db.prepare(sql).run(...toSqlParams(values));
|
|
3812
3879
|
return { success: true, affectedRows: Number(result.changes) };
|
|
@@ -4098,6 +4165,7 @@ var init_postgres = __esm({
|
|
|
4098
4165
|
"../orm/src/adapters/postgres.ts"() {
|
|
4099
4166
|
"use strict";
|
|
4100
4167
|
init_sqlDialect();
|
|
4168
|
+
init_sqlCrud();
|
|
4101
4169
|
init_sqlTranslator();
|
|
4102
4170
|
init_connectTimeout();
|
|
4103
4171
|
pg = null;
|
|
@@ -4299,21 +4367,17 @@ var init_postgres = __esm({
|
|
|
4299
4367
|
async insertAsync(table2, data) {
|
|
4300
4368
|
this.ensureConnected();
|
|
4301
4369
|
if (Array.isArray(data)) {
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
const sql2 = buildInsert(ANSI_DIALECT, table2, keys2);
|
|
4305
|
-
const paramsList = data.map((row) => keys2.map((k) => row[k]));
|
|
4370
|
+
const batch = buildInsertRows(ANSI_DIALECT, table2, data);
|
|
4371
|
+
if (!batch) return { success: true, affectedRows: 0 };
|
|
4306
4372
|
try {
|
|
4307
|
-
const result = await this.executeManyAsync(
|
|
4373
|
+
const result = await this.executeManyAsync(batch.sql, batch.paramsList);
|
|
4308
4374
|
if (result.lastId !== void 0) this._lastInsertId = result.lastId;
|
|
4309
4375
|
return { success: true, affectedRows: result.totalAffected, lastId: result.lastId };
|
|
4310
4376
|
} catch (e) {
|
|
4311
4377
|
return { success: false, affectedRows: 0, error: e.message };
|
|
4312
4378
|
}
|
|
4313
4379
|
}
|
|
4314
|
-
const
|
|
4315
|
-
const sql = buildInsert(POSTGRES_DIALECT, table2, keys, " RETURNING *");
|
|
4316
|
-
const values = Object.values(data);
|
|
4380
|
+
const { sql, values } = buildInsertRow(POSTGRES_DIALECT, table2, data, " RETURNING *");
|
|
4317
4381
|
try {
|
|
4318
4382
|
const result = await this.client.query(sql, values);
|
|
4319
4383
|
const insertedRow = result.rows[0];
|
|
@@ -4333,26 +4397,10 @@ var init_postgres = __esm({
|
|
|
4333
4397
|
}
|
|
4334
4398
|
async updateAsync(table2, data, filter, params) {
|
|
4335
4399
|
this.ensureConnected();
|
|
4336
|
-
const
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
if (typeof filter === "string") {
|
|
4341
|
-
const where = filter ? ` WHERE ${this.convertPlaceholders(filter, paramIndex)}` : "";
|
|
4342
|
-
const sql2 = `UPDATE ${POSTGRES_DIALECT.quote(table2)} SET ${setClauses}${where}`;
|
|
4343
|
-
const values2 = [...Object.values(data), ...params ?? []];
|
|
4344
|
-
try {
|
|
4345
|
-
const result = await this.client.query(sql2, values2);
|
|
4346
|
-
return { success: true, affectedRows: result.rowCount ?? 0 };
|
|
4347
|
-
} catch (e) {
|
|
4348
|
-
return { success: false, affectedRows: 0, error: e.message };
|
|
4349
|
-
}
|
|
4350
|
-
}
|
|
4351
|
-
const filterKeys = Object.keys(filter);
|
|
4352
|
-
const whereClauses = buildWhereClause(POSTGRES_DIALECT, filterKeys, paramIndex);
|
|
4353
|
-
paramIndex += filterKeys.length;
|
|
4354
|
-
const sql = `UPDATE ${POSTGRES_DIALECT.quote(table2)} SET ${setClauses} WHERE ${whereClauses}`;
|
|
4355
|
-
const values = [...Object.values(data), ...Object.values(filter)];
|
|
4400
|
+
const { sql, values } = buildUpdate(POSTGRES_DIALECT, table2, data, filter, params, {
|
|
4401
|
+
startAt: 1,
|
|
4402
|
+
convertFragment: (fragment, startAt) => this.convertPlaceholders(fragment, startAt)
|
|
4403
|
+
});
|
|
4356
4404
|
try {
|
|
4357
4405
|
const result = await this.client.query(sql, values);
|
|
4358
4406
|
return { success: true, affectedRows: result.rowCount ?? 0 };
|
|
@@ -4365,21 +4413,10 @@ var init_postgres = __esm({
|
|
|
4365
4413
|
}
|
|
4366
4414
|
async deleteAsync(table2, filter, params) {
|
|
4367
4415
|
this.ensureConnected();
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
return { success: true, affectedRows: result.rowCount ?? 0 };
|
|
4373
|
-
} catch (e) {
|
|
4374
|
-
return { success: false, affectedRows: 0, error: e.message };
|
|
4375
|
-
}
|
|
4376
|
-
}
|
|
4377
|
-
const filterKeys = Object.keys(filter);
|
|
4378
|
-
let paramIndex = 1;
|
|
4379
|
-
const whereClauses = buildWhereClause(POSTGRES_DIALECT, filterKeys, paramIndex);
|
|
4380
|
-
paramIndex += filterKeys.length;
|
|
4381
|
-
const sql = `DELETE FROM ${POSTGRES_DIALECT.quote(table2)} WHERE ${whereClauses}`;
|
|
4382
|
-
const values = Object.values(filter);
|
|
4416
|
+
const { sql, values } = buildDelete(POSTGRES_DIALECT, table2, filter, params, {
|
|
4417
|
+
startAt: 1,
|
|
4418
|
+
convertFragment: (fragment) => this.bindable(fragment, params)
|
|
4419
|
+
});
|
|
4383
4420
|
try {
|
|
4384
4421
|
const result = await this.client.query(sql, values);
|
|
4385
4422
|
return { success: true, affectedRows: result.rowCount ?? 0 };
|
|
@@ -4553,6 +4590,7 @@ var init_mysql = __esm({
|
|
|
4553
4590
|
"../orm/src/adapters/mysql.ts"() {
|
|
4554
4591
|
"use strict";
|
|
4555
4592
|
init_sqlDialect();
|
|
4593
|
+
init_sqlCrud();
|
|
4556
4594
|
init_sqlTranslator();
|
|
4557
4595
|
init_connectTimeout();
|
|
4558
4596
|
mysql2 = null;
|
|
@@ -4734,21 +4772,17 @@ var init_mysql = __esm({
|
|
|
4734
4772
|
async insertAsync(table2, data) {
|
|
4735
4773
|
this.ensureConnected();
|
|
4736
4774
|
if (Array.isArray(data)) {
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
const sql2 = buildInsert(MYSQL_DIALECT, table2, keys2);
|
|
4740
|
-
const paramsList = data.map((row) => keys2.map((k) => row[k]));
|
|
4775
|
+
const batch = buildInsertRows(MYSQL_DIALECT, table2, data);
|
|
4776
|
+
if (!batch) return { success: true, affectedRows: 0 };
|
|
4741
4777
|
try {
|
|
4742
|
-
const result = await this.executeManyAsync(
|
|
4778
|
+
const result = await this.executeManyAsync(batch.sql, batch.paramsList);
|
|
4743
4779
|
if (result.lastId !== void 0) this._lastInsertId = result.lastId;
|
|
4744
4780
|
return { success: true, affectedRows: result.totalAffected, lastId: result.lastId };
|
|
4745
4781
|
} catch (e) {
|
|
4746
4782
|
return { success: false, affectedRows: 0, error: e.message };
|
|
4747
4783
|
}
|
|
4748
4784
|
}
|
|
4749
|
-
const
|
|
4750
|
-
const sql = buildInsert(MYSQL_DIALECT, table2, keys);
|
|
4751
|
-
const values = Object.values(data);
|
|
4785
|
+
const { sql, values } = buildInsertRow(MYSQL_DIALECT, table2, data);
|
|
4752
4786
|
try {
|
|
4753
4787
|
const result = await this.queryPromise(sql, values);
|
|
4754
4788
|
this._lastInsertId = result.insertId ?? null;
|
|
@@ -4766,21 +4800,7 @@ var init_mysql = __esm({
|
|
|
4766
4800
|
}
|
|
4767
4801
|
async updateAsync(table2, data, filter, params) {
|
|
4768
4802
|
this.ensureConnected();
|
|
4769
|
-
const
|
|
4770
|
-
if (typeof filter === "string") {
|
|
4771
|
-
const where = filter ? ` WHERE ${filter}` : "";
|
|
4772
|
-
const sql2 = `UPDATE ${MYSQL_DIALECT.quote(table2)} SET ${setClauses}${where}`;
|
|
4773
|
-
const values2 = [...Object.values(data), ...params ?? []];
|
|
4774
|
-
try {
|
|
4775
|
-
const result = await this.queryPromise(sql2, values2);
|
|
4776
|
-
return { success: true, affectedRows: result.affectedRows ?? 0 };
|
|
4777
|
-
} catch (e) {
|
|
4778
|
-
return { success: false, affectedRows: 0, error: e.message };
|
|
4779
|
-
}
|
|
4780
|
-
}
|
|
4781
|
-
const whereClauses = buildWhereClause(MYSQL_DIALECT, Object.keys(filter));
|
|
4782
|
-
const sql = `UPDATE ${MYSQL_DIALECT.quote(table2)} SET ${setClauses} WHERE ${whereClauses}`;
|
|
4783
|
-
const values = [...Object.values(data), ...Object.values(filter)];
|
|
4803
|
+
const { sql, values } = buildUpdate(MYSQL_DIALECT, table2, data, filter, params);
|
|
4784
4804
|
try {
|
|
4785
4805
|
const result = await this.queryPromise(sql, values);
|
|
4786
4806
|
return { success: true, affectedRows: result.affectedRows ?? 0 };
|
|
@@ -4793,18 +4813,7 @@ var init_mysql = __esm({
|
|
|
4793
4813
|
}
|
|
4794
4814
|
async deleteAsync(table2, filter, params) {
|
|
4795
4815
|
this.ensureConnected();
|
|
4796
|
-
|
|
4797
|
-
const sql2 = filter ? `DELETE FROM \`${table2}\` WHERE ${filter}` : `DELETE FROM \`${table2}\``;
|
|
4798
|
-
try {
|
|
4799
|
-
const result = await this.queryPromise(sql2, params ?? []);
|
|
4800
|
-
return { success: true, affectedRows: result.affectedRows ?? 0 };
|
|
4801
|
-
} catch (e) {
|
|
4802
|
-
return { success: false, affectedRows: 0, error: e.message };
|
|
4803
|
-
}
|
|
4804
|
-
}
|
|
4805
|
-
const whereClauses = buildWhereClause(MYSQL_DIALECT, Object.keys(filter));
|
|
4806
|
-
const sql = `DELETE FROM ${MYSQL_DIALECT.quote(table2)} WHERE ${whereClauses}`;
|
|
4807
|
-
const values = Object.values(filter);
|
|
4816
|
+
const { sql, values } = buildDelete(MYSQL_DIALECT, table2, filter, params);
|
|
4808
4817
|
try {
|
|
4809
4818
|
const result = await this.queryPromise(sql, values);
|
|
4810
4819
|
return { success: true, affectedRows: result.affectedRows ?? 0 };
|
|
@@ -4956,6 +4965,7 @@ var init_mssql = __esm({
|
|
|
4956
4965
|
"../orm/src/adapters/mssql.ts"() {
|
|
4957
4966
|
"use strict";
|
|
4958
4967
|
init_sqlDialect();
|
|
4968
|
+
init_sqlCrud();
|
|
4959
4969
|
init_sqlTranslator();
|
|
4960
4970
|
init_connectTimeout();
|
|
4961
4971
|
tedious = null;
|
|
@@ -5212,21 +5222,17 @@ var init_mssql = __esm({
|
|
|
5212
5222
|
async insertAsync(table2, data) {
|
|
5213
5223
|
this.ensureConnected();
|
|
5214
5224
|
if (Array.isArray(data)) {
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
const sql2 = buildInsert({ quote: MSSQL_DIALECT.quote, marker: ANSI_DIALECT.marker }, table2, keys2);
|
|
5218
|
-
const paramsList = data.map((row) => keys2.map((k) => row[k]));
|
|
5225
|
+
const batch = buildInsertRows({ quote: MSSQL_DIALECT.quote, marker: ANSI_DIALECT.marker }, table2, data);
|
|
5226
|
+
if (!batch) return { success: true, affectedRows: 0 };
|
|
5219
5227
|
try {
|
|
5220
|
-
const result = await this.executeManyAsync(
|
|
5228
|
+
const result = await this.executeManyAsync(batch.sql, batch.paramsList);
|
|
5221
5229
|
if (result.lastId !== void 0) this._lastInsertId = result.lastId;
|
|
5222
5230
|
return { success: true, affectedRows: result.totalAffected, lastId: result.lastId };
|
|
5223
5231
|
} catch (e) {
|
|
5224
5232
|
return { success: false, affectedRows: 0, error: e.message };
|
|
5225
5233
|
}
|
|
5226
5234
|
}
|
|
5227
|
-
const
|
|
5228
|
-
const sql = buildInsert(MSSQL_DIALECT, table2, keys, "; SELECT SCOPE_IDENTITY() AS id", 0);
|
|
5229
|
-
const values = Object.values(data);
|
|
5235
|
+
const { sql, values } = buildInsertRow(MSSQL_DIALECT, table2, data, "; SELECT SCOPE_IDENTITY() AS id", { startAt: 0 });
|
|
5230
5236
|
try {
|
|
5231
5237
|
const result = await this.execSqlPromise(sql, values);
|
|
5232
5238
|
const id = result.rows[0]?.id ?? null;
|
|
@@ -5249,26 +5255,10 @@ var init_mssql = __esm({
|
|
|
5249
5255
|
}
|
|
5250
5256
|
async updateAsync(table2, data, filter, params) {
|
|
5251
5257
|
this.ensureConnected();
|
|
5252
|
-
const
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
if (typeof filter === "string") {
|
|
5257
|
-
const where = filter ? ` WHERE ${this.convertPlaceholders(filter, paramIndex)}` : "";
|
|
5258
|
-
const sql2 = `UPDATE ${MSSQL_DIALECT.quote(table2)} SET ${setClauses}${where}`;
|
|
5259
|
-
const values2 = [...Object.values(data), ...params ?? []];
|
|
5260
|
-
try {
|
|
5261
|
-
const result = await this.execSqlPromise(sql2, values2);
|
|
5262
|
-
return { success: true, affectedRows: result.rowCount };
|
|
5263
|
-
} catch (e) {
|
|
5264
|
-
return { success: false, affectedRows: 0, error: e.message };
|
|
5265
|
-
}
|
|
5266
|
-
}
|
|
5267
|
-
const filterKeys = Object.keys(filter);
|
|
5268
|
-
const whereClauses = buildWhereClause(MSSQL_DIALECT, filterKeys, paramIndex);
|
|
5269
|
-
paramIndex += filterKeys.length;
|
|
5270
|
-
const sql = `UPDATE ${MSSQL_DIALECT.quote(table2)} SET ${setClauses} WHERE ${whereClauses}`;
|
|
5271
|
-
const values = [...Object.values(data), ...Object.values(filter)];
|
|
5258
|
+
const { sql, values } = buildUpdate(MSSQL_DIALECT, table2, data, filter, params, {
|
|
5259
|
+
startAt: 0,
|
|
5260
|
+
convertFragment: (fragment, startAt) => this.convertPlaceholders(fragment, startAt)
|
|
5261
|
+
});
|
|
5272
5262
|
try {
|
|
5273
5263
|
const result = await this.execSqlPromise(sql, values);
|
|
5274
5264
|
return { success: true, affectedRows: result.rowCount };
|
|
@@ -5281,21 +5271,10 @@ var init_mssql = __esm({
|
|
|
5281
5271
|
}
|
|
5282
5272
|
async deleteAsync(table2, filter, params) {
|
|
5283
5273
|
this.ensureConnected();
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
return { success: true, affectedRows: result.rowCount };
|
|
5289
|
-
} catch (e) {
|
|
5290
|
-
return { success: false, affectedRows: 0, error: e.message };
|
|
5291
|
-
}
|
|
5292
|
-
}
|
|
5293
|
-
const filterKeys = Object.keys(filter);
|
|
5294
|
-
let paramIndex = 0;
|
|
5295
|
-
const whereClauses = buildWhereClause(MSSQL_DIALECT, filterKeys, paramIndex);
|
|
5296
|
-
paramIndex += filterKeys.length;
|
|
5297
|
-
const sql = `DELETE FROM ${MSSQL_DIALECT.quote(table2)} WHERE ${whereClauses}`;
|
|
5298
|
-
const values = Object.values(filter);
|
|
5274
|
+
const { sql, values } = buildDelete(MSSQL_DIALECT, table2, filter, params, {
|
|
5275
|
+
startAt: 0,
|
|
5276
|
+
convertFragment: (fragment) => this.bindable(fragment, params)
|
|
5277
|
+
});
|
|
5299
5278
|
try {
|
|
5300
5279
|
const result = await this.execSqlPromise(sql, values);
|
|
5301
5280
|
return { success: true, affectedRows: result.rowCount };
|
|
@@ -5548,6 +5527,7 @@ var init_firebird = __esm({
|
|
|
5548
5527
|
"../orm/src/adapters/firebird.ts"() {
|
|
5549
5528
|
"use strict";
|
|
5550
5529
|
init_sqlDialect();
|
|
5530
|
+
init_sqlCrud();
|
|
5551
5531
|
init_sqlTranslator();
|
|
5552
5532
|
init_connectTimeout();
|
|
5553
5533
|
firebird = null;
|
|
@@ -5918,17 +5898,13 @@ var init_firebird = __esm({
|
|
|
5918
5898
|
async insertAsync(table2, data) {
|
|
5919
5899
|
this.ensureConnected();
|
|
5920
5900
|
if (Array.isArray(data)) {
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
const
|
|
5924
|
-
const paramsList = data.map((row) => keys2.map((k) => row[k]));
|
|
5925
|
-
const result = await this.executeManyAsync(sql2, paramsList);
|
|
5901
|
+
const batch = buildInsertRows(FB_DIALECT, table2, data);
|
|
5902
|
+
if (!batch) return { success: true, affectedRows: 0 };
|
|
5903
|
+
const result = await this.executeManyAsync(batch.sql, batch.paramsList);
|
|
5926
5904
|
const lastId2 = await this.readGeneratorId(table2) ?? result.lastId;
|
|
5927
5905
|
return { success: true, affectedRows: result.totalAffected, lastId: lastId2 ?? void 0 };
|
|
5928
5906
|
}
|
|
5929
|
-
const
|
|
5930
|
-
const sql = buildInsert(FB_DIALECT, table2, keys);
|
|
5931
|
-
const values = Object.values(data);
|
|
5907
|
+
const { sql, values } = buildInsertRow(FB_DIALECT, table2, data);
|
|
5932
5908
|
await this.executePromise(sql, values);
|
|
5933
5909
|
const lastId = await this.readGeneratorId(table2);
|
|
5934
5910
|
return { success: true, affectedRows: 1, lastId: lastId ?? void 0 };
|
|
@@ -5938,18 +5914,7 @@ var init_firebird = __esm({
|
|
|
5938
5914
|
}
|
|
5939
5915
|
async updateAsync(table2, data, filter, params) {
|
|
5940
5916
|
this.ensureConnected();
|
|
5941
|
-
const
|
|
5942
|
-
if (typeof filter === "string") {
|
|
5943
|
-
const where = filter ? ` WHERE ${filter}` : "";
|
|
5944
|
-
const affected2 = await this.executeReturningCount(
|
|
5945
|
-
`UPDATE ${fbQuote(table2)} SET ${setClauses}${where}`,
|
|
5946
|
-
[...Object.values(data), ...params ?? []]
|
|
5947
|
-
);
|
|
5948
|
-
return { success: true, affectedRows: affected2 };
|
|
5949
|
-
}
|
|
5950
|
-
const whereClauses = buildWhereClause(FB_DIALECT, Object.keys(filter));
|
|
5951
|
-
const sql = `UPDATE ${FB_DIALECT.quote(table2)} SET ${setClauses} WHERE ${whereClauses}`;
|
|
5952
|
-
const values = [...Object.values(data), ...Object.values(filter)];
|
|
5917
|
+
const { sql, values } = buildUpdate(FB_DIALECT, table2, data, filter, params);
|
|
5953
5918
|
const affected = await this.executeReturningCount(sql, values);
|
|
5954
5919
|
return { success: true, affectedRows: affected };
|
|
5955
5920
|
}
|
|
@@ -5958,14 +5923,7 @@ var init_firebird = __esm({
|
|
|
5958
5923
|
}
|
|
5959
5924
|
async deleteAsync(table2, filter, params) {
|
|
5960
5925
|
this.ensureConnected();
|
|
5961
|
-
|
|
5962
|
-
const where = filter ? ` WHERE ${filter}` : "";
|
|
5963
|
-
const affected2 = await this.executeReturningCount(`DELETE FROM ${fbQuote(table2)}${where}`, params ?? []);
|
|
5964
|
-
return { success: true, affectedRows: affected2 };
|
|
5965
|
-
}
|
|
5966
|
-
const whereClauses = buildWhereClause(FB_DIALECT, Object.keys(filter));
|
|
5967
|
-
const sql = `DELETE FROM ${FB_DIALECT.quote(table2)} WHERE ${whereClauses}`;
|
|
5968
|
-
const values = Object.values(filter);
|
|
5926
|
+
const { sql, values } = buildDelete(FB_DIALECT, table2, filter, params);
|
|
5969
5927
|
const affected = await this.executeReturningCount(sql, values);
|
|
5970
5928
|
return { success: true, affectedRows: affected };
|
|
5971
5929
|
}
|
|
@@ -6797,6 +6755,7 @@ var init_odbc = __esm({
|
|
|
6797
6755
|
"../orm/src/adapters/odbc.ts"() {
|
|
6798
6756
|
"use strict";
|
|
6799
6757
|
init_sqlDialect();
|
|
6758
|
+
init_sqlCrud();
|
|
6800
6759
|
init_sqlTranslator();
|
|
6801
6760
|
init_connectTimeout();
|
|
6802
6761
|
odbcModule = null;
|
|
@@ -6996,16 +6955,12 @@ var init_odbc = __esm({
|
|
|
6996
6955
|
async insertAsync(table2, data) {
|
|
6997
6956
|
this.ensureConnected();
|
|
6998
6957
|
if (Array.isArray(data)) {
|
|
6999
|
-
|
|
7000
|
-
|
|
7001
|
-
const
|
|
7002
|
-
const paramsList = data.map((row) => keys2.map((k) => row[k]));
|
|
7003
|
-
const { totalAffected } = await this.executeManyAsync(sql2, paramsList);
|
|
6958
|
+
const batch = buildInsertRows(ANSI_DIALECT, table2, data);
|
|
6959
|
+
if (!batch) return { success: true, affectedRows: 0 };
|
|
6960
|
+
const { totalAffected } = await this.executeManyAsync(batch.sql, batch.paramsList);
|
|
7004
6961
|
return { success: true, affectedRows: totalAffected };
|
|
7005
6962
|
}
|
|
7006
|
-
const
|
|
7007
|
-
const sql = buildInsert(ANSI_DIALECT, table2, keys);
|
|
7008
|
-
const values = Object.values(data);
|
|
6963
|
+
const { sql, values } = buildInsertRow(ANSI_DIALECT, table2, data);
|
|
7009
6964
|
try {
|
|
7010
6965
|
const result = await this.connection.query(sql, values);
|
|
7011
6966
|
return { success: true, affectedRows: this.affectedCount(result), lastId: this._lastInsertId ?? void 0 };
|
|
@@ -7016,17 +6971,7 @@ var init_odbc = __esm({
|
|
|
7016
6971
|
/** Update rows in a table matching filter. */
|
|
7017
6972
|
async updateAsync(table2, data, filter, params) {
|
|
7018
6973
|
this.ensureConnected();
|
|
7019
|
-
const
|
|
7020
|
-
let whereSql;
|
|
7021
|
-
let values;
|
|
7022
|
-
if (typeof filter === "string") {
|
|
7023
|
-
whereSql = filter;
|
|
7024
|
-
values = [...Object.values(data), ...params ?? []];
|
|
7025
|
-
} else {
|
|
7026
|
-
whereSql = buildWhereClause(ANSI_DIALECT, Object.keys(filter));
|
|
7027
|
-
values = [...Object.values(data), ...Object.values(filter)];
|
|
7028
|
-
}
|
|
7029
|
-
const sql = `UPDATE ${ANSI_DIALECT.quote(table2)} SET ${setClauses} WHERE ${whereSql}`;
|
|
6974
|
+
const { sql, values } = buildUpdate(ANSI_DIALECT, table2, data, filter, params);
|
|
7030
6975
|
try {
|
|
7031
6976
|
const result = await this.connection.query(sql, values);
|
|
7032
6977
|
return { success: true, affectedRows: this.affectedCount(result) };
|
|
@@ -7045,18 +6990,7 @@ var init_odbc = __esm({
|
|
|
7045
6990
|
}
|
|
7046
6991
|
return { success: true, affectedRows: totalAffected };
|
|
7047
6992
|
}
|
|
7048
|
-
|
|
7049
|
-
const sql2 = filter ? `DELETE FROM "${table2}" WHERE ${filter}` : `DELETE FROM "${table2}"`;
|
|
7050
|
-
try {
|
|
7051
|
-
const result = await this.connection.query(sql2, params ?? []);
|
|
7052
|
-
return { success: true, affectedRows: this.affectedCount(result) };
|
|
7053
|
-
} catch (e) {
|
|
7054
|
-
return { success: false, affectedRows: 0, error: e.message };
|
|
7055
|
-
}
|
|
7056
|
-
}
|
|
7057
|
-
const whereClauses = buildWhereClause(ANSI_DIALECT, Object.keys(filter));
|
|
7058
|
-
const sql = `DELETE FROM ${ANSI_DIALECT.quote(table2)} WHERE ${whereClauses}`;
|
|
7059
|
-
const values = Object.values(filter);
|
|
6993
|
+
const { sql, values } = buildDelete(ANSI_DIALECT, table2, filter, params);
|
|
7060
6994
|
try {
|
|
7061
6995
|
const result = await this.connection.query(sql, values);
|
|
7062
6996
|
return { success: true, affectedRows: this.affectedCount(result) };
|
|
@@ -10776,11 +10710,11 @@ async function eagerLoadBelongsToRelation(instances, model, relatedClass, relati
|
|
|
10776
10710
|
const relatedPkColumn = relatedClass.getDbColumn(relatedPk);
|
|
10777
10711
|
const related = await eagerQueryRelated(relatedClass, relatedPkColumn, values);
|
|
10778
10712
|
if (nested.length > 0 && related.length > 0) await relatedClass._eagerLoad(related, nested);
|
|
10779
|
-
const
|
|
10780
|
-
for (const record of related)
|
|
10713
|
+
const lookup2 = {};
|
|
10714
|
+
for (const record of related) lookup2[_joinKey(record[relatedPk])] = record;
|
|
10781
10715
|
for (const instance of instances) {
|
|
10782
10716
|
const value = instance[fkProp];
|
|
10783
|
-
relationshipCache(instance)[relationName] = value !== void 0 && value !== null ?
|
|
10717
|
+
relationshipCache(instance)[relationName] = value !== void 0 && value !== null ? lookup2[_joinKey(value)] ?? null : null;
|
|
10784
10718
|
}
|
|
10785
10719
|
}
|
|
10786
10720
|
async function eagerLoadRelation(instances, model, relationName, nested) {
|
|
@@ -14467,7 +14401,7 @@ var init_attachment = __esm({
|
|
|
14467
14401
|
// ../orm/src/realtime/storage.ts
|
|
14468
14402
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
14469
14403
|
import { mkdirSync as mkdirSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2, statSync as statSync4 } from "node:fs";
|
|
14470
|
-
import { resolve as resolve6, sep } from "node:path";
|
|
14404
|
+
import { resolve as resolve6, sep as sep2 } from "node:path";
|
|
14471
14405
|
import { createRequire as createRequire6 } from "node:module";
|
|
14472
14406
|
function loadAwsSdk(packageName) {
|
|
14473
14407
|
try {
|
|
@@ -14520,7 +14454,7 @@ var init_storage = __esm({
|
|
|
14520
14454
|
// Resolve inside the root and reject any traversal attempt.
|
|
14521
14455
|
pathFor(key) {
|
|
14522
14456
|
const target = resolve6(this.directory, key);
|
|
14523
|
-
if (target !== this.directory && !target.startsWith(this.directory +
|
|
14457
|
+
if (target !== this.directory && !target.startsWith(this.directory + sep2)) {
|
|
14524
14458
|
throw new Error(`unsafe storage key: ${JSON.stringify(key)}`);
|
|
14525
14459
|
}
|
|
14526
14460
|
return target;
|
|
@@ -15037,6 +14971,7 @@ __export(src_exports, {
|
|
|
15037
14971
|
resolveDbPool: () => resolveDbPool,
|
|
15038
14972
|
resolveFirebirdCharset: () => resolveFirebirdCharset,
|
|
15039
14973
|
resolveGraphConnectTimeout: () => resolveGraphConnectTimeout,
|
|
14974
|
+
resolveSqlitePath: () => resolveSqlitePath,
|
|
15040
14975
|
rollback: () => rollback,
|
|
15041
14976
|
seedModels: () => seedModels,
|
|
15042
14977
|
seedOrm: () => seedOrm,
|
|
@@ -15422,9 +15357,9 @@ var init_cache = __esm({
|
|
|
15422
15357
|
return entry.value;
|
|
15423
15358
|
}
|
|
15424
15359
|
async set(key, value, ttl) {
|
|
15425
|
-
const
|
|
15360
|
+
const expiresAt2 = ttl > 0 ? Date.now() + ttl * 1e3 : 0;
|
|
15426
15361
|
this.store.delete(key);
|
|
15427
|
-
this.store.set(key, { value, expiresAt });
|
|
15362
|
+
this.store.set(key, { value, expiresAt: expiresAt2 });
|
|
15428
15363
|
while (this.store.size > this.maxEntries) {
|
|
15429
15364
|
const firstKey = this.store.keys().next().value;
|
|
15430
15365
|
if (firstKey !== void 0) this.store.delete(firstKey);
|
|
@@ -15857,8 +15792,8 @@ ${s}\r
|
|
|
15857
15792
|
const oldest = files.shift();
|
|
15858
15793
|
if (oldest) fs.unlinkSync(path.join(this.dir, oldest.name));
|
|
15859
15794
|
}
|
|
15860
|
-
const
|
|
15861
|
-
const entry = { key, value, expiresAt };
|
|
15795
|
+
const expiresAt2 = ttl > 0 ? Date.now() / 1e3 + ttl : 0;
|
|
15796
|
+
const entry = { key, value, expiresAt: expiresAt2 };
|
|
15862
15797
|
fs.writeFileSync(this.keyPath(key), JSON.stringify(entry));
|
|
15863
15798
|
} catch {
|
|
15864
15799
|
}
|
|
@@ -17511,7 +17446,7 @@ __export(engine_exports, {
|
|
|
17511
17446
|
});
|
|
17512
17447
|
import { createHash as createHash3, createHmac as createHmac3, randomBytes as randomBytes4 } from "node:crypto";
|
|
17513
17448
|
import { readFileSync as readFileSync6, existsSync as existsSync8, statSync as statSync6, realpathSync } from "node:fs";
|
|
17514
|
-
import { join as join11, resolve as resolve7, isAbsolute as isAbsolute4, sep as
|
|
17449
|
+
import { join as join11, resolve as resolve7, isAbsolute as isAbsolute4, sep as sep3 } from "node:path";
|
|
17515
17450
|
function jsonText(value) {
|
|
17516
17451
|
try {
|
|
17517
17452
|
const text = JSON.stringify(value);
|
|
@@ -17605,8 +17540,8 @@ function capCache(cache, maxEntries) {
|
|
|
17605
17540
|
}
|
|
17606
17541
|
function sweepExpiredCache(cache) {
|
|
17607
17542
|
const now = Date.now();
|
|
17608
|
-
for (const [key, [,
|
|
17609
|
-
if (
|
|
17543
|
+
for (const [key, [, expiresAt2]] of cache) {
|
|
17544
|
+
if (expiresAt2 <= now) cache.delete(key);
|
|
17610
17545
|
}
|
|
17611
17546
|
}
|
|
17612
17547
|
function tokenize(source) {
|
|
@@ -17852,15 +17787,15 @@ function findOutsideQuotes(expr, needle) {
|
|
|
17852
17787
|
}
|
|
17853
17788
|
return -1;
|
|
17854
17789
|
}
|
|
17855
|
-
function splitOutsideQuotes(expr,
|
|
17856
|
-
if (!expr.includes(
|
|
17790
|
+
function splitOutsideQuotes(expr, sep9) {
|
|
17791
|
+
if (!expr.includes(sep9)) return [expr];
|
|
17857
17792
|
const parts = [];
|
|
17858
17793
|
let currentStart = 0;
|
|
17859
17794
|
let inQuote = null;
|
|
17860
17795
|
let depth = 0;
|
|
17861
17796
|
let bracketDepth = 0;
|
|
17862
17797
|
let i = 0;
|
|
17863
|
-
const sepLen =
|
|
17798
|
+
const sepLen = sep9.length;
|
|
17864
17799
|
const lastStart = expr.length - sepLen;
|
|
17865
17800
|
while (i <= lastStart) {
|
|
17866
17801
|
const ch = expr[i];
|
|
@@ -17881,7 +17816,7 @@ function splitOutsideQuotes(expr, sep8) {
|
|
|
17881
17816
|
else if (ch === ")") depth--;
|
|
17882
17817
|
else if (ch === "[") bracketDepth++;
|
|
17883
17818
|
else if (ch === "]") bracketDepth--;
|
|
17884
|
-
if (depth === 0 && bracketDepth === 0 && expr.startsWith(
|
|
17819
|
+
if (depth === 0 && bracketDepth === 0 && expr.startsWith(sep9, i)) {
|
|
17885
17820
|
parts.push(expr.slice(currentStart, i));
|
|
17886
17821
|
i += sepLen;
|
|
17887
17822
|
currentStart = i;
|
|
@@ -18578,6 +18513,12 @@ function setFormTokenSessionId(sessionId) {
|
|
|
18578
18513
|
}
|
|
18579
18514
|
function _buildFormTokenJwt(descriptor = "") {
|
|
18580
18515
|
const secret = process.env.TINA4_SECRET ?? "";
|
|
18516
|
+
const secretBytes = Buffer.byteLength(secret, "utf8");
|
|
18517
|
+
if (secretBytes < 32) {
|
|
18518
|
+
throw new Error(
|
|
18519
|
+
`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.`
|
|
18520
|
+
);
|
|
18521
|
+
}
|
|
18581
18522
|
const ttlMinutes = parseInt(process.env.TINA4_TOKEN_LIMIT || "60", 10);
|
|
18582
18523
|
const header = { alg: "HS256", typ: "JWT" };
|
|
18583
18524
|
const now = Math.floor(Date.now() / 1e3);
|
|
@@ -18828,8 +18769,8 @@ var init_engine = __esm({
|
|
|
18828
18769
|
},
|
|
18829
18770
|
first: (v) => Array.isArray(v) ? v[0] ?? null : null,
|
|
18830
18771
|
last: (v) => Array.isArray(v) ? v[v.length - 1] ?? null : null,
|
|
18831
|
-
join: (v,
|
|
18832
|
-
split: (v,
|
|
18772
|
+
join: (v, sep9) => Array.isArray(v) ? v.map(String).join(sep9 !== void 0 ? String(sep9) : ", ") : String(v),
|
|
18773
|
+
split: (v, sep9) => String(v).split(sep9 !== void 0 ? String(sep9) : " "),
|
|
18833
18774
|
replace: (v, from, to) => {
|
|
18834
18775
|
const s = String(v);
|
|
18835
18776
|
if (from !== void 0 && typeof from === "object" && from !== null && !Array.isArray(from)) {
|
|
@@ -19233,7 +19174,7 @@ var init_engine = __esm({
|
|
|
19233
19174
|
}
|
|
19234
19175
|
const root = realpathSync(this.templateDir);
|
|
19235
19176
|
const real = realpathSync(filePath);
|
|
19236
|
-
if (real !== root && !real.startsWith(root +
|
|
19177
|
+
if (real !== root && !real.startsWith(root + sep3)) {
|
|
19237
19178
|
throw new Error(`Template path escapes the templates directory: ${name}`);
|
|
19238
19179
|
}
|
|
19239
19180
|
return readFileSync6(real, "utf-8");
|
|
@@ -20119,8 +20060,8 @@ var init_engine = __esm({
|
|
|
20119
20060
|
sweepExpiredCache(this.fragmentCache);
|
|
20120
20061
|
const cached = this.fragmentCache.get(cacheKey);
|
|
20121
20062
|
if (cached) {
|
|
20122
|
-
const [htmlContent,
|
|
20123
|
-
if (Date.now() <
|
|
20063
|
+
const [htmlContent, expiresAt2] = cached;
|
|
20064
|
+
if (Date.now() < expiresAt2) {
|
|
20124
20065
|
let i2 = start2 + 1;
|
|
20125
20066
|
let depth = 0;
|
|
20126
20067
|
while (i2 < tokens.length) {
|
|
@@ -20913,7 +20854,7 @@ function csrfBearerIsValid(req2, secret) {
|
|
|
20913
20854
|
const authHeader = req2.headers.authorization ?? "";
|
|
20914
20855
|
if (!authHeader.startsWith("Bearer ")) return false;
|
|
20915
20856
|
const bearerToken = authHeader.slice(7).trim();
|
|
20916
|
-
return Boolean(bearerToken && validToken(bearerToken, secret));
|
|
20857
|
+
return Boolean(bearerToken && isIdentityPayload(validToken(bearerToken, secret)));
|
|
20917
20858
|
}
|
|
20918
20859
|
function csrfRequestToken(req2) {
|
|
20919
20860
|
const query = req2.query ?? {};
|
|
@@ -22050,6 +21991,22 @@ var init_transport = __esm({
|
|
|
22050
21991
|
}
|
|
22051
21992
|
});
|
|
22052
21993
|
|
|
21994
|
+
// ../core/src/ssoIdentity.ts
|
|
21995
|
+
function liveSessionIdentity(stored) {
|
|
21996
|
+
if (!stored || typeof stored !== "object") return null;
|
|
21997
|
+
const identity = stored.identity;
|
|
21998
|
+
if (!identity || typeof identity !== "object" || !identity.issuer || !identity.subject) return null;
|
|
21999
|
+
const expiresAt2 = stored.expires_at ?? 0;
|
|
22000
|
+
if (typeof expiresAt2 !== "number") return null;
|
|
22001
|
+
if (expiresAt2 > 0 && Math.floor(Date.now() / 1e3) >= expiresAt2) return null;
|
|
22002
|
+
return identity;
|
|
22003
|
+
}
|
|
22004
|
+
var init_ssoIdentity = __esm({
|
|
22005
|
+
"../core/src/ssoIdentity.ts"() {
|
|
22006
|
+
"use strict";
|
|
22007
|
+
}
|
|
22008
|
+
});
|
|
22009
|
+
|
|
22053
22010
|
// ../core/src/authGate.ts
|
|
22054
22011
|
function enforceRouteAuth(req2, res, match, isDevAdmin) {
|
|
22055
22012
|
if (!(match.secure === true && match.noAuth !== true && !isDevAdmin)) {
|
|
@@ -22057,38 +22014,50 @@ function enforceRouteAuth(req2, res, match, isDevAdmin) {
|
|
|
22057
22014
|
}
|
|
22058
22015
|
const authHeader = req2.headers.authorization ?? "";
|
|
22059
22016
|
const headerToken = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : "";
|
|
22060
|
-
let
|
|
22017
|
+
let payload = null;
|
|
22061
22018
|
let tokenSource = "";
|
|
22062
|
-
|
|
22063
|
-
|
|
22064
|
-
|
|
22019
|
+
let resolvedToken = "";
|
|
22020
|
+
if (headerToken) {
|
|
22021
|
+
const candidate = validToken(headerToken);
|
|
22022
|
+
if (isIdentityPayload(candidate)) {
|
|
22023
|
+
payload = candidate;
|
|
22024
|
+
tokenSource = "header";
|
|
22025
|
+
resolvedToken = headerToken;
|
|
22026
|
+
}
|
|
22065
22027
|
}
|
|
22066
|
-
if (!
|
|
22028
|
+
if (!payload) {
|
|
22067
22029
|
const bodyToken = req2.body?.formToken;
|
|
22068
|
-
if (bodyToken
|
|
22069
|
-
|
|
22070
|
-
|
|
22030
|
+
if (bodyToken) {
|
|
22031
|
+
const candidate = validToken(bodyToken);
|
|
22032
|
+
if (isIdentityPayload(candidate)) {
|
|
22033
|
+
payload = candidate;
|
|
22034
|
+
tokenSource = "body";
|
|
22035
|
+
resolvedToken = bodyToken;
|
|
22036
|
+
}
|
|
22071
22037
|
}
|
|
22072
22038
|
}
|
|
22073
|
-
if (!
|
|
22074
|
-
const
|
|
22075
|
-
|
|
22076
|
-
if (identity?.issuer && identity?.subject) {
|
|
22039
|
+
if (!payload) {
|
|
22040
|
+
const identity = liveSessionIdentity(req2.session?.get?.("_tina4_sso"));
|
|
22041
|
+
if (identity) {
|
|
22077
22042
|
req2.user = identity;
|
|
22078
22043
|
return rbacForbidden(match, identity, res);
|
|
22079
22044
|
}
|
|
22080
22045
|
const sessionToken = req2.session?.get?.("token");
|
|
22081
|
-
if (sessionToken
|
|
22082
|
-
|
|
22083
|
-
|
|
22046
|
+
if (sessionToken) {
|
|
22047
|
+
const candidate = validToken(sessionToken);
|
|
22048
|
+
if (isIdentityPayload(candidate)) {
|
|
22049
|
+
payload = candidate;
|
|
22050
|
+
tokenSource = "session";
|
|
22051
|
+
resolvedToken = sessionToken;
|
|
22052
|
+
}
|
|
22084
22053
|
}
|
|
22085
22054
|
}
|
|
22086
|
-
if (!
|
|
22055
|
+
if (!payload) {
|
|
22087
22056
|
res.raw.writeHead(401, { "Content-Type": "application/json" });
|
|
22088
22057
|
res.raw.end(JSON.stringify({ error: "Unauthorized" }));
|
|
22089
22058
|
return true;
|
|
22090
22059
|
}
|
|
22091
|
-
req2.user =
|
|
22060
|
+
req2.user = payload;
|
|
22092
22061
|
if (tokenSource === "body") {
|
|
22093
22062
|
const fresh = refreshToken(resolvedToken);
|
|
22094
22063
|
if (fresh) {
|
|
@@ -22138,6 +22107,7 @@ var init_authGate = __esm({
|
|
|
22138
22107
|
"../core/src/authGate.ts"() {
|
|
22139
22108
|
"use strict";
|
|
22140
22109
|
init_auth();
|
|
22110
|
+
init_ssoIdentity();
|
|
22141
22111
|
}
|
|
22142
22112
|
});
|
|
22143
22113
|
|
|
@@ -23231,8 +23201,8 @@ var init_mongoHandler = __esm({
|
|
|
23231
23201
|
if (!result || result === "__EMPTY__") return null;
|
|
23232
23202
|
try {
|
|
23233
23203
|
const doc = JSON.parse(result);
|
|
23234
|
-
const
|
|
23235
|
-
if (
|
|
23204
|
+
const expiresAt2 = Number(doc?.expires_at ?? 0);
|
|
23205
|
+
if (expiresAt2 > 0 && Date.now() / 1e3 > expiresAt2) {
|
|
23236
23206
|
this.destroy(sessionId);
|
|
23237
23207
|
return null;
|
|
23238
23208
|
}
|
|
@@ -23458,6 +23428,9 @@ parentPort.on("message", (message) => {
|
|
|
23458
23428
|
|
|
23459
23429
|
// ../core/src/sessionHandlers/databaseHandler.ts
|
|
23460
23430
|
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
23431
|
+
function sleepSync2(ms) {
|
|
23432
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
23433
|
+
}
|
|
23461
23434
|
function column(row, name) {
|
|
23462
23435
|
if (name in row) return row[name];
|
|
23463
23436
|
return row[name.toUpperCase()];
|
|
@@ -23466,7 +23439,7 @@ function schemeOf2(url) {
|
|
|
23466
23439
|
const match = url.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):/);
|
|
23467
23440
|
return match ? match[1].toLowerCase() : "unknown";
|
|
23468
23441
|
}
|
|
23469
|
-
var CREATE_TABLE, DatabaseSessionHandler;
|
|
23442
|
+
var CREATE_TABLE, CREATE_ATTEMPTS, CREATE_RETRY_DELAY_MS, DatabaseSessionHandler;
|
|
23470
23443
|
var init_databaseHandler = __esm({
|
|
23471
23444
|
"../core/src/sessionHandlers/databaseHandler.ts"() {
|
|
23472
23445
|
"use strict";
|
|
@@ -23546,6 +23519,8 @@ var init_databaseHandler = __esm({
|
|
|
23546
23519
|
END
|
|
23547
23520
|
`
|
|
23548
23521
|
};
|
|
23522
|
+
CREATE_ATTEMPTS = 5;
|
|
23523
|
+
CREATE_RETRY_DELAY_MS = 50;
|
|
23549
23524
|
DatabaseSessionHandler = class {
|
|
23550
23525
|
sqliteHandle = null;
|
|
23551
23526
|
/** Set for SQLite. Null when this handler talks to a networked engine. */
|
|
@@ -23586,6 +23561,7 @@ var init_databaseHandler = __esm({
|
|
|
23586
23561
|
get sqlite() {
|
|
23587
23562
|
if (this.sqliteHandle === null) {
|
|
23588
23563
|
this.sqliteHandle = new DatabaseSync3(this.dbPath);
|
|
23564
|
+
this.sqliteHandle.exec("PRAGMA busy_timeout = 5000");
|
|
23589
23565
|
this.sqliteHandle.exec("PRAGMA journal_mode = WAL");
|
|
23590
23566
|
}
|
|
23591
23567
|
return this.sqliteHandle;
|
|
@@ -23673,15 +23649,55 @@ var init_databaseHandler = __esm({
|
|
|
23673
23649
|
sqlCommandSync(this.target, sql, params);
|
|
23674
23650
|
}
|
|
23675
23651
|
/**
|
|
23676
|
-
* Ensure the session table exists (called once on first use)
|
|
23652
|
+
* Ensure the session table exists (called once on first use) - safely when
|
|
23653
|
+
* several processes race to create it.
|
|
23654
|
+
*
|
|
23655
|
+
* Every per-engine statement above is check-then-act, so two processes can
|
|
23656
|
+
* both find the table absent and both CREATE; the loser errors, and every
|
|
23657
|
+
* engine spells that differently (a pg_type unique violation on PostgreSQL,
|
|
23658
|
+
* Msg 2714 on SQL Server, an RDB$RELATIONS unique violation on Firebird). So
|
|
23659
|
+
* a failure is judged by RE-CHECKING whether the table now exists, never by
|
|
23660
|
+
* parsing the message - the rescue the Firebird note above has always
|
|
23661
|
+
* assumed, and which was not here. MEASURED on the lab before it: six real
|
|
23662
|
+
* processes racing lost one to five of them on PostgreSQL, SQLite and
|
|
23663
|
+
* Firebird.
|
|
23664
|
+
*
|
|
23665
|
+
* The re-check is RETRIED because the loser can fail BEFORE the winner has
|
|
23666
|
+
* committed. On MySQL, CREATE TABLE IF NOT EXISTS takes a shared metadata
|
|
23667
|
+
* lock, checks, then upgrades it to exclusive; two upgrades deadlock, and when
|
|
23668
|
+
* MySQL cannot back the victim off silently it answers 1213 "Deadlock found"
|
|
23669
|
+
* while the winner's table is still invisible (tina4-php CI run 35972320442).
|
|
23670
|
+
* Bounded, so a genuine failure (no CREATE grant) still throws. Same attempts
|
|
23671
|
+
* and delays in all four frameworks.
|
|
23677
23672
|
*/
|
|
23678
23673
|
ensureTable() {
|
|
23679
23674
|
if (this.initialized) return;
|
|
23680
23675
|
const ddl = CREATE_TABLE[this.engine];
|
|
23681
|
-
|
|
23682
|
-
|
|
23676
|
+
for (let attempt = 1; ; attempt++) {
|
|
23677
|
+
try {
|
|
23678
|
+
if (this.target === null) this.sqlite.exec(ddl);
|
|
23679
|
+
else sqlCommandSync(this.target, ddl, []);
|
|
23680
|
+
break;
|
|
23681
|
+
} catch (error2) {
|
|
23682
|
+
if (this.tableExists()) break;
|
|
23683
|
+
if (attempt >= CREATE_ATTEMPTS) throw error2;
|
|
23684
|
+
sleepSync2(CREATE_RETRY_DELAY_MS * attempt);
|
|
23685
|
+
}
|
|
23686
|
+
}
|
|
23683
23687
|
this.initialized = true;
|
|
23684
23688
|
}
|
|
23689
|
+
/**
|
|
23690
|
+
* Whether tina4_session exists, asked the one way every engine answers alike:
|
|
23691
|
+
* a query against it that returns no rows succeeds only if it is there.
|
|
23692
|
+
*/
|
|
23693
|
+
tableExists() {
|
|
23694
|
+
try {
|
|
23695
|
+
this.query("SELECT 1 FROM tina4_session WHERE 1 = 0", []);
|
|
23696
|
+
return true;
|
|
23697
|
+
} catch {
|
|
23698
|
+
return false;
|
|
23699
|
+
}
|
|
23700
|
+
}
|
|
23685
23701
|
read(sessionId) {
|
|
23686
23702
|
this.ensureTable();
|
|
23687
23703
|
const rows = this.query(
|
|
@@ -23691,8 +23707,8 @@ var init_databaseHandler = __esm({
|
|
|
23691
23707
|
const row = rows[0];
|
|
23692
23708
|
if (!row) return null;
|
|
23693
23709
|
const now = Date.now() / 1e3;
|
|
23694
|
-
const
|
|
23695
|
-
if (
|
|
23710
|
+
const expiresAt2 = Number(column(row, "expires_at") ?? 0);
|
|
23711
|
+
if (expiresAt2 > 0 && expiresAt2 < now) {
|
|
23696
23712
|
this.destroy(sessionId);
|
|
23697
23713
|
return null;
|
|
23698
23714
|
}
|
|
@@ -23705,7 +23721,7 @@ var init_databaseHandler = __esm({
|
|
|
23705
23721
|
write(sessionId, data, ttl) {
|
|
23706
23722
|
this.ensureTable();
|
|
23707
23723
|
const json = JSON.stringify(data);
|
|
23708
|
-
const
|
|
23724
|
+
const expiresAt2 = ttl > 0 ? Date.now() / 1e3 + ttl : 0;
|
|
23709
23725
|
const existing = this.query(
|
|
23710
23726
|
"SELECT session_id FROM tina4_session WHERE session_id = ?",
|
|
23711
23727
|
[sessionId]
|
|
@@ -23713,12 +23729,12 @@ var init_databaseHandler = __esm({
|
|
|
23713
23729
|
if (existing.length > 0) {
|
|
23714
23730
|
this.exec(
|
|
23715
23731
|
"UPDATE tina4_session SET data = ?, expires_at = ? WHERE session_id = ?",
|
|
23716
|
-
[json,
|
|
23732
|
+
[json, expiresAt2, sessionId]
|
|
23717
23733
|
);
|
|
23718
23734
|
} else {
|
|
23719
23735
|
this.exec(
|
|
23720
23736
|
"INSERT INTO tina4_session (session_id, data, expires_at) VALUES (?, ?, ?)",
|
|
23721
|
-
[sessionId, json,
|
|
23737
|
+
[sessionId, json, expiresAt2]
|
|
23722
23738
|
);
|
|
23723
23739
|
}
|
|
23724
23740
|
}
|
|
@@ -24548,7 +24564,7 @@ var init_dispatchPipeline = __esm({
|
|
|
24548
24564
|
|
|
24549
24565
|
// ../core/src/static.ts
|
|
24550
24566
|
import { readFileSync as readFileSync9, realpathSync as realpathSync3, statSync as statSync8 } from "node:fs";
|
|
24551
|
-
import { join as join16, extname as extname3, sep as
|
|
24567
|
+
import { join as join16, extname as extname3, sep as sep4 } from "node:path";
|
|
24552
24568
|
import { gzipSync as gzipSync2 } from "node:zlib";
|
|
24553
24569
|
function isCompressible(contentType) {
|
|
24554
24570
|
return COMPRESSIBLE_PREFIXES2.some((prefix) => contentType.includes(prefix));
|
|
@@ -24579,8 +24595,8 @@ function tryServeStatic(staticDir, req2, res) {
|
|
|
24579
24595
|
}
|
|
24580
24596
|
const stat = statSync8(realPath);
|
|
24581
24597
|
if (!stat.isFile()) continue;
|
|
24582
|
-
if (realPath !== realDir && !realPath.startsWith(realDir +
|
|
24583
|
-
if (hasHiddenSegment(realPath.slice(realDir.length + 1),
|
|
24598
|
+
if (realPath !== realDir && !realPath.startsWith(realDir + sep4)) continue;
|
|
24599
|
+
if (hasHiddenSegment(realPath.slice(realDir.length + 1), sep4)) continue;
|
|
24584
24600
|
const ext = extname3(realPath);
|
|
24585
24601
|
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
24586
24602
|
const etag = `W/"${stat.size}-${Math.floor(stat.mtimeMs / 1e3)}"`;
|
|
@@ -25563,7 +25579,7 @@ __export(generate_exports, {
|
|
|
25563
25579
|
toTableName: () => toTableName
|
|
25564
25580
|
});
|
|
25565
25581
|
import { existsSync as existsSync16, mkdirSync as mkdirSync13, writeFileSync as writeFileSync10 } from "node:fs";
|
|
25566
|
-
import { join as join21, relative as relative2, resolve as resolve11, sep as
|
|
25582
|
+
import { join as join21, relative as relative2, resolve as resolve11, sep as sep5 } from "node:path";
|
|
25567
25583
|
function ensureDir(dir) {
|
|
25568
25584
|
if (__resolution.dryRun) return;
|
|
25569
25585
|
if (!existsSync16(dir)) {
|
|
@@ -25689,7 +25705,7 @@ function toRelPath(absPath) {
|
|
|
25689
25705
|
const cwd = process.cwd();
|
|
25690
25706
|
const rel = relative2(cwd, absPath);
|
|
25691
25707
|
if (!rel) return absPath;
|
|
25692
|
-
return
|
|
25708
|
+
return sep5 === "/" ? rel : rel.split(sep5).join("/");
|
|
25693
25709
|
}
|
|
25694
25710
|
function captureEditHints(absPath, content) {
|
|
25695
25711
|
if (!/\.(ts|tsx|js|mjs|cjs|jsx|sql|twig|html\.twig)$/.test(absPath)) return;
|
|
@@ -25761,12 +25777,6 @@ function printResolution() {
|
|
|
25761
25777
|
lines.push("");
|
|
25762
25778
|
process.stderr.write(lines.join("\n"));
|
|
25763
25779
|
}
|
|
25764
|
-
function toPlural(name) {
|
|
25765
|
-
const lower = name.toLowerCase();
|
|
25766
|
-
if (lower.endsWith("s")) return lower;
|
|
25767
|
-
if (lower.endsWith("y") && !/[aeiou]y$/i.test(lower)) return lower.slice(0, -1) + "ies";
|
|
25768
|
-
return lower + "s";
|
|
25769
|
-
}
|
|
25770
25780
|
function toCamel(name) {
|
|
25771
25781
|
return name.charAt(0).toLowerCase() + name.slice(1);
|
|
25772
25782
|
}
|
|
@@ -26199,7 +26209,7 @@ ${aiFill(`delete_${singular}`, {
|
|
|
26199
26209
|
}
|
|
26200
26210
|
function generateCrud(name, flags) {
|
|
26201
26211
|
const table2 = resolveTable(name, flags, { announce: false });
|
|
26202
|
-
const routeName =
|
|
26212
|
+
const routeName = pluralizeReserved(toSnake(name));
|
|
26203
26213
|
const isPublic = Boolean(flags.public);
|
|
26204
26214
|
if (!__resolution.jsonMode) console.log(`
|
|
26205
26215
|
Generating CRUD for ${name}...
|
|
@@ -26453,7 +26463,7 @@ void test${titleName};
|
|
|
26453
26463
|
function generateForm(name, flags) {
|
|
26454
26464
|
const fields = fieldsOrDefault(flags.fields || "");
|
|
26455
26465
|
const table2 = resolveTable(name, flags, { announce: false });
|
|
26456
|
-
const routeName =
|
|
26466
|
+
const routeName = pluralizeReserved(toSnake(name));
|
|
26457
26467
|
const inputTypes = {
|
|
26458
26468
|
string: "text",
|
|
26459
26469
|
str: "text",
|
|
@@ -26519,7 +26529,7 @@ function generateForm(name, flags) {
|
|
|
26519
26529
|
function generateView(name, flags) {
|
|
26520
26530
|
const fields = fieldsOrDefault(flags.fields || "");
|
|
26521
26531
|
const table2 = resolveTable(name, flags, { announce: false });
|
|
26522
|
-
const routeName =
|
|
26532
|
+
const routeName = pluralizeReserved(toSnake(name));
|
|
26523
26533
|
const cols = fields.map(([f]) => f);
|
|
26524
26534
|
const dir = resolve11("src/templates/pages");
|
|
26525
26535
|
ensureDir(dir);
|
|
@@ -27531,7 +27541,7 @@ var init_generate = __esm({
|
|
|
27531
27541
|
crud: ({ name, table: table2 }) => [
|
|
27532
27542
|
`Apply the migration: npx tina4nodejs migrate`,
|
|
27533
27543
|
`Serve and try: npx tina4nodejs serve -> visit /swagger`,
|
|
27534
|
-
`Run the gate test: npx tsx tests/${
|
|
27544
|
+
`Run the gate test: npx tsx tests/${pluralizeReserved(toSnake(name))}.test.ts`,
|
|
27535
27545
|
`Change fields: edit src/models/${name}.ts then re-run generate crud`
|
|
27536
27546
|
],
|
|
27537
27547
|
migration: () => [
|
|
@@ -27549,10 +27559,10 @@ var init_generate = __esm({
|
|
|
27549
27559
|
],
|
|
27550
27560
|
form: ({ name, table: table2 }) => [
|
|
27551
27561
|
`Render from a route: res.render("forms/${table2}.twig", { item })`,
|
|
27552
|
-
`Add the POST route: npx tina4nodejs generate route ${
|
|
27562
|
+
`Add the POST route: npx tina4nodejs generate route ${pluralizeReserved(toSnake(name))} --model ${name}`
|
|
27553
27563
|
],
|
|
27554
|
-
view: ({ table: table2 }) => [
|
|
27555
|
-
`Wire routes to render list -> ${
|
|
27564
|
+
view: ({ name, table: table2 }) => [
|
|
27565
|
+
`Wire routes to render list -> ${pluralizeReserved(toSnake(name))}.twig, detail -> ${table2}.twig`,
|
|
27556
27566
|
`Customize the templates in src/templates/pages/`
|
|
27557
27567
|
],
|
|
27558
27568
|
auth: () => [
|
|
@@ -27713,7 +27723,7 @@ function envTruthy(val) {
|
|
|
27713
27723
|
return ["true", "1", "yes", "on"].includes(val.trim().toLowerCase());
|
|
27714
27724
|
}
|
|
27715
27725
|
function isLoopback(ip) {
|
|
27716
|
-
if (ip == null || ip === "") return
|
|
27726
|
+
if (ip == null || ip === "") return false;
|
|
27717
27727
|
let addr = ip.trim().toLowerCase();
|
|
27718
27728
|
if (addr.startsWith("::ffff:")) addr = addr.slice(7);
|
|
27719
27729
|
return addr === "::1" || addr === "localhost" || addr.startsWith("127.");
|
|
@@ -29304,6 +29314,7 @@ __export(graphql_exports, {
|
|
|
29304
29314
|
graphqlAutoSchemaEnabled: () => graphqlAutoSchemaEnabled,
|
|
29305
29315
|
graphqlEndpoint: () => graphqlEndpoint,
|
|
29306
29316
|
graphqlMaxDepth: () => graphqlMaxDepth,
|
|
29317
|
+
graphqlMaxNodes: () => graphqlMaxNodes,
|
|
29307
29318
|
tokenize: () => tokenize2
|
|
29308
29319
|
});
|
|
29309
29320
|
function tokenize2(source) {
|
|
@@ -29343,7 +29354,12 @@ function graphqlMaxDepth() {
|
|
|
29343
29354
|
const n = parseInt(raw, 10);
|
|
29344
29355
|
return Number.isNaN(n) ? 50 : n;
|
|
29345
29356
|
}
|
|
29346
|
-
|
|
29357
|
+
function graphqlMaxNodes() {
|
|
29358
|
+
const raw = (process.env.TINA4_GRAPHQL_MAX_NODES ?? "1000").trim();
|
|
29359
|
+
const n = parseInt(raw, 10);
|
|
29360
|
+
return Number.isNaN(n) ? 1e3 : n;
|
|
29361
|
+
}
|
|
29362
|
+
var TOKEN_PATTERNS, ParseError, Parser, ComplexityError, GraphQL, GraphQLType;
|
|
29347
29363
|
var init_graphql = __esm({
|
|
29348
29364
|
"../core/src/graphql.ts"() {
|
|
29349
29365
|
"use strict";
|
|
@@ -29382,9 +29398,16 @@ var init_graphql = __esm({
|
|
|
29382
29398
|
Parser = class {
|
|
29383
29399
|
tokens;
|
|
29384
29400
|
pos;
|
|
29401
|
+
// F2: bound the parser's own recursion. A deeply nested query
|
|
29402
|
+
// ("{a{a{a...}}}") otherwise recurses in parseSelectionSet until the call
|
|
29403
|
+
// stack overflows — BEFORE the execution-time depth guard can run. Reusing
|
|
29404
|
+
// TINA4_GRAPHQL_MAX_DEPTH keeps parse and execution on one bound.
|
|
29405
|
+
depth = 0;
|
|
29406
|
+
maxDepth;
|
|
29385
29407
|
constructor(tokens) {
|
|
29386
29408
|
this.tokens = tokens;
|
|
29387
29409
|
this.pos = 0;
|
|
29410
|
+
this.maxDepth = graphqlMaxDepth();
|
|
29388
29411
|
}
|
|
29389
29412
|
peek() {
|
|
29390
29413
|
return this.pos < this.tokens.length ? this.tokens[this.pos] : null;
|
|
@@ -29466,6 +29489,17 @@ var init_graphql = __esm({
|
|
|
29466
29489
|
};
|
|
29467
29490
|
}
|
|
29468
29491
|
parseSelectionSet() {
|
|
29492
|
+
this.depth++;
|
|
29493
|
+
if (this.maxDepth > 0 && this.depth > this.maxDepth) {
|
|
29494
|
+
throw new Error(`Query exceeds maximum depth of ${this.maxDepth}`);
|
|
29495
|
+
}
|
|
29496
|
+
try {
|
|
29497
|
+
return this.parseSelectionSetInner();
|
|
29498
|
+
} finally {
|
|
29499
|
+
this.depth--;
|
|
29500
|
+
}
|
|
29501
|
+
}
|
|
29502
|
+
parseSelectionSetInner() {
|
|
29469
29503
|
this.expect("LBRACE");
|
|
29470
29504
|
const selections = [];
|
|
29471
29505
|
while (!this.match("RBRACE")) {
|
|
@@ -29620,6 +29654,8 @@ var init_graphql = __esm({
|
|
|
29620
29654
|
return t;
|
|
29621
29655
|
}
|
|
29622
29656
|
};
|
|
29657
|
+
ComplexityError = class extends Error {
|
|
29658
|
+
};
|
|
29623
29659
|
GraphQL = class _GraphQL {
|
|
29624
29660
|
types = /* @__PURE__ */ new Map();
|
|
29625
29661
|
queries = /* @__PURE__ */ new Map();
|
|
@@ -29642,6 +29678,12 @@ var init_graphql = __esm({
|
|
|
29642
29678
|
* spread / inline fragment, so circular fragments are caught too.
|
|
29643
29679
|
*/
|
|
29644
29680
|
maxDepth = graphqlMaxDepth();
|
|
29681
|
+
/**
|
|
29682
|
+
* Maximum expanded selection nodes per query (from `TINA4_GRAPHQL_MAX_NODES`,
|
|
29683
|
+
* default 1000; `<= 0` disables). Bounds query WIDTH — fragment bombs and
|
|
29684
|
+
* alias explosions — which the depth guard does not. See {@link graphqlMaxNodes}.
|
|
29685
|
+
*/
|
|
29686
|
+
maxNodes = graphqlMaxNodes();
|
|
29645
29687
|
/**
|
|
29646
29688
|
* Decorator-style resolver registration.
|
|
29647
29689
|
*
|
|
@@ -29789,6 +29831,16 @@ var init_graphql = __esm({
|
|
|
29789
29831
|
return { data: null, errors: [{ message: "No operation found" }] };
|
|
29790
29832
|
}
|
|
29791
29833
|
const op = operations[0];
|
|
29834
|
+
if (this.maxNodes > 0) {
|
|
29835
|
+
try {
|
|
29836
|
+
this.assertComplexity(op.selections, fragments);
|
|
29837
|
+
} catch (e) {
|
|
29838
|
+
if (e instanceof ComplexityError) {
|
|
29839
|
+
return { data: null, errors: [{ message: e.message }] };
|
|
29840
|
+
}
|
|
29841
|
+
throw e;
|
|
29842
|
+
}
|
|
29843
|
+
}
|
|
29792
29844
|
const resolvers = op.operation === "query" ? this.queries : this.mutations;
|
|
29793
29845
|
for (const vdef of op.variables) {
|
|
29794
29846
|
if (!(vdef.name in vars) && vdef.default !== void 0) {
|
|
@@ -29814,6 +29866,36 @@ var init_graphql = __esm({
|
|
|
29814
29866
|
* instead of recursing until the interpreter stack overflows. Top-level
|
|
29815
29867
|
* starts at depth 1; `maxDepth <= 0` disables the guard.
|
|
29816
29868
|
*/
|
|
29869
|
+
/**
|
|
29870
|
+
* Count the expanded selection nodes, throwing {@link ComplexityError} past
|
|
29871
|
+
* `maxNodes` (F2). Fragment spreads are expanded (so a fragment bomb is
|
|
29872
|
+
* counted, not the small unexpanded document) and every field — including
|
|
29873
|
+
* each alias — counts once. The count is capped by `maxNodes` and the walk by
|
|
29874
|
+
* `maxDepth`, so a circular fragment trips the budget instead of looping and
|
|
29875
|
+
* the check itself can never cost more than the budget.
|
|
29876
|
+
*/
|
|
29877
|
+
assertComplexity(selections, fragments) {
|
|
29878
|
+
const depthCap = this.maxDepth > 0 ? this.maxDepth : 1e3;
|
|
29879
|
+
let count = 0;
|
|
29880
|
+
const walk2 = (sels, depth) => {
|
|
29881
|
+
if (depth > depthCap) return;
|
|
29882
|
+
for (const sel of sels) {
|
|
29883
|
+
count++;
|
|
29884
|
+
if (count > this.maxNodes) {
|
|
29885
|
+
throw new ComplexityError(`Query exceeds maximum complexity of ${this.maxNodes} nodes`);
|
|
29886
|
+
}
|
|
29887
|
+
if (sel.kind === "fragment_spread") {
|
|
29888
|
+
const frag = fragments.get(sel.name);
|
|
29889
|
+
if (frag) walk2(frag.selections, depth + 1);
|
|
29890
|
+
} else if (sel.kind === "inline_fragment") {
|
|
29891
|
+
walk2(sel.selections, depth + 1);
|
|
29892
|
+
} else if (sel.kind === "field" && sel.selections) {
|
|
29893
|
+
walk2(sel.selections, depth + 1);
|
|
29894
|
+
}
|
|
29895
|
+
}
|
|
29896
|
+
};
|
|
29897
|
+
walk2(selections, 1);
|
|
29898
|
+
}
|
|
29817
29899
|
async resolveSelectionsInto(selections, resolvers, parent, variables, context, fragments, target, depth) {
|
|
29818
29900
|
const errors = [];
|
|
29819
29901
|
if (this.maxDepth > 0 && depth > this.maxDepth) {
|
|
@@ -31016,7 +31098,8 @@ function wsAuthorized(route, headers, queryString = "", subprotocol = "") {
|
|
|
31016
31098
|
const token = wsToken(headers, queryString, subprotocol);
|
|
31017
31099
|
if (!token) return [null, false];
|
|
31018
31100
|
const payload = validToken(token);
|
|
31019
|
-
return [
|
|
31101
|
+
if (!isIdentityPayload(payload)) return [null, false];
|
|
31102
|
+
return [payload, true];
|
|
31020
31103
|
}
|
|
31021
31104
|
function offeredBearerSubprotocol(subprotocol) {
|
|
31022
31105
|
return subprotocol.split(",").some((p) => p.trim().toLowerCase() === "bearer");
|
|
@@ -32211,12 +32294,36 @@ var init_mongoBackend = __esm({
|
|
|
32211
32294
|
}
|
|
32212
32295
|
}
|
|
32213
32296
|
else if (operation === "size") {
|
|
32214
|
-
|
|
32215
|
-
|
|
32216
|
-
|
|
32217
|
-
|
|
32297
|
+
// ADR-0022 dec 7: size(status) must not answer a different question.
|
|
32298
|
+
// The dead aliases count the .dead_letter store (== deadLetters().length);
|
|
32299
|
+
// reserved/completed count by status; else pending. data = status.
|
|
32300
|
+
const status = data || "pending";
|
|
32301
|
+
let count: number;
|
|
32302
|
+
if (status === "dead" || status === "failed" || status === "dead_letter") {
|
|
32303
|
+
count = await col.countDocuments({ queue: queueName + ".dead_letter" });
|
|
32304
|
+
} else if (status === "reserved" || status === "completed") {
|
|
32305
|
+
count = await col.countDocuments({ queue: queueName, status });
|
|
32306
|
+
} else {
|
|
32307
|
+
count = await col.countDocuments({ queue: queueName, status: "pending" });
|
|
32308
|
+
}
|
|
32218
32309
|
process.stdout.write(String(count));
|
|
32219
32310
|
}
|
|
32311
|
+
else if (operation === "reject") {
|
|
32312
|
+
// Dead-letter NOW, no retry (ADR-0023). Floor attempts at maxRetries
|
|
32313
|
+
// so it reads as exhausted (parity with fail()'s dead-letter branch
|
|
32314
|
+
// and size("dead")). data = JSON { id, reason, maxRetries }.
|
|
32315
|
+
const info = JSON.parse(data);
|
|
32316
|
+
const doc = await col.findOne({ queue: queueName, id: info.id });
|
|
32317
|
+
if (doc) {
|
|
32318
|
+
const attempts = Math.max((doc.attempts || 0) + 1, info.maxRetries);
|
|
32319
|
+
await col.insertOne({
|
|
32320
|
+
...doc, _id: undefined, attempts,
|
|
32321
|
+
status: "dead", queue: queueName + ".dead_letter", error: info.reason,
|
|
32322
|
+
});
|
|
32323
|
+
await col.deleteOne({ _id: doc._id, queue: queueName });
|
|
32324
|
+
}
|
|
32325
|
+
process.stdout.write("__OK__");
|
|
32326
|
+
}
|
|
32220
32327
|
else if (operation === "clear") {
|
|
32221
32328
|
await col.deleteMany({ queue: queueName });
|
|
32222
32329
|
process.stdout.write("__CLEARED__");
|
|
@@ -32452,11 +32559,20 @@ var init_mongoBackend = __esm({
|
|
|
32452
32559
|
return null;
|
|
32453
32560
|
}
|
|
32454
32561
|
}
|
|
32455
|
-
size(queue) {
|
|
32456
|
-
const result = this.execSync("size", queue);
|
|
32562
|
+
size(queue, status2 = "pending") {
|
|
32563
|
+
const result = this.execSync("size", queue, status2);
|
|
32457
32564
|
const num = parseInt(result, 10);
|
|
32458
32565
|
return isNaN(num) ? 0 : num;
|
|
32459
32566
|
}
|
|
32567
|
+
/**
|
|
32568
|
+
* Dead-letter a job immediately — no retry (ADR-0023 reject()). Distinct from
|
|
32569
|
+
* fail(): fail() requeues until maxRetries is spent. attempts is floored at
|
|
32570
|
+
* maxRetries so deadLetters()/size("dead") (which filter/scope to the
|
|
32571
|
+
* dead-letter store) see it.
|
|
32572
|
+
*/
|
|
32573
|
+
reject(queue, id, reason, maxRetries) {
|
|
32574
|
+
this.execSync("reject", queue, JSON.stringify({ id, reason, maxRetries }));
|
|
32575
|
+
}
|
|
32460
32576
|
clear(queue) {
|
|
32461
32577
|
this.execSync("clear", queue);
|
|
32462
32578
|
}
|
|
@@ -32551,7 +32667,9 @@ function createJob(data, queue) {
|
|
|
32551
32667
|
queue._failJob(job.topic, job, reason, queue.getMaxRetries());
|
|
32552
32668
|
},
|
|
32553
32669
|
reject(reason = "") {
|
|
32554
|
-
job.
|
|
32670
|
+
job.status = "dead";
|
|
32671
|
+
job.error = reason;
|
|
32672
|
+
queue._rejectJob(job.topic, job, reason, queue.getMaxRetries());
|
|
32555
32673
|
},
|
|
32556
32674
|
retry(delaySeconds) {
|
|
32557
32675
|
queue._retryJob(job.topic, job, delaySeconds);
|
|
@@ -33160,6 +33278,20 @@ var init_liteBackend = __esm({
|
|
|
33160
33278
|
* dead-letter file -- two spellings of the same intent that previously
|
|
33161
33279
|
* diverged.
|
|
33162
33280
|
*/
|
|
33281
|
+
/**
|
|
33282
|
+
* Reject a job permanently — dead-letter it NOW, no retry (ADR-0023).
|
|
33283
|
+
*
|
|
33284
|
+
* Unlike failJob(), which requeues until maxRetries is spent, rejectJob()
|
|
33285
|
+
* moves a known-poison job straight to the dead-letter store. attempts is
|
|
33286
|
+
* floored at maxRetries so deadLetters() (attempts >= maxRetries) returns it
|
|
33287
|
+
* and size("dead") agrees.
|
|
33288
|
+
*/
|
|
33289
|
+
rejectJob(queue, job, reason, maxRetries) {
|
|
33290
|
+
this.clearReservation(queue, job.id);
|
|
33291
|
+
job.attempts = Math.max((job.attempts || 0) + 1, maxRetries);
|
|
33292
|
+
job.error = reason;
|
|
33293
|
+
this.deadLetter(queue, job, reason);
|
|
33294
|
+
}
|
|
33163
33295
|
retryJob(queue, job, delaySeconds) {
|
|
33164
33296
|
this.clearReservation(queue, job.id);
|
|
33165
33297
|
try {
|
|
@@ -33381,7 +33513,7 @@ var init_queue = __esm({
|
|
|
33381
33513
|
size(status2 = "pending") {
|
|
33382
33514
|
const q = this.topic;
|
|
33383
33515
|
if (this.externalBackend) {
|
|
33384
|
-
return this.externalBackend.size(q);
|
|
33516
|
+
return this.externalBackend.size(q, status2);
|
|
33385
33517
|
}
|
|
33386
33518
|
return this.liteBackend.size(q, status2);
|
|
33387
33519
|
}
|
|
@@ -33649,6 +33781,17 @@ var init_queue = __esm({
|
|
|
33649
33781
|
}
|
|
33650
33782
|
this.liteBackend.failJob(queue, job, error2, maxRetries, this._retryBackoff);
|
|
33651
33783
|
}
|
|
33784
|
+
/**
|
|
33785
|
+
* Dead-letter a job immediately — no retry (ADR-0023 reject()). Distinct from
|
|
33786
|
+
* _failJob, which retries until maxRetries is spent.
|
|
33787
|
+
*/
|
|
33788
|
+
_rejectJob(queue, job, reason, maxRetries) {
|
|
33789
|
+
if (this.externalBackend?.reject) {
|
|
33790
|
+
this.externalBackend.reject(queue, job.id, reason, maxRetries);
|
|
33791
|
+
return;
|
|
33792
|
+
}
|
|
33793
|
+
this.liteBackend.rejectJob(queue, job, reason, maxRetries);
|
|
33794
|
+
}
|
|
33652
33795
|
/**
|
|
33653
33796
|
* Re-queue a job back to the main queue directory with incremented attempts.
|
|
33654
33797
|
*/
|
|
@@ -35773,7 +35916,7 @@ ${end}
|
|
|
35773
35916
|
// ../core/src/devAdmin.ts
|
|
35774
35917
|
import { cpus as osCpus } from "node:os";
|
|
35775
35918
|
import { readFileSync as readFileSync20, writeFileSync as writeFileSync16, existsSync as existsSync23, readdirSync as readdirSync15, mkdirSync as mkdirSync19, copyFileSync as copyFileSync2, statSync as statSync17, openSync as openSync4, closeSync as closeSync4, fstatSync as fstatSync2, fchmodSync as fchmodSync2, ftruncateSync, constants as constants3, realpathSync as realpathSync6, lstatSync } from "node:fs";
|
|
35776
|
-
import { join as join28, dirname as dirname13, resolve as resolve17, relative as relative9, sep as
|
|
35919
|
+
import { join as join28, dirname as dirname13, resolve as resolve17, relative as relative9, sep as sep8, basename as basename6 } from "node:path";
|
|
35777
35920
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
35778
35921
|
import { timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
35779
35922
|
function escapeHtml(value) {
|
|
@@ -36164,7 +36307,7 @@ function safeJoin(projectRoot3, rel, extraRoots = []) {
|
|
|
36164
36307
|
const resolved = realPathOf(resolve17(root, rel));
|
|
36165
36308
|
const roots = [root, ...extraRoots.filter(Boolean).map((r) => realPathOf(resolve17(r)))];
|
|
36166
36309
|
for (const r of roots) {
|
|
36167
|
-
if (resolved === r || resolved.startsWith(r.endsWith(
|
|
36310
|
+
if (resolved === r || resolved.startsWith(r.endsWith(sep8) ? r : r + sep8)) return resolved;
|
|
36168
36311
|
}
|
|
36169
36312
|
} catch {
|
|
36170
36313
|
}
|
|
@@ -39360,13 +39503,20 @@ var sso_exports = {};
|
|
|
39360
39503
|
__export(sso_exports, {
|
|
39361
39504
|
SSO: () => Sso,
|
|
39362
39505
|
Sso: () => Sso,
|
|
39363
|
-
SsoError: () => SsoError
|
|
39506
|
+
SsoError: () => SsoError,
|
|
39507
|
+
liveSessionIdentity: () => liveSessionIdentity
|
|
39364
39508
|
});
|
|
39365
39509
|
import { createHash as createHash9, randomBytes as randomBytes7, timingSafeEqual as timingSafeEqual3 } from "node:crypto";
|
|
39510
|
+
function expiresAt(tokens) {
|
|
39511
|
+
const lifetime = Number(tokens.expires_in ?? 0);
|
|
39512
|
+
return lifetime > 0 ? Math.floor(Date.now() / 1e3) + lifetime : 0;
|
|
39513
|
+
}
|
|
39366
39514
|
var SsoError, Sso;
|
|
39367
39515
|
var init_sso = __esm({
|
|
39368
39516
|
"../core/src/sso.ts"() {
|
|
39369
39517
|
"use strict";
|
|
39518
|
+
init_ssoIdentity();
|
|
39519
|
+
init_ssoIdentity();
|
|
39370
39520
|
SsoError = class extends Error {
|
|
39371
39521
|
};
|
|
39372
39522
|
Sso = class _Sso {
|
|
@@ -39572,13 +39722,12 @@ var init_sso = __esm({
|
|
|
39572
39722
|
access_token: tokens.access_token,
|
|
39573
39723
|
refresh_token: tokens.refresh_token,
|
|
39574
39724
|
id_token: tokens.id_token,
|
|
39575
|
-
expires_at:
|
|
39725
|
+
expires_at: expiresAt(tokens)
|
|
39576
39726
|
});
|
|
39577
39727
|
return { identity, return_to: _Sso.safeReturn(pending.return_to) };
|
|
39578
39728
|
}
|
|
39579
39729
|
identity(requestOrSession) {
|
|
39580
|
-
const
|
|
39581
|
-
const identity = stored?.identity ?? null;
|
|
39730
|
+
const identity = liveSessionIdentity(this.session(requestOrSession)?.get(_Sso.SESSION_KEY));
|
|
39582
39731
|
if (identity && requestOrSession?.session) requestOrSession.user = identity;
|
|
39583
39732
|
return identity;
|
|
39584
39733
|
}
|
|
@@ -39605,7 +39754,7 @@ var init_sso = __esm({
|
|
|
39605
39754
|
access_token: tokens.access_token,
|
|
39606
39755
|
refresh_token: tokens.refresh_token ?? stored.refresh_token,
|
|
39607
39756
|
id_token: tokens.id_token ?? stored.id_token,
|
|
39608
|
-
expires_at:
|
|
39757
|
+
expires_at: expiresAt(tokens)
|
|
39609
39758
|
});
|
|
39610
39759
|
return identity;
|
|
39611
39760
|
} catch (error2) {
|
|
@@ -39697,8 +39846,8 @@ function writeMcpDiscovery(projectRoot3, port) {
|
|
|
39697
39846
|
const lines = contents.split(/\r?\n/);
|
|
39698
39847
|
const already = lines.some((l) => l.trim() === GITIGNORE_LINE || l.trim() === ".tina4");
|
|
39699
39848
|
if (!already) {
|
|
39700
|
-
const
|
|
39701
|
-
fs9.writeFileSync(gitignorePath, `${contents}${
|
|
39849
|
+
const sep9 = contents.endsWith("\n") || contents === "" ? "" : "\n";
|
|
39850
|
+
fs9.writeFileSync(gitignorePath, `${contents}${sep9}${GITIGNORE_LINE}
|
|
39702
39851
|
`, "utf-8");
|
|
39703
39852
|
}
|
|
39704
39853
|
}
|
|
@@ -39747,12 +39896,12 @@ function bannerSurfaceLines(port, opts) {
|
|
|
39747
39896
|
];
|
|
39748
39897
|
}
|
|
39749
39898
|
function swaggerAdvertised() {
|
|
39750
|
-
const
|
|
39899
|
+
const TRUTHY3 = ["true", "1", "yes", "on"];
|
|
39751
39900
|
const raw = (process.env.TINA4_SWAGGER_ENABLED ?? "").trim().toLowerCase();
|
|
39752
39901
|
if (raw === "") {
|
|
39753
|
-
return
|
|
39902
|
+
return TRUTHY3.includes((process.env.TINA4_DEBUG ?? "").trim().toLowerCase());
|
|
39754
39903
|
}
|
|
39755
|
-
return
|
|
39904
|
+
return TRUTHY3.includes(raw);
|
|
39756
39905
|
}
|
|
39757
39906
|
async function autoMigrateOnStartup(migrationDir = "migrations", base = process.cwd()) {
|
|
39758
39907
|
const dir = resolve20(base, migrationDir);
|
|
@@ -40671,8 +40820,9 @@ function wireLocaleGlobal(frondEngine, base) {
|
|
|
40671
40820
|
async function startServer(config) {
|
|
40672
40821
|
loadEnv(".env.local");
|
|
40673
40822
|
loadEnv();
|
|
40674
|
-
const { ensureDevSecret: ensureDevSecret2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
|
|
40823
|
+
const { ensureDevSecret: ensureDevSecret2, requireBootSecret: requireBootSecret2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
|
|
40675
40824
|
ensureDevSecret2();
|
|
40825
|
+
requireBootSecret2();
|
|
40676
40826
|
_checkLegacyEnvVars();
|
|
40677
40827
|
const resolved = resolvePortAndHost(config);
|
|
40678
40828
|
const host = resolved.host;
|
|
@@ -42337,6 +42487,178 @@ var init_service = __esm({
|
|
|
42337
42487
|
}
|
|
42338
42488
|
});
|
|
42339
42489
|
|
|
42490
|
+
// ../core/src/ssrf.ts
|
|
42491
|
+
import { isIP } from "node:net";
|
|
42492
|
+
import { lookup } from "node:dns/promises";
|
|
42493
|
+
function v4ToBytes(ip) {
|
|
42494
|
+
const parts = ip.split(".");
|
|
42495
|
+
if (parts.length !== 4) return null;
|
|
42496
|
+
const bytes = new Uint8Array(4);
|
|
42497
|
+
for (let i = 0; i < 4; i++) {
|
|
42498
|
+
const n = Number(parts[i]);
|
|
42499
|
+
if (!Number.isInteger(n) || n < 0 || n > 255 || !/^\d+$/.test(parts[i])) return null;
|
|
42500
|
+
bytes[i] = n;
|
|
42501
|
+
}
|
|
42502
|
+
return bytes;
|
|
42503
|
+
}
|
|
42504
|
+
function v6ToBytes(input) {
|
|
42505
|
+
let ip = input.split("%")[0];
|
|
42506
|
+
const lastColon = ip.lastIndexOf(":");
|
|
42507
|
+
const tail = ip.slice(lastColon + 1);
|
|
42508
|
+
if (tail.includes(".")) {
|
|
42509
|
+
const v4 = v4ToBytes(tail);
|
|
42510
|
+
if (!v4) return null;
|
|
42511
|
+
const hextetA = (v4[0] << 8 | v4[1]).toString(16);
|
|
42512
|
+
const hextetB = (v4[2] << 8 | v4[3]).toString(16);
|
|
42513
|
+
ip = ip.slice(0, lastColon + 1) + hextetA + ":" + hextetB;
|
|
42514
|
+
}
|
|
42515
|
+
const halves = ip.split("::");
|
|
42516
|
+
if (halves.length > 2) return null;
|
|
42517
|
+
const head = halves[0] ? halves[0].split(":") : [];
|
|
42518
|
+
const back = halves.length === 2 ? halves[1] ? halves[1].split(":") : [] : [];
|
|
42519
|
+
let groups;
|
|
42520
|
+
if (halves.length === 1) {
|
|
42521
|
+
if (head.length !== 8) return null;
|
|
42522
|
+
groups = head;
|
|
42523
|
+
} else {
|
|
42524
|
+
const missing = 8 - (head.length + back.length);
|
|
42525
|
+
if (missing < 0) return null;
|
|
42526
|
+
groups = [...head, ...Array(missing).fill("0"), ...back];
|
|
42527
|
+
}
|
|
42528
|
+
if (groups.length !== 8) return null;
|
|
42529
|
+
const bytes = new Uint8Array(16);
|
|
42530
|
+
for (let i = 0; i < 8; i++) {
|
|
42531
|
+
if (!/^[0-9a-fA-F]{1,4}$/.test(groups[i])) return null;
|
|
42532
|
+
const g = parseInt(groups[i], 16);
|
|
42533
|
+
bytes[i * 2] = g >> 8 & 255;
|
|
42534
|
+
bytes[i * 2 + 1] = g & 255;
|
|
42535
|
+
}
|
|
42536
|
+
return bytes;
|
|
42537
|
+
}
|
|
42538
|
+
function ipToBytes(ip) {
|
|
42539
|
+
const cleaned = ip.split("%")[0];
|
|
42540
|
+
const family = isIP(cleaned);
|
|
42541
|
+
if (family === 4) return v4ToBytes(cleaned);
|
|
42542
|
+
if (family === 6) {
|
|
42543
|
+
const bytes = v6ToBytes(cleaned);
|
|
42544
|
+
if (!bytes) return null;
|
|
42545
|
+
const isMapped = bytes.subarray(0, 10).every((b) => b === 0) && bytes[10] === 255 && bytes[11] === 255;
|
|
42546
|
+
return isMapped ? bytes.subarray(12) : bytes;
|
|
42547
|
+
}
|
|
42548
|
+
return null;
|
|
42549
|
+
}
|
|
42550
|
+
function parseCidr(cidr) {
|
|
42551
|
+
const [netStr, bitsStr] = cidr.split("/");
|
|
42552
|
+
const net4 = ipToBytes(netStr);
|
|
42553
|
+
if (!net4) return null;
|
|
42554
|
+
return { net: net4, bits: Number(bitsStr) };
|
|
42555
|
+
}
|
|
42556
|
+
function inCidr(packed, cidr) {
|
|
42557
|
+
if (packed.length !== cidr.net.length) return false;
|
|
42558
|
+
const whole = Math.floor(cidr.bits / 8);
|
|
42559
|
+
const remainder = cidr.bits % 8;
|
|
42560
|
+
for (let i = 0; i < whole; i++) {
|
|
42561
|
+
if (packed[i] !== cidr.net[i]) return false;
|
|
42562
|
+
}
|
|
42563
|
+
if (remainder === 0) return true;
|
|
42564
|
+
const mask = 255 << 8 - remainder & 255;
|
|
42565
|
+
return (packed[whole] & mask) === (cidr.net[whole] & mask);
|
|
42566
|
+
}
|
|
42567
|
+
function allowPrivateRequests() {
|
|
42568
|
+
return TRUTHY2.has((process.env[ALLOW_PRIVATE_ENV] ?? "").trim().toLowerCase());
|
|
42569
|
+
}
|
|
42570
|
+
function isBlockedAddress(ip) {
|
|
42571
|
+
const packed = ipToBytes(ip);
|
|
42572
|
+
if (!packed) return true;
|
|
42573
|
+
return BLOCKED.some((cidr) => inCidr(packed, cidr));
|
|
42574
|
+
}
|
|
42575
|
+
function matchesAllowList(host, port, resolved, allowHosts) {
|
|
42576
|
+
const hostLower = host.toLowerCase();
|
|
42577
|
+
for (const raw of allowHosts ?? []) {
|
|
42578
|
+
const entry = String(raw).trim().toLowerCase();
|
|
42579
|
+
if (!entry) continue;
|
|
42580
|
+
if (entry === hostLower || entry === `${hostLower}:${port}`) return true;
|
|
42581
|
+
if (entry.includes("/")) {
|
|
42582
|
+
const cidr = parseCidr(entry);
|
|
42583
|
+
if (cidr && resolved.some((ip) => {
|
|
42584
|
+
const packed = ipToBytes(ip);
|
|
42585
|
+
return packed !== null && inCidr(packed, cidr);
|
|
42586
|
+
})) {
|
|
42587
|
+
return true;
|
|
42588
|
+
}
|
|
42589
|
+
}
|
|
42590
|
+
}
|
|
42591
|
+
return false;
|
|
42592
|
+
}
|
|
42593
|
+
function logBlock(host, ip) {
|
|
42594
|
+
void Promise.resolve().then(() => (init_logger(), logger_exports)).then((mod) => mod.Log?.warning?.(`SSRF guard blocked outbound request to ${host} (${ip})`)).catch(() => void 0);
|
|
42595
|
+
}
|
|
42596
|
+
async function guardUrl(url, allowHosts) {
|
|
42597
|
+
let parsed;
|
|
42598
|
+
try {
|
|
42599
|
+
parsed = new URL(url);
|
|
42600
|
+
} catch {
|
|
42601
|
+
throw new SsrfError(`Blocked request: '${url}' is not a valid URL`);
|
|
42602
|
+
}
|
|
42603
|
+
const scheme = parsed.protocol.replace(/:$/, "").toLowerCase();
|
|
42604
|
+
if (scheme !== "http" && scheme !== "https") {
|
|
42605
|
+
throw new SsrfError(`Blocked request: URL scheme '${scheme || "(none)"}' is not http or https`);
|
|
42606
|
+
}
|
|
42607
|
+
const host = parsed.hostname.replace(/^\[|\]$/g, "");
|
|
42608
|
+
if (!host) {
|
|
42609
|
+
throw new SsrfError("Blocked request: URL has no host");
|
|
42610
|
+
}
|
|
42611
|
+
const port = parsed.port ? Number(parsed.port) : scheme === "https" ? 443 : 80;
|
|
42612
|
+
if (allowPrivateRequests()) return;
|
|
42613
|
+
let resolved;
|
|
42614
|
+
try {
|
|
42615
|
+
const results = await lookup(host, { all: true });
|
|
42616
|
+
resolved = results.map((r) => r.address);
|
|
42617
|
+
} catch {
|
|
42618
|
+
throw new SsrfError(`Blocked request to ${host}: cannot resolve host`);
|
|
42619
|
+
}
|
|
42620
|
+
if (resolved.length === 0) {
|
|
42621
|
+
throw new SsrfError(`Blocked request to ${host}: cannot resolve host`);
|
|
42622
|
+
}
|
|
42623
|
+
if (matchesAllowList(host, port, resolved, allowHosts)) return;
|
|
42624
|
+
for (const ip of resolved) {
|
|
42625
|
+
if (isBlockedAddress(ip)) {
|
|
42626
|
+
logBlock(host, ip);
|
|
42627
|
+
throw new SsrfError(
|
|
42628
|
+
`Blocked request to private/internal address ${ip} (host ${host}): set ${ALLOW_PRIVATE_ENV}=true to allow, or pass an allow-list.`
|
|
42629
|
+
);
|
|
42630
|
+
}
|
|
42631
|
+
}
|
|
42632
|
+
}
|
|
42633
|
+
var ALLOW_PRIVATE_ENV, TRUTHY2, SsrfError, BLOCKED_CIDRS, BLOCKED;
|
|
42634
|
+
var init_ssrf = __esm({
|
|
42635
|
+
"../core/src/ssrf.ts"() {
|
|
42636
|
+
"use strict";
|
|
42637
|
+
ALLOW_PRIVATE_ENV = "TINA4_ALLOW_PRIVATE_REQUESTS";
|
|
42638
|
+
TRUTHY2 = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
|
|
42639
|
+
SsrfError = class extends Error {
|
|
42640
|
+
constructor(message) {
|
|
42641
|
+
super(message);
|
|
42642
|
+
this.name = "SsrfError";
|
|
42643
|
+
}
|
|
42644
|
+
};
|
|
42645
|
+
BLOCKED_CIDRS = [
|
|
42646
|
+
"0.0.0.0/8",
|
|
42647
|
+
"10.0.0.0/8",
|
|
42648
|
+
"127.0.0.0/8",
|
|
42649
|
+
"169.254.0.0/16",
|
|
42650
|
+
"172.16.0.0/12",
|
|
42651
|
+
"192.168.0.0/16",
|
|
42652
|
+
"100.64.0.0/10",
|
|
42653
|
+
"::1/128",
|
|
42654
|
+
"::/128",
|
|
42655
|
+
"fc00::/7",
|
|
42656
|
+
"fe80::/10"
|
|
42657
|
+
];
|
|
42658
|
+
BLOCKED = BLOCKED_CIDRS.map(parseCidr).filter((c) => c !== null);
|
|
42659
|
+
}
|
|
42660
|
+
});
|
|
42661
|
+
|
|
42340
42662
|
// ../core/src/api.ts
|
|
42341
42663
|
import http from "node:http";
|
|
42342
42664
|
import https from "node:https";
|
|
@@ -42493,6 +42815,7 @@ var init_api = __esm({
|
|
|
42493
42815
|
"../core/src/api.ts"() {
|
|
42494
42816
|
"use strict";
|
|
42495
42817
|
init_version();
|
|
42818
|
+
init_ssrf();
|
|
42496
42819
|
ApiStreamError = class extends Error {
|
|
42497
42820
|
constructor(message, status2 = null) {
|
|
42498
42821
|
super(message);
|
|
@@ -42547,6 +42870,7 @@ var init_api = __esm({
|
|
|
42547
42870
|
transportFn;
|
|
42548
42871
|
cookiesEnabled;
|
|
42549
42872
|
cookies;
|
|
42873
|
+
allowHosts;
|
|
42550
42874
|
/**
|
|
42551
42875
|
* Construct an Api client.
|
|
42552
42876
|
*
|
|
@@ -42586,6 +42910,7 @@ var init_api = __esm({
|
|
|
42586
42910
|
this.transportFn = void 0;
|
|
42587
42911
|
this.cookiesEnabled = false;
|
|
42588
42912
|
this.cookies = {};
|
|
42913
|
+
this.allowHosts = [];
|
|
42589
42914
|
if (typeof authHeaderOrOptions === "object" && authHeaderOrOptions !== null) {
|
|
42590
42915
|
const opts = authHeaderOrOptions;
|
|
42591
42916
|
this.authHeader = opts.authHeader ?? "";
|
|
@@ -42595,6 +42920,7 @@ var init_api = __esm({
|
|
|
42595
42920
|
this.retryBackoff = opts.retryBackoff ?? 0.5;
|
|
42596
42921
|
this.transportFn = opts.transport;
|
|
42597
42922
|
this.cookiesEnabled = opts.cookies ?? false;
|
|
42923
|
+
this.allowHosts = opts.allowHosts ?? [];
|
|
42598
42924
|
if (opts.bearerToken != null) {
|
|
42599
42925
|
this.setBearerToken(opts.bearerToken);
|
|
42600
42926
|
} else if (opts.username != null && opts.password != null) {
|
|
@@ -42908,7 +43234,12 @@ var init_api = __esm({
|
|
|
42908
43234
|
* body-phase timeout is applied by the caller (streamBytes) via
|
|
42909
43235
|
* `res.destroy()`.
|
|
42910
43236
|
*/
|
|
42911
|
-
openStreamRequest(method, url, headers, data, connectSec) {
|
|
43237
|
+
async openStreamRequest(method, url, headers, data, connectSec) {
|
|
43238
|
+
try {
|
|
43239
|
+
await guardUrl(url, this.allowHosts);
|
|
43240
|
+
} catch (err) {
|
|
43241
|
+
throw new ApiStreamError(err instanceof Error ? err.message : String(err), null);
|
|
43242
|
+
}
|
|
42912
43243
|
return new Promise((resolve29, reject) => {
|
|
42913
43244
|
let parsed;
|
|
42914
43245
|
try {
|
|
@@ -42967,10 +43298,11 @@ var init_api = __esm({
|
|
|
42967
43298
|
*/
|
|
42968
43299
|
buildRequest(method, contentType, body, extraHeaders, targetUrl) {
|
|
42969
43300
|
const headers = { "User-Agent": `Tina4/${TINA4_VERSION}`, ...this.headers };
|
|
42970
|
-
|
|
43301
|
+
const sameOriginAsBase = targetUrl === void 0 || sameOrigin(targetUrl, this.baseUrl);
|
|
43302
|
+
if (this.authHeader && sameOriginAsBase) {
|
|
42971
43303
|
headers["Authorization"] = this.authHeader;
|
|
42972
43304
|
}
|
|
42973
|
-
if (this.cookiesEnabled) {
|
|
43305
|
+
if (this.cookiesEnabled && sameOriginAsBase) {
|
|
42974
43306
|
const cookieHeader = this.cookieHeader();
|
|
42975
43307
|
if (cookieHeader) {
|
|
42976
43308
|
headers["Cookie"] = cookieHeader;
|
|
@@ -43071,7 +43403,12 @@ var init_api = __esm({
|
|
|
43071
43403
|
* bearer token / session cookie never leaks to a host you didn't
|
|
43072
43404
|
* authenticate to.
|
|
43073
43405
|
*/
|
|
43074
|
-
performRequest(method, url, headers, data, redirectsLeft) {
|
|
43406
|
+
async performRequest(method, url, headers, data, redirectsLeft) {
|
|
43407
|
+
try {
|
|
43408
|
+
await guardUrl(url, this.allowHosts);
|
|
43409
|
+
} catch (err) {
|
|
43410
|
+
return { kind: "error", error: err instanceof Error ? err.message : String(err) };
|
|
43411
|
+
}
|
|
43075
43412
|
return new Promise((resolve29) => {
|
|
43076
43413
|
let parsed;
|
|
43077
43414
|
try {
|
|
@@ -45829,6 +46166,7 @@ var CURVE, RECORD_SIZE, MAX_PAYLOAD, PushError, Push;
|
|
|
45829
46166
|
var init_push = __esm({
|
|
45830
46167
|
"../core/src/push.ts"() {
|
|
45831
46168
|
"use strict";
|
|
46169
|
+
init_ssrf();
|
|
45832
46170
|
CURVE = "prime256v1";
|
|
45833
46171
|
RECORD_SIZE = 4096;
|
|
45834
46172
|
MAX_PAYLOAD = RECORD_SIZE - 17;
|
|
@@ -45903,10 +46241,19 @@ var init_push = __esm({
|
|
|
45903
46241
|
return { rawPublic, rawPrivate };
|
|
45904
46242
|
}
|
|
45905
46243
|
async deliver(endpoint, config, keys, body) {
|
|
46244
|
+
try {
|
|
46245
|
+
await guardUrl(endpoint.toString(), this.options.allowHosts);
|
|
46246
|
+
} catch (error2) {
|
|
46247
|
+
if (error2 instanceof SsrfError) throw new PushError(error2.message);
|
|
46248
|
+
throw error2;
|
|
46249
|
+
}
|
|
45906
46250
|
let response;
|
|
45907
46251
|
try {
|
|
45908
46252
|
response = await fetch(endpoint, {
|
|
45909
46253
|
method: "POST",
|
|
46254
|
+
// A real push service answers the POST directly; a redirect from a push
|
|
46255
|
+
// endpoint is not followed to a private address (ADR-0084).
|
|
46256
|
+
redirect: "manual",
|
|
45910
46257
|
headers: {
|
|
45911
46258
|
Authorization: `vapid t=${vapidToken(endpoint.toString(), config.subject, keys.rawPrivate, keys.rawPublic)}, k=${config.publicKey}`,
|
|
45912
46259
|
"Content-Encoding": "aes128gcm",
|
|
@@ -47993,6 +48340,7 @@ var init_testClient = __esm({
|
|
|
47993
48340
|
reqHeaders["content-length"] = String(Buffer.byteLength(rawBody));
|
|
47994
48341
|
}
|
|
47995
48342
|
const socket = new Socket();
|
|
48343
|
+
Object.defineProperty(socket, "remoteAddress", { value: "127.0.0.1" });
|
|
47996
48344
|
const rawReq = new IncomingMessage2(socket);
|
|
47997
48345
|
rawReq.method = method.toUpperCase();
|
|
47998
48346
|
rawReq.url = path10;
|
|
@@ -48365,6 +48713,7 @@ var init_validator = __esm({
|
|
|
48365
48713
|
var src_exports3 = {};
|
|
48366
48714
|
__export(src_exports3, {
|
|
48367
48715
|
AI_TOOLS: () => AI_TOOLS,
|
|
48716
|
+
ALLOW_PRIVATE_ENV: () => ALLOW_PRIVATE_ENV,
|
|
48368
48717
|
APPLICATION_FORM: () => APPLICATION_FORM,
|
|
48369
48718
|
APPLICATION_JSON: () => APPLICATION_JSON,
|
|
48370
48719
|
APPLICATION_OCTET: () => APPLICATION_OCTET,
|
|
@@ -48472,6 +48821,7 @@ __export(src_exports3, {
|
|
|
48472
48821
|
Session: () => Session,
|
|
48473
48822
|
Sso: () => Sso,
|
|
48474
48823
|
SsoError: () => SsoError,
|
|
48824
|
+
SsrfError: () => SsrfError,
|
|
48475
48825
|
TAKEOVER_KILLED: () => TAKEOVER_KILLED,
|
|
48476
48826
|
TAKEOVER_NOTHING: () => TAKEOVER_NOTHING,
|
|
48477
48827
|
TAKEOVER_REFUSALS: () => TAKEOVER_REFUSALS,
|
|
@@ -48505,6 +48855,7 @@ __export(src_exports3, {
|
|
|
48505
48855
|
addHtmlHelpers: () => addHtmlHelpers,
|
|
48506
48856
|
algorithmAvailable: () => algorithmAvailable,
|
|
48507
48857
|
allEnv: () => allEnv,
|
|
48858
|
+
allowPrivateRequests: () => allowPrivateRequests,
|
|
48508
48859
|
any: () => any,
|
|
48509
48860
|
attachCsrfFromEnv: () => attachCsrfFromEnv,
|
|
48510
48861
|
authMiddleware: () => authMiddleware,
|
|
@@ -48570,6 +48921,7 @@ __export(src_exports3, {
|
|
|
48570
48921
|
graphqlAutoSchemaEnabled: () => graphqlAutoSchemaEnabled,
|
|
48571
48922
|
graphqlEndpoint: () => graphqlEndpoint,
|
|
48572
48923
|
graphqlMaxDepth: () => graphqlMaxDepth,
|
|
48924
|
+
guardUrl: () => guardUrl,
|
|
48573
48925
|
handle: () => handle,
|
|
48574
48926
|
handleFeedbackTurn: () => handleFeedbackTurn,
|
|
48575
48927
|
handleFeedbackWidgetJs: () => handleFeedbackWidgetJs,
|
|
@@ -48583,6 +48935,7 @@ __export(src_exports3, {
|
|
|
48583
48935
|
installAll: () => installAll,
|
|
48584
48936
|
installSelected: () => installSelected,
|
|
48585
48937
|
isBannerSuppressed: () => isBannerSuppressed,
|
|
48938
|
+
isBlockedAddress: () => isBlockedAddress,
|
|
48586
48939
|
isDebugMode: () => isDebugMode,
|
|
48587
48940
|
isDev: () => isDev,
|
|
48588
48941
|
isInstalled: () => isInstalled,
|
|
@@ -48702,6 +49055,7 @@ var init_src3 = __esm({
|
|
|
48702
49055
|
init_service();
|
|
48703
49056
|
init_cache();
|
|
48704
49057
|
init_api();
|
|
49058
|
+
init_ssrf();
|
|
48705
49059
|
init_context();
|
|
48706
49060
|
init_events();
|
|
48707
49061
|
init_devAdmin();
|