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.
Files changed (255) 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 +19979 -18102
  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 +19976 -18097
  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 +48 -7
  58. package/packages/core/src/auth.ts +94 -14
  59. package/packages/core/src/authGate.ts +35 -20
  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 +108 -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 +9 -0
  85. package/packages/core/src/job.ts +16 -1
  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 +11 -1
  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 +1181 -1097
  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 +24 -0
  101. package/packages/core/src/queue.ts +23 -2
  102. package/packages/core/src/queueBackends/kafkaBackend.ts +8 -0
  103. package/packages/core/src/queueBackends/liteBackend.ts +23 -0
  104. package/packages/core/src/queueBackends/mongoBackend.ts +61 -14
  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 +79 -25
  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 +68 -3
  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 +20 -4
  125. package/packages/core/src/ssoIdentity.ts +19 -0
  126. package/packages/core/src/ssrf.ts +221 -0
  127. package/packages/core/src/static.ts +8 -0
  128. package/packages/core/src/test.ts +8 -0
  129. package/packages/core/src/testClient.ts +11 -0
  130. package/packages/core/src/testing.ts +8 -0
  131. package/packages/core/src/transport.ts +369 -0
  132. package/packages/core/src/trustedProxy.ts +8 -0
  133. package/packages/core/src/types.ts +13 -0
  134. package/packages/core/src/validator.ts +8 -0
  135. package/packages/core/src/version.ts +8 -0
  136. package/packages/core/src/websocket.ts +12 -2
  137. package/packages/core/src/websocketBackplane.ts +49 -32
  138. package/packages/core/src/websocketConnection.ts +8 -0
  139. package/packages/core/src/wsdl.ts +197 -115
  140. package/packages/core/templates/errors/302.twig +8 -0
  141. package/packages/core/templates/errors/401.twig +8 -0
  142. package/packages/core/templates/errors/403.twig +8 -0
  143. package/packages/core/templates/errors/404.twig +8 -0
  144. package/packages/core/templates/errors/500.twig +8 -0
  145. package/packages/core/templates/errors/502.twig +8 -0
  146. package/packages/core/templates/errors/503.twig +8 -0
  147. package/packages/core/templates/errors/base.twig +8 -0
  148. package/packages/frond/dist/index.js +157 -17
  149. package/packages/frond/src/engine.ts +186 -17
  150. package/packages/frond/src/index.ts +8 -0
  151. package/packages/orm/dist/index.js +8641 -6765
  152. package/packages/orm/src/adapters/firebird.ts +50 -3
  153. package/packages/orm/src/adapters/mongodb.ts +8 -0
  154. package/packages/orm/src/adapters/mssql.ts +29 -7
  155. package/packages/orm/src/adapters/mysql.ts +17 -1
  156. package/packages/orm/src/adapters/odbc.ts +11 -0
  157. package/packages/orm/src/adapters/pg-types.d.ts +8 -0
  158. package/packages/orm/src/adapters/postgres.ts +34 -8
  159. package/packages/orm/src/adapters/sqlDialect.ts +57 -0
  160. package/packages/orm/src/adapters/sqlite.ts +26 -0
  161. package/packages/orm/src/autoCrud.ts +78 -36
  162. package/packages/orm/src/baseModel.ts +61 -29
  163. package/packages/orm/src/cachedDatabase.ts +67 -1
  164. package/packages/orm/src/connectTimeout.ts +8 -0
  165. package/packages/orm/src/database.ts +649 -401
  166. package/packages/orm/src/databaseResult.ts +8 -0
  167. package/packages/orm/src/databaseUrl.ts +8 -0
  168. package/packages/orm/src/docstore.ts +23 -1
  169. package/packages/orm/src/fakeData.ts +8 -0
  170. package/packages/orm/src/graph/adapters/arango.ts +8 -0
  171. package/packages/orm/src/graph/adapters/bolt.ts +8 -0
  172. package/packages/orm/src/graph/adapters/ultipa.ts +8 -0
  173. package/packages/orm/src/graph/connectTimeout.ts +8 -0
  174. package/packages/orm/src/graph/errors.ts +8 -0
  175. package/packages/orm/src/graph/graphAdapter.ts +8 -0
  176. package/packages/orm/src/graph/graphDatabase.ts +8 -0
  177. package/packages/orm/src/graph/graphUrl.ts +10 -1
  178. package/packages/orm/src/graph/shapes.ts +8 -0
  179. package/packages/orm/src/index.ts +147 -139
  180. package/packages/orm/src/migration.ts +8 -0
  181. package/packages/orm/src/model.ts +8 -0
  182. package/packages/orm/src/modelCollection.ts +8 -0
  183. package/packages/orm/src/point.ts +8 -0
  184. package/packages/orm/src/query.ts +149 -33
  185. package/packages/orm/src/queryBuilder.ts +8 -0
  186. package/packages/orm/src/realtime/index.ts +8 -0
  187. package/packages/orm/src/realtime/models/attachment.ts +8 -0
  188. package/packages/orm/src/realtime/models/channel.ts +8 -0
  189. package/packages/orm/src/realtime/models/channelMember.ts +8 -0
  190. package/packages/orm/src/realtime/models/message.ts +8 -0
  191. package/packages/orm/src/realtime/models/workspace.ts +8 -0
  192. package/packages/orm/src/realtime/realtime.ts +8 -0
  193. package/packages/orm/src/realtime/storage.ts +43 -12
  194. package/packages/orm/src/seeder.ts +8 -0
  195. package/packages/orm/src/seederTable.ts +8 -0
  196. package/packages/orm/src/seederTypes.ts +8 -0
  197. package/packages/orm/src/sqlTranslator.ts +185 -8
  198. package/packages/orm/src/types.ts +15 -0
  199. package/packages/orm/src/validation.ts +8 -0
  200. package/packages/swagger/src/generator.ts +8 -0
  201. package/packages/swagger/src/index.ts +8 -0
  202. package/packages/swagger/src/ui.ts +8 -0
  203. package/types/core/src/api.d.ts +6 -0
  204. package/types/core/src/auth.d.ts +30 -0
  205. package/types/core/src/background.d.ts +0 -16
  206. package/types/core/src/cache.d.ts +6 -0
  207. package/types/core/src/devAdmin.d.ts +18 -12
  208. package/types/core/src/docs.d.ts +0 -17
  209. package/types/core/src/errorOverlay.d.ts +0 -24
  210. package/types/core/src/events.d.ts +0 -12
  211. package/types/core/src/feedback.d.ts +0 -23
  212. package/types/core/src/graphql.d.ts +25 -21
  213. package/types/core/src/htmlElement.d.ts +0 -15
  214. package/types/core/src/index.d.ts +1 -0
  215. package/types/core/src/job.d.ts +1 -0
  216. package/types/core/src/messenger.d.ts +12 -1
  217. package/types/core/src/metrics.d.ts +2 -0
  218. package/types/core/src/middleware.d.ts +45 -1
  219. package/types/core/src/mqtt.d.ts +0 -33
  220. package/types/core/src/optionalPackage.d.ts +25 -0
  221. package/types/core/src/plan.d.ts +0 -10
  222. package/types/core/src/projectIndex.d.ts +0 -10
  223. package/types/core/src/push.d.ts +5 -0
  224. package/types/core/src/queue.d.ts +7 -1
  225. package/types/core/src/queueBackends/liteBackend.d.ts +9 -0
  226. package/types/core/src/queueBackends/mongoBackend.d.ts +10 -1
  227. package/types/core/src/request.d.ts +12 -0
  228. package/types/core/src/response.d.ts +13 -0
  229. package/types/core/src/server.d.ts +19 -0
  230. package/types/core/src/sessionHandlers/childError.d.ts +0 -15
  231. package/types/core/src/sessionHandlers/databaseHandler.d.ts +25 -1
  232. package/types/core/src/sso.d.ts +1 -0
  233. package/types/core/src/ssoIdentity.d.ts +10 -0
  234. package/types/core/src/ssrf.d.ts +19 -0
  235. package/types/core/src/transport.d.ts +28 -0
  236. package/types/core/src/trustedProxy.d.ts +0 -16
  237. package/types/core/src/types.d.ts +5 -0
  238. package/types/core/src/websocketBackplane.d.ts +1 -0
  239. package/types/core/src/wsdl.d.ts +8 -22
  240. package/types/frond/src/engine.d.ts +7 -1
  241. package/types/orm/src/adapters/firebird.d.ts +12 -0
  242. package/types/orm/src/adapters/mssql.d.ts +11 -0
  243. package/types/orm/src/adapters/mysql.d.ts +2 -0
  244. package/types/orm/src/adapters/postgres.d.ts +11 -0
  245. package/types/orm/src/adapters/sqlDialect.d.ts +25 -0
  246. package/types/orm/src/adapters/sqlite.d.ts +7 -0
  247. package/types/orm/src/baseModel.d.ts +5 -0
  248. package/types/orm/src/cachedDatabase.d.ts +27 -0
  249. package/types/orm/src/database.d.ts +46 -7
  250. package/types/orm/src/graph/graphUrl.d.ts +0 -8
  251. package/types/orm/src/index.d.ts +1 -1
  252. package/types/orm/src/query.d.ts +48 -2
  253. package/types/orm/src/realtime/storage.d.ts +2 -1
  254. package/types/orm/src/sqlTranslator.d.ts +70 -18
  255. 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
  *
