tina4-nodejs 3.13.137 → 3.13.138

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.
Files changed (242) hide show
  1. package/CLAUDE.md +33 -3
  2. package/COMMERCIAL-LICENSE.md +8 -0
  3. package/LICENSE +373 -0
  4. package/NOTICE +11 -0
  5. package/README.md +24 -2
  6. package/package.json +44 -6
  7. package/packages/cli/dist/bin.js +19466 -18048
  8. package/packages/cli/src/bin.ts +8 -0
  9. package/packages/cli/src/commands/build.ts +8 -0
  10. package/packages/cli/src/commands/generate.ts +8 -0
  11. package/packages/cli/src/commands/init.ts +8 -0
  12. package/packages/cli/src/commands/lint.ts +8 -0
  13. package/packages/cli/src/commands/migrate.ts +8 -0
  14. package/packages/cli/src/commands/migrateCreate.ts +8 -0
  15. package/packages/cli/src/commands/migrateRollback.ts +8 -0
  16. package/packages/cli/src/commands/migrateStatus.ts +8 -0
  17. package/packages/cli/src/commands/queue.ts +8 -0
  18. package/packages/cli/src/commands/routes.ts +8 -0
  19. package/packages/cli/src/commands/seed.ts +8 -0
  20. package/packages/cli/src/commands/serve.ts +11 -0
  21. package/packages/cli/src/commands/test.ts +8 -0
  22. package/packages/core/dist/index.js +19458 -18043
  23. package/packages/core/gallery/auth/src/routes/api/gallery/auth/login/post.ts +8 -0
  24. package/packages/core/gallery/auth/src/routes/api/gallery/auth/verify/get.ts +8 -0
  25. package/packages/core/gallery/auth/src/routes/gallery/auth/get.ts +8 -0
  26. package/packages/core/gallery/database/src/routes/api/gallery/db/notes/get.ts +8 -0
  27. package/packages/core/gallery/database/src/routes/api/gallery/db/notes/post.ts +8 -0
  28. package/packages/core/gallery/database/src/routes/api/gallery/db/tables/get.ts +8 -0
  29. package/packages/core/gallery/error-overlay/src/routes/api/gallery/crash/get.ts +8 -0
  30. package/packages/core/gallery/orm/src/routes/api/gallery/products/get.ts +8 -0
  31. package/packages/core/gallery/orm/src/routes/api/gallery/products/post.ts +8 -0
  32. package/packages/core/gallery/queue/src/lib/queueDb.ts +8 -0
  33. package/packages/core/gallery/queue/src/routes/api/gallery/queue/consume/post.ts +8 -0
  34. package/packages/core/gallery/queue/src/routes/api/gallery/queue/fail/post.ts +8 -0
  35. package/packages/core/gallery/queue/src/routes/api/gallery/queue/produce/post.ts +8 -0
  36. package/packages/core/gallery/queue/src/routes/api/gallery/queue/retry/post.ts +8 -0
  37. package/packages/core/gallery/queue/src/routes/api/gallery/queue/status/get.ts +8 -0
  38. package/packages/core/gallery/queue/src/routes/gallery/queue/get.ts +8 -0
  39. package/packages/core/gallery/rest-api/src/routes/api/gallery/hello/get.ts +8 -0
  40. package/packages/core/gallery/rest-api/src/routes/api/gallery/hello/post.ts +8 -0
  41. package/packages/core/gallery/templates/src/routes/gallery/page/get.ts +8 -0
  42. package/packages/core/gallery/templates/src/templates/gallery_page.twig +8 -0
  43. package/packages/core/public/THIRD-PARTY-NOTICES.md +55 -0
  44. package/packages/core/public/bundled-component-provenance.json +48 -0
  45. package/packages/core/public/js/tina4js.min.js +13 -3
  46. package/packages/core/public/licenses/swagger-ui-3.10.0-original-notice.txt +11 -0
  47. package/packages/core/public/licenses/swagger-ui-5.17.14-NOTICE.txt +2 -0
  48. package/packages/core/public/licenses/swagger-ui-Apache-2.0.txt +202 -0
  49. package/packages/core/public/licenses/tina4-css-MIT.txt +21 -0
  50. package/packages/core/public/licenses/tina4-dev-admin-components.json +240 -0
  51. package/packages/core/public/licenses/tina4-dev-admin-third-party.txt +919 -0
  52. package/packages/core/public/licenses/tina4-js-MIT.txt +20 -0
  53. package/packages/core/src/__feedback/widget.js +8 -0
  54. package/packages/core/src/_missing.ts +8 -0
  55. package/packages/core/src/ai.ts +8 -0
  56. package/packages/core/src/aiClient.ts +8 -0
  57. package/packages/core/src/api.ts +15 -3
  58. package/packages/core/src/auth.ts +21 -8
  59. package/packages/core/src/authGate.ts +8 -0
  60. package/packages/core/src/background.ts +8 -0
  61. package/packages/core/src/cache.ts +43 -4
  62. package/packages/core/src/constants.ts +8 -0
  63. package/packages/core/src/container.ts +8 -0
  64. package/packages/core/src/context/chunker.ts +8 -0
  65. package/packages/core/src/context/index.ts +8 -0
  66. package/packages/core/src/devAdmin.ts +185 -59
  67. package/packages/core/src/devMailbox.ts +8 -0
  68. package/packages/core/src/dispatchPipeline.ts +8 -0
  69. package/packages/core/src/docs.ts +8 -0
  70. package/packages/core/src/docsAutoDiscovery.ts +8 -0
  71. package/packages/core/src/docsParser.ts +8 -0
  72. package/packages/core/src/docsScanner.ts +44 -0
  73. package/packages/core/src/docsSignatures.ts +8 -0
  74. package/packages/core/src/dotenv.ts +8 -0
  75. package/packages/core/src/env.ts +8 -0
  76. package/packages/core/src/errorOverlay.ts +8 -0
  77. package/packages/core/src/events.ts +8 -0
  78. package/packages/core/src/fakeData.ts +8 -0
  79. package/packages/core/src/feedback.ts +8 -0
  80. package/packages/core/src/graphql.ts +16 -5
  81. package/packages/core/src/health.ts +8 -0
  82. package/packages/core/src/htmlElement.ts +8 -0
  83. package/packages/core/src/i18n.ts +8 -0
  84. package/packages/core/src/index.ts +8 -0
  85. package/packages/core/src/job.ts +8 -0
  86. package/packages/core/src/logger.ts +8 -0
  87. package/packages/core/src/mcp.test.ts +8 -0
  88. package/packages/core/src/mcp.ts +8 -0
  89. package/packages/core/src/messenger.ts +143 -86
  90. package/packages/core/src/metrics.ts +13 -0
  91. package/packages/core/src/middleware.ts +1178 -1096
  92. package/packages/core/src/mqtt.ts +8 -0
  93. package/packages/core/src/mqttMessage.ts +8 -0
  94. package/packages/core/src/optionalPackage.ts +48 -0
  95. package/packages/core/src/plan.ts +8 -0
  96. package/packages/core/src/portTakeover.ts +8 -0
  97. package/packages/core/src/projectIndex.ts +8 -0
  98. package/packages/core/src/projectIndexExtractors.ts +8 -0
  99. package/packages/core/src/projectIndexStorage.ts +8 -0
  100. package/packages/core/src/push.ts +8 -0
  101. package/packages/core/src/queue.ts +8 -0
  102. package/packages/core/src/queueBackends/kafkaBackend.ts +8 -0
  103. package/packages/core/src/queueBackends/liteBackend.ts +8 -0
  104. package/packages/core/src/queueBackends/mongoBackend.ts +21 -8
  105. package/packages/core/src/queueBackends/rabbitmqBackend.ts +8 -0
  106. package/packages/core/src/rateLimiter.ts +8 -0
  107. package/packages/core/src/request.ts +59 -10
  108. package/packages/core/src/response.ts +79 -5
  109. package/packages/core/src/routeDiscovery.ts +8 -0
  110. package/packages/core/src/router.ts +8 -0
  111. package/packages/core/src/server.ts +73 -24
  112. package/packages/core/src/service.ts +8 -0
  113. package/packages/core/src/session.ts +8 -0
  114. package/packages/core/src/sessionHandlers/childError.ts +8 -0
  115. package/packages/core/src/sessionHandlers/databaseHandler.ts +8 -0
  116. package/packages/core/src/sessionHandlers/memcachedHandler.ts +8 -0
  117. package/packages/core/src/sessionHandlers/mongoClient.ts +8 -0
  118. package/packages/core/src/sessionHandlers/mongoHandler.ts +8 -0
  119. package/packages/core/src/sessionHandlers/respClient.ts +8 -0
  120. package/packages/core/src/sessionHandlers/sqlClient.ts +8 -0
  121. package/packages/core/src/sessionHandlers/syncBridge.ts +8 -0
  122. package/packages/core/src/sessionHandlers/syncSocket.ts +8 -0
  123. package/packages/core/src/sessionHandlers/valkeyHandler.ts +8 -0
  124. package/packages/core/src/sso.ts +8 -0
  125. package/packages/core/src/static.ts +8 -0
  126. package/packages/core/src/test.ts +8 -0
  127. package/packages/core/src/testClient.ts +8 -0
  128. package/packages/core/src/testing.ts +8 -0
  129. package/packages/core/src/transport.ts +369 -0
  130. package/packages/core/src/trustedProxy.ts +8 -0
  131. package/packages/core/src/types.ts +13 -0
  132. package/packages/core/src/validator.ts +8 -0
  133. package/packages/core/src/version.ts +8 -0
  134. package/packages/core/src/websocket.ts +8 -0
  135. package/packages/core/src/websocketBackplane.ts +49 -32
  136. package/packages/core/src/websocketConnection.ts +8 -0
  137. package/packages/core/src/wsdl.ts +197 -115
  138. package/packages/core/templates/errors/302.twig +8 -0
  139. package/packages/core/templates/errors/401.twig +8 -0
  140. package/packages/core/templates/errors/403.twig +8 -0
  141. package/packages/core/templates/errors/404.twig +8 -0
  142. package/packages/core/templates/errors/500.twig +8 -0
  143. package/packages/core/templates/errors/502.twig +8 -0
  144. package/packages/core/templates/errors/503.twig +8 -0
  145. package/packages/core/templates/errors/base.twig +8 -0
  146. package/packages/frond/dist/index.js +151 -17
  147. package/packages/frond/src/engine.ts +177 -17
  148. package/packages/frond/src/index.ts +8 -0
  149. package/packages/orm/dist/index.js +8125 -6708
  150. package/packages/orm/src/adapters/firebird.ts +50 -3
  151. package/packages/orm/src/adapters/mongodb.ts +8 -0
  152. package/packages/orm/src/adapters/mssql.ts +29 -7
  153. package/packages/orm/src/adapters/mysql.ts +17 -1
  154. package/packages/orm/src/adapters/odbc.ts +11 -0
  155. package/packages/orm/src/adapters/pg-types.d.ts +8 -0
  156. package/packages/orm/src/adapters/postgres.ts +34 -8
  157. package/packages/orm/src/adapters/sqlDialect.ts +57 -0
  158. package/packages/orm/src/adapters/sqlite.ts +26 -0
  159. package/packages/orm/src/autoCrud.ts +78 -36
  160. package/packages/orm/src/baseModel.ts +61 -29
  161. package/packages/orm/src/cachedDatabase.ts +67 -1
  162. package/packages/orm/src/connectTimeout.ts +8 -0
  163. package/packages/orm/src/database.ts +649 -401
  164. package/packages/orm/src/databaseResult.ts +8 -0
  165. package/packages/orm/src/databaseUrl.ts +8 -0
  166. package/packages/orm/src/docstore.ts +23 -1
  167. package/packages/orm/src/fakeData.ts +8 -0
  168. package/packages/orm/src/graph/adapters/arango.ts +8 -0
  169. package/packages/orm/src/graph/adapters/bolt.ts +8 -0
  170. package/packages/orm/src/graph/adapters/ultipa.ts +8 -0
  171. package/packages/orm/src/graph/connectTimeout.ts +8 -0
  172. package/packages/orm/src/graph/errors.ts +8 -0
  173. package/packages/orm/src/graph/graphAdapter.ts +8 -0
  174. package/packages/orm/src/graph/graphDatabase.ts +8 -0
  175. package/packages/orm/src/graph/graphUrl.ts +10 -1
  176. package/packages/orm/src/graph/shapes.ts +8 -0
  177. package/packages/orm/src/index.ts +147 -139
  178. package/packages/orm/src/migration.ts +8 -0
  179. package/packages/orm/src/model.ts +8 -0
  180. package/packages/orm/src/modelCollection.ts +8 -0
  181. package/packages/orm/src/point.ts +8 -0
  182. package/packages/orm/src/query.ts +149 -33
  183. package/packages/orm/src/queryBuilder.ts +8 -0
  184. package/packages/orm/src/realtime/index.ts +8 -0
  185. package/packages/orm/src/realtime/models/attachment.ts +8 -0
  186. package/packages/orm/src/realtime/models/channel.ts +8 -0
  187. package/packages/orm/src/realtime/models/channelMember.ts +8 -0
  188. package/packages/orm/src/realtime/models/message.ts +8 -0
  189. package/packages/orm/src/realtime/models/workspace.ts +8 -0
  190. package/packages/orm/src/realtime/realtime.ts +8 -0
  191. package/packages/orm/src/realtime/storage.ts +43 -12
  192. package/packages/orm/src/seeder.ts +8 -0
  193. package/packages/orm/src/seederTable.ts +8 -0
  194. package/packages/orm/src/seederTypes.ts +8 -0
  195. package/packages/orm/src/sqlTranslator.ts +185 -8
  196. package/packages/orm/src/types.ts +15 -0
  197. package/packages/orm/src/validation.ts +8 -0
  198. package/packages/swagger/src/generator.ts +8 -0
  199. package/packages/swagger/src/index.ts +8 -0
  200. package/packages/swagger/src/ui.ts +8 -0
  201. package/types/core/src/background.d.ts +0 -16
  202. package/types/core/src/cache.d.ts +6 -0
  203. package/types/core/src/devAdmin.d.ts +18 -12
  204. package/types/core/src/docs.d.ts +0 -17
  205. package/types/core/src/errorOverlay.d.ts +0 -24
  206. package/types/core/src/events.d.ts +0 -12
  207. package/types/core/src/feedback.d.ts +0 -23
  208. package/types/core/src/graphql.d.ts +0 -21
  209. package/types/core/src/htmlElement.d.ts +0 -15
  210. package/types/core/src/messenger.d.ts +12 -1
  211. package/types/core/src/metrics.d.ts +2 -0
  212. package/types/core/src/middleware.d.ts +45 -1
  213. package/types/core/src/mqtt.d.ts +0 -33
  214. package/types/core/src/optionalPackage.d.ts +25 -0
  215. package/types/core/src/plan.d.ts +0 -10
  216. package/types/core/src/projectIndex.d.ts +0 -10
  217. package/types/core/src/queueBackends/mongoBackend.d.ts +2 -0
  218. package/types/core/src/request.d.ts +12 -0
  219. package/types/core/src/response.d.ts +13 -0
  220. package/types/core/src/server.d.ts +19 -0
  221. package/types/core/src/sessionHandlers/childError.d.ts +0 -15
  222. package/types/core/src/transport.d.ts +28 -0
  223. package/types/core/src/trustedProxy.d.ts +0 -16
  224. package/types/core/src/types.d.ts +5 -0
  225. package/types/core/src/websocketBackplane.d.ts +1 -0
  226. package/types/core/src/wsdl.d.ts +8 -22
  227. package/types/frond/src/engine.d.ts +7 -1
  228. package/types/orm/src/adapters/firebird.d.ts +12 -0
  229. package/types/orm/src/adapters/mssql.d.ts +11 -0
  230. package/types/orm/src/adapters/mysql.d.ts +2 -0
  231. package/types/orm/src/adapters/postgres.d.ts +11 -0
  232. package/types/orm/src/adapters/sqlDialect.d.ts +25 -0
  233. package/types/orm/src/adapters/sqlite.d.ts +7 -0
  234. package/types/orm/src/baseModel.d.ts +5 -0
  235. package/types/orm/src/cachedDatabase.d.ts +27 -0
  236. package/types/orm/src/database.d.ts +46 -7
  237. package/types/orm/src/graph/graphUrl.d.ts +0 -8
  238. package/types/orm/src/index.d.ts +1 -1
  239. package/types/orm/src/query.d.ts +48 -2
  240. package/types/orm/src/realtime/storage.d.ts +2 -1
  241. package/types/orm/src/sqlTranslator.d.ts +70 -18
  242. 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
