tina4-nodejs 3.13.136 → 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 (243) 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 +19440 -18017
  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 +19432 -18012
  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 +22 -7
  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 +8135 -6713
  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 +21 -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/aiClient.d.ts +2 -2
  202. package/types/core/src/background.d.ts +0 -16
  203. package/types/core/src/cache.d.ts +6 -0
  204. package/types/core/src/devAdmin.d.ts +18 -12
  205. package/types/core/src/docs.d.ts +0 -17
  206. package/types/core/src/errorOverlay.d.ts +0 -24
  207. package/types/core/src/events.d.ts +0 -12
  208. package/types/core/src/feedback.d.ts +0 -23
  209. package/types/core/src/graphql.d.ts +0 -21
  210. package/types/core/src/htmlElement.d.ts +0 -15
  211. package/types/core/src/messenger.d.ts +12 -1
  212. package/types/core/src/metrics.d.ts +2 -0
  213. package/types/core/src/middleware.d.ts +45 -1
  214. package/types/core/src/mqtt.d.ts +0 -33
  215. package/types/core/src/optionalPackage.d.ts +25 -0
  216. package/types/core/src/plan.d.ts +0 -10
  217. package/types/core/src/projectIndex.d.ts +0 -10
  218. package/types/core/src/queueBackends/mongoBackend.d.ts +2 -0
  219. package/types/core/src/request.d.ts +12 -0
  220. package/types/core/src/response.d.ts +13 -0
  221. package/types/core/src/server.d.ts +19 -0
  222. package/types/core/src/sessionHandlers/childError.d.ts +0 -15
  223. package/types/core/src/transport.d.ts +28 -0
  224. package/types/core/src/trustedProxy.d.ts +0 -16
  225. package/types/core/src/types.d.ts +5 -0
  226. package/types/core/src/websocketBackplane.d.ts +1 -0
  227. package/types/core/src/wsdl.d.ts +8 -22
  228. package/types/frond/src/engine.d.ts +7 -1
  229. package/types/orm/src/adapters/firebird.d.ts +12 -0
  230. package/types/orm/src/adapters/mssql.d.ts +11 -0
  231. package/types/orm/src/adapters/mysql.d.ts +2 -0
  232. package/types/orm/src/adapters/postgres.d.ts +11 -0
  233. package/types/orm/src/adapters/sqlDialect.d.ts +25 -0
  234. package/types/orm/src/adapters/sqlite.d.ts +7 -0
  235. package/types/orm/src/baseModel.d.ts +5 -0
  236. package/types/orm/src/cachedDatabase.d.ts +27 -0
  237. package/types/orm/src/database.d.ts +46 -7
  238. package/types/orm/src/graph/graphUrl.d.ts +0 -8
  239. package/types/orm/src/index.d.ts +1 -1
  240. package/types/orm/src/query.d.ts +48 -2
  241. package/types/orm/src/realtime/storage.d.ts +2 -1
  242. package/types/orm/src/sqlTranslator.d.ts +70 -18
  243. 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
  * Zero-dependency MQTT 3.1.1 client — the protocol every broker and every IoT
3
11
  * device already speaks.
@@ -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
  * One MQTT 3.1.1 application message as delivered by the broker.
3
11
  *