@@ -13,6 +21,7 @@ import {
13
21
  createSign,
14
22
  randomBytes,
15
23
  } from "node:crypto";
24
+ import { guardUrl, SsrfError } from "./ssrf.js";
16
25
 
17
26
  const CURVE = "prime256v1";
18
27
  const RECORD_SIZE = 4096;
@@ -32,6 +41,11 @@ export interface PushOptions {
32
41
  privateKey?: string;
33
42
  ttl?: number;
34
43
  urgency?: "very-low" | "low" | "normal" | "high";
44
+ /**
45
+ * SSRF guard (ADR-0084): an explicit allow-list of hosts / host:port / CIDRs
46
+ * that bypass the private-endpoint refusal even with the opt-out off.
47
+ */
48
+ allowHosts?: string[];
35
49
  }
36
50
 
37
51
  export interface PushResult {
@@ -259,10 +273,20 @@ export class Push {
259
273
  }
260
274
 
261
275
  private async deliver(endpoint: URL, config: { subject: string; publicKey: string; privateKey: string }, keys: { rawPublic: Buffer; rawPrivate: Buffer }, body: Buffer): Promise<PushResult> {
276
+ // SSRF guard (ADR-0084): refuse a private/internal push endpoint.
277
+ try {
278
+ await guardUrl(endpoint.toString(), this.options.allowHosts);
279
+ } catch (error) {
280
+ if (error instanceof SsrfError) throw new PushError(error.message);
281
+ throw error;
282
+ }
262
283
  let response: Response;
263
284
  try {
264
285
  response = await fetch(endpoint, {
265
286
  method: "POST",
287
+ // A real push service answers the POST directly; a redirect from a push
288
+ // endpoint is not followed to a private address (ADR-0084).
289
+ redirect: "manual",
266
290
  headers: {
267
291
  Authorization: `vapid t=${vapidToken(endpoint.toString(), config.subject, keys.rawPrivate, keys.rawPublic)}, k=${config.publicKey}`,
268
292
  "Content-Encoding": "aes128gcm",
@@ -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
  *
@@ -119,7 +127,7 @@ export interface ConsumeOptions {
119
127
  export interface QueueBackendInterface {
120
128
  push(queue: string, payload: unknown, delay?: number, priority?: number): string;
121
129
  pop(queue: string): QueueJob | null;
122
- size(queue: string): number;
130
+ size(queue: string, status?: string): number;
123
131
  clear(queue: string): void;
124
132
  /**
125
133
  * Release whatever connection the backend holds, and be safe to call twice.
@@ -144,6 +152,7 @@ export interface QueueBackendInterface {
144
152
  // persistent-connection rewrite lands.
145
153
  complete?(queue: string, id: string): void;
146
154
  fail?(queue: string, id: string, error: string, maxRetries: number, retryBackoff: number): void;
155
+ reject?(queue: string, id: string, reason: string, maxRetries: number): void;
147
156
  /**
148
157
  * Explicit manual re-queue: returns true if the id was found and revived,
149
158
  * false otherwise (parity with Python's backend.retry_job()). A backend may
@@ -424,7 +433,7 @@ export class Queue {
424
433
  const q = this.topic;
425
434
 
426
435
  if (this.externalBackend) {
427
- return this.externalBackend.size(q);
436
+ return this.externalBackend.size(q, status);
428
437
  }
429
438
  return this.liteBackend.size(q, status);
430
439
  }
@@ -750,6 +759,18 @@ export class Queue {
750
759
  this.liteBackend.failJob(queue, job, error, maxRetries, this._retryBackoff);
751
760
  }
752
761
 
762
+ /**
763
+ * Dead-letter a job immediately — no retry (ADR-0023 reject()). Distinct from
764
+ * _failJob, which retries until maxRetries is spent.
765
+ */
766
+ _rejectJob(queue: string, job: QueueJob, reason: string, maxRetries: number): void {
767
+ if (this.externalBackend?.reject) {
768
+ this.externalBackend.reject(queue, job.id, reason, maxRetries);
769
+ return;
770
+ }
771
+ this.liteBackend.rejectJob(queue, job, reason, maxRetries);
772
+ }
773
+
753
774
  /**
754
775
  * Re-queue a job back to the main queue directory with incremented attempts.
755
776
  */
@@ -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.
@@ -685,6 +693,21 @@ export class LiteBackend {
685
693
  * dead-letter file -- two spellings of the same intent that previously
686
694
  * diverged.
687
695
  */
696
+ /**
697
+ * Reject a job permanently — dead-letter it NOW, no retry (ADR-0023).
698
+ *
699
+ * Unlike failJob(), which requeues until maxRetries is spent, rejectJob()
700
+ * moves a known-poison job straight to the dead-letter store. attempts is
701
+ * floored at maxRetries so deadLetters() (attempts >= maxRetries) returns it
702
+ * and size("dead") agrees.
703
+ */
704
+ rejectJob(queue: string, job: QueueJob, reason: string, maxRetries: number): void {
705
+ this.clearReservation(queue, job.id);
706
+ job.attempts = Math.max((job.attempts || 0) + 1, maxRetries);
707
+ job.error = reason;
708
+ this.deadLetter(queue, job, reason);
709
+ }
710
+
688
711
  retryJob(queue: string, job: QueueJob, delaySeconds?: number): void {
689
712
  // Clear the reservation — the consumer acknowledged (with an explicit retry).
690
713
  this.clearReservation(queue, job.id);
@@ -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)};
@@ -294,12 +307,36 @@ export class MongoBackend implements QueueBackend {
294
307
  }
295
308
  }
296
309
  else if (operation === "size") {
297
- const count = await col.countDocuments({
298
- queue: queueName,
299
- status: "pending",
300
- });
310
+ // ADR-0022 dec 7: size(status) must not answer a different question.
311
+ // The dead aliases count the .dead_letter store (== deadLetters().length);
312
+ // reserved/completed count by status; else pending. data = status.
313
+ const status = data || "pending";
314
+ let count: number;
315
+ if (status === "dead" || status === "failed" || status === "dead_letter") {
316
+ count = await col.countDocuments({ queue: queueName + ".dead_letter" });
317
+ } else if (status === "reserved" || status === "completed") {
318
+ count = await col.countDocuments({ queue: queueName, status });
319
+ } else {
320
+ count = await col.countDocuments({ queue: queueName, status: "pending" });
321
+ }
301
322
  process.stdout.write(String(count));
302
323
  }
324
+ else if (operation === "reject") {
325
+ // Dead-letter NOW, no retry (ADR-0023). Floor attempts at maxRetries
326
+ // so it reads as exhausted (parity with fail()'s dead-letter branch
327
+ // and size("dead")). data = JSON { id, reason, maxRetries }.
328
+ const info = JSON.parse(data);
329
+ const doc = await col.findOne({ queue: queueName, id: info.id });
330
+ if (doc) {
331
+ const attempts = Math.max((doc.attempts || 0) + 1, info.maxRetries);
332
+ await col.insertOne({
333
+ ...doc, _id: undefined, attempts,
334
+ status: "dead", queue: queueName + ".dead_letter", error: info.reason,
335
+ });
336
+ await col.deleteOne({ _id: doc._id, queue: queueName });
337
+ }
338
+ process.stdout.write("__OK__");
339
+ }
303
340
  else if (operation === "clear") {
304
341
  await col.deleteMany({ queue: queueName });
305
342
  process.stdout.write("__CLEARED__");
@@ -546,12 +583,22 @@ export class MongoBackend implements QueueBackend {
546
583
  }
547
584
  }
548
585
 
549
- size(queue: string): number {
550
- const result = this.execSync("size", queue);
586
+ size(queue: string, status: string = "pending"): number {
587
+ const result = this.execSync("size", queue, status);
551
588
  const num = parseInt(result, 10);
552
589
  return isNaN(num) ? 0 : num;
553
590
  }
554
591
 
592
+ /**
593
+ * Dead-letter a job immediately — no retry (ADR-0023 reject()). Distinct from
594
+ * fail(): fail() requeues until maxRetries is spent. attempts is floored at
595
+ * maxRetries so deadLetters()/size("dead") (which filter/scope to the
596
+ * dead-letter store) see it.
597
+ */
598
+ reject(queue: string, id: string, reason: string, maxRetries: number): void {
599
+ this.execSync("reject", queue, JSON.stringify({ id, reason, maxRetries }));
600
+ }
601
+
555
602
  clear(queue: string): void {
556
603
  this.execSync("clear", queue);
557
604
  }
@@ -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);