- .replace(/\s*\bAUTOINCREMENT\b/gi, "");
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.replace(/\s*AUTOINCREMENT\b/gi, "");
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
- return sql.replace(/\?/g, "%s");
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
- let count = 0;
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 —
@@ -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 -> trust the browser's classification
21
- * (cross-site refused; same-origin / same-site / none ok).
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),
@@ -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;
@@ -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
  *
@@ -105,7 +105,6 @@ export declare class Messenger {
105
105
  private fromAddress;
106
106
  private fromName;
107
107
  private encryption;
108
- private useTls;
109
108
  /** Whether an SMTP host was actually configured (see the constructor). */
110
109
  private smtpConfigured;
111
110
  /** The local mailbox, present only when this messenger captures. */
@@ -116,6 +115,13 @@ export declare class Messenger {
116
115
  private imapPass;
117
116
  private imapEncryption;
118
117
  constructor(options?: MessengerOptions);
118
+ /**
119
+ * How the SMTP connection is secured (ADR-0071 section 1). Port 465 is the
120
+ * implicit-TLS submission port whatever the setting; `ssl` is implicit TLS on
121
+ * any port; `tls` / `starttls` upgrade with a required STARTTLS; `none` is
122
+ * plaintext on purpose.
123
+ */
124
+ private smtpTransport;
119
125
  /**
120
126
  * Read-only IMAP encryption mode for inspection / tests.
121
127
  * Returns one of "tls", "starttls", "none", "ssl".
@@ -142,6 +148,11 @@ export declare class Messenger {
142
148
  private prepareRecipients;
143
149
  private connectSmtpSocket;
144
150
  private requireSmtpResponse;
151
+ /**
152
+ * Connect, read the greeting, EHLO, and - for the STARTTLS transport - upgrade.
153
+ * STARTTLS is REQUIRED: when the server does not offer it the session fails
154
+ * here, before AUTH or MAIL FROM is ever sent.
155
+ */
145
156
  private openSmtpSession;
146
157
  private authenticateSmtp;
147
158
  private sendSmtpEnvelope;
@@ -3,4 +3,6 @@ export declare class MetricsEngineError extends Error {
3
3
  }
4
4
  export declare function enginePath(): string | null;
5
5
  export declare function fullAnalysis(root?: string): Record<string, any>;
6
+ /** The root the last full scan covered ("" before any scan). */
7
+ export declare function metricsScanRoot(): string;
6
8
  export declare function fileDetail(filePath: string): Record<string, any>;
@@ -164,6 +164,8 @@ export interface CorsConfig {
164
164
  }
165
165
  /** Reset the CORS warn-once ledger. Test seam. */
166
166
  export declare function resetCorsWarnings(): void;
167
+ /** The reasons warned about so far in this process (bounded by construction). */
168
+ export declare function corsWarningReasons(): string[];
167
169
  /**
168
170
  * The resolved CORS policy — ONE implementation of the rules.
169
171
  *
@@ -210,9 +212,35 @@ export declare class CorsPolicy {
210
212
  /**
211
213
  * The CORS headers for a request origin. `isPreflight` adds Max-Age, which
212
214
  * the Fetch Standard only defines for a preflight response.
215
+ *
216
+ * `sameOrigin` (see isSameOriginRequest) marks a request whose Origin is the
217
+ * request's OWN origin. Browsers send Origin on every same-origin POST/PUT/
218
+ * PATCH/DELETE, and such a request needs no CORS headers at all, so it is not
219
+ * a CORS event: it is never warned about (tina4-python#139). It is handled by
220
+ * the configured policy exactly like any other request - the flag silences a
221
+ * warning and never grants anything, so a spoofed Host gains nothing.
222
+ *
223
+ * A warning never advises '*': it names the one origin the operator could add.
213
224
  */
214
- headersFor(requestOrigin: string, isPreflight: boolean): Record<string, string>;
225
+ headersFor(requestOrigin: string, isPreflight: boolean, sameOrigin?: boolean): Record<string, string>;
215
226
  }
227
+ /**
228
+ * Is the request SAME-origin: does its Origin equal the request's own origin?
229
+ *
230
+ * The own origin is the request's scheme (first hop of X-Forwarded-Proto, else
231
+ * the native TLS socket) and host (first hop of X-Forwarded-Host, else Host).
232
+ * Both sides go through the WHATWG URL parser, so scheme and host compare
233
+ * case-insensitively and http:80 / https:443 equal the port-less form. An
234
+ * unparseable Origin (including the opaque "null") is never same-origin.
235
+ *
236
+ * Proxy headers are honoured without a trusted-proxy check on purpose: the
237
+ * answer only decides whether a CORS WARNING is logged, never what the policy
238
+ * grants, so a forged header gains the sender nothing.
239
+ */
240
+ export declare function isSameOriginRequest(req: {
241
+ headers: Record<string, string | string[] | undefined>;
242
+ socket?: unknown;
243
+ }): boolean;
216
244
  /**
217
245
  * Built-in CORS middleware (function form).
218
246
  *
@@ -302,6 +330,22 @@ export declare class RequestLogger {
302
330
  * Router.use(SecurityHeadersMiddleware);
303
331
  */
304
332
  export declare class SecurityHeadersMiddleware {
333
+ /**
334
+ * Run BEFORE route matching (ADR-0012), so EVERY response gets the headers,
335
+ * not only a matched route's. Static files, the SPA index.html at "/",
336
+ * template pages, 404s and 405s are all answered by the not-found fallback
337
+ * chain, which only the pre-match pass reaches. As a post-match middleware
338
+ * this left an SPA's front door with no CSP and frameable (tina4-python#137).
339
+ * The headers read no route metadata, so nothing is lost by running early.
340
+ */
341
+ static preMatch: boolean;
342
+ /**
343
+ * The canonical security header set (SECHDR-DEC-01) with its environment
344
+ * overrides, WITHOUT Strict-Transport-Security, which depends on the
345
+ * request's scheme. Also what a transport rejection carries (ADR-0068
346
+ * section 4), written before the scheme is known.
347
+ */
348
+ static canonicalHeaders(): Record<string, string>;
305
349
  static beforeSecurity(req: Tina4Request, res: Tina4Response): [Tina4Request, Tina4Response];
306
350
  /** Warn-once ledger for the default-CSP heads-up (per process). */
307
351
  private static cspDefaultWarned;