@@ -0,0 +1,48 @@
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
+
9
+ /**
10
+ * Optional peer packages: the database drivers and service clients an
11
+ * APPLICATION installs, never the framework (ADR-0067).
12
+ *
13
+ * tina4-nodejs installs as exactly one package. `pg`, `mongodb`, `redis` and
14
+ * the `@aws-sdk` S3 client are optional peerDependencies, which npm does not
15
+ * install, so for most apps "missing" is the normal state. Every feature that
16
+ * reaches for one must therefore fail at the point of use with the exact
17
+ * command that fixes it - never a bare MODULE_NOT_FOUND, never a silent no-op.
18
+ */
19
+
20
+ /** The actionable "install it" error, one wording for every optional peer. */
21
+ export function optionalPackageMissing(feature: string, packageNames: string[]): Error {
22
+ const quoted = packageNames.map((name) => `'${name}'`).join(" and ");
23
+ const [noun, pronoun] = packageNames.length === 1 ? ["package is", "it"] : ["packages are", "them"];
24
+ return new Error(
25
+ `The ${quoted} ${noun} required for ${feature}. ` +
26
+ `Install ${pronoun} with: npm install ${packageNames.join(" ")}`,
27
+ );
28
+ }
29
+
30
+ /**
31
+ * Resolve an optional peer from the FRAMEWORK's location and return its module
32
+ * URL, or throw the actionable error. Synchronous, so a constructor can refuse
33
+ * immediately (parity with the Python master, which raises ImportError in the
34
+ * constructor) instead of leaving a rejected promise for later.
35
+ *
36
+ * Resolving from here rather than from process.cwd() is what finds the peer an
37
+ * app installed, wherever the server was started from.
38
+ *
39
+ * @param installTogether packages the feature needs as a set (S3 needs the
40
+ * client AND the presigner), all named in the one install command.
41
+ */
42
+ export function resolveOptionalPackage(packageName: string, feature: string, installTogether: string[] = [packageName]): string {
43
+ try {
44
+ return import.meta.resolve(packageName);
45
+ } catch {
46
+ throw optionalPackageMissing(feature, installTogether);
47
+ }
48
+ }
@@ -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
  * Project plan management — persistent, human-readable task state.
3
11
  *
@@ -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
  * Identity-checked port takeover, shared by the CLI and the runtime paths.
3
11
  *
@@ -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
  * Project index — lightweight, persistent "where is what" map.
3
11
  *
@@ -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 * as path from "node:path";
2
10
  import type { FileEntry, FileRoute } from "./projectIndex.js";
3
11
 
@@ -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 * as fs from "node:fs";
2
10
  import * as path from "node:path";
3
11
  import * as crypto from "node:crypto";
@@ -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
  * Provider-neutral Web Push delivery using Node's built-in crypto and fetch.
3
11
  *
@@ -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 Queue — Unified job queue with pluggable backends, zero dependencies.
3
11
  *
@@ -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 Kafka Queue Backend — Kafka protocol via raw TCP, zero dependencies.
3
11
  *
@@ -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
  * LiteBackend — file-based queue backend for Tina4 Queue.
3
11
  * Stores jobs as JSON files on disk. Zero dependencies.
@@ -1,5 +1,14 @@
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
- * Tina4 MongoDB Queue Backend — uses `mongodb` npm package via dynamic import.
10
+ * Tina4 MongoDB Queue Backend — uses the `mongodb` npm package, which the app
11
+ * installs (`npm install mongodb`; an optional peer, ADR-0067).
3
12
  *
4
13
  * Implements the same interface as the file-based queue but uses MongoDB
5
14
  * for message storage and delivery. Atomic pop via findOneAndUpdate.
@@ -21,6 +30,7 @@
21
30
  import { randomUUID } from "node:crypto";
22
31
  import { execFileSync } from "node:child_process";
23
32
  import type { QueueJob } from "../queue.js";
33
+ import { resolveOptionalPackage } from "../optionalPackage.js";
24
34
 
25
35
  // ── Types ────────────────────────────────────────────────────
26
36
 
