tina4-nodejs 3.13.137 → 3.13.139
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 +33 -3
- package/COMMERCIAL-LICENSE.md +8 -0
- package/LICENSE +373 -0
- package/NOTICE +11 -0
- package/README.md +24 -2
- package/package.json +44 -6
- package/packages/cli/dist/bin.js +19979 -18102
- package/packages/cli/src/bin.ts +8 -0
- package/packages/cli/src/commands/build.ts +8 -0
- package/packages/cli/src/commands/generate.ts +8 -0
- package/packages/cli/src/commands/init.ts +8 -0
- package/packages/cli/src/commands/lint.ts +8 -0
- package/packages/cli/src/commands/migrate.ts +8 -0
- package/packages/cli/src/commands/migrateCreate.ts +8 -0
- package/packages/cli/src/commands/migrateRollback.ts +8 -0
- package/packages/cli/src/commands/migrateStatus.ts +8 -0
- package/packages/cli/src/commands/queue.ts +8 -0
- package/packages/cli/src/commands/routes.ts +8 -0
- package/packages/cli/src/commands/seed.ts +8 -0
- package/packages/cli/src/commands/serve.ts +11 -0
- package/packages/cli/src/commands/test.ts +8 -0
- package/packages/core/dist/index.js +19976 -18097
- package/packages/core/gallery/auth/src/routes/api/gallery/auth/login/post.ts +8 -0
- package/packages/core/gallery/auth/src/routes/api/gallery/auth/verify/get.ts +8 -0
- package/packages/core/gallery/auth/src/routes/gallery/auth/get.ts +8 -0
- package/packages/core/gallery/database/src/routes/api/gallery/db/notes/get.ts +8 -0
- package/packages/core/gallery/database/src/routes/api/gallery/db/notes/post.ts +8 -0
- package/packages/core/gallery/database/src/routes/api/gallery/db/tables/get.ts +8 -0
- package/packages/core/gallery/error-overlay/src/routes/api/gallery/crash/get.ts +8 -0
- package/packages/core/gallery/orm/src/routes/api/gallery/products/get.ts +8 -0
- package/packages/core/gallery/orm/src/routes/api/gallery/products/post.ts +8 -0
- package/packages/core/gallery/queue/src/lib/queueDb.ts +8 -0
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/consume/post.ts +8 -0
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/fail/post.ts +8 -0
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/produce/post.ts +8 -0
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/retry/post.ts +8 -0
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/status/get.ts +8 -0
- package/packages/core/gallery/queue/src/routes/gallery/queue/get.ts +8 -0
- package/packages/core/gallery/rest-api/src/routes/api/gallery/hello/get.ts +8 -0
- package/packages/core/gallery/rest-api/src/routes/api/gallery/hello/post.ts +8 -0
- package/packages/core/gallery/templates/src/routes/gallery/page/get.ts +8 -0
- package/packages/core/gallery/templates/src/templates/gallery_page.twig +8 -0
- package/packages/core/public/THIRD-PARTY-NOTICES.md +55 -0
- package/packages/core/public/bundled-component-provenance.json +48 -0
- package/packages/core/public/js/tina4js.min.js +13 -3
- package/packages/core/public/licenses/swagger-ui-3.10.0-original-notice.txt +11 -0
- package/packages/core/public/licenses/swagger-ui-5.17.14-NOTICE.txt +2 -0
- package/packages/core/public/licenses/swagger-ui-Apache-2.0.txt +202 -0
- package/packages/core/public/licenses/tina4-css-MIT.txt +21 -0
- package/packages/core/public/licenses/tina4-dev-admin-components.json +240 -0
- package/packages/core/public/licenses/tina4-dev-admin-third-party.txt +919 -0
- package/packages/core/public/licenses/tina4-js-MIT.txt +20 -0
- package/packages/core/src/__feedback/widget.js +8 -0
- package/packages/core/src/_missing.ts +8 -0
- package/packages/core/src/ai.ts +8 -0
- package/packages/core/src/aiClient.ts +8 -0
- package/packages/core/src/api.ts +48 -7
- package/packages/core/src/auth.ts +94 -14
- package/packages/core/src/authGate.ts +35 -20
- package/packages/core/src/background.ts +8 -0
- package/packages/core/src/cache.ts +43 -4
- package/packages/core/src/constants.ts +8 -0
- package/packages/core/src/container.ts +8 -0
- package/packages/core/src/context/chunker.ts +8 -0
- package/packages/core/src/context/index.ts +8 -0
- package/packages/core/src/devAdmin.ts +185 -59
- package/packages/core/src/devMailbox.ts +8 -0
- package/packages/core/src/dispatchPipeline.ts +8 -0
- package/packages/core/src/docs.ts +8 -0
- package/packages/core/src/docsAutoDiscovery.ts +8 -0
- package/packages/core/src/docsParser.ts +8 -0
- package/packages/core/src/docsScanner.ts +44 -0
- package/packages/core/src/docsSignatures.ts +8 -0
- package/packages/core/src/dotenv.ts +8 -0
- package/packages/core/src/env.ts +8 -0
- package/packages/core/src/errorOverlay.ts +8 -0
- package/packages/core/src/events.ts +8 -0
- package/packages/core/src/fakeData.ts +8 -0
- package/packages/core/src/feedback.ts +8 -0
- package/packages/core/src/graphql.ts +108 -5
- package/packages/core/src/health.ts +8 -0
- package/packages/core/src/htmlElement.ts +8 -0
- package/packages/core/src/i18n.ts +8 -0
- package/packages/core/src/index.ts +9 -0
- package/packages/core/src/job.ts +16 -1
- package/packages/core/src/logger.ts +8 -0
- package/packages/core/src/mcp.test.ts +8 -0
- package/packages/core/src/mcp.ts +11 -1
- package/packages/core/src/messenger.ts +143 -86
- package/packages/core/src/metrics.ts +13 -0
- package/packages/core/src/middleware.ts +1181 -1097
- package/packages/core/src/mqtt.ts +8 -0
- package/packages/core/src/mqttMessage.ts +8 -0
- package/packages/core/src/optionalPackage.ts +48 -0
- package/packages/core/src/plan.ts +8 -0
- package/packages/core/src/portTakeover.ts +8 -0
- package/packages/core/src/projectIndex.ts +8 -0
- package/packages/core/src/projectIndexExtractors.ts +8 -0
- package/packages/core/src/projectIndexStorage.ts +8 -0
- package/packages/core/src/push.ts +24 -0
- package/packages/core/src/queue.ts +23 -2
- package/packages/core/src/queueBackends/kafkaBackend.ts +8 -0
- package/packages/core/src/queueBackends/liteBackend.ts +23 -0
- package/packages/core/src/queueBackends/mongoBackend.ts +61 -14
- package/packages/core/src/queueBackends/rabbitmqBackend.ts +8 -0
- package/packages/core/src/rateLimiter.ts +8 -0
- package/packages/core/src/request.ts +59 -10
- package/packages/core/src/response.ts +79 -5
- package/packages/core/src/routeDiscovery.ts +8 -0
- package/packages/core/src/router.ts +8 -0
- package/packages/core/src/server.ts +79 -25
- package/packages/core/src/service.ts +8 -0
- package/packages/core/src/session.ts +8 -0
- package/packages/core/src/sessionHandlers/childError.ts +8 -0
- package/packages/core/src/sessionHandlers/databaseHandler.ts +68 -3
- package/packages/core/src/sessionHandlers/memcachedHandler.ts +8 -0
- package/packages/core/src/sessionHandlers/mongoClient.ts +8 -0
- package/packages/core/src/sessionHandlers/mongoHandler.ts +8 -0
- package/packages/core/src/sessionHandlers/respClient.ts +8 -0
- package/packages/core/src/sessionHandlers/sqlClient.ts +8 -0
- package/packages/core/src/sessionHandlers/syncBridge.ts +8 -0
- package/packages/core/src/sessionHandlers/syncSocket.ts +8 -0
- package/packages/core/src/sessionHandlers/valkeyHandler.ts +8 -0
- package/packages/core/src/sso.ts +20 -4
- package/packages/core/src/ssoIdentity.ts +19 -0
- package/packages/core/src/ssrf.ts +221 -0
- package/packages/core/src/static.ts +8 -0
- package/packages/core/src/test.ts +8 -0
- package/packages/core/src/testClient.ts +11 -0
- package/packages/core/src/testing.ts +8 -0
- package/packages/core/src/transport.ts +369 -0
- package/packages/core/src/trustedProxy.ts +8 -0
- package/packages/core/src/types.ts +13 -0
- package/packages/core/src/validator.ts +8 -0
- package/packages/core/src/version.ts +8 -0
- package/packages/core/src/websocket.ts +12 -2
- package/packages/core/src/websocketBackplane.ts +49 -32
- package/packages/core/src/websocketConnection.ts +8 -0
- package/packages/core/src/wsdl.ts +197 -115
- package/packages/core/templates/errors/302.twig +8 -0
- package/packages/core/templates/errors/401.twig +8 -0
- package/packages/core/templates/errors/403.twig +8 -0
- package/packages/core/templates/errors/404.twig +8 -0
- package/packages/core/templates/errors/500.twig +8 -0
- package/packages/core/templates/errors/502.twig +8 -0
- package/packages/core/templates/errors/503.twig +8 -0
- package/packages/core/templates/errors/base.twig +8 -0
- package/packages/frond/dist/index.js +157 -17
- package/packages/frond/src/engine.ts +186 -17
- package/packages/frond/src/index.ts +8 -0
- package/packages/orm/dist/index.js +8641 -6765
- package/packages/orm/src/adapters/firebird.ts +50 -3
- package/packages/orm/src/adapters/mongodb.ts +8 -0
- package/packages/orm/src/adapters/mssql.ts +29 -7
- package/packages/orm/src/adapters/mysql.ts +17 -1
- package/packages/orm/src/adapters/odbc.ts +11 -0
- package/packages/orm/src/adapters/pg-types.d.ts +8 -0
- package/packages/orm/src/adapters/postgres.ts +34 -8
- package/packages/orm/src/adapters/sqlDialect.ts +57 -0
- package/packages/orm/src/adapters/sqlite.ts +26 -0
- package/packages/orm/src/autoCrud.ts +78 -36
- package/packages/orm/src/baseModel.ts +61 -29
- package/packages/orm/src/cachedDatabase.ts +67 -1
- package/packages/orm/src/connectTimeout.ts +8 -0
- package/packages/orm/src/database.ts +649 -401
- package/packages/orm/src/databaseResult.ts +8 -0
- package/packages/orm/src/databaseUrl.ts +8 -0
- package/packages/orm/src/docstore.ts +23 -1
- package/packages/orm/src/fakeData.ts +8 -0
- package/packages/orm/src/graph/adapters/arango.ts +8 -0
- package/packages/orm/src/graph/adapters/bolt.ts +8 -0
- package/packages/orm/src/graph/adapters/ultipa.ts +8 -0
- package/packages/orm/src/graph/connectTimeout.ts +8 -0
- package/packages/orm/src/graph/errors.ts +8 -0
- package/packages/orm/src/graph/graphAdapter.ts +8 -0
- package/packages/orm/src/graph/graphDatabase.ts +8 -0
- package/packages/orm/src/graph/graphUrl.ts +10 -1
- package/packages/orm/src/graph/shapes.ts +8 -0
- package/packages/orm/src/index.ts +147 -139
- package/packages/orm/src/migration.ts +8 -0
- package/packages/orm/src/model.ts +8 -0
- package/packages/orm/src/modelCollection.ts +8 -0
- package/packages/orm/src/point.ts +8 -0
- package/packages/orm/src/query.ts +149 -33
- package/packages/orm/src/queryBuilder.ts +8 -0
- package/packages/orm/src/realtime/index.ts +8 -0
- package/packages/orm/src/realtime/models/attachment.ts +8 -0
- package/packages/orm/src/realtime/models/channel.ts +8 -0
- package/packages/orm/src/realtime/models/channelMember.ts +8 -0
- package/packages/orm/src/realtime/models/message.ts +8 -0
- package/packages/orm/src/realtime/models/workspace.ts +8 -0
- package/packages/orm/src/realtime/realtime.ts +8 -0
- package/packages/orm/src/realtime/storage.ts +43 -12
- package/packages/orm/src/seeder.ts +8 -0
- package/packages/orm/src/seederTable.ts +8 -0
- package/packages/orm/src/seederTypes.ts +8 -0
- package/packages/orm/src/sqlTranslator.ts +185 -8
- package/packages/orm/src/types.ts +15 -0
- package/packages/orm/src/validation.ts +8 -0
- package/packages/swagger/src/generator.ts +8 -0
- package/packages/swagger/src/index.ts +8 -0
- package/packages/swagger/src/ui.ts +8 -0
- package/types/core/src/api.d.ts +6 -0
- package/types/core/src/auth.d.ts +30 -0
- package/types/core/src/background.d.ts +0 -16
- package/types/core/src/cache.d.ts +6 -0
- package/types/core/src/devAdmin.d.ts +18 -12
- package/types/core/src/docs.d.ts +0 -17
- package/types/core/src/errorOverlay.d.ts +0 -24
- package/types/core/src/events.d.ts +0 -12
- package/types/core/src/feedback.d.ts +0 -23
- package/types/core/src/graphql.d.ts +25 -21
- package/types/core/src/htmlElement.d.ts +0 -15
- package/types/core/src/index.d.ts +1 -0
- package/types/core/src/job.d.ts +1 -0
- package/types/core/src/messenger.d.ts +12 -1
- package/types/core/src/metrics.d.ts +2 -0
- package/types/core/src/middleware.d.ts +45 -1
- package/types/core/src/mqtt.d.ts +0 -33
- package/types/core/src/optionalPackage.d.ts +25 -0
- package/types/core/src/plan.d.ts +0 -10
- package/types/core/src/projectIndex.d.ts +0 -10
- 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 +10 -1
- package/types/core/src/request.d.ts +12 -0
- package/types/core/src/response.d.ts +13 -0
- package/types/core/src/server.d.ts +19 -0
- package/types/core/src/sessionHandlers/childError.d.ts +0 -15
- 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/core/src/transport.d.ts +28 -0
- package/types/core/src/trustedProxy.d.ts +0 -16
- package/types/core/src/types.d.ts +5 -0
- package/types/core/src/websocketBackplane.d.ts +1 -0
- package/types/core/src/wsdl.d.ts +8 -22
- package/types/frond/src/engine.d.ts +7 -1
- package/types/orm/src/adapters/firebird.d.ts +12 -0
- package/types/orm/src/adapters/mssql.d.ts +11 -0
- package/types/orm/src/adapters/mysql.d.ts +2 -0
- package/types/orm/src/adapters/postgres.d.ts +11 -0
- package/types/orm/src/adapters/sqlDialect.d.ts +25 -0
- package/types/orm/src/adapters/sqlite.d.ts +7 -0
- package/types/orm/src/baseModel.d.ts +5 -0
- package/types/orm/src/cachedDatabase.d.ts +27 -0
- package/types/orm/src/database.d.ts +46 -7
- package/types/orm/src/graph/graphUrl.d.ts +0 -8
- package/types/orm/src/index.d.ts +1 -1
- package/types/orm/src/query.d.ts +48 -2
- package/types/orm/src/realtime/storage.d.ts +2 -1
- package/types/orm/src/sqlTranslator.d.ts +70 -18
- package/types/orm/src/types.d.ts +6 -0
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
/**
|
|
2
10
|
* Tina4 SQL Translation — Cross-engine SQL translator.
|
|
3
11
|
*
|
|
@@ -259,11 +267,11 @@ export class SQLTranslator {
|
|
|
259
267
|
return sql
|
|
260
268
|
.replace(/\bBIGINT\s+PRIMARY\s+KEY\s+AUTOINCREMENT\b/gi, "BIGSERIAL PRIMARY KEY")
|
|
261
269
|
.replace(/\bINTEGER\s+PRIMARY\s+KEY\s+AUTOINCREMENT\b/gi, "SERIAL PRIMARY KEY")
|
|
262
|
-
.
|
|
270
|
+
.split(/\bAUTOINCREMENT\b/gi).map((part, index, parts) => index < parts.length - 1 ? part.trimEnd() : part).join("");
|
|
263
271
|
case "mssql":
|
|
264
272
|
return sql.replace(/AUTOINCREMENT/gi, "IDENTITY(1,1)");
|
|
265
273
|
case "firebird":
|
|
266
|
-
return sql.
|
|
274
|
+
return sql.split(/\bAUTOINCREMENT\b/gi).map((part, index, parts) => index < parts.length - 1 ? part.trimEnd() : part).join("");
|
|
267
275
|
default:
|
|
268
276
|
return sql;
|
|
269
277
|
}
|
|
@@ -322,18 +330,105 @@ export class SQLTranslator {
|
|
|
322
330
|
*/
|
|
323
331
|
static placeholderStyle(sql: string, style: string): string {
|
|
324
332
|
if (style === "%s") {
|
|
325
|
-
|
|
333
|
+
// A pyformat driver (psycopg, PyMySQL) reads EVERY `%` as the start of a
|
|
334
|
+
// placeholder once parameters are passed, literals included, so a literal
|
|
335
|
+
// `%` is doubled before the real `?` markers become `%s` (tina4-python#138).
|
|
336
|
+
const doubled = sql.replace(/%/g, "%%");
|
|
337
|
+
return SQLTranslator.replacePlaceholders(doubled, () => "%s");
|
|
326
338
|
}
|
|
327
339
|
if (style.startsWith(":")) {
|
|
328
|
-
|
|
329
|
-
return sql.replace(/\?/g, () => {
|
|
330
|
-
count++;
|
|
331
|
-
return `:${count}`;
|
|
332
|
-
});
|
|
340
|
+
return SQLTranslator.replacePlaceholders(sql, (i) => `:${i + 1}`);
|
|
333
341
|
}
|
|
334
342
|
return sql;
|
|
335
343
|
}
|
|
336
344
|
|
|
345
|
+
/**
|
|
346
|
+
* Offsets of the REAL `?` placeholders in a statement: every `?` that is not
|
|
347
|
+
* inside a string literal ('...', Postgres E'...', dollar-quoted $$...$$ /
|
|
348
|
+
* $tag$...$tag$), a quoted identifier ("..." or `...`), or a comment (-- to
|
|
349
|
+
* end of line, /* ... *\/). A plain text replace turned the `?` in
|
|
350
|
+
* `SELECT 'why?' AS v, ? AS n` into a placeholder and shifted every binding
|
|
351
|
+
* after it (tina4-python#138).
|
|
352
|
+
*
|
|
353
|
+
* @param sql Raw SQL, exactly as the caller wrote it.
|
|
354
|
+
* @param options.backslashEscapes Treat `\` as an escape inside '...' and
|
|
355
|
+
* "..." as well (MySQL's default string syntax). E'...' always does.
|
|
356
|
+
*/
|
|
357
|
+
static placeholderPositions(sql: string, options: { backslashEscapes?: boolean } = {}): number[] {
|
|
358
|
+
const positions: number[] = [];
|
|
359
|
+
const n = sql.length;
|
|
360
|
+
const isIdent = (ch: string | undefined): boolean => !!ch && /[A-Za-z0-9_]/.test(ch);
|
|
361
|
+
let i = 0;
|
|
362
|
+
while (i < n) {
|
|
363
|
+
const c = sql[i];
|
|
364
|
+
const next = sql[i + 1];
|
|
365
|
+
|
|
366
|
+
if (c === "'" || c === '"' || c === "`") {
|
|
367
|
+
// E'...' (Postgres escape string) honours backslash escapes; so does
|
|
368
|
+
// every quoted string when the engine does (MySQL).
|
|
369
|
+
const prev = sql[i - 1];
|
|
370
|
+
const escapeString = c === "'" && (prev === "E" || prev === "e") && !isIdent(sql[i - 2]);
|
|
371
|
+
const backslash = c !== "`" && (escapeString || options.backslashEscapes === true);
|
|
372
|
+
i++;
|
|
373
|
+
while (i < n) {
|
|
374
|
+
if (backslash && sql[i] === "\\") { i += 2; continue; }
|
|
375
|
+
if (sql[i] === c) {
|
|
376
|
+
if (sql[i + 1] === c) { i += 2; continue; }
|
|
377
|
+
i++;
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
i++;
|
|
381
|
+
}
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (c === "-" && next === "-") {
|
|
386
|
+
while (i < n && sql[i] !== "\n") i++;
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (c === "/" && next === "*") {
|
|
391
|
+
const end = sql.indexOf("*/", i + 2);
|
|
392
|
+
i = end === -1 ? n : end + 2;
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (c === "$" && !isIdent(sql[i - 1])) {
|
|
397
|
+
const tag = sql.slice(i).match(/^\$([A-Za-z_][A-Za-z0-9_]*)?\$/);
|
|
398
|
+
if (tag) {
|
|
399
|
+
const end = sql.indexOf(tag[0], i + tag[0].length);
|
|
400
|
+
i = end === -1 ? n : end + tag[0].length;
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (c === "?") positions.push(i);
|
|
406
|
+
i++;
|
|
407
|
+
}
|
|
408
|
+
return positions;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Replace each REAL `?` placeholder (see placeholderPositions) with
|
|
413
|
+
* `marker(index)`, index counting from 0. Literals, quoted identifiers and
|
|
414
|
+
* comments are copied through untouched.
|
|
415
|
+
*/
|
|
416
|
+
static replacePlaceholders(
|
|
417
|
+
sql: string,
|
|
418
|
+
marker: (index: number) => string,
|
|
419
|
+
options: { backslashEscapes?: boolean } = {},
|
|
420
|
+
): string {
|
|
421
|
+
const positions = SQLTranslator.placeholderPositions(sql, options);
|
|
422
|
+
if (positions.length === 0) return sql;
|
|
423
|
+
let out = "";
|
|
424
|
+
let last = 0;
|
|
425
|
+
positions.forEach((pos, index) => {
|
|
426
|
+
out += sql.slice(last, pos) + marker(index);
|
|
427
|
+
last = pos + 1;
|
|
428
|
+
});
|
|
429
|
+
return out + sql.slice(last);
|
|
430
|
+
}
|
|
431
|
+
|
|
337
432
|
/**
|
|
338
433
|
* Detect and strip RETURNING clause from INSERT/UPDATE statements.
|
|
339
434
|
* Returns the cleaned SQL and the list of RETURNING columns.
|
|
@@ -607,6 +702,88 @@ export class SQLTranslator {
|
|
|
607
702
|
return re.test(SQLTranslator.scrubSqlText(sql));
|
|
608
703
|
}
|
|
609
704
|
|
|
705
|
+
/**
|
|
706
|
+
* Strip a trailing TOP-LEVEL `ORDER BY` so the statement can be wrapped in
|
|
707
|
+
* `SELECT COUNT(*) FROM (<sql>) AS _count_query` for the row-count probe.
|
|
708
|
+
*
|
|
709
|
+
* SQL Server rejects an ORDER BY inside a derived table unless it carries
|
|
710
|
+
* TOP/OFFSET/FETCH (error 1033), so the probe failed - and the total fell
|
|
711
|
+
* back to the page length - for any read ending in ORDER BY. ORDER BY cannot
|
|
712
|
+
* change a COUNT, so dropping it for the probe ONLY is safe; the paginated
|
|
713
|
+
* query keeps it. An ORDER BY nested in a subquery, or one already legalised
|
|
714
|
+
* by a following OFFSET/FETCH/FOR, is left intact. Positions are found on the
|
|
715
|
+
* scrubbed text, so an `ORDER BY` inside a literal or comment is ignored.
|
|
716
|
+
* Parity with Python `_strip_trailing_order_by` / PHP `stripTrailingOrderBy`.
|
|
717
|
+
*
|
|
718
|
+
* @param sql The read statement the probe is about to wrap.
|
|
719
|
+
*/
|
|
720
|
+
static stripTrailingOrderBy(sql: string): string {
|
|
721
|
+
const scrubbed = SQLTranslator.scrubSqlText(sql ?? "");
|
|
722
|
+
const re = /\bORDER\s+BY\b/gi;
|
|
723
|
+
let lastTopLevel = -1;
|
|
724
|
+
for (let m = re.exec(scrubbed); m; m = re.exec(scrubbed)) {
|
|
725
|
+
const before = scrubbed.slice(0, m.index);
|
|
726
|
+
const balancedBefore = (before.match(/\(/g)?.length ?? 0) === (before.match(/\)/g)?.length ?? 0);
|
|
727
|
+
let depth = 0;
|
|
728
|
+
let balancedAfter = true;
|
|
729
|
+
for (const ch of scrubbed.slice(m.index)) {
|
|
730
|
+
if (ch === "(") depth++;
|
|
731
|
+
else if (ch === ")" && --depth < 0) { balancedAfter = false; break; }
|
|
732
|
+
}
|
|
733
|
+
if (balancedBefore && balancedAfter) lastTopLevel = m.index;
|
|
734
|
+
}
|
|
735
|
+
if (lastTopLevel === -1) return sql;
|
|
736
|
+
if (/\b(?:OFFSET|FETCH|FOR)\b/i.test(scrubbed.slice(lastTopLevel))) return sql;
|
|
737
|
+
return sql.slice(0, lastTopLevel).trimEnd();
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* True when the statement CHANGES data, even though it may return rows.
|
|
742
|
+
*
|
|
743
|
+
* fetch()/fetchOne() are the natural way to run a write that RETURNS rows
|
|
744
|
+
* (`INSERT ... RETURNING id`, SQL Server's `OUTPUT inserted.id`), so they
|
|
745
|
+
* cannot treat every statement as a read (tina4-python#133). A write is one
|
|
746
|
+
* whose first word is a DML verb, or a `WITH` whose body holds one (a
|
|
747
|
+
* data-modifying CTE ends in SELECT). The shared cross-framework contract:
|
|
748
|
+
* a write through fetch/fetchOne runs exactly once, with no COUNT probe and
|
|
749
|
+
* no LIMIT/OFFSET/ROWS/TOP pagination, is never cached, and commits like
|
|
750
|
+
* execute(). Python's `_is_write_statement` answers identically.
|
|
751
|
+
*
|
|
752
|
+
* Literals and comments are scrubbed first, so `WHERE note = 'DELETE'` stays
|
|
753
|
+
* a read. Erring towards "write" is the safe direction: a read misread as a
|
|
754
|
+
* write only loses its pagination and cache for that one call.
|
|
755
|
+
*
|
|
756
|
+
* @param sql Raw SQL, exactly as the caller wrote it.
|
|
757
|
+
*/
|
|
758
|
+
static isWriteStatement(sql: string): boolean {
|
|
759
|
+
const scrubbed = SQLTranslator.scrubSqlText(sql ?? "").replace(/^[\s(]+/, "");
|
|
760
|
+
const verb = (scrubbed.match(/^[A-Za-z]+/)?.[0] ?? "").toUpperCase();
|
|
761
|
+
if (["INSERT", "UPDATE", "DELETE", "MERGE", "UPSERT", "REPLACE"].includes(verb)) return true;
|
|
762
|
+
if (verb === "WITH") return /\b(INSERT|UPDATE|DELETE|MERGE)\b/i.test(scrubbed);
|
|
763
|
+
return false;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* Which kind of rows a statement produces, for execute() (cross-framework
|
|
768
|
+
* contract): "read" (SELECT, WITH ... SELECT, VALUES, SHOW, EXPLAIN,
|
|
769
|
+
* DESCRIBE), "returning" (a write with RETURNING, or SQL Server's OUTPUT
|
|
770
|
+
* inserted./deleted.), "procedure" (CALL / EXEC / EXECUTE, which may return a
|
|
771
|
+
* result set), or null for a statement that produces none. A data-modifying
|
|
772
|
+
* CTE (WITH ... INSERT ... SELECT) produces rows too, so it is "read" here and
|
|
773
|
+
* a write for isWriteStatement(). Literals and comments are scrubbed first.
|
|
774
|
+
*
|
|
775
|
+
* @param sql Raw SQL, exactly as the caller wrote it.
|
|
776
|
+
*/
|
|
777
|
+
static rowsKind(sql: string): "read" | "returning" | "procedure" | null {
|
|
778
|
+
const scrubbed = SQLTranslator.scrubSqlText(sql ?? "").replace(/^[\s(]+/, "");
|
|
779
|
+
const verb = (scrubbed.match(/^[A-Za-z]+/)?.[0] ?? "").toUpperCase();
|
|
780
|
+
if (["SELECT", "WITH", "VALUES", "SHOW", "EXPLAIN", "DESCRIBE"].includes(verb)) return "read";
|
|
781
|
+
if (["CALL", "EXEC", "EXECUTE"].includes(verb)) return "procedure";
|
|
782
|
+
if (SQLTranslator.isWriteStatement(sql)
|
|
783
|
+
&& /\bRETURNING\b|\bOUTPUT\s+(INSERTED|DELETED)\./i.test(scrubbed)) return "returning";
|
|
784
|
+
return null;
|
|
785
|
+
}
|
|
786
|
+
|
|
610
787
|
/**
|
|
611
788
|
* Append `LIMIT`/`OFFSET` to a statement unless it already carries its own.
|
|
612
789
|
*
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
export type FieldType = "string" | "integer" | "number" | "numeric" | "decimal" | "boolean" | "datetime" | "text" | "json" | "foreignKey" | "point";
|
|
2
10
|
|
|
3
11
|
export interface FieldDefinition {
|
|
@@ -119,6 +127,13 @@ export interface DatabaseAdapter {
|
|
|
119
127
|
/** Return the canonical, credential-free engine name ("sqlite", "postgres", ...). */
|
|
120
128
|
getDatabaseType(): string;
|
|
121
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Quote a table/column name the ORM emits, in this engine's dialect
|
|
132
|
+
* (Python master: `quote_identifier`). Optional: an adapter without it gets
|
|
133
|
+
* the ANSI `"name"` default through `quoteIdentifier(adapter, name)`.
|
|
134
|
+
*/
|
|
135
|
+
quoteIdentifier?(name: string): string;
|
|
136
|
+
|
|
122
137
|
/** Execute a statement (INSERT, UPDATE, DELETE, DDL). */
|
|
123
138
|
execute(sql: string, params?: unknown[]): unknown;
|
|
124
139
|
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
import type { FieldDefinition } from "./types.js";
|
|
2
10
|
|
|
3
11
|
export interface ValidationError {
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
import type { RouteDefinition } from "../../core/src/index.js";
|
|
2
10
|
import type { ModelDefinition, FieldDefinition } from "../../orm/src/index.js";
|
|
3
11
|
|
|
@@ -1,2 +1,10 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
export { generate, addSecurityScheme, addSchema, resetRegistry } from "./generator.js";
|
|
2
10
|
export { createSwaggerRoutes, swaggerEnabled } from "./ui.js";
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
import type { Tina4Request, Tina4Response, RouteDefinition } from "../../core/src/index.js";
|
|
2
10
|
|
|
3
11
|
// The UI assets load from a CDN by default (a documented architecture decision —
|
package/types/core/src/api.d.ts
CHANGED
|
@@ -121,6 +121,11 @@ export interface ApiOptions {
|
|
|
121
121
|
* persisted; scoped to this instance.
|
|
122
122
|
*/
|
|
123
123
|
cookies?: boolean;
|
|
124
|
+
/**
|
|
125
|
+
* SSRF guard (ADR-0084): an explicit allow-list of hosts / host:port / CIDRs
|
|
126
|
+
* that bypass the private-address refusal even with the opt-out off.
|
|
127
|
+
*/
|
|
128
|
+
allowHosts?: string[];
|
|
124
129
|
}
|
|
125
130
|
/**
|
|
126
131
|
* Split an async byte iterable into UTF-8 lines. Handles LF and CRLF; a
|
|
@@ -155,6 +160,7 @@ export declare class Api {
|
|
|
155
160
|
private transportFn?;
|
|
156
161
|
private cookiesEnabled;
|
|
157
162
|
private cookies;
|
|
163
|
+
private allowHosts;
|
|
158
164
|
/**
|
|
159
165
|
* Construct an Api client.
|
|
160
166
|
*
|
package/types/core/src/auth.d.ts
CHANGED
|
@@ -1,4 +1,34 @@
|
|
|
1
1
|
import type { Middleware } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Token purposes that are never an identity (ADR-0079 s1). Frond's formToken()
|
|
4
|
+
* signs a JWT with the same TINA4_SECRET as an auth token, marked
|
|
5
|
+
* `type: "form"`. A form token proves where a write came from, never who the
|
|
6
|
+
* caller is, so every identity gate refuses these purposes. validToken itself
|
|
7
|
+
* stays purpose-neutral: the CSRF middleware needs it for form tokens.
|
|
8
|
+
*/
|
|
9
|
+
export declare const NON_IDENTITY_TOKEN_TYPES: readonly string[];
|
|
10
|
+
/**
|
|
11
|
+
* Minimum HMAC key length in bytes (ADR-0079 s2): the HS256 output size, RFC
|
|
12
|
+
* 7518 s3.2. A blank key is the shortest case - anyone can reproduce it.
|
|
13
|
+
*/
|
|
14
|
+
export declare const MIN_SECRET_BYTES = 32;
|
|
15
|
+
/**
|
|
16
|
+
* True when a VERIFIED payload may stand for a caller's identity. A payload
|
|
17
|
+
* whose `type` is a reserved non-identity purpose (a Frond form token) is
|
|
18
|
+
* refused; application payloads are otherwise untouched.
|
|
19
|
+
*/
|
|
20
|
+
export declare function isIdentityPayload(payload: Record<string, unknown> | null | undefined): payload is Record<string, unknown>;
|
|
21
|
+
/** The actionable error for a weak HMAC key, or null when the key is usable. */
|
|
22
|
+
export declare function insecureSecretMessage(secret: string | undefined | null): string | null;
|
|
23
|
+
/**
|
|
24
|
+
* Refuse to boot with a secret that makes tokens forgeable (ADR-0079 s2).
|
|
25
|
+
* Outside dev a blank TINA4_SECRET is refused; in any mode a set-but-short one
|
|
26
|
+
* is refused. Dev with a blank secret has already had one minted into
|
|
27
|
+
* .env.local by ensureDevSecret(). RS256 keys are PEMs and are not measured.
|
|
28
|
+
*
|
|
29
|
+
* @throws Error with the actionable message naming TINA4_SECRET.
|
|
30
|
+
*/
|
|
31
|
+
export declare function requireBootSecret(): void;
|
|
2
32
|
/**
|
|
3
33
|
* Ensure a usable TINA4_SECRET exists. Run ONCE at server boot, after env load
|
|
4
34
|
* and before auth is used. Mirrors Python's `ensure_dev_secret()`.
|
|
@@ -1,19 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Background tasks — periodic callbacks that run alongside the HTTP server.
|
|
3
|
-
*
|
|
4
|
-
* Mirrors Python's `tina4_python.core.server.background(fn, interval=1.0)`.
|
|
5
|
-
* Use this instead of `setInterval` directly, so timers integrate with the
|
|
6
|
-
* server lifecycle and clear cleanly on graceful shutdown (SIGTERM/SIGINT)
|
|
7
|
-
* or when `stopAllBackgroundTasks()` is called.
|
|
8
|
-
*
|
|
9
|
-
* import { background } from "@tina4/core";
|
|
10
|
-
*
|
|
11
|
-
* background(() => processQueue(), 2); // every 2 seconds
|
|
12
|
-
* background(async () => await healthCheck(), 30); // async also fine
|
|
13
|
-
*
|
|
14
|
-
* Errors thrown from a callback are caught and logged so a single failing
|
|
15
|
-
* task cannot bring down the rest of the timer wheel.
|
|
16
|
-
*/
|
|
17
1
|
/**
|
|
18
2
|
* Register a callback to run periodically alongside the HTTP server.
|
|
19
3
|
*
|
|
@@ -102,6 +102,12 @@ interface CacheBackend {
|
|
|
102
102
|
/** One-time async connect/probe. Resolves once the backend has decided
|
|
103
103
|
* availability; createBackend() awaits this before falling back to file. */
|
|
104
104
|
ready?(): Promise<void>;
|
|
105
|
+
/**
|
|
106
|
+
* When unavailable because the app has not installed the backend's driver
|
|
107
|
+
* (an optional peer, ADR-0067): the actionable install message, appended to
|
|
108
|
+
* the fallback warning. null for every other cause.
|
|
109
|
+
*/
|
|
110
|
+
missingDriver?(): string | null;
|
|
105
111
|
}
|
|
106
112
|
/** Public shape of a unified cache backend (for cross-package reuse). */
|
|
107
113
|
export type { CacheBackend };
|
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tina4 Dev Admin — Built-in development dashboard, zero dependencies.
|
|
3
|
-
*
|
|
4
|
-
* Auto-registered admin panel for development mode.
|
|
5
|
-
* Provides API endpoints and a single-page UI at /__dev/ for:
|
|
6
|
-
* - Route inspector (all registered routes, methods)
|
|
7
|
-
* - Message log (tracked debug messages)
|
|
8
|
-
* - Request inspector (captured HTTP requests)
|
|
9
|
-
* - System info (Node.js version, V8, memory, uptime, platform)
|
|
10
|
-
*/
|
|
11
1
|
import type { Router } from "./router.js";
|
|
12
2
|
import type { RouteHandler, Tina4Request } from "./types.js";
|
|
13
3
|
/** Safe HTTP methods that never carry a state change — they skip the write gate. */
|
|
@@ -17,8 +7,8 @@ export declare const DEV_SAFE_METHODS: Set<string>;
|
|
|
17
7
|
*
|
|
18
8
|
* A drive-by CSRF is a BROWSER cross-origin request, and a modern browser always
|
|
19
9
|
* sends `Sec-Fetch-Site` (and any browser sends `Origin` on a cross-origin POST):
|
|
20
|
-
* - Sec-Fetch-Site present ->
|
|
21
|
-
* (
|
|
10
|
+
* - Sec-Fetch-Site present -> only same-origin (the dashboard itself) and
|
|
11
|
+
* none (a typed URL) pass; same-site is a different origin (ADR-0082).
|
|
22
12
|
* - else Origin present -> require its host to match the request Host.
|
|
23
13
|
* - else neither header -> not a browser cross-origin request (curl, a test
|
|
24
14
|
* client, a server-side caller); it cannot be a drive-by, so allow here and
|
|
@@ -38,6 +28,22 @@ export declare function devMutationDenial(req: Tina4Request): {
|
|
|
38
28
|
status: number;
|
|
39
29
|
error: string;
|
|
40
30
|
} | null;
|
|
31
|
+
/**
|
|
32
|
+
* True when a Host header names the loopback machine or the configured
|
|
33
|
+
* TINA4_HOST (ADR-0082), so a rebound DNS name cannot reach /__dev. A missing
|
|
34
|
+
* Host is not a browser request (every browser sends one); the peer gate
|
|
35
|
+
* governs it instead.
|
|
36
|
+
*/
|
|
37
|
+
export declare function devHostAllowed(hostHeader: string | undefined | null): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Return `{status, error}` to REFUSE any /__dev request (read or write), or
|
|
40
|
+
* `null` to allow (ADR-0082): Host allow-list, then same-origin, then the
|
|
41
|
+
* loopback peer (the MCP surface keeps its own 404 gate for the peer part).
|
|
42
|
+
*/
|
|
43
|
+
export declare function devRequestDenial(req: Tina4Request): {
|
|
44
|
+
status: number;
|
|
45
|
+
error: string;
|
|
46
|
+
} | null;
|
|
41
47
|
/**
|
|
42
48
|
* True when `rel` names secret material the file endpoints must never serve
|
|
43
49
|
* (DEVADMIN-DEC-03): `.env` / `.env.*` (the `.env.example` template is allowed),
|
package/types/core/src/docs.d.ts
CHANGED
|
@@ -1,20 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tina4 Live API RAG — `Docs` module.
|
|
3
|
-
*
|
|
4
|
-
* Walks framework packages (`@tina4/core`, `@tina4/orm`, `@tina4/swagger`,
|
|
5
|
-
* `@tina4/frond`) and the user project's `src/` tree via lightweight TS
|
|
6
|
-
* regex parsing (no AST — works on .ts files without importing them, so
|
|
7
|
-
* user-code import errors don't break reflection).
|
|
8
|
-
*
|
|
9
|
-
* Exposes ranked search, class/method specs, a flat index, MCP-style
|
|
10
|
-
* static mirrors, and a Markdown drift/sync helper. Zero new runtime
|
|
11
|
-
* dependencies — Node stdlib only.
|
|
12
|
-
*
|
|
13
|
-
* Spec: plan/v3/22-LIVE-API-RAG.md
|
|
14
|
-
*
|
|
15
|
-
* Method names follow Tina4 Node.js convention (camelCase) — see PHP
|
|
16
|
-
* `Tina4\Docs` and Python `tina4_python.docs.Docs` for parity references.
|
|
17
|
-
*/
|
|
18
1
|
export interface DocsHit {
|
|
19
2
|
fqn: string;
|
|
20
3
|
kind: "class" | "method" | "function" | "property";
|
|
@@ -1,27 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tina4 Debug — Rich error overlay for development mode.
|
|
3
|
-
*
|
|
4
|
-
* Renders a rich HTML error page (exception type + message, the full stack with a
|
|
5
|
-
* seven-line source window per frame, request details, environment) when an unhandled
|
|
6
|
-
* exception reaches the server dispatch in development.
|
|
7
|
-
*
|
|
8
|
-
* import { renderErrorOverlay, isDebugMode } from "./errorOverlay.js";
|
|
9
|
-
*
|
|
10
|
-
* try {
|
|
11
|
-
* await handler(req, res);
|
|
12
|
-
* } catch (err) {
|
|
13
|
-
* if (isDebugMode()) res.html(renderErrorOverlay(err as Error, req), 500);
|
|
14
|
-
* }
|
|
15
|
-
*
|
|
16
|
-
* Dev-only: the caller gates this on isDebugMode() (TINA4_DEBUG). The production 500 is
|
|
17
|
-
* NOT rendered here — the server dispatch renders errors/500.twig with an empty
|
|
18
|
-
* error_message (CWE-209), so the exception detail stays in the server log only.
|
|
19
|
-
*
|
|
20
|
-
* Sensitive request fields (Authorization / Cookie / Set-Cookie headers and
|
|
21
|
-
* password-like body/param keys) are redacted even in the dev overlay, the frame count
|
|
22
|
-
* is capped, and the caller wraps this render in a guard, so a broken overlay or a
|
|
23
|
-
* recursive stack still yields a bounded, safe 500.
|
|
24
|
-
*/
|
|
25
1
|
/**
|
|
26
2
|
* Render a rich HTML error overlay.
|
|
27
3
|
*
|
|
@@ -1,15 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tina4 Events — Simple observer pattern for decoupled communication.
|
|
3
|
-
*
|
|
4
|
-
* Zero-dependency event system. Fire events, register listeners.
|
|
5
|
-
*
|
|
6
|
-
* Events.on("user.created", (user) => console.log(`Welcome ${user.name}!`));
|
|
7
|
-
* Events.emit("user.created", { name: "Alice", email: "alice@example.com" });
|
|
8
|
-
*
|
|
9
|
-
* One-time listeners:
|
|
10
|
-
*
|
|
11
|
-
* Events.once("app.ready", () => console.log("App started!"));
|
|
12
|
-
*/
|
|
13
1
|
export declare class Events {
|
|
14
2
|
/**
|
|
15
3
|
* Register a listener for an event.
|
|
@@ -1,26 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Customer feedback widget — Tier 4 port from tina4-python.
|
|
3
|
-
*
|
|
4
|
-
* End-users of a shipped Tina4 app give UX feedback via a floating bubble
|
|
5
|
-
* widget. Widget visibility + API are gated by TWO env flags:
|
|
6
|
-
*
|
|
7
|
-
* - TINA4_ENABLE_FEEDBACK master switch (explicit opt-in)
|
|
8
|
-
* - TINA4_FEEDBACK_WHITELIST comma-separated emails / user IDs
|
|
9
|
-
*
|
|
10
|
-
* Architecture (mirrors Python `tina4_python/dev_admin/__init__.py`
|
|
11
|
-
* lines 1440-1645):
|
|
12
|
-
*
|
|
13
|
-
* 1. Framework middleware injects <script src="/__feedback/widget.js">
|
|
14
|
-
* into HTML responses for whitelisted users only.
|
|
15
|
-
* 2. Widget POSTs to /__feedback/api/turn for each conversational turn.
|
|
16
|
-
* 3. That handler verifies whitelist + rate-limit, stamps the user
|
|
17
|
-
* identity server-side (client cannot fake `sender`), then forwards
|
|
18
|
-
* to the Rust agent's /feedback/intake.
|
|
19
|
-
*
|
|
20
|
-
* The widget is for END USERS of a shipped app — the /__dev paths get
|
|
21
|
-
* skipped so the dev admin's own chat bubble doesn't sit on top of the
|
|
22
|
-
* customer feedback bubble.
|
|
23
|
-
*/
|
|
24
1
|
import type { RouteHandler, Tina4Request } from "./types.js";
|
|
25
2
|
import type { Router } from "./router.js";
|
|
26
3
|
/**
|
|
@@ -1,24 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tina4 GraphQL — Zero-dependency GraphQL engine.
|
|
3
|
-
*
|
|
4
|
-
* Recursive-descent parser, schema builder, and query executor.
|
|
5
|
-
*
|
|
6
|
-
* import { GraphQL } from "@tina4/core";
|
|
7
|
-
*
|
|
8
|
-
* const gql = new GraphQL();
|
|
9
|
-
* gql.addType("User", { id: { type: "ID" }, name: { type: "String" } });
|
|
10
|
-
* gql.addQuery("user", { id: "ID!" }, "User", (root, args) => getUser(args.id));
|
|
11
|
-
* const result = gql.execute('{ user(id: "1") { name } }');
|
|
12
|
-
*
|
|
13
|
-
* Supported:
|
|
14
|
-
* - Queries, mutations
|
|
15
|
-
* - Variables, default values
|
|
16
|
-
* - Aliases
|
|
17
|
-
* - Nested selections
|
|
18
|
-
* - List types ([Type])
|
|
19
|
-
* - Non-null types (Type!)
|
|
20
|
-
* - Error capture (resolver exceptions become GraphQL errors)
|
|
21
|
-
*/
|
|
22
1
|
export interface GraphQLField {
|
|
23
2
|
type: string;
|
|
24
3
|
description?: string;
|
|
@@ -59,6 +38,16 @@ export declare function graphqlAutoSchemaEnabled(): boolean;
|
|
|
59
38
|
* set `<= 0` to disable the guard. A non-numeric value falls back to 50.
|
|
60
39
|
*/
|
|
61
40
|
export declare function graphqlMaxDepth(): number;
|
|
41
|
+
/**
|
|
42
|
+
* F2: maximum number of expanded selection nodes a single query may request.
|
|
43
|
+
* The depth guard bounds NESTING but not WIDTH: a fragment that spreads another
|
|
44
|
+
* fragment many times (a "fragment bomb") stays shallow while expanding to
|
|
45
|
+
* millions of fields, and a query with thousands of aliases of one field is not
|
|
46
|
+
* deep at all. This budget bounds the expanded selection tree (fragments
|
|
47
|
+
* expanded, aliases counted) so both are rejected before any resolver runs.
|
|
48
|
+
* `TINA4_GRAPHQL_MAX_NODES` overrides the default (1000); `<= 0` disables it.
|
|
49
|
+
*/
|
|
50
|
+
export declare function graphqlMaxNodes(): number;
|
|
62
51
|
export declare class GraphQL {
|
|
63
52
|
private types;
|
|
64
53
|
private queries;
|
|
@@ -74,6 +63,12 @@ export declare class GraphQL {
|
|
|
74
63
|
* spread / inline fragment, so circular fragments are caught too.
|
|
75
64
|
*/
|
|
76
65
|
maxDepth: number;
|
|
66
|
+
/**
|
|
67
|
+
* Maximum expanded selection nodes per query (from `TINA4_GRAPHQL_MAX_NODES`,
|
|
68
|
+
* default 1000; `<= 0` disables). Bounds query WIDTH — fragment bombs and
|
|
69
|
+
* alias explosions — which the depth guard does not. See {@link graphqlMaxNodes}.
|
|
70
|
+
*/
|
|
71
|
+
maxNodes: number;
|
|
77
72
|
/**
|
|
78
73
|
* Decorator-style resolver registration.
|
|
79
74
|
*
|
|
@@ -145,6 +140,15 @@ export declare class GraphQL {
|
|
|
145
140
|
* instead of recursing until the interpreter stack overflows. Top-level
|
|
146
141
|
* starts at depth 1; `maxDepth <= 0` disables the guard.
|
|
147
142
|
*/
|
|
143
|
+
/**
|
|
144
|
+
* Count the expanded selection nodes, throwing {@link ComplexityError} past
|
|
145
|
+
* `maxNodes` (F2). Fragment spreads are expanded (so a fragment bomb is
|
|
146
|
+
* counted, not the small unexpanded document) and every field — including
|
|
147
|
+
* each alias — counts once. The count is capped by `maxNodes` and the walk by
|
|
148
|
+
* `maxDepth`, so a circular fragment trips the budget instead of looping and
|
|
149
|
+
* the check itself can never cost more than the budget.
|
|
150
|
+
*/
|
|
151
|
+
private assertComplexity;
|
|
148
152
|
private resolveSelectionsInto;
|
|
149
153
|
/**
|
|
150
154
|
* Generate SDL schema string.
|
|
@@ -1,18 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Programmatic HTML builder — avoids string concatenation.
|
|
3
|
-
*
|
|
4
|
-
* Usage:
|
|
5
|
-
* const el = new HtmlElement("div", { class: "card" }, ["Hello"]);
|
|
6
|
-
* el.toString(); // '<div class="card">Hello</div>'
|
|
7
|
-
*
|
|
8
|
-
* // Builder pattern
|
|
9
|
-
* const el = htmlElement("div")(htmlElement("p")("Text"));
|
|
10
|
-
*
|
|
11
|
-
* // Helper functions
|
|
12
|
-
* const h: Record<string, any> = {};
|
|
13
|
-
* addHtmlHelpers(h);
|
|
14
|
-
* const html = h._div({ class: "card" }, h._p("Hello"));
|
|
15
|
-
*/
|
|
16
1
|
/**
|
|
17
2
|
* Raw — marker for trusted, pre-sanitised HTML that must render UNESCAPED.
|
|
18
3
|
*
|
|
@@ -40,6 +40,7 @@ export type { ServiceOptions, ServiceContext, ServiceHandler, ServiceInfo } from
|
|
|
40
40
|
export { responseCache, clearCache, cacheStats, cacheGet, cacheSet, cacheDelete, cacheClear, cacheBackendStats, sweep, createBackend, _resetBackend } from "./cache.js";
|
|
41
41
|
export type { ResponseCacheConfig, CacheBackend } from "./cache.js";
|
|
42
42
|
export { Api, ApiStreamError, parseLineStream, parseSseStream } from "./api.js";
|
|
43
|
+
export { guardUrl, isBlockedAddress, allowPrivateRequests, SsrfError, ALLOW_PRIVATE_ENV } from "./ssrf.js";
|
|
43
44
|
export type { ApiResult, ApiOptions, ApiTransport, DownloadResult, UploadOptions, StreamOptions, SseEvent } from "./api.js";
|
|
44
45
|
export { Context, defaultContext, existingContext, fts5Supported, _sharedContexts } from "./context/index.js";
|
|
45
46
|
export type { SearchHit } from "./context/index.js";
|
package/types/core/src/job.d.ts
CHANGED
|
@@ -31,6 +31,7 @@ export interface JobLifecycle {
|
|
|
31
31
|
export type QueueJob = JobData & JobLifecycle;
|
|
32
32
|
export interface JobQueueBridge {
|
|
33
33
|
_failJob(topic: string, job: QueueJob, reason: string, maxRetries: number): void;
|
|
34
|
+
_rejectJob(topic: string, job: QueueJob, reason: string, maxRetries: number): void;
|
|
34
35
|
_retryJob(topic: string, job: QueueJob, delaySeconds?: number): void;
|
|
35
36
|
_completeJob(topic: string, job: QueueJob): void;
|
|
36
37
|
getMaxRetries(): number;
|