@@ -71,8 +81,17 @@ export class MongoBackend implements QueueBackend {
71
81
  private collection: string;
72
82
  private visibilityTimeout: number;
73
83
  private maxRetries: number;
84
+ /** Module URL of the app-installed `mongodb` driver, resolved once. */
85
+ private driverUrl: string;
74
86
 
75
87
  constructor(config?: MongoConfig) {
88
+ // Optional peer (ADR-0067), resolved HERE, on the main thread. Every
89
+ // operation runs in a child process that swallows its own failures, so a
90
+ // driver missing there used to surface as "MongoDB push failed" and a pop
91
+ // that silently returned nothing. The Python master raises in its
92
+ // constructor; so does this. Resolving from the framework's location also
93
+ // means the child finds the driver whatever directory the server runs in.
94
+ this.driverUrl = resolveOptionalPackage("mongodb", "the MongoDB queue backend");
76
95
  this.host = config?.host ?? process.env.TINA4_MONGO_HOST ?? "localhost";
77
96
  this.port = config?.port
78
97
  ?? (process.env.TINA4_MONGO_PORT ? parseInt(process.env.TINA4_MONGO_PORT, 10) : 27017);
@@ -136,13 +155,7 @@ export class MongoBackend implements QueueBackend {
136
155
  buildScript(operation: string, queue: string, data?: string): string {
137
156
  return `
138
157
  async function main() {
139
- let mongodb;
140
- try {
141
- mongodb = await import("mongodb");
142
- } catch {
143
- process.stderr.write("mongodb package not installed — run: npm install mongodb");
144
- process.exit(1);
145
- }
158
+ const mongodb = await import(${JSON.stringify(this.driverUrl)});
146
159
 
147
160
  const { MongoClient } = mongodb;
148
161
  const uri = ${JSON.stringify(this.uri)};
@@ -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 RabbitMQ Queue Backend — AMQP 0-9-1 via raw TCP, zero dependencies.
3
11
  *
@@ -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 { Middleware, Tina4Request, Tina4Response } from "./types.js";
2
10
  import { resolveClientIp } from "./trustedProxy.js";
3
11
 
@@ -1,8 +1,17 @@
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 { IncomingMessage, IncomingHttpHeaders } from "node:http";
2
10
  import { mkdirSync, realpathSync, writeFileSync } from "node:fs";
3
11
  import { dirname, join } from "node:path";
4
12
  import type { Tina4Request, UploadedFile } from "./types.js";
5
- import { resolveClientIp } from "./trustedProxy.js";
13
+ import { resolveClientIp, isTrustedProxy } from "./trustedProxy.js";
14
+ import { Log } from "./logger.js";
6
15
 
7
16
  /**
8
17
  * Wrap Node's `IncomingHttpHeaders` in a Proxy so mixed-case lookups
@@ -53,10 +62,11 @@ export function createRequest(req: IncomingMessage): Tina4Request {
53
62
  makeCaseInsensitiveHeaders(req.headers);
54
63
 
55
64
  // Resolve scheme + host honouring proxy headers — parity with PHP/Python/Ruby.
56
- const xfProto = req.headers["x-forwarded-proto"];
65
+ const trustedPeer = isTrustedProxy(req.socket?.remoteAddress ?? "");
66
+ const xfProto = trustedPeer ? req.headers["x-forwarded-proto"] : undefined;
57
67
  const proto = (Array.isArray(xfProto) ? xfProto[0] : xfProto)
58
68
  ?? ((req.socket as { encrypted?: boolean })?.encrypted ? "https" : "http");
59
- const xfHost = req.headers["x-forwarded-host"];
69
+ const xfHost = trustedPeer ? req.headers["x-forwarded-host"] : undefined;
60
70
  const host = (Array.isArray(xfHost) ? xfHost[0] : xfHost)
61
71
  ?? (req.headers.host ?? "localhost");
62
72
 
@@ -148,13 +158,50 @@ export function createRequest(req: IncomingMessage): Tina4Request {
148
158
  return tReq;
149
159
  }
150
160
 
151
- /** Maximum upload size in bytes (default 10 MB). Override via TINA4_MAX_UPLOAD_SIZE env var. */
152
- const TINA4_MAX_UPLOAD_SIZE = parseInt(process.env.TINA4_MAX_UPLOAD_SIZE ?? "10485760", 10);
161
+
162
+ // ── Request limits (ADR-0068) ────────────────────────────────────────────────
163
+ // Here rather than in a module of their own: request.ts sits below the server
164
+ // and transport.ts, so both can read them without an import cycle, and the
165
+ // core barrel's eager module graph stays one module smaller.
166
+
167
+ export const DEFAULT_MAX_UPLOAD_SIZE = 10_485_760;
168
+
169
+ const warnedLimits = new Set<string>();
170
+
171
+ /**
172
+ * Read a numeric limit from the environment. A value that is not a usable
173
+ * number falls back to the default with a warning naming the variable (the
174
+ * same rule as PHP's Server::resolveLimit) - a typo must never be what
175
+ * disables a DoS guard.
176
+ */
177
+ export function resolveLimit(name: string, fallback: number, zeroAllowed = false): number {
178
+ const raw = (process.env[name] ?? "").trim();
179
+ if (raw === "") return fallback;
180
+ const value = /^[0-9]+$/.test(raw) ? Number(raw) : NaN;
181
+ if (Number.isSafeInteger(value) && (value > 0 || (value === 0 && zeroAllowed))) return value;
182
+ if (!warnedLimits.has(`${name}=${raw}`)) {
183
+ warnedLimits.add(`${name}=${raw}`);
184
+ Log.warning(`${name}=${raw} is not a usable limit - using ${fallback}`);
185
+ }
186
+ return fallback;
187
+ }
188
+
189
+ /** TINA4_MAX_UPLOAD_SIZE, read when it is needed so a value from .env counts. */
190
+ export function maxUploadSize(): number {
191
+ return resolveLimit("TINA4_MAX_UPLOAD_SIZE", DEFAULT_MAX_UPLOAD_SIZE);
192
+ }
193
+
194
+ /** The one 413 wording (ADR-0068 section 4). */
195
+ export function bodyTooLargeMessage(bytes: number | bigint | string, limit: number): string {
196
+ return `Request body (${bytes} bytes) exceeds TINA4_MAX_UPLOAD_SIZE (${limit} bytes)`;
197
+ }
198
+
199
+
153
200
 
154
201
  export class PayloadTooLargeError extends Error {
155
202
  public statusCode = 413;
156
203
  constructor(actual: number, limit: number) {
157
- super(`Request body (${actual} bytes) exceeds TINA4_MAX_UPLOAD_SIZE (${limit} bytes)`);
204
+ super(bodyTooLargeMessage(actual, limit));
158
205
  this.name = "PayloadTooLargeError";
159
206
  }
160
207
  }
@@ -164,9 +211,11 @@ async function parseBody(req: Tina4Request): Promise<void> {
164
211
  if (method === "GET" || method === "HEAD" || method === "OPTIONS") return;
165
212
 
166
213
  // Check content-length header against upload size limit before reading body
214
+ const uploadLimit = maxUploadSize();
215
+
167
216
  const declaredLength = parseInt(req.headers["content-length"] ?? "0", 10);
168
- if (declaredLength > TINA4_MAX_UPLOAD_SIZE) {
169
- throw new PayloadTooLargeError(declaredLength, TINA4_MAX_UPLOAD_SIZE);
217
+ if (declaredLength > uploadLimit) {
218
+ throw new PayloadTooLargeError(declaredLength, uploadLimit);
170
219
  }
171
220
 
172
221
  const contentType = req.headers["content-type"] ?? "";
@@ -190,10 +239,10 @@ async function parseBody(req: Tina4Request): Promise<void> {
190
239
  req.on("data", (chunk: Buffer) => {
191
240
  if (refused) return;
192
241
  received += chunk.length;
193
- if (received > TINA4_MAX_UPLOAD_SIZE) {
242
+ if (received > uploadLimit) {
194
243
  refused = true;
195
244
  chunks.length = 0; // drop what we have; the request is dead
196
- reject(new PayloadTooLargeError(received, TINA4_MAX_UPLOAD_SIZE));
245
+ reject(new PayloadTooLargeError(received, uploadLimit));
197
246
  return;
198
247
  }
199
248
  chunks.push(chunk);
@@ -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 { ServerResponse } from "node:http";
2
10
  import fs from "node:fs";
3
11
  import nodePath from "node:path";
@@ -24,6 +32,39 @@ async function closeSource(source: AsyncIterable<unknown>): Promise<void> {
24
32
  }
25
33
  }
26
34
 
35
+ /** RFC 9110 `token`: one or more `tchar`. */
36
+ const HEADER_TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
37
+
38
+ /**
39
+ * The caller's name as a JSON string literal with non-ASCII escaped, byte-
40
+ * identical to Python's json.dumps (ADR-0068), so a name that is itself broken
41
+ * cannot break the error message or the log line carrying it.
42
+ */
43
+ export function quoteHeaderName(name: string): string {
44
+ return JSON.stringify(name).replace(/[\u007f-\uffff]/g, (c) => "\\u" + c.charCodeAt(0).toString(16).padStart(4, "0"));
45
+ }
46
+
47
+ /** A TypeError carrying node:http's own error code, as setHeader() throws natively. */
48
+ function headerRefusal(message: string, code: string): TypeError {
49
+ return Object.assign(new TypeError(message), { code });
50
+ }
51
+
52
+ /**
53
+ * Refuse a response header whose name is not an HTTP token or whose value
54
+ * carries CR, LF or NUL, with the exact ADR-0068 wording. node:http refuses
55
+ * these too, but names a broken header raw (a CR/LF in the name reaches the
56
+ * log line) - this is the ADR's form, checked first.
57
+ */
58
+ export function assertSafeHeader(name: string, value: unknown): void {
59
+ if (!HEADER_TOKEN.test(name)) {
60
+ throw headerRefusal(`Header name must be a valid HTTP token [${quoteHeaderName(name)}]`, "ERR_INVALID_HTTP_TOKEN");
61
+ }
62
+ const values = Array.isArray(value) ? value : [value];
63
+ if (values.some((item) => /[\r\n\0]/.test(String(item)))) {
64
+ throw headerRefusal(`Invalid character in header content [${quoteHeaderName(name)}]`, "ERR_INVALID_CHAR");
65
+ }
66
+ }
67
+
27
68
  /** Cache Frond instances by template directory to avoid repeated instantiation. */
28
69
  const _frondCache = new Map<string, InstanceType<any>>();
29
70
 
@@ -147,10 +188,24 @@ export function createResponse(res: ServerResponse): Tina4Response {
147
188
  res.end(chunk, encoding);
148
189
  }
149
190
  };
191
+ // Every header this Response sets goes through here, so every one is
192
+ // checked (ADR-0068) - even after the headers are sent, since an unsafe
193
+ // value is a bug in the code that built it either way.
150
194
  const safeSetHeader = (name: string, value: string | number | readonly string[]) => {
195
+ assertSafeHeader(name, value);
151
196
  if (!res.headersSent) res.setHeader(name, value);
152
197
  };
153
198
 
199
+ // True once the route set Content-Type with header(); response(data) then
200
+ // keeps it instead of detecting one (ADR-0072, #144).
201
+ let contentTypeFromHeader = false;
202
+ const setDetectedContentType = (contentType: string) => {
203
+ if (!contentTypeFromHeader) safeSetHeader("Content-Type", contentType);
204
+ };
205
+ const noteContentTypeHeader = (name: string) => {
206
+ if (name.toLowerCase() === "content-type") contentTypeFromHeader = true;
207
+ };
208
+
154
209
  // ── The callable: response(data, status, contentType) ──
155
210
  const response = function (data?: unknown, statusCode?: number, contentType?: string): Tina4Response {
156
211
  if (res.headersSent) return response;
@@ -158,6 +213,10 @@ export function createResponse(res: ServerResponse): Tina4Response {
158
213
  // Normalise ORM models / collections / query results so handlers can
159
214
  // `return response(model)` without serialising by hand.
160
215
  data = toJsonable(data);
216
+ // Any byte array is a binary body: view it as a Buffer (no copy) so every branch below writes it as bytes.
217
+ if (data instanceof Uint8Array && !Buffer.isBuffer(data)) {
218
+ data = Buffer.from(data.buffer, data.byteOffset, data.byteLength);
219
+ }
161
220
 
162
221
  if (statusCode !== undefined) {
163
222
  res.statusCode = statusCode;
@@ -166,21 +225,23 @@ export function createResponse(res: ServerResponse): Tina4Response {
166
225
  if (contentType) {
167
226
  // Explicit content type
168
227
  safeSetHeader("Content-Type", contentType);
169
- if (typeof data === "object" && data !== null && !Buffer.isBuffer(data)) {
228
+ if (Buffer.isBuffer(data)) {
229
+ safeEnd(data);
230
+ } else if (typeof data === "object" && data !== null) {
170
231
  safeEnd(JSON.stringify(data));
171
232
  } else {
172
233
  safeEnd(data == null ? "" : String(data));
173
234
  }
174
235
  } else if (typeof data === "object" && data !== null && !Buffer.isBuffer(data)) {
175
236
  // dict/array → auto JSON
176
- safeSetHeader("Content-Type", "application/json");
237
+ setDetectedContentType("application/json");
177
238
  safeEnd(JSON.stringify(data));
178
239
  } else if (typeof data === "string") {
179
240
  const trimmed = data.trim();
180
241
  if (trimmed.startsWith("<") && trimmed.endsWith(">")) {
181
- safeSetHeader("Content-Type", "text/html; charset=utf-8");
242
+ setDetectedContentType("text/html; charset=utf-8");
182
243
  } else {
183
- safeSetHeader("Content-Type", "text/plain; charset=utf-8");
244
+ setDetectedContentType("text/plain; charset=utf-8");
184
245
  }
185
246
  safeEnd(data);
186
247
  } else if (Buffer.isBuffer(data)) {
@@ -191,7 +252,7 @@ export function createResponse(res: ServerResponse): Tina4Response {
191
252
  } else if (data == null) {
192
253
  safeEnd("");
193
254
  } else {
194
- safeSetHeader("Content-Type", "text/plain; charset=utf-8");
255
+ setDetectedContentType("text/plain; charset=utf-8");
195
256
  safeEnd(String(data));
196
257
  }
197
258
 
@@ -246,6 +307,7 @@ export function createResponse(res: ServerResponse): Tina4Response {
246
307
 
247
308
  response.header = function (name: string, value: string | number | readonly string[]): Tina4Response {
248
309
  safeSetHeader(name, value);
310
+ noteContentTypeHeader(name);
249
311
  return response;
250
312
  };
251
313
 
@@ -254,9 +316,11 @@ export function createResponse(res: ServerResponse): Tina4Response {
254
316
  */
255
317
  response.addHeader = function (name: string, value: string): void {
256
318
  safeSetHeader(name, value);
319
+ noteContentTypeHeader(name);
257
320
  };
258
321
 
259
322
  response.redirect = function (url: string, code?: number): Tina4Response {
323
+ assertSafeHeader("Location", url); // before the status changes
260
324
  if (res.headersSent) return response;
261
325
  res.statusCode = code ?? 302;
262
326
  safeSetHeader("Location", url);
@@ -265,6 +329,15 @@ export function createResponse(res: ServerResponse): Tina4Response {
265
329
  };
266
330
 
267
331
  response.cookie = function (name: string, value: string, options?: CookieOptions): Tina4Response {
332
+ // The name and value are percent-encoded below, so they cannot carry CR,
333
+ // LF, NUL or ';' to the wire and keep being encoded (ADR-0068). Attribute
334
+ // values are written as given, so one carrying those is refused: a ';'
335
+ // would add an attribute (say Domain=) the application never set.
336
+ for (const attribute of [options?.path, options?.domain, options?.sameSite, options?.maxAge]) {
337
+ if (attribute !== undefined && /[\r\n\0;]/.test(String(attribute))) {
338
+ throw headerRefusal(`Invalid character in cookie content [${quoteHeaderName(name)}]`, "ERR_INVALID_CHAR");
339
+ }
340
+ }
268
341
  const parts = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];
269
342
  if (options?.maxAge !== undefined) parts.push(`Max-Age=${options.maxAge}`);
270
343
  if (options?.expires) parts.push(`Expires=${options.expires.toUTCString()}`);
@@ -419,6 +492,7 @@ export function createResponse(res: ServerResponse): Tina4Response {
419
492
  source: AsyncIterable<string | Buffer>,
420
493
  contentType: string = "text/event-stream",
421
494
  ): Promise<Tina4Response> {
495
+ assertSafeHeader("Content-Type", contentType);
422
496
  if (res.headersSent) return response;
423
497
  res.writeHead(200, {
424
498
  "Content-Type": contentType,
@@ -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 { readdirSync, statSync, mkdirSync, writeFileSync, existsSync } from "node:fs";
2
10
  import { join, relative, basename, extname } from "node:path";
3
11
  import type { RouteDefinition, RouteHandler, RouteMeta } from "./types.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 { RouteHandler, RouteDefinition, RouteMeta, Middleware, MiddlewareSpec, Tina4Request, Tina4Response, WebSocketRouteHandler, WebSocketRouteDefinition } from "./types.js";
2
10
  import { isTruthy } from "./dotenv.js";
3
11
  import { MiddlewareRunner, isMiddlewareClass } from "./middleware.js";