@warlock.js/core 5.30.0 → 5.31.0

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 (797) hide show
  1. package/CHANGELOG.md +142 -36
  2. package/README.md +28 -23
  3. package/bin/source-load-hook.mjs +63 -0
  4. package/bin/warlock.js +102 -42
  5. package/esm/cli/commands/add.command.mjs +4 -2
  6. package/esm/cli/commands/add.command.mjs.map +1 -1
  7. package/esm/cli/commands/agents-md/agents-md-command.action.mjs +69 -0
  8. package/esm/cli/commands/agents-md/agents-md-command.action.mjs.map +1 -0
  9. package/esm/cli/commands/agents-md.command.mjs +38 -0
  10. package/esm/cli/commands/agents-md.command.mjs.map +1 -0
  11. package/esm/cli/commands/board/board-command.action.mjs +279 -0
  12. package/esm/cli/commands/board/board-command.action.mjs.map +1 -0
  13. package/esm/cli/commands/board.command.mjs +78 -0
  14. package/esm/cli/commands/board.command.mjs.map +1 -0
  15. package/esm/cli/commands/build.command.mjs +6 -1
  16. package/esm/cli/commands/build.command.mjs.map +1 -1
  17. package/esm/cli/commands/check/check-command.action.mjs +57 -0
  18. package/esm/cli/commands/check/check-command.action.mjs.map +1 -0
  19. package/esm/cli/commands/check/child-typecheck.mjs +66 -0
  20. package/esm/cli/commands/check/child-typecheck.mjs.map +1 -0
  21. package/esm/cli/commands/check/format-problems.mjs +43 -0
  22. package/esm/cli/commands/check/format-problems.mjs.map +1 -0
  23. package/esm/cli/commands/check/typecheck-command.action.mjs +61 -0
  24. package/esm/cli/commands/check/typecheck-command.action.mjs.map +1 -0
  25. package/esm/cli/commands/check.command.mjs +50 -0
  26. package/esm/cli/commands/check.command.mjs.map +1 -0
  27. package/esm/cli/commands/dev-server.command.mjs +9 -1
  28. package/esm/cli/commands/dev-server.command.mjs.map +1 -1
  29. package/esm/cli/commands/doctor/boot-for-diagnostics.mjs +8 -3
  30. package/esm/cli/commands/doctor/boot-for-diagnostics.mjs.map +1 -1
  31. package/esm/cli/commands/doctor/check.types.d.mts +76 -0
  32. package/esm/cli/commands/doctor/check.types.d.mts.map +1 -0
  33. package/esm/cli/commands/doctor/checks/config.check.mjs +1 -0
  34. package/esm/cli/commands/doctor/checks/config.check.mjs.map +1 -1
  35. package/esm/cli/commands/doctor/checks/connectors.check.mjs +2 -0
  36. package/esm/cli/commands/doctor/checks/connectors.check.mjs.map +1 -1
  37. package/esm/cli/commands/doctor/checks/handler-signature.check.mjs +1 -0
  38. package/esm/cli/commands/doctor/checks/handler-signature.check.mjs.map +1 -1
  39. package/esm/cli/commands/doctor/checks/health.check.mjs +3 -0
  40. package/esm/cli/commands/doctor/checks/health.check.mjs.map +1 -1
  41. package/esm/cli/commands/doctor/checks/jwt-secret.check.mjs +1 -0
  42. package/esm/cli/commands/doctor/checks/jwt-secret.check.mjs.map +1 -1
  43. package/esm/cli/commands/doctor/checks/locales-ts-deprecated.check.mjs +1 -0
  44. package/esm/cli/commands/doctor/checks/locales-ts-deprecated.check.mjs.map +1 -1
  45. package/esm/cli/commands/doctor/checks/optional-peers.check.mjs +1 -0
  46. package/esm/cli/commands/doctor/checks/optional-peers.check.mjs.map +1 -1
  47. package/esm/cli/commands/doctor/checks/release-hygiene.check.mjs +4 -0
  48. package/esm/cli/commands/doctor/checks/release-hygiene.check.mjs.map +1 -1
  49. package/esm/cli/commands/doctor/checks/routes.check.mjs +3 -0
  50. package/esm/cli/commands/doctor/checks/routes.check.mjs.map +1 -1
  51. package/esm/cli/commands/doctor/doctor-command.action.mjs +14 -6
  52. package/esm/cli/commands/doctor/doctor-command.action.mjs.map +1 -1
  53. package/esm/cli/commands/doctor/format-report.mjs +30 -2
  54. package/esm/cli/commands/doctor/format-report.mjs.map +1 -1
  55. package/esm/cli/commands/doctor.command.mjs +4 -0
  56. package/esm/cli/commands/doctor.command.mjs.map +1 -1
  57. package/esm/cli/commands/explain/explain-command.action.mjs +64 -0
  58. package/esm/cli/commands/explain/explain-command.action.mjs.map +1 -0
  59. package/esm/cli/commands/explain.command.mjs +23 -0
  60. package/esm/cli/commands/explain.command.mjs.map +1 -0
  61. package/esm/cli/commands/generate/generate.command.mjs +124 -124
  62. package/esm/cli/commands/generate/generate.command.mjs.map +1 -1
  63. package/esm/cli/commands/generate/generators/endpoint.generator.mjs +311 -0
  64. package/esm/cli/commands/generate/generators/endpoint.generator.mjs.map +1 -0
  65. package/esm/cli/commands/generate/generators/migration.generator.mjs +1 -1
  66. package/esm/cli/commands/generate/generators/migration.generator.mjs.map +1 -1
  67. package/esm/cli/commands/generate/generators/model.generator.mjs +1 -1
  68. package/esm/cli/commands/generate/templates/endpoint-stubs.mjs +341 -0
  69. package/esm/cli/commands/generate/templates/endpoint-stubs.mjs.map +1 -0
  70. package/esm/cli/commands/generate/templates/stubs.mjs +19 -6
  71. package/esm/cli/commands/generate/templates/stubs.mjs.map +1 -1
  72. package/esm/cli/commands/generate/utils/endpoint-schema.mjs +137 -0
  73. package/esm/cli/commands/generate/utils/endpoint-schema.mjs.map +1 -0
  74. package/esm/cli/commands/generate/utils/name-parser.mjs +1 -1
  75. package/esm/cli/commands/generate/utils/name-parser.mjs.map +1 -1
  76. package/esm/cli/commands/generate/utils/prompt.mjs.map +1 -1
  77. package/esm/cli/commands/generate/utils/writer.mjs +38 -13
  78. package/esm/cli/commands/generate/utils/writer.mjs.map +1 -1
  79. package/esm/cli/commands/inspect/inspect-command.action.mjs +72 -0
  80. package/esm/cli/commands/inspect/inspect-command.action.mjs.map +1 -0
  81. package/esm/cli/commands/inspect.command.mjs +39 -0
  82. package/esm/cli/commands/inspect.command.mjs.map +1 -0
  83. package/esm/cli/commands/mcp/mcp-command.action.mjs +103 -0
  84. package/esm/cli/commands/mcp/mcp-command.action.mjs.map +1 -0
  85. package/esm/cli/commands/mcp.command.mjs +76 -0
  86. package/esm/cli/commands/mcp.command.mjs.map +1 -0
  87. package/esm/cli/commands/openapi-generator.action.mjs.map +1 -1
  88. package/esm/cli/commands/openapi-generator.command.mjs.map +1 -1
  89. package/esm/cli/commands/postman-generator.action.mjs.map +1 -1
  90. package/esm/cli/commands/postman-generator.command.mjs.map +1 -1
  91. package/esm/cli/commands/routes/routes-diff.action.mjs +1 -1
  92. package/esm/cli/commands/test.command.mjs +131 -0
  93. package/esm/cli/commands/test.command.mjs.map +1 -0
  94. package/esm/cli/commands/trace/trace-command.action.mjs +145 -0
  95. package/esm/cli/commands/trace/trace-command.action.mjs.map +1 -0
  96. package/esm/cli/commands/trace.command.mjs +60 -0
  97. package/esm/cli/commands/trace.command.mjs.map +1 -0
  98. package/esm/cli/commands/typecheck.command.mjs +40 -0
  99. package/esm/cli/commands/typecheck.command.mjs.map +1 -0
  100. package/esm/cli/commands/upgrade.command.mjs +46 -0
  101. package/esm/cli/commands/upgrade.command.mjs.map +1 -0
  102. package/esm/cli/flush-and-exit.mjs +34 -0
  103. package/esm/cli/flush-and-exit.mjs.map +1 -0
  104. package/esm/cli/framework-cli-commands.mjs +23 -1
  105. package/esm/cli/framework-cli-commands.mjs.map +1 -1
  106. package/esm/cli/parse-cli-args.mjs +1 -0
  107. package/esm/cli/parse-cli-args.mjs.map +1 -1
  108. package/esm/config/config-loader.d.mts.map +1 -1
  109. package/esm/config/config-loader.mjs +34 -13
  110. package/esm/config/config-loader.mjs.map +1 -1
  111. package/esm/config/config-schema.d.mts +67 -0
  112. package/esm/config/config-schema.d.mts.map +1 -0
  113. package/esm/config/config-schema.mjs +77 -0
  114. package/esm/config/config-schema.mjs.map +1 -0
  115. package/esm/config/index.d.mts +6 -0
  116. package/esm/config/index.mjs +1 -0
  117. package/esm/connectors/assert-no-reserved-connector-names.d.mts.map +1 -1
  118. package/esm/connectors/assert-no-reserved-connector-names.mjs +2 -1
  119. package/esm/connectors/assert-no-reserved-connector-names.mjs.map +1 -1
  120. package/esm/connectors/assert-unique-connector-names.d.mts.map +1 -1
  121. package/esm/connectors/assert-unique-connector-names.mjs +26 -2
  122. package/esm/connectors/assert-unique-connector-names.mjs.map +1 -1
  123. package/esm/connectors/http-connector.d.mts.map +1 -1
  124. package/esm/connectors/http-connector.mjs +3 -2
  125. package/esm/connectors/http-connector.mjs.map +1 -1
  126. package/esm/connectors/single-server-warnings.mjs.map +1 -1
  127. package/esm/connectors/socket-connector.d.mts.map +1 -1
  128. package/esm/connectors/socket-connector.mjs.map +1 -1
  129. package/esm/container/index.d.mts.map +1 -1
  130. package/esm/container/index.mjs.map +1 -1
  131. package/esm/database/migrate-action.mjs.map +1 -1
  132. package/esm/database/models/database-log/database-log.d.mts +0 -28
  133. package/esm/database/models/database-log/database-log.d.mts.map +1 -1
  134. package/esm/database/seeds/seeder.errors.d.mts +4 -0
  135. package/esm/database/seeds/seeder.errors.d.mts.map +1 -1
  136. package/esm/database/seeds/seeder.errors.mjs +2 -0
  137. package/esm/database/seeds/seeder.errors.mjs.map +1 -1
  138. package/esm/dev-server/boot-precondition-error.mjs +1 -0
  139. package/esm/dev-server/boot-precondition-error.mjs.map +1 -1
  140. package/esm/dev-server/create-worker.mjs +15 -9
  141. package/esm/dev-server/create-worker.mjs.map +1 -1
  142. package/esm/dev-server/dev-agent-tooling.mjs +135 -0
  143. package/esm/dev-server/dev-agent-tooling.mjs.map +1 -0
  144. package/esm/dev-server/dev-logger.mjs.map +1 -1
  145. package/esm/dev-server/development-server.mjs +8 -0
  146. package/esm/dev-server/development-server.mjs.map +1 -1
  147. package/esm/dev-server/files-orchestrator.mjs +4 -5
  148. package/esm/dev-server/files-orchestrator.mjs.map +1 -1
  149. package/esm/dev-server/files-watcher.mjs +1 -1
  150. package/esm/dev-server/health-checker/checkers/eslint-health-checker.mjs +2 -3
  151. package/esm/dev-server/health-checker/checkers/eslint-health-checker.mjs.map +1 -1
  152. package/esm/dev-server/health-checker/checkers/oxlint-health-checker.mjs +33 -0
  153. package/esm/dev-server/health-checker/checkers/oxlint-health-checker.mjs.map +1 -0
  154. package/esm/dev-server/health-checker/default-health-checkers.mjs +31 -0
  155. package/esm/dev-server/health-checker/default-health-checkers.mjs.map +1 -0
  156. package/esm/dev-server/health-checker/file-health-checker.contract.d.mts +9 -3
  157. package/esm/dev-server/health-checker/file-health-checker.contract.d.mts.map +1 -1
  158. package/esm/dev-server/health-checker/files-healthcare.manager.mjs +15 -4
  159. package/esm/dev-server/health-checker/files-healthcare.manager.mjs.map +1 -1
  160. package/esm/dev-server/health-checker/oxlint-runner.mjs +302 -0
  161. package/esm/dev-server/health-checker/oxlint-runner.mjs.map +1 -0
  162. package/esm/dev-server/health-checker/workers/oxlint-health.worker.d.mts +1 -0
  163. package/esm/dev-server/health-checker/workers/oxlint-health.worker.mjs +42 -0
  164. package/esm/dev-server/health-checker/workers/oxlint-health.worker.mjs.map +1 -0
  165. package/esm/dev-server/loader/loader-port-acks.mjs +1 -1
  166. package/esm/dev-server/loader/loader-port-acks.mjs.map +1 -1
  167. package/esm/dev-server/loader/register-loader.mjs +1 -1
  168. package/esm/dev-server/loader/register-loader.mjs.map +1 -1
  169. package/esm/dev-server/loader/resolve-hook.mjs +1 -1
  170. package/esm/dev-server/loader/transpile-cache.mjs +1 -1
  171. package/esm/dev-server/model-resource-typings.mjs.map +1 -1
  172. package/esm/dev-server/module-loader.mjs +1 -0
  173. package/esm/dev-server/module-loader.mjs.map +1 -1
  174. package/esm/dev-server/parse-imports.mjs +1 -1
  175. package/esm/dev-server/parse-imports.mjs.map +1 -1
  176. package/esm/dev-server/route-types-publisher.mjs +1 -1
  177. package/esm/dev-server/start-development-server.mjs.map +1 -1
  178. package/esm/dev-server/translation-keys-sources.mjs +1 -1
  179. package/esm/dev-server/translation-keys-sources.mjs.map +1 -1
  180. package/esm/dev-server/tsconfig-manager.mjs.map +1 -1
  181. package/esm/dev-server/type-generator.mjs +3 -3
  182. package/esm/dev-server/type-generator.mjs.map +1 -1
  183. package/esm/dev-server/types.d.mts.map +1 -1
  184. package/esm/errors/container-key-missing-error.mjs +1 -0
  185. package/esm/errors/container-key-missing-error.mjs.map +1 -1
  186. package/esm/errors/error-codes.d.mts +653 -0
  187. package/esm/errors/error-codes.d.mts.map +1 -0
  188. package/esm/errors/error-codes.mjs +634 -0
  189. package/esm/errors/error-codes.mjs.map +1 -0
  190. package/esm/errors/esbuild-binary-missing-error.mjs +1 -0
  191. package/esm/errors/esbuild-binary-missing-error.mjs.map +1 -1
  192. package/esm/errors/index.d.mts +3 -1
  193. package/esm/errors/index.mjs +3 -1
  194. package/esm/errors/unknown-locale-error.mjs +1 -0
  195. package/esm/errors/unknown-locale-error.mjs.map +1 -1
  196. package/esm/errors/warlock-error.d.mts +23 -0
  197. package/esm/errors/warlock-error.d.mts.map +1 -0
  198. package/esm/errors/warlock-error.mjs +22 -0
  199. package/esm/errors/warlock-error.mjs.map +1 -0
  200. package/esm/generations/add-command.action.mjs +19 -3
  201. package/esm/generations/add-command.action.mjs.map +1 -1
  202. package/esm/generations/features/access.feature.mjs +1 -1
  203. package/esm/generations/features/access.feature.mjs.map +1 -1
  204. package/esm/generations/features/bull-board.feature.mjs +1 -1
  205. package/esm/generations/features/bull-board.feature.mjs.map +1 -1
  206. package/esm/generations/features/mysql.feature.mjs.map +1 -1
  207. package/esm/generations/features/notifications.feature.mjs +5 -2
  208. package/esm/generations/features/notifications.feature.mjs.map +1 -1
  209. package/esm/generations/features/queue.feature.mjs +1 -1
  210. package/esm/generations/features/queue.feature.mjs.map +1 -1
  211. package/esm/generations/features/react-email.feature.mjs +1 -1
  212. package/esm/generations/features/react-email.feature.mjs.map +1 -1
  213. package/esm/generations/features/shadcn.feature.mjs +1 -1
  214. package/esm/generations/features/shadcn.feature.mjs.map +1 -1
  215. package/esm/generations/features/shared/link-ai-package-import.mjs +1 -1
  216. package/esm/generations/features/shared/link-ai-package-import.mjs.map +1 -1
  217. package/esm/generations/features/shared/merge-web-sitemap-config.mjs.map +1 -1
  218. package/esm/generations/features/shared/patch-tsconfig-include.mjs +1 -1
  219. package/esm/generations/features/shared/patch-tsconfig-include.mjs.map +1 -1
  220. package/esm/generations/features/shared/relocate-conflicting-home-route.mjs +1 -1
  221. package/esm/generations/features/shared/relocate-conflicting-home-route.mjs.map +1 -1
  222. package/esm/generations/features/sitemap.feature.mjs +1 -1
  223. package/esm/generations/features/sitemap.feature.mjs.map +1 -1
  224. package/esm/generations/features/tailwind.feature.mjs +1 -1
  225. package/esm/generations/features/tailwind.feature.mjs.map +1 -1
  226. package/esm/generations/features/test.feature.mjs +11 -6
  227. package/esm/generations/features/test.feature.mjs.map +1 -1
  228. package/esm/generations/features/web.feature.mjs +1 -1
  229. package/esm/generations/features/web.feature.mjs.map +1 -1
  230. package/esm/generations/generation-fs.mjs +73 -0
  231. package/esm/generations/generation-fs.mjs.map +1 -0
  232. package/esm/generations/generation-run.mjs +185 -0
  233. package/esm/generations/generation-run.mjs.map +1 -0
  234. package/esm/generations/stubs.mjs +155 -13
  235. package/esm/generations/stubs.mjs.map +1 -1
  236. package/esm/http/bounded-stream-queue.d.mts +10 -0
  237. package/esm/http/bounded-stream-queue.d.mts.map +1 -0
  238. package/esm/http/bounded-stream-queue.mjs +97 -0
  239. package/esm/http/bounded-stream-queue.mjs.map +1 -0
  240. package/esm/http/build-rate-limit-options.d.mts +13 -0
  241. package/esm/http/build-rate-limit-options.d.mts.map +1 -1
  242. package/esm/http/build-rate-limit-options.mjs.map +1 -1
  243. package/esm/http/conditional-request.d.mts +86 -0
  244. package/esm/http/conditional-request.d.mts.map +1 -0
  245. package/esm/http/conditional-request.mjs +121 -0
  246. package/esm/http/conditional-request.mjs.map +1 -0
  247. package/esm/http/csp.d.mts +2 -0
  248. package/esm/http/csp.d.mts.map +1 -1
  249. package/esm/http/csp.mjs +1 -0
  250. package/esm/http/csp.mjs.map +1 -1
  251. package/esm/http/csrf-origin-policy.mjs.map +1 -1
  252. package/esm/http/database/RequestLog.d.mts +24 -84
  253. package/esm/http/database/RequestLog.d.mts.map +1 -1
  254. package/esm/http/errors/errors.d.mts +2 -0
  255. package/esm/http/errors/errors.d.mts.map +1 -1
  256. package/esm/http/errors/errors.mjs +1 -0
  257. package/esm/http/errors/errors.mjs.map +1 -1
  258. package/esm/http/escape-header-value.d.mts +18 -0
  259. package/esm/http/escape-header-value.d.mts.map +1 -0
  260. package/esm/http/escape-header-value.mjs +27 -0
  261. package/esm/http/escape-header-value.mjs.map +1 -0
  262. package/esm/http/flush-pending-cookies.mjs.map +1 -1
  263. package/esm/http/index.d.mts +5 -2
  264. package/esm/http/index.mjs +4 -1
  265. package/esm/http/lazy-static.mjs +194 -0
  266. package/esm/http/lazy-static.mjs.map +1 -0
  267. package/esm/http/middleware/idempotency.middleware.mjs.map +1 -1
  268. package/esm/http/plugins.d.mts.map +1 -1
  269. package/esm/http/plugins.mjs +7 -4
  270. package/esm/http/plugins.mjs.map +1 -1
  271. package/esm/http/rate-limit-cache-store.mjs +2 -1
  272. package/esm/http/rate-limit-cache-store.mjs.map +1 -1
  273. package/esm/http/request-log-snapshot.d.mts +12 -5
  274. package/esm/http/request-log-snapshot.d.mts.map +1 -1
  275. package/esm/http/request-log-snapshot.mjs +83 -4
  276. package/esm/http/request-log-snapshot.mjs.map +1 -1
  277. package/esm/http/request.d.mts +2 -2
  278. package/esm/http/request.d.mts.map +1 -1
  279. package/esm/http/request.mjs +5 -4
  280. package/esm/http/request.mjs.map +1 -1
  281. package/esm/http/response.d.mts +11 -1
  282. package/esm/http/response.d.mts.map +1 -1
  283. package/esm/http/response.mjs +164 -76
  284. package/esm/http/response.mjs.map +1 -1
  285. package/esm/http/safe-redirect.d.mts +51 -0
  286. package/esm/http/safe-redirect.d.mts.map +1 -0
  287. package/esm/http/safe-redirect.mjs +80 -0
  288. package/esm/http/safe-redirect.mjs.map +1 -0
  289. package/esm/http/serialized.d.mts.map +1 -1
  290. package/esm/http/server.d.mts +3 -1
  291. package/esm/http/server.d.mts.map +1 -1
  292. package/esm/http/server.mjs +14 -4
  293. package/esm/http/server.mjs.map +1 -1
  294. package/esm/http/stream-transport.mjs +4 -2
  295. package/esm/http/stream-transport.mjs.map +1 -1
  296. package/esm/http/types.d.mts +44 -11
  297. package/esm/http/types.d.mts.map +1 -1
  298. package/esm/http/uploads/generate-image-variant.mjs +1 -1
  299. package/esm/http/uploads/generate-image-variants.d.mts +10 -7
  300. package/esm/http/uploads/generate-image-variants.d.mts.map +1 -1
  301. package/esm/http/uploads/generate-image-variants.mjs +61 -24
  302. package/esm/http/uploads/generate-image-variants.mjs.map +1 -1
  303. package/esm/http/uploads/image-variant-cache-entries.mjs +78 -0
  304. package/esm/http/uploads/image-variant-cache-entries.mjs.map +1 -0
  305. package/esm/http/uploads/image-variant-cache.mjs +72 -30
  306. package/esm/http/uploads/image-variant-cache.mjs.map +1 -1
  307. package/esm/http/uploads/image-variant-types.d.mts +2 -0
  308. package/esm/http/uploads/image-variant-types.d.mts.map +1 -1
  309. package/esm/http/uploads/image-variants-config-error.d.mts +3 -1
  310. package/esm/http/uploads/image-variants-config-error.d.mts.map +1 -1
  311. package/esm/http/uploads/image-variants-config-error.mjs +3 -2
  312. package/esm/http/uploads/image-variants-config-error.mjs.map +1 -1
  313. package/esm/http/uploads/load-variant-source.mjs +1 -1
  314. package/esm/http/uploads/load-variant-source.mjs.map +1 -1
  315. package/esm/http/uploads/purge-image-variants.d.mts +7 -2
  316. package/esm/http/uploads/purge-image-variants.d.mts.map +1 -1
  317. package/esm/http/uploads/purge-image-variants.mjs +16 -16
  318. package/esm/http/uploads/purge-image-variants.mjs.map +1 -1
  319. package/esm/http/uploads/remote-variant-source.mjs +115 -0
  320. package/esm/http/uploads/remote-variant-source.mjs.map +1 -0
  321. package/esm/http/uploads/resolve-image-variant.d.mts +3 -4
  322. package/esm/http/uploads/resolve-image-variant.d.mts.map +1 -1
  323. package/esm/http/uploads/resolve-image-variant.mjs +10 -18
  324. package/esm/http/uploads/resolve-image-variant.mjs.map +1 -1
  325. package/esm/http/uploads/resolve-image-variants-config.mjs +5 -3
  326. package/esm/http/uploads/resolve-image-variants-config.mjs.map +1 -1
  327. package/esm/http/uploads/resolve-remote-image-variant.mjs +89 -0
  328. package/esm/http/uploads/resolve-remote-image-variant.mjs.map +1 -0
  329. package/esm/http/uploads/trim-image-variant-cache.mjs +193 -0
  330. package/esm/http/uploads/trim-image-variant-cache.mjs.map +1 -0
  331. package/esm/http/uploads/uploaded-file.controller.d.mts +5 -3
  332. package/esm/http/uploads/uploaded-file.controller.d.mts.map +1 -1
  333. package/esm/http/uploads/uploaded-file.controller.mjs +16 -8
  334. package/esm/http/uploads/uploaded-file.controller.mjs.map +1 -1
  335. package/esm/http/uploads/variant-cache-key.mjs +1 -1
  336. package/esm/http/uploads-types.d.mts +6 -2
  337. package/esm/http/uploads-types.d.mts.map +1 -1
  338. package/esm/index.d.mts +17 -6
  339. package/esm/index.mjs +14 -3
  340. package/esm/localization/parse-locale-dictionary.d.mts +2 -0
  341. package/esm/localization/parse-locale-dictionary.d.mts.map +1 -1
  342. package/esm/localization/parse-locale-dictionary.mjs +1 -0
  343. package/esm/localization/parse-locale-dictionary.mjs.map +1 -1
  344. package/esm/mail/config.d.mts +12 -1
  345. package/esm/mail/config.d.mts.map +1 -1
  346. package/esm/mail/config.mjs +16 -1
  347. package/esm/mail/config.mjs.map +1 -1
  348. package/esm/mail/index.d.mts +1 -1
  349. package/esm/mail/index.mjs +1 -1
  350. package/esm/net/index.d.mts +2 -0
  351. package/esm/net/index.mjs +3 -0
  352. package/esm/openapi/build-openapi-document.d.mts.map +1 -1
  353. package/esm/openapi/build-openapi-document.mjs +8 -5
  354. package/esm/openapi/build-openapi-document.mjs.map +1 -1
  355. package/esm/openapi/error-responses.mjs +28 -2
  356. package/esm/openapi/error-responses.mjs.map +1 -1
  357. package/esm/openapi/postman/build-request-body.mjs.map +1 -1
  358. package/esm/openapi/postman/build-request-url.mjs.map +1 -1
  359. package/esm/openapi/postman/build-saved-responses.mjs.map +1 -1
  360. package/esm/openapi/postman/example-from-schema.d.mts.map +1 -1
  361. package/esm/openapi/postman/example-from-schema.mjs.map +1 -1
  362. package/esm/openapi/postman/openapi-to-postman-collection.d.mts.map +1 -1
  363. package/esm/openapi/postman/openapi-to-postman-collection.mjs.map +1 -1
  364. package/esm/openapi/postman/schema-refs.mjs.map +1 -1
  365. package/esm/openapi/postman/uuid-v5.mjs.map +1 -1
  366. package/esm/openapi/request-schemas.mjs.map +1 -1
  367. package/esm/openapi/response-schemas.mjs.map +1 -1
  368. package/esm/openapi/schema-registry.d.mts +1 -0
  369. package/esm/openapi/schema-registry.mjs.map +1 -1
  370. package/esm/openapi/security.mjs.map +1 -1
  371. package/esm/openapi/types.d.mts +3 -2
  372. package/esm/openapi/types.d.mts.map +1 -1
  373. package/esm/production/assert-generated-imports.mjs +1 -0
  374. package/esm/production/assert-generated-imports.mjs.map +1 -1
  375. package/esm/production/production-builder.mjs +12 -4
  376. package/esm/production/production-builder.mjs.map +1 -1
  377. package/esm/production/resolve-build-config.mjs +1 -0
  378. package/esm/production/resolve-build-config.mjs.map +1 -1
  379. package/esm/production/route-registration-child.mjs.map +1 -1
  380. package/esm/production/route-registration-snapshot.mjs.map +1 -1
  381. package/esm/repositories/contracts/query-builder.contract.d.mts.map +1 -1
  382. package/esm/repositories/contracts/types.d.mts.map +1 -1
  383. package/esm/repositories/repository.manager.mjs.map +1 -1
  384. package/esm/resource/resource.d.mts.map +1 -1
  385. package/esm/resource/resource.mjs.map +1 -1
  386. package/esm/resource/types.d.mts.map +1 -1
  387. package/esm/restful/restful.d.mts +5 -5
  388. package/esm/restful/restful.d.mts.map +1 -1
  389. package/esm/restful/restful.mjs.map +1 -1
  390. package/esm/router/build-route-rate-limit.mjs.map +1 -1
  391. package/esm/router/dev-dispatch.mjs +31 -12
  392. package/esm/router/dev-dispatch.mjs.map +1 -1
  393. package/esm/router/index.d.mts +4 -2
  394. package/esm/router/index.mjs +2 -0
  395. package/esm/router/log-request-lifecycle.mjs.map +1 -1
  396. package/esm/router/positional-handler-diagnostics.d.mts.map +1 -1
  397. package/esm/router/positional-handler-diagnostics.mjs.map +1 -1
  398. package/esm/router/resolve-route-rate-limit.mjs +47 -0
  399. package/esm/router/resolve-route-rate-limit.mjs.map +1 -0
  400. package/esm/router/resolve-route-validation.mjs +19 -0
  401. package/esm/router/resolve-route-validation.mjs.map +1 -0
  402. package/esm/router/route-guard-names.mjs +30 -0
  403. package/esm/router/route-guard-names.mjs.map +1 -0
  404. package/esm/router/route-name-taken-error.d.mts +14 -0
  405. package/esm/router/route-name-taken-error.d.mts.map +1 -0
  406. package/esm/router/route-name-taken-error.mjs +18 -0
  407. package/esm/router/route-name-taken-error.mjs.map +1 -0
  408. package/esm/router/router.d.mts +32 -13
  409. package/esm/router/router.d.mts.map +1 -1
  410. package/esm/router/router.mjs +71 -38
  411. package/esm/router/router.mjs.map +1 -1
  412. package/esm/router/types.d.mts +137 -17
  413. package/esm/router/types.d.mts.map +1 -1
  414. package/esm/router/unguarded-route-diagnostics.d.mts +40 -0
  415. package/esm/router/unguarded-route-diagnostics.d.mts.map +1 -0
  416. package/esm/router/unguarded-route-diagnostics.mjs +95 -0
  417. package/esm/router/unguarded-route-diagnostics.mjs.map +1 -0
  418. package/esm/storage/drivers/cloud-driver.d.mts.map +1 -1
  419. package/esm/storage/drivers/cloud-driver.mjs.map +1 -1
  420. package/esm/storage/drivers/local-driver.d.mts.map +1 -1
  421. package/esm/storage/drivers/local-driver.mjs.map +1 -1
  422. package/esm/storage/local-disk-roots.mjs +24 -0
  423. package/esm/storage/local-disk-roots.mjs.map +1 -0
  424. package/esm/storage/storage.d.mts +14 -0
  425. package/esm/storage/storage.d.mts.map +1 -1
  426. package/esm/storage/storage.mjs +22 -0
  427. package/esm/storage/storage.mjs.map +1 -1
  428. package/esm/storage/types.d.mts.map +1 -1
  429. package/esm/storage/utils/storage-not-initialized-error.d.mts +2 -0
  430. package/esm/storage/utils/storage-not-initialized-error.d.mts.map +1 -1
  431. package/esm/storage/utils/storage-not-initialized-error.mjs +1 -0
  432. package/esm/storage/utils/storage-not-initialized-error.mjs.map +1 -1
  433. package/esm/tests/authenticated-test-client.d.mts +32 -0
  434. package/esm/tests/authenticated-test-client.d.mts.map +1 -0
  435. package/esm/tests/authenticated-test-client.mjs +40 -0
  436. package/esm/tests/authenticated-test-client.mjs.map +1 -0
  437. package/esm/tests/factories.d.mts +59 -0
  438. package/esm/tests/factories.d.mts.map +1 -0
  439. package/esm/tests/factories.mjs +105 -0
  440. package/esm/tests/factories.mjs.map +1 -0
  441. package/esm/tests/fakes/cache-fake.d.mts +29 -0
  442. package/esm/tests/fakes/cache-fake.d.mts.map +1 -0
  443. package/esm/tests/fakes/cache-fake.mjs +79 -0
  444. package/esm/tests/fakes/cache-fake.mjs.map +1 -0
  445. package/esm/tests/fakes/clock-fake.d.mts +32 -0
  446. package/esm/tests/fakes/clock-fake.d.mts.map +1 -0
  447. package/esm/tests/fakes/clock-fake.mjs +69 -0
  448. package/esm/tests/fakes/clock-fake.mjs.map +1 -0
  449. package/esm/tests/fakes/fake-assertion-error.d.mts +13 -0
  450. package/esm/tests/fakes/fake-assertion-error.d.mts.map +1 -0
  451. package/esm/tests/fakes/fake-assertion-error.mjs +20 -0
  452. package/esm/tests/fakes/fake-assertion-error.mjs.map +1 -0
  453. package/esm/tests/fakes/ids-fake.d.mts +22 -0
  454. package/esm/tests/fakes/ids-fake.d.mts.map +1 -0
  455. package/esm/tests/fakes/ids-fake.mjs +57 -0
  456. package/esm/tests/fakes/ids-fake.mjs.map +1 -0
  457. package/esm/tests/fakes/index.d.mts +8 -0
  458. package/esm/tests/fakes/index.mjs +10 -0
  459. package/esm/tests/fakes/mail-fake.d.mts +41 -0
  460. package/esm/tests/fakes/mail-fake.d.mts.map +1 -0
  461. package/esm/tests/fakes/mail-fake.mjs +111 -0
  462. package/esm/tests/fakes/mail-fake.mjs.map +1 -0
  463. package/esm/tests/fakes/queue-fake.d.mts +48 -0
  464. package/esm/tests/fakes/queue-fake.d.mts.map +1 -0
  465. package/esm/tests/fakes/queue-fake.mjs +134 -0
  466. package/esm/tests/fakes/queue-fake.mjs.map +1 -0
  467. package/esm/tests/fakes/storage-fake.d.mts +31 -0
  468. package/esm/tests/fakes/storage-fake.d.mts.map +1 -0
  469. package/esm/tests/fakes/storage-fake.mjs +122 -0
  470. package/esm/tests/fakes/storage-fake.mjs.map +1 -0
  471. package/esm/tests/fakes/test-fakes.d.mts +39 -0
  472. package/esm/tests/fakes/test-fakes.d.mts.map +1 -0
  473. package/esm/tests/fakes/test-fakes.mjs +90 -0
  474. package/esm/tests/fakes/test-fakes.mjs.map +1 -0
  475. package/esm/tests/index.d.mts +14 -2
  476. package/esm/tests/index.mjs +15 -2
  477. package/esm/tests/snapshot-response.d.mts +34 -0
  478. package/esm/tests/snapshot-response.d.mts.map +1 -0
  479. package/esm/tests/snapshot-response.mjs +42 -0
  480. package/esm/tests/snapshot-response.mjs.map +1 -0
  481. package/esm/tests/start-http-development-server.d.mts +22 -0
  482. package/esm/tests/start-http-development-server.d.mts.map +1 -1
  483. package/esm/tests/start-http-development-server.mjs +36 -3
  484. package/esm/tests/start-http-development-server.mjs.map +1 -1
  485. package/esm/tests/test-helpers.d.mts +20 -1
  486. package/esm/tests/test-helpers.d.mts.map +1 -1
  487. package/esm/tests/test-helpers.mjs +40 -27
  488. package/esm/tests/test-helpers.mjs.map +1 -1
  489. package/esm/tests/test-http-server.d.mts +62 -0
  490. package/esm/tests/test-http-server.d.mts.map +1 -0
  491. package/esm/tests/test-http-server.mjs +119 -0
  492. package/esm/tests/test-http-server.mjs.map +1 -0
  493. package/esm/tests/vitest-setup.d.mts +11 -0
  494. package/esm/tests/vitest-setup.d.mts.map +1 -1
  495. package/esm/tests/vitest-setup.mjs +12 -4
  496. package/esm/tests/vitest-setup.mjs.map +1 -1
  497. package/esm/tooling/agents-md/agents-md-markers.d.mts +27 -0
  498. package/esm/tooling/agents-md/agents-md-markers.d.mts.map +1 -0
  499. package/esm/tooling/agents-md/agents-md-markers.mjs +59 -0
  500. package/esm/tooling/agents-md/agents-md-markers.mjs.map +1 -0
  501. package/esm/tooling/agents-md/agents-md-refresher.d.mts +36 -0
  502. package/esm/tooling/agents-md/agents-md-refresher.d.mts.map +1 -0
  503. package/esm/tooling/agents-md/agents-md-refresher.mjs +72 -0
  504. package/esm/tooling/agents-md/agents-md-refresher.mjs.map +1 -0
  505. package/esm/tooling/agents-md/index.mjs +7 -0
  506. package/esm/tooling/agents-md/installed-skills.d.mts +30 -0
  507. package/esm/tooling/agents-md/installed-skills.d.mts.map +1 -0
  508. package/esm/tooling/agents-md/installed-skills.mjs +74 -0
  509. package/esm/tooling/agents-md/installed-skills.mjs.map +1 -0
  510. package/esm/tooling/agents-md/render-agents-md.d.mts +33 -0
  511. package/esm/tooling/agents-md/render-agents-md.d.mts.map +1 -0
  512. package/esm/tooling/agents-md/render-agents-md.mjs +195 -0
  513. package/esm/tooling/agents-md/render-agents-md.mjs.map +1 -0
  514. package/esm/tooling/agents-md/write-agents-md.d.mts +33 -0
  515. package/esm/tooling/agents-md/write-agents-md.d.mts.map +1 -0
  516. package/esm/tooling/agents-md/write-agents-md.mjs +49 -0
  517. package/esm/tooling/agents-md/write-agents-md.mjs.map +1 -0
  518. package/esm/tooling/board/board-message.d.mts +31 -0
  519. package/esm/tooling/board/board-message.d.mts.map +1 -0
  520. package/esm/tooling/board/board-message.mjs +86 -0
  521. package/esm/tooling/board/board-message.mjs.map +1 -0
  522. package/esm/tooling/board/board.d.mts +109 -0
  523. package/esm/tooling/board/board.d.mts.map +1 -0
  524. package/esm/tooling/board/board.mjs +406 -0
  525. package/esm/tooling/board/board.mjs.map +1 -0
  526. package/esm/tooling/board/board.types.d.mts +72 -0
  527. package/esm/tooling/board/board.types.d.mts.map +1 -0
  528. package/esm/tooling/board/board.types.mjs +54 -0
  529. package/esm/tooling/board/board.types.mjs.map +1 -0
  530. package/esm/tooling/check/changed-files.d.mts +33 -0
  531. package/esm/tooling/check/changed-files.d.mts.map +1 -0
  532. package/esm/tooling/check/changed-files.mjs +73 -0
  533. package/esm/tooling/check/changed-files.mjs.map +1 -0
  534. package/esm/tooling/check/check-report.types.d.mts +62 -0
  535. package/esm/tooling/check/check-report.types.d.mts.map +1 -0
  536. package/esm/tooling/check/check-report.types.mjs +37 -0
  537. package/esm/tooling/check/check-report.types.mjs.map +1 -0
  538. package/esm/tooling/check/code-style-conventions.mjs +106 -0
  539. package/esm/tooling/check/code-style-conventions.mjs.map +1 -0
  540. package/esm/tooling/check/conventions-step.d.mts +34 -0
  541. package/esm/tooling/check/conventions-step.d.mts.map +1 -0
  542. package/esm/tooling/check/conventions-step.mjs +184 -0
  543. package/esm/tooling/check/conventions-step.mjs.map +1 -0
  544. package/esm/tooling/check/environment-step.d.mts +18 -0
  545. package/esm/tooling/check/environment-step.d.mts.map +1 -0
  546. package/esm/tooling/check/environment-step.mjs +78 -0
  547. package/esm/tooling/check/environment-step.mjs.map +1 -0
  548. package/esm/tooling/check/index.d.mts +8 -0
  549. package/esm/tooling/check/index.mjs +10 -0
  550. package/esm/tooling/check/lint-step.d.mts +19 -0
  551. package/esm/tooling/check/lint-step.d.mts.map +1 -0
  552. package/esm/tooling/check/lint-step.mjs +111 -0
  553. package/esm/tooling/check/lint-step.mjs.map +1 -0
  554. package/esm/tooling/check/request-input.mjs +132 -0
  555. package/esm/tooling/check/request-input.mjs.map +1 -0
  556. package/esm/tooling/check/run-check.d.mts +35 -0
  557. package/esm/tooling/check/run-check.d.mts.map +1 -0
  558. package/esm/tooling/check/run-check.mjs +192 -0
  559. package/esm/tooling/check/run-check.mjs.map +1 -0
  560. package/esm/tooling/check/run-tests.d.mts +48 -0
  561. package/esm/tooling/check/run-tests.d.mts.map +1 -0
  562. package/esm/tooling/check/run-tests.mjs +93 -0
  563. package/esm/tooling/check/run-tests.mjs.map +1 -0
  564. package/esm/tooling/check/security-conventions.mjs +648 -0
  565. package/esm/tooling/check/security-conventions.mjs.map +1 -0
  566. package/esm/tooling/check/source-ast.mjs +195 -0
  567. package/esm/tooling/check/source-ast.mjs.map +1 -0
  568. package/esm/tooling/check/source-files.mjs +27 -0
  569. package/esm/tooling/check/source-files.mjs.map +1 -0
  570. package/esm/tooling/check/tests-step.d.mts +34 -0
  571. package/esm/tooling/check/tests-step.d.mts.map +1 -0
  572. package/esm/tooling/check/tests-step.mjs +177 -0
  573. package/esm/tooling/check/tests-step.mjs.map +1 -0
  574. package/esm/tooling/dev-state/dev-server-state.d.mts +39 -0
  575. package/esm/tooling/dev-state/dev-server-state.d.mts.map +1 -0
  576. package/esm/tooling/dev-state/dev-server-state.mjs +67 -0
  577. package/esm/tooling/dev-state/dev-server-state.mjs.map +1 -0
  578. package/esm/tooling/devtools/devtools-client.d.mts +91 -0
  579. package/esm/tooling/devtools/devtools-client.d.mts.map +1 -0
  580. package/esm/tooling/devtools/devtools-client.mjs +155 -0
  581. package/esm/tooling/devtools/devtools-client.mjs.map +1 -0
  582. package/esm/tooling/generate/generation.d.mts +78 -0
  583. package/esm/tooling/generate/generation.d.mts.map +1 -0
  584. package/esm/tooling/generate/generation.mjs +123 -0
  585. package/esm/tooling/generate/generation.mjs.map +1 -0
  586. package/esm/tooling/generate/generator-spec.d.mts +57 -0
  587. package/esm/tooling/generate/generator-spec.d.mts.map +1 -0
  588. package/esm/tooling/generate/generator-spec.mjs +102 -0
  589. package/esm/tooling/generate/generator-spec.mjs.map +1 -0
  590. package/esm/tooling/generate/index.mjs +6 -0
  591. package/esm/tooling/generate/project-files.mjs +121 -0
  592. package/esm/tooling/generate/project-files.mjs.map +1 -0
  593. package/esm/tooling/generate/run-cli.d.mts +40 -0
  594. package/esm/tooling/generate/run-cli.d.mts.map +1 -0
  595. package/esm/tooling/generate/run-cli.mjs +93 -0
  596. package/esm/tooling/generate/run-cli.mjs.map +1 -0
  597. package/esm/tooling/generate/unified-diff.d.mts +37 -0
  598. package/esm/tooling/generate/unified-diff.d.mts.map +1 -0
  599. package/esm/tooling/generate/unified-diff.mjs +161 -0
  600. package/esm/tooling/generate/unified-diff.mjs.map +1 -0
  601. package/esm/tooling/index.d.mts +41 -0
  602. package/esm/tooling/index.mjs +45 -0
  603. package/esm/tooling/inspect/boot-for-inspect.d.mts +23 -0
  604. package/esm/tooling/inspect/boot-for-inspect.d.mts.map +1 -0
  605. package/esm/tooling/inspect/boot-for-inspect.mjs +63 -0
  606. package/esm/tooling/inspect/boot-for-inspect.mjs.map +1 -0
  607. package/esm/tooling/inspect/collect-config.mjs +77 -0
  608. package/esm/tooling/inspect/collect-config.mjs.map +1 -0
  609. package/esm/tooling/inspect/collect-models.d.mts +9 -0
  610. package/esm/tooling/inspect/collect-models.d.mts.map +1 -0
  611. package/esm/tooling/inspect/collect-models.mjs +154 -0
  612. package/esm/tooling/inspect/collect-models.mjs.map +1 -0
  613. package/esm/tooling/inspect/collect-packages.d.mts +10 -0
  614. package/esm/tooling/inspect/collect-packages.d.mts.map +1 -0
  615. package/esm/tooling/inspect/collect-packages.mjs +157 -0
  616. package/esm/tooling/inspect/collect-packages.mjs.map +1 -0
  617. package/esm/tooling/inspect/collect-pages.mjs +108 -0
  618. package/esm/tooling/inspect/collect-pages.mjs.map +1 -0
  619. package/esm/tooling/inspect/collect-routes.mjs +168 -0
  620. package/esm/tooling/inspect/collect-routes.mjs.map +1 -0
  621. package/esm/tooling/inspect/event-recorder.d.mts +37 -0
  622. package/esm/tooling/inspect/event-recorder.d.mts.map +1 -0
  623. package/esm/tooling/inspect/event-recorder.mjs +91 -0
  624. package/esm/tooling/inspect/event-recorder.mjs.map +1 -0
  625. package/esm/tooling/inspect/index.d.mts +7 -0
  626. package/esm/tooling/inspect/index.mjs +7 -0
  627. package/esm/tooling/inspect/inspect-app.d.mts +53 -0
  628. package/esm/tooling/inspect/inspect-app.d.mts.map +1 -0
  629. package/esm/tooling/inspect/inspect-app.mjs +92 -0
  630. package/esm/tooling/inspect/inspect-app.mjs.map +1 -0
  631. package/esm/tooling/inspect/inspect-report.schema.d.mts +15 -0
  632. package/esm/tooling/inspect/inspect-report.schema.d.mts.map +1 -0
  633. package/esm/tooling/inspect/inspect-report.schema.mjs +463 -0
  634. package/esm/tooling/inspect/inspect-report.schema.mjs.map +1 -0
  635. package/esm/tooling/inspect/inspect-report.types.d.mts +196 -0
  636. package/esm/tooling/inspect/inspect-report.types.d.mts.map +1 -0
  637. package/esm/tooling/inspect/inspect-report.types.mjs +33 -0
  638. package/esm/tooling/inspect/inspect-report.types.mjs.map +1 -0
  639. package/esm/tooling/mcp/create-mcp-context.d.mts +26 -0
  640. package/esm/tooling/mcp/create-mcp-context.d.mts.map +1 -0
  641. package/esm/tooling/mcp/create-mcp-context.mjs +75 -0
  642. package/esm/tooling/mcp/create-mcp-context.mjs.map +1 -0
  643. package/esm/tooling/mcp/db-schema.d.mts +49 -0
  644. package/esm/tooling/mcp/db-schema.d.mts.map +1 -0
  645. package/esm/tooling/mcp/db-schema.mjs +56 -0
  646. package/esm/tooling/mcp/db-schema.mjs.map +1 -0
  647. package/esm/tooling/mcp/docs-search.d.mts +72 -0
  648. package/esm/tooling/mcp/docs-search.d.mts.map +1 -0
  649. package/esm/tooling/mcp/docs-search.mjs +178 -0
  650. package/esm/tooling/mcp/docs-search.mjs.map +1 -0
  651. package/esm/tooling/mcp/index.mjs +9 -0
  652. package/esm/tooling/mcp/load-ai-tools.d.mts +62 -0
  653. package/esm/tooling/mcp/load-ai-tools.d.mts.map +1 -0
  654. package/esm/tooling/mcp/load-ai-tools.mjs +44 -0
  655. package/esm/tooling/mcp/load-ai-tools.mjs.map +1 -0
  656. package/esm/tooling/mcp/mcp-tools.d.mts +60 -0
  657. package/esm/tooling/mcp/mcp-tools.d.mts.map +1 -0
  658. package/esm/tooling/mcp/mcp-tools.mjs +513 -0
  659. package/esm/tooling/mcp/mcp-tools.mjs.map +1 -0
  660. package/esm/tooling/mcp/start-mcp-server.d.mts +56 -0
  661. package/esm/tooling/mcp/start-mcp-server.d.mts.map +1 -0
  662. package/esm/tooling/mcp/start-mcp-server.mjs +126 -0
  663. package/esm/tooling/mcp/start-mcp-server.mjs.map +1 -0
  664. package/esm/tooling/mcp/tool-input.d.mts +75 -0
  665. package/esm/tooling/mcp/tool-input.d.mts.map +1 -0
  666. package/esm/tooling/mcp/tool-input.mjs +72 -0
  667. package/esm/tooling/mcp/tool-input.mjs.map +1 -0
  668. package/esm/tooling/problem.d.mts +45 -0
  669. package/esm/tooling/problem.d.mts.map +1 -0
  670. package/esm/tooling/problem.mjs +28 -0
  671. package/esm/tooling/problem.mjs.map +1 -0
  672. package/esm/tooling/trace/trace-report.types.d.mts +71 -0
  673. package/esm/tooling/trace/trace-report.types.d.mts.map +1 -0
  674. package/esm/tooling/trace/trace-report.types.mjs +7 -0
  675. package/esm/tooling/trace/trace-report.types.mjs.map +1 -0
  676. package/esm/tooling/trace/trace-request.d.mts +83 -0
  677. package/esm/tooling/trace/trace-request.d.mts.map +1 -0
  678. package/esm/tooling/trace/trace-request.mjs +241 -0
  679. package/esm/tooling/trace/trace-request.mjs.map +1 -0
  680. package/esm/tooling/typecheck/index.d.mts +1 -0
  681. package/esm/tooling/typecheck/index.mjs +3 -0
  682. package/esm/tooling/typecheck/run-typecheck.d.mts +59 -0
  683. package/esm/tooling/typecheck/run-typecheck.d.mts.map +1 -0
  684. package/esm/tooling/typecheck/run-typecheck.mjs +153 -0
  685. package/esm/tooling/typecheck/run-typecheck.mjs.map +1 -0
  686. package/esm/upgrade/codemods/head-viewport.mjs +46 -0
  687. package/esm/upgrade/codemods/head-viewport.mjs.map +1 -0
  688. package/esm/upgrade/codemods/helpers.mjs +81 -0
  689. package/esm/upgrade/codemods/helpers.mjs.map +1 -0
  690. package/esm/upgrade/codemods/index.mjs +34 -0
  691. package/esm/upgrade/codemods/index.mjs.map +1 -0
  692. package/esm/upgrade/codemods/logger-sync-calls.mjs +57 -0
  693. package/esm/upgrade/codemods/logger-sync-calls.mjs.map +1 -0
  694. package/esm/upgrade/codemods/package-json.mjs +122 -0
  695. package/esm/upgrade/codemods/package-json.mjs.map +1 -0
  696. package/esm/upgrade/codemods/request-log-snapshot.mjs +82 -0
  697. package/esm/upgrade/codemods/request-log-snapshot.mjs.map +1 -0
  698. package/esm/upgrade/codemods/review-sites.mjs +262 -0
  699. package/esm/upgrade/codemods/review-sites.mjs.map +1 -0
  700. package/esm/upgrade/diff.mjs +119 -0
  701. package/esm/upgrade/diff.mjs.map +1 -0
  702. package/esm/upgrade/format-upgrade-report.mjs +32 -0
  703. package/esm/upgrade/format-upgrade-report.mjs.map +1 -0
  704. package/esm/upgrade/load-ts-morph.mjs +48 -0
  705. package/esm/upgrade/load-ts-morph.mjs.map +1 -0
  706. package/esm/upgrade/notes.mjs +16 -0
  707. package/esm/upgrade/notes.mjs.map +1 -0
  708. package/esm/upgrade/run-upgrade.mjs +246 -0
  709. package/esm/upgrade/run-upgrade.mjs.map +1 -0
  710. package/esm/upgrade/upgrade-command.action.mjs +37 -0
  711. package/esm/upgrade/upgrade-command.action.mjs.map +1 -0
  712. package/esm/upgrade/version.mjs +39 -0
  713. package/esm/upgrade/version.mjs.map +1 -0
  714. package/esm/use-cases/use-cases-registry.mjs.map +1 -1
  715. package/esm/utils/clock.d.mts +33 -0
  716. package/esm/utils/clock.d.mts.map +1 -0
  717. package/esm/utils/clock.mjs +27 -0
  718. package/esm/utils/clock.mjs.map +1 -0
  719. package/esm/utils/framework-vesion.mjs +1 -1
  720. package/esm/utils/framework-vesion.mjs.map +1 -1
  721. package/esm/utils/ids.d.mts +32 -0
  722. package/esm/utils/ids.d.mts.map +1 -0
  723. package/esm/utils/ids.mjs +33 -0
  724. package/esm/utils/ids.mjs.map +1 -0
  725. package/esm/utils/index.d.mts +3 -0
  726. package/esm/utils/index.mjs +3 -0
  727. package/esm/utils/runtime.d.mts +59 -0
  728. package/esm/utils/runtime.d.mts.map +1 -0
  729. package/esm/utils/runtime.mjs +85 -0
  730. package/esm/utils/runtime.mjs.map +1 -0
  731. package/esm/validation/index.d.mts +1 -1
  732. package/esm/validation/types.d.mts +57 -2
  733. package/esm/validation/types.d.mts.map +1 -1
  734. package/esm/validation/validateAll.mjs +19 -2
  735. package/esm/validation/validateAll.mjs.map +1 -1
  736. package/esm/validation/validation-failure-status.mjs +15 -0
  737. package/esm/validation/validation-failure-status.mjs.map +1 -0
  738. package/esm/validation/validators/file-validator.d.mts +0 -16
  739. package/esm/validation/validators/file-validator.d.mts.map +1 -1
  740. package/esm/warlock-config/bundle-config.mjs +1 -1
  741. package/esm/warlock-config/types.d.mts +27 -3
  742. package/esm/warlock-config/types.d.mts.map +1 -1
  743. package/llms-full.txt +3166 -736
  744. package/llms.txt +16 -6
  745. package/package.json +43 -13
  746. package/schemas/inspect-report.schema.json +554 -0
  747. package/skills/README.md +9 -0
  748. package/skills/add-connector/SKILL.md +26 -38
  749. package/skills/benchmark-code/SKILL.md +30 -24
  750. package/skills/build-restful/SKILL.md +14 -14
  751. package/skills/build-url/SKILL.md +18 -18
  752. package/skills/configure-app/SKILL.md +107 -14
  753. package/skills/create-controller/SKILL.md +25 -12
  754. package/skills/create-module/SKILL.md +3 -3
  755. package/skills/define-resource/SKILL.md +32 -29
  756. package/skills/encrypt-data/SKILL.md +21 -19
  757. package/skills/generate-openapi/SKILL.md +50 -24
  758. package/skills/hash-password/SKILL.md +2 -2
  759. package/skills/health-checks/SKILL.md +24 -7
  760. package/skills/index.md +9 -4
  761. package/skills/lower-stage3-decorators/SKILL.md +4 -4
  762. package/skills/make-endpoint/SKILL.md +110 -0
  763. package/skills/model-factories/SKILL.md +92 -0
  764. package/skills/process-image/SKILL.md +32 -30
  765. package/skills/register-route/SKILL.md +109 -10
  766. package/skills/request-memo/SKILL.md +1 -1
  767. package/skills/request-tracing/SKILL.md +4 -1
  768. package/skills/resolve-path/SKILL.md +45 -44
  769. package/skills/retry-operation/SKILL.md +20 -11
  770. package/skills/run-app/SKILL.md +106 -22
  771. package/skills/send-response/SKILL.md +179 -14
  772. package/skills/store-file/SKILL.md +93 -53
  773. package/skills/test-fakes/SKILL.md +123 -0
  774. package/skills/test-http/SKILL.md +125 -21
  775. package/skills/test-service/SKILL.md +41 -1
  776. package/skills/update-packages/SKILL.md +29 -18
  777. package/skills/upgrade-from-5-29/SKILL.md +413 -0
  778. package/skills/upload-file/SKILL.md +31 -33
  779. package/skills/use-app-context/SKILL.md +38 -31
  780. package/skills/use-middleware/SKILL.md +45 -25
  781. package/skills/use-model-transformers/SKILL.md +37 -28
  782. package/skills/use-repository/SKILL.md +2 -2
  783. package/skills/validate-input/SKILL.md +72 -28
  784. package/skills/warlock-agents-md/SKILL.md +96 -0
  785. package/skills/warlock-board/SKILL.md +123 -0
  786. package/skills/warlock-check/SKILL.md +132 -0
  787. package/skills/warlock-conventions/SKILL.md +115 -0
  788. package/skills/warlock-doctor/SKILL.md +55 -22
  789. package/skills/warlock-inspect/SKILL.md +134 -0
  790. package/skills/warlock-mcp/SKILL.md +120 -0
  791. package/skills/warlock-routes/SKILL.md +18 -11
  792. package/skills/warlock-trace/SKILL.md +81 -0
  793. package/skills/wire-socket/SKILL.md +8 -8
  794. package/skills/write-seeder/SKILL.md +46 -32
  795. package/skills/write-use-case/SKILL.md +40 -27
  796. package/esm/http/uploads/matches-if-none-match.mjs +0 -13
  797. package/esm/http/uploads/matches-if-none-match.mjs.map +0 -1
@@ -3,7 +3,7 @@ import { VariantOutputFormat } from "./image-variant-types.mjs";
3
3
 
4
4
  //#region ../../packages/core/src/http/uploads/resolve-image-variant.d.ts
5
5
  /**
6
- * What to render: a file on a local disk and a variant named in
6
+ * What to render: a file on a storage disk and a variant named in
7
7
  * `uploads.images.variants`
8
8
  */
9
9
  type ResolveImageVariantInput = {
@@ -21,7 +21,7 @@ type ResolveImageVariantInput = {
21
21
  */
22
22
  format?: VariantOutputFormat;
23
23
  /**
24
- * Local disk the source lives on; the default disk when omitted
24
+ * Disk the source lives on; the default disk when omitted
25
25
  */
26
26
  disk?: StorageDriverName;
27
27
  };
@@ -41,7 +41,7 @@ type ResolvedImageVariant = {
41
41
  contentType: string;
42
42
  };
43
43
  /**
44
- * Resolve, and render on a cache miss, one variant of an image on a local
44
+ * Resolve, and render on a cache miss, one variant of an image on a storage
45
45
  * disk. This is what `uploadedFileController` serves; call it from your own
46
46
  * route to serve variants of a private disk behind authentication:
47
47
  *
@@ -61,7 +61,6 @@ type ResolvedImageVariant = {
61
61
  * are pruned once a new derivative is written.
62
62
  *
63
63
  * @returns `undefined` when the path does not resolve to a file on the disk
64
- * @throws ImageVariantsConfigError when the disk is not a local disk
65
64
  * @throws HttpError(400) when variants are not configured, or the variant or
66
65
  * format is not allowed
67
66
  * @throws HttpError(413) when the source is over the configured limits
@@ -1 +1 @@
1
- {"version":3,"file":"resolve-image-variant.d.mts","names":[],"sources":["../../../../../../../../../packages/core/src/http/uploads/resolve-image-variant.ts"],"mappings":";;;;;;AAoCA;;KAAY,wBAAA;EAoBc;;;EAhBxB,IAAA;EAWS;;;EANT,OAAA;EAWwB;AAM1B;;;EAXE,MAAA,GAAS,mBAAA;EAeT;;;EAVA,IAAA,GAAO,iBAAiB;AAAA;;AAmBb;AAwCb;KArDY,oBAAA;;;;EAIV,YAAA;EAmDQ;;;EA9CR,IAAA;EAEA,MAAA,EAAQ,mBAAmB;EAE3B,WAAA;AAAA;AA0C6B;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,iBAFT,mBAAA,CACpB,KAAA,EAAO,wBAAA,GACN,OAAA,CAAQ,oBAAA"}
1
+ {"version":3,"file":"resolve-image-variant.d.mts","names":[],"sources":["../../../../../../../../../packages/core/src/http/uploads/resolve-image-variant.ts"],"mappings":";;;;;;AAoCA;;KAAY,wBAAA;EAoBc;;;EAhBxB,IAAA;EAWS;;;EANT,OAAA;EAWwB;AAM1B;;;EAXE,MAAA,GAAS,mBAAA;EAeT;;;EAVA,IAAA,GAAO,iBAAiB;AAAA;;AAmBb;AA6Bb;KA1CY,oBAAA;;;;EAIV,YAAA;EAwCQ;;;EAnCR,IAAA;EAEA,MAAA,EAAQ,mBAAmB;EAE3B,WAAA;AAAA;AA+B6B;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,iBAFT,mBAAA,CACpB,KAAA,EAAO,wBAAA,GACN,OAAA,CAAQ,oBAAA"}
@@ -2,14 +2,14 @@ import { HttpError } from "../errors/errors.mjs";
2
2
  import { uploadsConfig } from "../uploads-config.mjs";
3
3
  import { variantCacheKey, variantCachePath, variantGenerationTag, variantSourceDirectory, variantSourceKey } from "./variant-cache-key.mjs";
4
4
  import { pruneStaleVariantGenerationsIfCurrent, retryOnVanishedDirectory, warnIfVariantCacheIsPublic } from "./image-variant-cache.mjs";
5
- import { generateImageVariant } from "./generate-image-variant.mjs";
6
- import { ImageVariantsConfigError } from "./image-variants-config-error.mjs";
5
+ import { localDiskRoot } from "./local-disk-root.mjs";
7
6
  import { loadVariantSource } from "./load-variant-source.mjs";
7
+ import { generateImageVariant } from "./generate-image-variant.mjs";
8
8
  import { resolveImageVariantsConfig } from "./resolve-image-variants-config.mjs";
9
- import { resolveVariantCandidate } from "./resolve-variant-candidate.mjs";
9
+ import { freshImageVariant, scheduleImageVariantCacheTrim } from "./trim-image-variant-cache.mjs";
10
10
  import { variantGenerations } from "./variant-generations.mjs";
11
- import { localDiskRoot } from "./local-disk-root.mjs";
12
- import fs from "node:fs/promises";
11
+ import { resolveRemoteImageVariant } from "./resolve-remote-image-variant.mjs";
12
+ import { resolveVariantCandidate } from "./resolve-variant-candidate.mjs";
13
13
 
14
14
  //#region ../../packages/core/src/http/uploads/resolve-image-variant.ts
15
15
  const CONTENT_TYPES = {
@@ -18,16 +18,8 @@ const CONTENT_TYPES = {
18
18
  webp: "image/webp",
19
19
  avif: "image/avif"
20
20
  };
21
- async function exists(target) {
22
- try {
23
- await fs.access(target);
24
- return true;
25
- } catch {
26
- return false;
27
- }
28
- }
29
21
  /**
30
- * Resolve, and render on a cache miss, one variant of an image on a local
22
+ * Resolve, and render on a cache miss, one variant of an image on a storage
31
23
  * disk. This is what `uploadedFileController` serves; call it from your own
32
24
  * route to serve variants of a private disk behind authentication:
33
25
  *
@@ -47,7 +39,6 @@ async function exists(target) {
47
39
  * are pruned once a new derivative is written.
48
40
  *
49
41
  * @returns `undefined` when the path does not resolve to a file on the disk
50
- * @throws ImageVariantsConfigError when the disk is not a local disk
51
42
  * @throws HttpError(400) when variants are not configured, or the variant or
52
43
  * format is not allowed
53
44
  * @throws HttpError(413) when the source is over the configured limits
@@ -55,7 +46,7 @@ async function exists(target) {
55
46
  */
56
47
  async function resolveImageVariant(input) {
57
48
  const diskRoot = localDiskRoot(input.disk);
58
- if (!diskRoot) throw new ImageVariantsConfigError(input.disk === void 0 ? "Image variants need a local storage driver with a root." : `Image variants need a local disk; "${input.disk}" is not one.`);
49
+ if (!diskRoot) return resolveRemoteImageVariant(input);
59
50
  const images = resolveImageVariantsConfig(uploadsConfig("images"), diskRoot);
60
51
  if (!images) throw new HttpError(400, "Image variants are not configured.");
61
52
  const variant = Object.prototype.hasOwnProperty.call(images.variants, input.variant) ? images.variants[input.variant] : void 0;
@@ -79,8 +70,8 @@ async function resolveImageVariant(input) {
79
70
  sourceKey,
80
71
  generation
81
72
  }, hash, format);
82
- if (!await exists(target)) await variantGenerations.run(target, async () => {
83
- if (await exists(target)) return;
73
+ if (!await freshImageVariant(target, images.maxCacheAge)) await variantGenerations.run(target, async () => {
74
+ if (await freshImageVariant(target, images.maxCacheAge)) return;
84
75
  const image = await loadVariantSource(source.absolutePath, {
85
76
  maxSourceBytes: images.maxSourceBytes,
86
77
  maxSourcePixels: images.maxSourcePixels,
@@ -92,6 +83,7 @@ async function resolveImageVariant(input) {
92
83
  format,
93
84
  targetPath: target
94
85
  }));
86
+ scheduleImageVariantCacheTrim(images, target);
95
87
  await pruneStaleVariantGenerationsIfCurrent(source.absolutePath, variantSourceDirectory(images.cacheDirectory, sourceKey), generation);
96
88
  });
97
89
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"resolve-image-variant.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/http/uploads/resolve-image-variant.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport type { StorageDriverName } from \"../../storage/types\";\nimport { HttpError } from \"../errors\";\nimport { uploadsConfig } from \"../uploads-config\";\nimport { generateImageVariant } from \"./generate-image-variant\";\nimport {\n pruneStaleVariantGenerationsIfCurrent,\n retryOnVanishedDirectory,\n warnIfVariantCacheIsPublic,\n} from \"./image-variant-cache\";\nimport { ImageVariantsConfigError } from \"./image-variants-config-error\";\nimport type { VariantOutputFormat } from \"./image-variant-types\";\nimport { loadVariantSource } from \"./load-variant-source\";\nimport { localDiskRoot } from \"./local-disk-root\";\nimport { resolveImageVariantsConfig } from \"./resolve-image-variants-config\";\nimport { resolveVariantCandidate } from \"./resolve-variant-candidate\";\nimport {\n variantCacheKey,\n variantCachePath,\n variantGenerationTag,\n variantSourceDirectory,\n variantSourceKey,\n} from \"./variant-cache-key\";\nimport { variantGenerations } from \"./variant-generations\";\n\nconst CONTENT_TYPES: Record<VariantOutputFormat, string> = {\n jpeg: \"image/jpeg\",\n png: \"image/png\",\n webp: \"image/webp\",\n avif: \"image/avif\",\n};\n\n/**\n * What to render: a file on a local disk and a variant named in\n * `uploads.images.variants`\n */\nexport type ResolveImageVariantInput = {\n /**\n * Path of the source, relative to the disk root\n */\n path: string;\n\n /**\n * Variant name from `uploads.images.variants`\n */\n variant: string;\n\n /**\n * Output format; defaults to the source's own format. Must be the source\n * format or one listed in `uploads.images.formats`.\n */\n format?: VariantOutputFormat;\n\n /**\n * Local disk the source lives on; the default disk when omitted\n */\n disk?: StorageDriverName;\n};\n\n/**\n * A derivative on disk, ready to send\n */\nexport type ResolvedImageVariant = {\n /**\n * Absolute path of the cached derivative\n */\n absolutePath: string;\n\n /**\n * sha256 of everything the derivative depends on; a strong ETag value\n */\n hash: string;\n\n format: VariantOutputFormat;\n\n contentType: string;\n};\n\nasync function exists(target: string): Promise<boolean> {\n try {\n await fs.access(target);\n\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Resolve, and render on a cache miss, one variant of an image on a local\n * disk. This is what `uploadedFileController` serves; call it from your own\n * route to serve variants of a private disk behind authentication:\n *\n * ```ts\n * const variant = await resolveImageVariant({ disk: \"private\", path, variant: \"thumb\" });\n * if (!variant) return response.notFound();\n * return response.sendBuffer(await fs.readFile(variant.absolutePath), {\n * contentType: variant.contentType,\n * });\n * ```\n *\n * The same guards apply as on the public route: the path must stay inside\n * the disk root and outside the derivative cache, the source must be a\n * jpeg/png/webp/avif within `maxSourceBytes`/`maxSourcePixels`, and only\n * declared variants and formats render. Derivatives are generated once per\n * key, even under concurrent calls, and older versions of a rewritten source\n * are pruned once a new derivative is written.\n *\n * @returns `undefined` when the path does not resolve to a file on the disk\n * @throws ImageVariantsConfigError when the disk is not a local disk\n * @throws HttpError(400) when variants are not configured, or the variant or\n * format is not allowed\n * @throws HttpError(413) when the source is over the configured limits\n * @throws HttpError(415) when the source is not jpeg, png, webp or avif\n */\nexport async function resolveImageVariant(\n input: ResolveImageVariantInput,\n): Promise<ResolvedImageVariant | undefined> {\n const diskRoot = localDiskRoot(input.disk);\n\n if (!diskRoot) {\n throw new ImageVariantsConfigError(\n input.disk === undefined\n ? \"Image variants need a local storage driver with a root.\"\n : `Image variants need a local disk; \"${input.disk}\" is not one.`,\n );\n }\n\n const images = resolveImageVariantsConfig(uploadsConfig(\"images\"), diskRoot);\n\n if (!images) {\n throw new HttpError(400, \"Image variants are not configured.\");\n }\n\n const variant = Object.prototype.hasOwnProperty.call(images.variants, input.variant)\n ? images.variants[input.variant]\n : undefined;\n\n if (variant === undefined) {\n throw new HttpError(400, `Unknown variant \"${input.variant}\".`);\n }\n\n if (input.format !== undefined && !images.formats.includes(input.format as never)) {\n throw new HttpError(400, `Format \"${input.format}\" is not allowed.`);\n }\n\n warnIfVariantCacheIsPublic(images.cacheDirectory);\n\n const candidate = await resolveVariantCandidate(input.path, diskRoot, images);\n\n if (!candidate) return undefined;\n\n const { source, stats, sourceFormat } = candidate;\n const format: VariantOutputFormat = input.format ?? sourceFormat;\n const hash = variantCacheKey({\n relativePath: source.relativePath,\n sourceSize: stats.size,\n sourceMtimeMs: stats.mtimeMs,\n variant,\n format,\n });\n const sourceKey = variantSourceKey(source.root, source.relativePath);\n const generation = variantGenerationTag(stats.size, stats.mtimeMs);\n const target = variantCachePath(images.cacheDirectory, { sourceKey, generation }, hash, format);\n\n if (!(await exists(target))) {\n // Keyed on the target path, which includes the disk's real root: the\n // same file on two disks (a mirror copied with its mtime) has the same\n // `hash`, and joining the other disk's flight would leave this target\n // unwritten.\n await variantGenerations.run(target, async () => {\n if (await exists(target)) return;\n\n const image = await loadVariantSource(source.absolutePath, {\n maxSourceBytes: images.maxSourceBytes,\n maxSourcePixels: images.maxSourcePixels,\n expectedFormat: sourceFormat,\n });\n\n await retryOnVanishedDirectory(() =>\n generateImageVariant({ image: image.clone(), variant, format, targetPath: target }),\n );\n await pruneStaleVariantGenerationsIfCurrent(\n source.absolutePath,\n variantSourceDirectory(images.cacheDirectory, sourceKey),\n generation,\n );\n });\n }\n\n return { absolutePath: target, hash, format, contentType: CONTENT_TYPES[format] };\n}\n"],"mappings":";;;;;;;;;;;;;;AAyBA,MAAM,gBAAqD;CACzD,MAAM;CACN,KAAK;CACL,MAAM;CACN,MAAM;AACR;AAgDA,eAAe,OAAO,QAAkC;CACtD,IAAI;EACF,MAAM,GAAG,OAAO,MAAM;EAEtB,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,eAAsB,oBACpB,OAC2C;CAC3C,MAAM,WAAW,cAAc,MAAM,IAAI;CAEzC,IAAI,CAAC,UACH,MAAM,IAAI,yBACR,MAAM,SAAS,SACX,4DACA,sCAAsC,MAAM,KAAK,cACvD;CAGF,MAAM,SAAS,2BAA2B,cAAc,QAAQ,GAAG,QAAQ;CAE3E,IAAI,CAAC,QACH,MAAM,IAAI,UAAU,KAAK,oCAAoC;CAG/D,MAAM,UAAU,OAAO,UAAU,eAAe,KAAK,OAAO,UAAU,MAAM,OAAO,IAC/E,OAAO,SAAS,MAAM,WACtB;CAEJ,IAAI,YAAY,QACd,MAAM,IAAI,UAAU,KAAK,oBAAoB,MAAM,QAAQ,GAAG;CAGhE,IAAI,MAAM,WAAW,UAAa,CAAC,OAAO,QAAQ,SAAS,MAAM,MAAe,GAC9E,MAAM,IAAI,UAAU,KAAK,WAAW,MAAM,OAAO,kBAAkB;CAGrE,2BAA2B,OAAO,cAAc;CAEhD,MAAM,YAAY,MAAM,wBAAwB,MAAM,MAAM,UAAU,MAAM;CAE5E,IAAI,CAAC,WAAW,OAAO;CAEvB,MAAM,EAAE,QAAQ,OAAO,iBAAiB;CACxC,MAAM,SAA8B,MAAM,UAAU;CACpD,MAAM,OAAO,gBAAgB;EAC3B,cAAc,OAAO;EACrB,YAAY,MAAM;EAClB,eAAe,MAAM;EACrB;EACA;CACF,CAAC;CACD,MAAM,YAAY,iBAAiB,OAAO,MAAM,OAAO,YAAY;CACnE,MAAM,aAAa,qBAAqB,MAAM,MAAM,MAAM,OAAO;CACjE,MAAM,SAAS,iBAAiB,OAAO,gBAAgB;EAAE;EAAW;CAAW,GAAG,MAAM,MAAM;CAE9F,IAAI,CAAE,MAAM,OAAO,MAAM,GAKvB,MAAM,mBAAmB,IAAI,QAAQ,YAAY;EAC/C,IAAI,MAAM,OAAO,MAAM,GAAG;EAE1B,MAAM,QAAQ,MAAM,kBAAkB,OAAO,cAAc;GACzD,gBAAgB,OAAO;GACvB,iBAAiB,OAAO;GACxB,gBAAgB;EAClB,CAAC;EAED,MAAM,+BACJ,qBAAqB;GAAE,OAAO,MAAM,MAAM;GAAG;GAAS;GAAQ,YAAY;EAAO,CAAC,CACpF;EACA,MAAM,sCACJ,OAAO,cACP,uBAAuB,OAAO,gBAAgB,SAAS,GACvD,UACF;CACF,CAAC;CAGH,OAAO;EAAE,cAAc;EAAQ;EAAM;EAAQ,aAAa,cAAc;CAAQ;AAClF"}
1
+ {"version":3,"file":"resolve-image-variant.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/http/uploads/resolve-image-variant.ts"],"sourcesContent":["import type { StorageDriverName } from \"../../storage/types\";\nimport { HttpError } from \"../errors\";\nimport { uploadsConfig } from \"../uploads-config\";\nimport { generateImageVariant } from \"./generate-image-variant\";\nimport {\n pruneStaleVariantGenerationsIfCurrent,\n retryOnVanishedDirectory,\n warnIfVariantCacheIsPublic,\n} from \"./image-variant-cache\";\nimport { resolveRemoteImageVariant } from \"./resolve-remote-image-variant\";\nimport type { VariantOutputFormat } from \"./image-variant-types\";\nimport { loadVariantSource } from \"./load-variant-source\";\nimport { localDiskRoot } from \"./local-disk-root\";\nimport { resolveImageVariantsConfig } from \"./resolve-image-variants-config\";\nimport { resolveVariantCandidate } from \"./resolve-variant-candidate\";\nimport {\n variantCacheKey,\n variantCachePath,\n variantGenerationTag,\n variantSourceDirectory,\n variantSourceKey,\n} from \"./variant-cache-key\";\nimport { freshImageVariant, scheduleImageVariantCacheTrim } from \"./trim-image-variant-cache\";\nimport { variantGenerations } from \"./variant-generations\";\n\nconst CONTENT_TYPES: Record<VariantOutputFormat, string> = {\n jpeg: \"image/jpeg\",\n png: \"image/png\",\n webp: \"image/webp\",\n avif: \"image/avif\",\n};\n\n/**\n * What to render: a file on a storage disk and a variant named in\n * `uploads.images.variants`\n */\nexport type ResolveImageVariantInput = {\n /**\n * Path of the source, relative to the disk root\n */\n path: string;\n\n /**\n * Variant name from `uploads.images.variants`\n */\n variant: string;\n\n /**\n * Output format; defaults to the source's own format. Must be the source\n * format or one listed in `uploads.images.formats`.\n */\n format?: VariantOutputFormat;\n\n /**\n * Disk the source lives on; the default disk when omitted\n */\n disk?: StorageDriverName;\n};\n\n/**\n * A derivative on disk, ready to send\n */\nexport type ResolvedImageVariant = {\n /**\n * Absolute path of the cached derivative\n */\n absolutePath: string;\n\n /**\n * sha256 of everything the derivative depends on; a strong ETag value\n */\n hash: string;\n\n format: VariantOutputFormat;\n\n contentType: string;\n};\n\n/**\n * Resolve, and render on a cache miss, one variant of an image on a storage\n * disk. This is what `uploadedFileController` serves; call it from your own\n * route to serve variants of a private disk behind authentication:\n *\n * ```ts\n * const variant = await resolveImageVariant({ disk: \"private\", path, variant: \"thumb\" });\n * if (!variant) return response.notFound();\n * return response.sendBuffer(await fs.readFile(variant.absolutePath), {\n * contentType: variant.contentType,\n * });\n * ```\n *\n * The same guards apply as on the public route: the path must stay inside\n * the disk root and outside the derivative cache, the source must be a\n * jpeg/png/webp/avif within `maxSourceBytes`/`maxSourcePixels`, and only\n * declared variants and formats render. Derivatives are generated once per\n * key, even under concurrent calls, and older versions of a rewritten source\n * are pruned once a new derivative is written.\n *\n * @returns `undefined` when the path does not resolve to a file on the disk\n * @throws HttpError(400) when variants are not configured, or the variant or\n * format is not allowed\n * @throws HttpError(413) when the source is over the configured limits\n * @throws HttpError(415) when the source is not jpeg, png, webp or avif\n */\nexport async function resolveImageVariant(\n input: ResolveImageVariantInput,\n): Promise<ResolvedImageVariant | undefined> {\n const diskRoot = localDiskRoot(input.disk);\n\n if (!diskRoot) return resolveRemoteImageVariant(input);\n\n const images = resolveImageVariantsConfig(uploadsConfig(\"images\"), diskRoot);\n\n if (!images) {\n throw new HttpError(400, \"Image variants are not configured.\");\n }\n\n const variant = Object.prototype.hasOwnProperty.call(images.variants, input.variant)\n ? images.variants[input.variant]\n : undefined;\n\n if (variant === undefined) {\n throw new HttpError(400, `Unknown variant \"${input.variant}\".`);\n }\n\n if (input.format !== undefined && !images.formats.includes(input.format as never)) {\n throw new HttpError(400, `Format \"${input.format}\" is not allowed.`);\n }\n\n warnIfVariantCacheIsPublic(images.cacheDirectory);\n\n const candidate = await resolveVariantCandidate(input.path, diskRoot, images);\n\n if (!candidate) return undefined;\n\n const { source, stats, sourceFormat } = candidate;\n const format: VariantOutputFormat = input.format ?? sourceFormat;\n const hash = variantCacheKey({\n relativePath: source.relativePath,\n sourceSize: stats.size,\n sourceMtimeMs: stats.mtimeMs,\n variant,\n format,\n });\n const sourceKey = variantSourceKey(source.root, source.relativePath);\n const generation = variantGenerationTag(stats.size, stats.mtimeMs);\n const target = variantCachePath(images.cacheDirectory, { sourceKey, generation }, hash, format);\n\n if (!(await freshImageVariant(target, images.maxCacheAge))) {\n // Keyed on the target path, which includes the disk's real root: the\n // same file on two disks (a mirror copied with its mtime) has the same\n // `hash`, and joining the other disk's flight would leave this target\n // unwritten.\n await variantGenerations.run(target, async () => {\n if (await freshImageVariant(target, images.maxCacheAge)) return;\n\n const image = await loadVariantSource(source.absolutePath, {\n maxSourceBytes: images.maxSourceBytes,\n maxSourcePixels: images.maxSourcePixels,\n expectedFormat: sourceFormat,\n });\n\n await retryOnVanishedDirectory(() =>\n generateImageVariant({ image: image.clone(), variant, format, targetPath: target }),\n );\n scheduleImageVariantCacheTrim(images, target);\n await pruneStaleVariantGenerationsIfCurrent(\n source.absolutePath,\n variantSourceDirectory(images.cacheDirectory, sourceKey),\n generation,\n );\n });\n }\n\n return { absolutePath: target, hash, format, contentType: CONTENT_TYPES[format] };\n}\n"],"mappings":";;;;;;;;;;;;;;AAyBA,MAAM,gBAAqD;CACzD,MAAM;CACN,KAAK;CACL,MAAM;CACN,MAAM;AACR;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0EA,eAAsB,oBACpB,OAC2C;CAC3C,MAAM,WAAW,cAAc,MAAM,IAAI;CAEzC,IAAI,CAAC,UAAU,OAAO,0BAA0B,KAAK;CAErD,MAAM,SAAS,2BAA2B,cAAc,QAAQ,GAAG,QAAQ;CAE3E,IAAI,CAAC,QACH,MAAM,IAAI,UAAU,KAAK,oCAAoC;CAG/D,MAAM,UAAU,OAAO,UAAU,eAAe,KAAK,OAAO,UAAU,MAAM,OAAO,IAC/E,OAAO,SAAS,MAAM,WACtB;CAEJ,IAAI,YAAY,QACd,MAAM,IAAI,UAAU,KAAK,oBAAoB,MAAM,QAAQ,GAAG;CAGhE,IAAI,MAAM,WAAW,UAAa,CAAC,OAAO,QAAQ,SAAS,MAAM,MAAe,GAC9E,MAAM,IAAI,UAAU,KAAK,WAAW,MAAM,OAAO,kBAAkB;CAGrE,2BAA2B,OAAO,cAAc;CAEhD,MAAM,YAAY,MAAM,wBAAwB,MAAM,MAAM,UAAU,MAAM;CAE5E,IAAI,CAAC,WAAW,OAAO;CAEvB,MAAM,EAAE,QAAQ,OAAO,iBAAiB;CACxC,MAAM,SAA8B,MAAM,UAAU;CACpD,MAAM,OAAO,gBAAgB;EAC3B,cAAc,OAAO;EACrB,YAAY,MAAM;EAClB,eAAe,MAAM;EACrB;EACA;CACF,CAAC;CACD,MAAM,YAAY,iBAAiB,OAAO,MAAM,OAAO,YAAY;CACnE,MAAM,aAAa,qBAAqB,MAAM,MAAM,MAAM,OAAO;CACjE,MAAM,SAAS,iBAAiB,OAAO,gBAAgB;EAAE;EAAW;CAAW,GAAG,MAAM,MAAM;CAE9F,IAAI,CAAE,MAAM,kBAAkB,QAAQ,OAAO,WAAW,GAKtD,MAAM,mBAAmB,IAAI,QAAQ,YAAY;EAC/C,IAAI,MAAM,kBAAkB,QAAQ,OAAO,WAAW,GAAG;EAEzD,MAAM,QAAQ,MAAM,kBAAkB,OAAO,cAAc;GACzD,gBAAgB,OAAO;GACvB,iBAAiB,OAAO;GACxB,gBAAgB;EAClB,CAAC;EAED,MAAM,+BACJ,qBAAqB;GAAE,OAAO,MAAM,MAAM;GAAG;GAAS;GAAQ,YAAY;EAAO,CAAC,CACpF;EACA,8BAA8B,QAAQ,MAAM;EAC5C,MAAM,sCACJ,OAAO,cACP,uBAAuB,OAAO,gBAAgB,SAAS,GACvD,UACF;CACF,CAAC;CAGH,OAAO;EAAE,cAAc;EAAQ;EAAM;EAAQ,aAAa,cAAc;CAAQ;AAClF"}
@@ -1,5 +1,5 @@
1
1
  import { ImageVariantsConfigError } from "./image-variants-config-error.mjs";
2
- import path from "node:path";
2
+ import { variantCacheDirectoryFor } from "./image-variant-cache.mjs";
3
3
 
4
4
  //#region ../../packages/core/src/http/uploads/resolve-image-variants-config.ts
5
5
  /**
@@ -76,13 +76,15 @@ function resolveImageVariantsConfig(images, storageRoot) {
76
76
  if (!rawVariants || typeof rawVariants !== "object" || Object.keys(rawVariants).length === 0) throw new ImageVariantsConfigError("uploads.images.variants must declare at least one variant.");
77
77
  const variants = {};
78
78
  for (const [name, raw] of Object.entries(rawVariants)) variants[name] = resolveVariant(name, raw);
79
- const cacheDirectory = images.cacheDirectory ? path.resolve(images.cacheDirectory) : path.join(storageRoot, ".cache", "image-variants");
79
+ const cacheDirectory = variantCacheDirectoryFor(storageRoot, images);
80
80
  return {
81
81
  variants,
82
82
  formats: resolveFormats(images.formats),
83
83
  maxSourceBytes: resolveLimit("maxSourceBytes", images.maxSourceBytes, DEFAULT_MAX_SOURCE_BYTES),
84
84
  maxSourcePixels: resolveLimit("maxSourcePixels", images.maxSourcePixels, DEFAULT_MAX_SOURCE_PIXELS),
85
- cacheDirectory
85
+ cacheDirectory,
86
+ maxCacheBytes: resolveLimit("maxCacheBytes", images.maxCacheBytes, 512 * 1024 * 1024),
87
+ maxCacheAge: resolveLimit("maxCacheAge", images.maxCacheAge, 10080 * 60)
86
88
  };
87
89
  }
88
90
 
@@ -1 +1 @@
1
- {"version":3,"file":"resolve-image-variants-config.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/http/uploads/resolve-image-variants-config.ts"],"sourcesContent":["import path from \"node:path\";\nimport type {\n ImageVariantDefinition,\n ImageVariantFit,\n ImageVariantOutputFormat,\n UploadsImagesConfigurations,\n} from \"../uploads-types\";\nimport { ImageVariantsConfigError } from \"./image-variants-config-error\";\nimport type { ResolvedImageVariantsConfig } from \"./image-variant-types\";\n\n/**\n * Largest width or height a variant may declare\n */\nexport const MAX_VARIANT_DIMENSION = 8192;\n\n/**\n * Default `maxSourceBytes`: 25 MB\n */\nexport const DEFAULT_MAX_SOURCE_BYTES = 25 * 1024 * 1024;\n\n/**\n * Default `maxSourcePixels`\n */\nexport const DEFAULT_MAX_SOURCE_PIXELS = 40_000_000;\n\nconst FITS: readonly ImageVariantFit[] = [\"cover\", \"contain\", \"inside\"];\n\nconst OUTPUT_FORMATS: readonly ImageVariantOutputFormat[] = [\"avif\", \"webp\"];\n\nconst VARIANT_NAME = /^[A-Za-z0-9_-]+$/;\n\nfunction isPositiveInteger(value: unknown, max = Number.MAX_SAFE_INTEGER): value is number {\n return typeof value === \"number\" && Number.isInteger(value) && value >= 1 && value <= max;\n}\n\nfunction resolveVariant(name: string, raw: unknown): ImageVariantDefinition {\n const key = `uploads.images.variants.${name}`;\n\n if (!VARIANT_NAME.test(name)) {\n throw new ImageVariantsConfigError(\n `${key}: a variant name may only use letters, digits, \"-\" and \"_\".`,\n );\n }\n\n if (!raw || typeof raw !== \"object\") {\n throw new ImageVariantsConfigError(`${key} must be an object with a width.`);\n }\n\n const { width, height, fit, quality, enlarge } = raw as Record<string, unknown>;\n\n if (!isPositiveInteger(width, MAX_VARIANT_DIMENSION)) {\n throw new ImageVariantsConfigError(\n `${key}.width must be an integer from 1 to ${MAX_VARIANT_DIMENSION}, got ${JSON.stringify(width)}.`,\n );\n }\n\n const variant: ImageVariantDefinition = { width };\n\n if (height !== undefined) {\n if (!isPositiveInteger(height, MAX_VARIANT_DIMENSION)) {\n throw new ImageVariantsConfigError(\n `${key}.height must be an integer from 1 to ${MAX_VARIANT_DIMENSION}, got ${JSON.stringify(height)}.`,\n );\n }\n\n variant.height = height;\n }\n\n if (fit !== undefined) {\n if (!FITS.includes(fit as ImageVariantFit)) {\n throw new ImageVariantsConfigError(\n `${key}.fit must be one of ${FITS.join(\", \")}, got ${JSON.stringify(fit)}.`,\n );\n }\n\n variant.fit = fit as ImageVariantFit;\n }\n\n if (quality !== undefined) {\n if (!isPositiveInteger(quality, 100)) {\n throw new ImageVariantsConfigError(\n `${key}.quality must be an integer from 1 to 100, got ${JSON.stringify(quality)}.`,\n );\n }\n\n variant.quality = quality;\n }\n\n if (enlarge !== undefined && typeof enlarge !== \"boolean\") {\n throw new ImageVariantsConfigError(\n `${key}.enlarge must be a boolean, got ${JSON.stringify(enlarge)}.`,\n );\n }\n\n // Always set, never left undefined: the effective flag (default false) is\n // part of the normalized variant, so toggling it changes the cache key.\n variant.enlarge = enlarge === true;\n\n return variant;\n}\n\nfunction resolveFormats(raw: unknown): readonly ImageVariantOutputFormat[] {\n if (raw === undefined) return [];\n\n if (!Array.isArray(raw)) {\n throw new ImageVariantsConfigError(`uploads.images.formats must be an array.`);\n }\n\n for (const format of raw) {\n if (!OUTPUT_FORMATS.includes(format)) {\n throw new ImageVariantsConfigError(\n `uploads.images.formats may only contain ${OUTPUT_FORMATS.join(\", \")}, got ${JSON.stringify(format)}.`,\n );\n }\n }\n\n return [...new Set(raw as ImageVariantOutputFormat[])];\n}\n\nfunction resolveLimit(name: string, raw: unknown, fallback: number): number {\n if (raw === undefined) return fallback;\n\n if (!isPositiveInteger(raw)) {\n throw new ImageVariantsConfigError(\n `uploads.images.${name} must be a positive integer, got ${JSON.stringify(raw)}.`,\n );\n }\n\n return raw;\n}\n\n/**\n * Validate the `uploads.images` config and apply its defaults.\n *\n * Every variant is copied field by field, so an unknown key in the config can\n * never reach sharp, and the result is what the cache key is computed from.\n *\n * @param images - the raw `uploads.images` value\n * @param storageRoot - the local storage root, base of the default cache directory\n * @returns the resolved config, or `undefined` when variants are not configured\n * @throws ImageVariantsConfigError when any value is out of bounds\n */\nexport function resolveImageVariantsConfig(\n images: UploadsImagesConfigurations | undefined,\n storageRoot: string,\n): ResolvedImageVariantsConfig | undefined {\n if (images === undefined || images === null) return undefined;\n\n if (typeof images !== \"object\") {\n throw new ImageVariantsConfigError(\"uploads.images must be an object.\");\n }\n\n const rawVariants = (images as { variants?: unknown }).variants;\n\n if (!rawVariants || typeof rawVariants !== \"object\" || Object.keys(rawVariants).length === 0) {\n throw new ImageVariantsConfigError(\n \"uploads.images.variants must declare at least one variant.\",\n );\n }\n\n const variants: Record<string, ImageVariantDefinition> = {};\n\n for (const [name, raw] of Object.entries(rawVariants)) {\n variants[name] = resolveVariant(name, raw);\n }\n\n const cacheDirectory = images.cacheDirectory\n ? path.resolve(images.cacheDirectory)\n : path.join(storageRoot, \".cache\", \"image-variants\");\n\n return {\n variants,\n formats: resolveFormats(images.formats),\n maxSourceBytes: resolveLimit(\"maxSourceBytes\", images.maxSourceBytes, DEFAULT_MAX_SOURCE_BYTES),\n maxSourcePixels: resolveLimit(\n \"maxSourcePixels\",\n images.maxSourcePixels,\n DEFAULT_MAX_SOURCE_PIXELS,\n ),\n cacheDirectory,\n };\n}\n"],"mappings":";;;;;;;AAaA,MAAa,wBAAwB;;;;AAKrC,MAAa,2BAA2B,KAAK,OAAO;;;;AAKpD,MAAa,4BAA4B;AAEzC,MAAM,OAAmC;CAAC;CAAS;CAAW;AAAQ;AAEtE,MAAM,iBAAsD,CAAC,QAAQ,MAAM;AAE3E,MAAM,eAAe;AAErB,SAAS,kBAAkB,OAAgB,MAAM,OAAO,kBAAmC;CACzF,OAAO,OAAO,UAAU,YAAY,OAAO,UAAU,KAAK,KAAK,SAAS,KAAK,SAAS;AACxF;AAEA,SAAS,eAAe,MAAc,KAAsC;CAC1E,MAAM,MAAM,2BAA2B;CAEvC,IAAI,CAAC,aAAa,KAAK,IAAI,GACzB,MAAM,IAAI,yBACR,GAAG,IAAI,4DACT;CAGF,IAAI,CAAC,OAAO,OAAO,QAAQ,UACzB,MAAM,IAAI,yBAAyB,GAAG,IAAI,iCAAiC;CAG7E,MAAM,EAAE,OAAO,QAAQ,KAAK,SAAS,YAAY;CAEjD,IAAI,CAAC,kBAAkB,WAA4B,GACjD,MAAM,IAAI,yBACR,GAAG,IAAI,sCAAsC,sBAAsB,QAAQ,KAAK,UAAU,KAAK,EAAE,EACnG;CAGF,MAAM,UAAkC,EAAE,MAAM;CAEhD,IAAI,WAAW,QAAW;EACxB,IAAI,CAAC,kBAAkB,YAA6B,GAClD,MAAM,IAAI,yBACR,GAAG,IAAI,uCAAuC,sBAAsB,QAAQ,KAAK,UAAU,MAAM,EAAE,EACrG;EAGF,QAAQ,SAAS;CACnB;CAEA,IAAI,QAAQ,QAAW;EACrB,IAAI,CAAC,KAAK,SAAS,GAAsB,GACvC,MAAM,IAAI,yBACR,GAAG,IAAI,sBAAsB,KAAK,KAAK,IAAI,EAAE,QAAQ,KAAK,UAAU,GAAG,EAAE,EAC3E;EAGF,QAAQ,MAAM;CAChB;CAEA,IAAI,YAAY,QAAW;EACzB,IAAI,CAAC,kBAAkB,SAAS,GAAG,GACjC,MAAM,IAAI,yBACR,GAAG,IAAI,iDAAiD,KAAK,UAAU,OAAO,EAAE,EAClF;EAGF,QAAQ,UAAU;CACpB;CAEA,IAAI,YAAY,UAAa,OAAO,YAAY,WAC9C,MAAM,IAAI,yBACR,GAAG,IAAI,kCAAkC,KAAK,UAAU,OAAO,EAAE,EACnE;CAKF,QAAQ,UAAU,YAAY;CAE9B,OAAO;AACT;AAEA,SAAS,eAAe,KAAmD;CACzE,IAAI,QAAQ,QAAW,OAAO,CAAC;CAE/B,IAAI,CAAC,MAAM,QAAQ,GAAG,GACpB,MAAM,IAAI,yBAAyB,0CAA0C;CAG/E,KAAK,MAAM,UAAU,KACnB,IAAI,CAAC,eAAe,SAAS,MAAM,GACjC,MAAM,IAAI,yBACR,2CAA2C,eAAe,KAAK,IAAI,EAAE,QAAQ,KAAK,UAAU,MAAM,EAAE,EACtG;CAIJ,OAAO,CAAC,GAAG,IAAI,IAAI,GAAiC,CAAC;AACvD;AAEA,SAAS,aAAa,MAAc,KAAc,UAA0B;CAC1E,IAAI,QAAQ,QAAW,OAAO;CAE9B,IAAI,CAAC,kBAAkB,GAAG,GACxB,MAAM,IAAI,yBACR,kBAAkB,KAAK,mCAAmC,KAAK,UAAU,GAAG,EAAE,EAChF;CAGF,OAAO;AACT;;;;;;;;;;;;AAaA,SAAgB,2BACd,QACA,aACyC;CACzC,IAAI,WAAW,UAAa,WAAW,MAAM,OAAO;CAEpD,IAAI,OAAO,WAAW,UACpB,MAAM,IAAI,yBAAyB,mCAAmC;CAGxE,MAAM,cAAe,OAAkC;CAEvD,IAAI,CAAC,eAAe,OAAO,gBAAgB,YAAY,OAAO,KAAK,WAAW,CAAC,CAAC,WAAW,GACzF,MAAM,IAAI,yBACR,4DACF;CAGF,MAAM,WAAmD,CAAC;CAE1D,KAAK,MAAM,CAAC,MAAM,QAAQ,OAAO,QAAQ,WAAW,GAClD,SAAS,QAAQ,eAAe,MAAM,GAAG;CAG3C,MAAM,iBAAiB,OAAO,iBAC1B,KAAK,QAAQ,OAAO,cAAc,IAClC,KAAK,KAAK,aAAa,UAAU,gBAAgB;CAErD,OAAO;EACL;EACA,SAAS,eAAe,OAAO,OAAO;EACtC,gBAAgB,aAAa,kBAAkB,OAAO,gBAAgB,wBAAwB;EAC9F,iBAAiB,aACf,mBACA,OAAO,iBACP,yBACF;EACA;CACF;AACF"}
1
+ {"version":3,"file":"resolve-image-variants-config.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/http/uploads/resolve-image-variants-config.ts"],"sourcesContent":["import type {\n ImageVariantDefinition,\n ImageVariantFit,\n ImageVariantOutputFormat,\n UploadsImagesConfigurations,\n} from \"../uploads-types\";\nimport { variantCacheDirectoryFor } from \"./image-variant-cache\";\nimport { ImageVariantsConfigError } from \"./image-variants-config-error\";\nimport type { ResolvedImageVariantsConfig } from \"./image-variant-types\";\n\n/**\n * Largest width or height a variant may declare\n */\nexport const MAX_VARIANT_DIMENSION = 8192;\n\n/**\n * Default `maxSourceBytes`: 25 MB\n */\nexport const DEFAULT_MAX_SOURCE_BYTES = 25 * 1024 * 1024;\n\n/**\n * Default `maxSourcePixels`\n */\nexport const DEFAULT_MAX_SOURCE_PIXELS = 40_000_000;\n\nconst FITS: readonly ImageVariantFit[] = [\"cover\", \"contain\", \"inside\"];\n\nconst OUTPUT_FORMATS: readonly ImageVariantOutputFormat[] = [\"avif\", \"webp\"];\n\nconst VARIANT_NAME = /^[A-Za-z0-9_-]+$/;\n\nfunction isPositiveInteger(value: unknown, max = Number.MAX_SAFE_INTEGER): value is number {\n return typeof value === \"number\" && Number.isInteger(value) && value >= 1 && value <= max;\n}\n\nfunction resolveVariant(name: string, raw: unknown): ImageVariantDefinition {\n const key = `uploads.images.variants.${name}`;\n\n if (!VARIANT_NAME.test(name)) {\n throw new ImageVariantsConfigError(\n `${key}: a variant name may only use letters, digits, \"-\" and \"_\".`,\n );\n }\n\n if (!raw || typeof raw !== \"object\") {\n throw new ImageVariantsConfigError(`${key} must be an object with a width.`);\n }\n\n const { width, height, fit, quality, enlarge } = raw as Record<string, unknown>;\n\n if (!isPositiveInteger(width, MAX_VARIANT_DIMENSION)) {\n throw new ImageVariantsConfigError(\n `${key}.width must be an integer from 1 to ${MAX_VARIANT_DIMENSION}, got ${JSON.stringify(width)}.`,\n );\n }\n\n const variant: ImageVariantDefinition = { width };\n\n if (height !== undefined) {\n if (!isPositiveInteger(height, MAX_VARIANT_DIMENSION)) {\n throw new ImageVariantsConfigError(\n `${key}.height must be an integer from 1 to ${MAX_VARIANT_DIMENSION}, got ${JSON.stringify(height)}.`,\n );\n }\n\n variant.height = height;\n }\n\n if (fit !== undefined) {\n if (!FITS.includes(fit as ImageVariantFit)) {\n throw new ImageVariantsConfigError(\n `${key}.fit must be one of ${FITS.join(\", \")}, got ${JSON.stringify(fit)}.`,\n );\n }\n\n variant.fit = fit as ImageVariantFit;\n }\n\n if (quality !== undefined) {\n if (!isPositiveInteger(quality, 100)) {\n throw new ImageVariantsConfigError(\n `${key}.quality must be an integer from 1 to 100, got ${JSON.stringify(quality)}.`,\n );\n }\n\n variant.quality = quality;\n }\n\n if (enlarge !== undefined && typeof enlarge !== \"boolean\") {\n throw new ImageVariantsConfigError(\n `${key}.enlarge must be a boolean, got ${JSON.stringify(enlarge)}.`,\n );\n }\n\n // Always set, never left undefined: the effective flag (default false) is\n // part of the normalized variant, so toggling it changes the cache key.\n variant.enlarge = enlarge === true;\n\n return variant;\n}\n\nfunction resolveFormats(raw: unknown): readonly ImageVariantOutputFormat[] {\n if (raw === undefined) return [];\n\n if (!Array.isArray(raw)) {\n throw new ImageVariantsConfigError(`uploads.images.formats must be an array.`);\n }\n\n for (const format of raw) {\n if (!OUTPUT_FORMATS.includes(format)) {\n throw new ImageVariantsConfigError(\n `uploads.images.formats may only contain ${OUTPUT_FORMATS.join(\", \")}, got ${JSON.stringify(format)}.`,\n );\n }\n }\n\n return [...new Set(raw as ImageVariantOutputFormat[])];\n}\n\nfunction resolveLimit(name: string, raw: unknown, fallback: number): number {\n if (raw === undefined) return fallback;\n\n if (!isPositiveInteger(raw)) {\n throw new ImageVariantsConfigError(\n `uploads.images.${name} must be a positive integer, got ${JSON.stringify(raw)}.`,\n );\n }\n\n return raw;\n}\n\n/**\n * Validate the `uploads.images` config and apply its defaults.\n *\n * Every variant is copied field by field, so an unknown key in the config can\n * never reach sharp, and the result is what the cache key is computed from.\n *\n * @param images - the raw `uploads.images` value\n * @param storageRoot - the local storage root, base of the default cache directory\n * @returns the resolved config, or `undefined` when variants are not configured\n * @throws ImageVariantsConfigError when any value is out of bounds\n */\nexport function resolveImageVariantsConfig(\n images: UploadsImagesConfigurations | undefined,\n storageRoot: string,\n): ResolvedImageVariantsConfig | undefined {\n if (images === undefined || images === null) return undefined;\n\n if (typeof images !== \"object\") {\n throw new ImageVariantsConfigError(\"uploads.images must be an object.\");\n }\n\n const rawVariants = (images as { variants?: unknown }).variants;\n\n if (!rawVariants || typeof rawVariants !== \"object\" || Object.keys(rawVariants).length === 0) {\n throw new ImageVariantsConfigError(\n \"uploads.images.variants must declare at least one variant.\",\n );\n }\n\n const variants: Record<string, ImageVariantDefinition> = {};\n\n for (const [name, raw] of Object.entries(rawVariants)) {\n variants[name] = resolveVariant(name, raw);\n }\n\n const cacheDirectory = variantCacheDirectoryFor(storageRoot, images);\n\n return {\n variants,\n formats: resolveFormats(images.formats),\n maxSourceBytes: resolveLimit(\"maxSourceBytes\", images.maxSourceBytes, DEFAULT_MAX_SOURCE_BYTES),\n maxSourcePixels: resolveLimit(\n \"maxSourcePixels\",\n images.maxSourcePixels,\n DEFAULT_MAX_SOURCE_PIXELS,\n ),\n cacheDirectory,\n maxCacheBytes: resolveLimit(\"maxCacheBytes\", images.maxCacheBytes, 512 * 1024 * 1024),\n maxCacheAge: resolveLimit(\"maxCacheAge\", images.maxCacheAge, 7 * 24 * 60 * 60),\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,89 @@
1
+ import { HttpError } from "../errors/errors.mjs";
2
+ import { uploadsConfig } from "../uploads-config.mjs";
3
+ import { variantCacheKey, variantCachePath, variantSourceDirectory, variantSourceKey } from "./variant-cache-key.mjs";
4
+ import { retryOnVanishedDirectory, warnIfVariantCacheIsPublic } from "./image-variant-cache.mjs";
5
+ import { isVariantSourceFormat } from "./detect-image-format.mjs";
6
+ import { remoteVariantDisk, remoteVariantSource } from "./remote-variant-source.mjs";
7
+ import { generateImageVariant } from "./generate-image-variant.mjs";
8
+ import { resolveImageVariantsConfig } from "./resolve-image-variants-config.mjs";
9
+ import { freshImageVariant, scheduleImageVariantCacheTrim } from "./trim-image-variant-cache.mjs";
10
+ import { variantGenerations } from "./variant-generations.mjs";
11
+ import path from "node:path";
12
+ import { atomicWriteAsync } from "@warlock.js/fs";
13
+ import fs from "node:fs/promises";
14
+
15
+ //#region ../../packages/core/src/http/uploads/resolve-remote-image-variant.ts
16
+ /** Cloud sources are read through their driver; derivatives remain in a local cache. */
17
+ async function resolveRemoteImageVariant(input) {
18
+ const disk = remoteVariantDisk(input.disk);
19
+ const images = resolveImageVariantsConfig(uploadsConfig("images"), disk.cacheRoot);
20
+ if (!images) throw new HttpError(400, "Image variants are not configured.");
21
+ if (!Object.prototype.hasOwnProperty.call(images.variants, input.variant)) throw new HttpError(400, `Unknown variant "${input.variant}".`);
22
+ if (input.format !== void 0 && !images.formats.includes(input.format)) throw new HttpError(400, `Format "${input.format}" is not allowed.`);
23
+ const source = await remoteVariantSource(input.path, disk, images);
24
+ if (!source) return void 0;
25
+ try {
26
+ return await renderRemoteImageVariant(input, disk, images, source);
27
+ } finally {
28
+ source.release();
29
+ }
30
+ }
31
+ /** Ingest rendering reuses one source handle and its memoized bounded download. */
32
+ async function renderRemoteImageVariant(input, disk, images, source) {
33
+ const variant = Object.prototype.hasOwnProperty.call(images.variants, input.variant) ? images.variants[input.variant] : void 0;
34
+ if (!variant) throw new HttpError(400, `Unknown variant "${input.variant}".`);
35
+ if (input.format !== void 0 && !images.formats.includes(input.format)) throw new HttpError(400, `Format "${input.format}" is not allowed.`);
36
+ const generation = source.metadataGeneration ?? (await source.load()).generation;
37
+ const sourceKey = variantSourceKey(disk.root, source.sourcePath);
38
+ const manifestPath = path.join(variantSourceDirectory(images.cacheDirectory, sourceKey), generation, "source.json");
39
+ let format = input.format;
40
+ if (!format) {
41
+ try {
42
+ const savedFormat = JSON.parse(await fs.readFile(manifestPath, "utf8"))?.format;
43
+ if (isVariantSourceFormat(savedFormat)) format = savedFormat;
44
+ } catch (error) {
45
+ if (error.code !== "ENOENT" && !(error instanceof SyntaxError)) throw error;
46
+ }
47
+ format ??= (await source.load()).sourceFormat;
48
+ }
49
+ const hash = variantCacheKey({
50
+ relativePath: JSON.stringify([
51
+ disk.root,
52
+ source.sourcePath,
53
+ generation
54
+ ]),
55
+ sourceSize: 0,
56
+ sourceMtimeMs: 0,
57
+ variant,
58
+ format
59
+ });
60
+ const target = variantCachePath(images.cacheDirectory, {
61
+ sourceKey,
62
+ generation
63
+ }, hash, format);
64
+ warnIfVariantCacheIsPublic(images.cacheDirectory);
65
+ if (!await freshImageVariant(target, images.maxCacheAge)) await variantGenerations.run(target, async () => {
66
+ if (await freshImageVariant(target, images.maxCacheAge)) return;
67
+ const loaded = await source.load();
68
+ await retryOnVanishedDirectory(async () => {
69
+ await generateImageVariant({
70
+ image: loaded.image.clone(),
71
+ variant,
72
+ format,
73
+ targetPath: target
74
+ });
75
+ await atomicWriteAsync(manifestPath, JSON.stringify({ format: loaded.sourceFormat }));
76
+ });
77
+ scheduleImageVariantCacheTrim(images, target);
78
+ });
79
+ return {
80
+ absolutePath: target,
81
+ hash,
82
+ format,
83
+ contentType: `image/${format}`
84
+ };
85
+ }
86
+
87
+ //#endregion
88
+ export { renderRemoteImageVariant, resolveRemoteImageVariant };
89
+ //# sourceMappingURL=resolve-remote-image-variant.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-remote-image-variant.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/http/uploads/resolve-remote-image-variant.ts"],"sourcesContent":["import { atomicWriteAsync } from \"@warlock.js/fs\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { HttpError } from \"../errors\";\nimport { uploadsConfig } from \"../uploads-config\";\nimport { isVariantSourceFormat } from \"./detect-image-format\";\nimport { generateImageVariant } from \"./generate-image-variant\";\nimport { retryOnVanishedDirectory, warnIfVariantCacheIsPublic } from \"./image-variant-cache\";\nimport type { ResolvedImageVariantsConfig, VariantOutputFormat } from \"./image-variant-types\";\nimport {\n remoteVariantDisk,\n remoteVariantSource,\n type RemoteVariantSource,\n} from \"./remote-variant-source\";\nimport { resolveImageVariantsConfig } from \"./resolve-image-variants-config\";\nimport type { ResolveImageVariantInput, ResolvedImageVariant } from \"./resolve-image-variant\";\nimport { freshImageVariant, scheduleImageVariantCacheTrim } from \"./trim-image-variant-cache\";\nimport {\n variantCacheKey,\n variantCachePath,\n variantSourceDirectory,\n variantSourceKey,\n} from \"./variant-cache-key\";\nimport { variantGenerations } from \"./variant-generations\";\n\n/** Cloud sources are read through their driver; derivatives remain in a local cache. */\nexport async function resolveRemoteImageVariant(\n input: ResolveImageVariantInput,\n): Promise<ResolvedImageVariant | undefined> {\n const disk = remoteVariantDisk(input.disk);\n const images = resolveImageVariantsConfig(uploadsConfig(\"images\"), disk.cacheRoot);\n if (!images) throw new HttpError(400, \"Image variants are not configured.\");\n if (!Object.prototype.hasOwnProperty.call(images.variants, input.variant))\n throw new HttpError(400, `Unknown variant \"${input.variant}\".`);\n if (input.format !== undefined && !images.formats.includes(input.format as never))\n throw new HttpError(400, `Format \"${input.format}\" is not allowed.`);\n const source = await remoteVariantSource(input.path, disk, images);\n if (!source) return undefined;\n try {\n return await renderRemoteImageVariant(input, disk, images, source);\n } finally {\n source.release();\n }\n}\n\n/** Ingest rendering reuses one source handle and its memoized bounded download. */\nexport async function renderRemoteImageVariant(\n input: ResolveImageVariantInput,\n disk: ReturnType<typeof remoteVariantDisk>,\n images: ResolvedImageVariantsConfig,\n source: RemoteVariantSource,\n): Promise<ResolvedImageVariant> {\n const variant = Object.prototype.hasOwnProperty.call(images.variants, input.variant)\n ? images.variants[input.variant]\n : undefined;\n if (!variant) throw new HttpError(400, `Unknown variant \"${input.variant}\".`);\n if (input.format !== undefined && !images.formats.includes(input.format as never))\n throw new HttpError(400, `Format \"${input.format}\" is not allowed.`);\n const generation = source.metadataGeneration ?? (await source.load()).generation;\n const sourceKey = variantSourceKey(disk.root, source.sourcePath);\n const manifestPath = path.join(\n variantSourceDirectory(images.cacheDirectory, sourceKey),\n generation,\n \"source.json\",\n );\n let format: VariantOutputFormat | undefined = input.format;\n if (!format) {\n try {\n const saved: unknown = JSON.parse(await fs.readFile(manifestPath, \"utf8\"));\n const savedFormat = (saved as { format?: unknown } | null)?.format;\n if (isVariantSourceFormat(savedFormat as never)) format = savedFormat as VariantOutputFormat;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\" && !(error instanceof SyntaxError))\n throw error;\n }\n format ??= (await source.load()).sourceFormat;\n }\n const hash = variantCacheKey({\n relativePath: JSON.stringify([disk.root, source.sourcePath, generation]),\n sourceSize: 0,\n sourceMtimeMs: 0,\n variant,\n format,\n });\n const target = variantCachePath(images.cacheDirectory, { sourceKey, generation }, hash, format);\n warnIfVariantCacheIsPublic(images.cacheDirectory);\n if (!(await freshImageVariant(target, images.maxCacheAge))) {\n await variantGenerations.run(target, async () => {\n if (await freshImageVariant(target, images.maxCacheAge)) return;\n const loaded = await source.load();\n await retryOnVanishedDirectory(async () => {\n await generateImageVariant({\n image: loaded.image.clone(),\n variant,\n format,\n targetPath: target,\n });\n await atomicWriteAsync(manifestPath, JSON.stringify({ format: loaded.sourceFormat }));\n });\n scheduleImageVariantCacheTrim(images, target);\n // Retain other generations: an older concurrent download must not prune\n // a newer object's derivatives. Background eviction reclaims them.\n });\n }\n return { absolutePath: target, hash, format, contentType: `image/${format}` };\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA0BA,eAAsB,0BACpB,OAC2C;CAC3C,MAAM,OAAO,kBAAkB,MAAM,IAAI;CACzC,MAAM,SAAS,2BAA2B,cAAc,QAAQ,GAAG,KAAK,SAAS;CACjF,IAAI,CAAC,QAAQ,MAAM,IAAI,UAAU,KAAK,oCAAoC;CAC1E,IAAI,CAAC,OAAO,UAAU,eAAe,KAAK,OAAO,UAAU,MAAM,OAAO,GACtE,MAAM,IAAI,UAAU,KAAK,oBAAoB,MAAM,QAAQ,GAAG;CAChE,IAAI,MAAM,WAAW,UAAa,CAAC,OAAO,QAAQ,SAAS,MAAM,MAAe,GAC9E,MAAM,IAAI,UAAU,KAAK,WAAW,MAAM,OAAO,kBAAkB;CACrE,MAAM,SAAS,MAAM,oBAAoB,MAAM,MAAM,MAAM,MAAM;CACjE,IAAI,CAAC,QAAQ,OAAO;CACpB,IAAI;EACF,OAAO,MAAM,yBAAyB,OAAO,MAAM,QAAQ,MAAM;CACnE,UAAU;EACR,OAAO,QAAQ;CACjB;AACF;;AAGA,eAAsB,yBACpB,OACA,MACA,QACA,QAC+B;CAC/B,MAAM,UAAU,OAAO,UAAU,eAAe,KAAK,OAAO,UAAU,MAAM,OAAO,IAC/E,OAAO,SAAS,MAAM,WACtB;CACJ,IAAI,CAAC,SAAS,MAAM,IAAI,UAAU,KAAK,oBAAoB,MAAM,QAAQ,GAAG;CAC5E,IAAI,MAAM,WAAW,UAAa,CAAC,OAAO,QAAQ,SAAS,MAAM,MAAe,GAC9E,MAAM,IAAI,UAAU,KAAK,WAAW,MAAM,OAAO,kBAAkB;CACrE,MAAM,aAAa,OAAO,uBAAuB,MAAM,OAAO,KAAK,EAAC,CAAE;CACtE,MAAM,YAAY,iBAAiB,KAAK,MAAM,OAAO,UAAU;CAC/D,MAAM,eAAe,KAAK,KACxB,uBAAuB,OAAO,gBAAgB,SAAS,GACvD,YACA,aACF;CACA,IAAI,SAA0C,MAAM;CACpD,IAAI,CAAC,QAAQ;EACX,IAAI;GAEF,MAAM,cADiB,KAAK,MAAM,MAAM,GAAG,SAAS,cAAc,MAAM,CAC/C,CAAC,EAAkC;GAC5D,IAAI,sBAAsB,WAAoB,GAAG,SAAS;EAC5D,SAAS,OAAO;GACd,IAAK,MAAgC,SAAS,YAAY,EAAE,iBAAiB,cAC3E,MAAM;EACV;EACA,YAAY,MAAM,OAAO,KAAK,EAAC,CAAE;CACnC;CACA,MAAM,OAAO,gBAAgB;EAC3B,cAAc,KAAK,UAAU;GAAC,KAAK;GAAM,OAAO;GAAY;EAAU,CAAC;EACvE,YAAY;EACZ,eAAe;EACf;EACA;CACF,CAAC;CACD,MAAM,SAAS,iBAAiB,OAAO,gBAAgB;EAAE;EAAW;CAAW,GAAG,MAAM,MAAM;CAC9F,2BAA2B,OAAO,cAAc;CAChD,IAAI,CAAE,MAAM,kBAAkB,QAAQ,OAAO,WAAW,GACtD,MAAM,mBAAmB,IAAI,QAAQ,YAAY;EAC/C,IAAI,MAAM,kBAAkB,QAAQ,OAAO,WAAW,GAAG;EACzD,MAAM,SAAS,MAAM,OAAO,KAAK;EACjC,MAAM,yBAAyB,YAAY;GACzC,MAAM,qBAAqB;IACzB,OAAO,OAAO,MAAM,MAAM;IAC1B;IACA;IACA,YAAY;GACd,CAAC;GACD,MAAM,iBAAiB,cAAc,KAAK,UAAU,EAAE,QAAQ,OAAO,aAAa,CAAC,CAAC;EACtF,CAAC;EACD,8BAA8B,QAAQ,MAAM;CAG9C,CAAC;CAEH,OAAO;EAAE,cAAc;EAAQ;EAAM;EAAQ,aAAa,SAAS;CAAS;AAC9E"}
@@ -0,0 +1,193 @@
1
+ import { variantCacheGenerations } from "./image-variant-cache-entries.mjs";
2
+ import { log } from "@warlock.js/logger";
3
+ import fs from "node:fs/promises";
4
+
5
+ //#region ../../packages/core/src/http/uploads/trim-image-variant-cache.ts
6
+ const SWEEP_INTERVAL_MS = 6e4;
7
+ /**
8
+ * A writer's temporary file older than this is an orphan left by a crashed
9
+ * write and is removed by the sweep. Younger ones are never touched: a writer
10
+ * may still be renaming them into place.
11
+ */
12
+ const ORPHAN_TEMP_AGE_MS = 60 * 6e4;
13
+ /**
14
+ * A generation directory left without derivatives is removed only when it
15
+ * was last changed longer ago than this, so a directory a writer has just
16
+ * created for its first derivative survives. The writers also retry once when
17
+ * their directory vanishes between `mkdir` and the rename.
18
+ */
19
+ const EMPTY_GENERATION_GRACE_MS = 5 * 6e4;
20
+ const caches = /* @__PURE__ */ new Map();
21
+ function cacheState(directory) {
22
+ let state = caches.get(directory);
23
+ if (!state) {
24
+ state = {
25
+ lastSweep: 0,
26
+ lastWalk: 0,
27
+ bytes: 0,
28
+ sizes: /* @__PURE__ */ new Map(),
29
+ keep: /* @__PURE__ */ new Set(),
30
+ reconciled: false
31
+ };
32
+ caches.set(directory, state);
33
+ }
34
+ return state;
35
+ }
36
+ function errorCode(error) {
37
+ return error?.code;
38
+ }
39
+ /** Age is measured from generation; hits do not extend a derivative's life. */
40
+ async function freshImageVariant(target, maxAgeSeconds) {
41
+ try {
42
+ const stats = await fs.stat(target);
43
+ return stats.isFile() && Date.now() - stats.mtimeMs < maxAgeSeconds * 1e3;
44
+ } catch (error) {
45
+ if (errorCode(error) === "ENOENT") return false;
46
+ throw error;
47
+ }
48
+ }
49
+ /**
50
+ * Whether a scheduled check must walk the cache. It may skip only when a walk
51
+ * has already reconciled the byte estimate, the cache is within its byte
52
+ * budget, nothing retained has expired (new writes are always younger than
53
+ * the oldest retained derivative), and a walk ran within the last
54
+ * `maxCacheAge`, which also bounds how long an orphaned temporary can linger.
55
+ */
56
+ function sweepIsDue(state, images) {
57
+ const maxAgeMs = images.maxCacheAge * 1e3;
58
+ const now = Date.now();
59
+ return !state.reconciled || state.bytes > images.maxCacheBytes || state.oldestMtimeMs !== void 0 && state.oldestMtimeMs <= now - maxAgeMs || now - state.lastWalk >= maxAgeMs;
60
+ }
61
+ /**
62
+ * Schedule housekeeping only after a write. Hits do no cache-directory I/O.
63
+ * Writes update a running byte estimate; at most one background check per
64
+ * minute walks the cache, and only when it may have something to reclaim
65
+ * (see {@link sweepIsDue}).
66
+ */
67
+ function scheduleImageVariantCacheTrim(images, target) {
68
+ const state = cacheState(images.cacheDirectory);
69
+ fs.stat(target).then((stats) => {
70
+ state.bytes += stats.size - (state.sizes.get(target) ?? 0);
71
+ state.sizes.set(target, stats.size);
72
+ state.oldestMtimeMs = Math.min(state.oldestMtimeMs ?? stats.mtimeMs, stats.mtimeMs);
73
+ if (state.timer) return;
74
+ const delay = Math.max(0, state.lastSweep + SWEEP_INTERVAL_MS - Date.now());
75
+ state.timer = setTimeout(() => {
76
+ state.timer = void 0;
77
+ if (!sweepIsDue(state, images)) {
78
+ state.lastSweep = Date.now();
79
+ return;
80
+ }
81
+ trimImageVariantCache(images, target).catch((error) => {
82
+ log.warn("uploads", "image-variants", error);
83
+ });
84
+ }, delay);
85
+ state.timer.unref();
86
+ }).catch((error) => {
87
+ if (errorCode(error) !== "ENOENT") log.warn("uploads", "image-variants", error);
88
+ });
89
+ }
90
+ /** Remove a temporary only when it is a regular file older than {@link ORPHAN_TEMP_AGE_MS}. */
91
+ async function removeOrphanTemporary(target) {
92
+ try {
93
+ const stats = await fs.lstat(target);
94
+ if (stats.isFile() && Date.now() - stats.mtimeMs > 36e5) await fs.rm(target, { force: true });
95
+ } catch (error) {
96
+ if (errorCode(error) !== "ENOENT") throw error;
97
+ }
98
+ }
99
+ /**
100
+ * Remove a generation left without derivatives: its manifest, then the
101
+ * directory itself with a non-recursive `rmdir`, so anything a writer has put
102
+ * there since the walk keeps the directory in place.
103
+ */
104
+ async function removeEmptyGeneration(generation) {
105
+ if (generation.derivatives > 0) return;
106
+ if (Date.now() - generation.mtimeMs <= 3e5) return;
107
+ if (generation.manifest) await fs.rm(generation.manifest, { force: true });
108
+ try {
109
+ if (!(await fs.lstat(generation.directory)).isDirectory()) return;
110
+ await fs.rmdir(generation.directory);
111
+ } catch (error) {
112
+ const code = errorCode(error);
113
+ if (code !== "ENOENT" && code !== "ENOTEMPTY" && code !== "EEXIST") throw error;
114
+ }
115
+ }
116
+ /**
117
+ * Join an in-flight sweep rather than queueing another walk. Retain the file
118
+ * about to be served, even if it alone exceeds the byte budget. Only paths in
119
+ * the generated cache layout are touched and symlinks are never followed:
120
+ * expired and oldest derivatives are evicted, writer temporaries older than
121
+ * {@link ORPHAN_TEMP_AGE_MS} are removed, and a generation directory left
122
+ * without derivatives loses its manifest and the directory itself once it is
123
+ * older than {@link EMPTY_GENERATION_GRACE_MS}. The cache root, shard and
124
+ * source directories, user folders and unrelated files stay in place.
125
+ */
126
+ async function trimImageVariantCache(images, keep) {
127
+ const state = cacheState(images.cacheDirectory);
128
+ state.keep.add(keep);
129
+ if (state.sweep) return state.sweep;
130
+ state.lastSweep = Date.now();
131
+ state.lastWalk = state.lastSweep;
132
+ const sweep = (async () => {
133
+ const files = [];
134
+ const generations = [];
135
+ for await (const found of variantCacheGenerations(images.cacheDirectory)) {
136
+ let mtimeMs;
137
+ try {
138
+ mtimeMs = (await fs.lstat(found.directory)).mtimeMs;
139
+ } catch (error) {
140
+ if (errorCode(error) === "ENOENT") continue;
141
+ throw error;
142
+ }
143
+ const generation = {
144
+ directory: found.directory,
145
+ mtimeMs,
146
+ derivatives: 0
147
+ };
148
+ generations.push(generation);
149
+ for (const entry of found.entries) if (entry.type === "manifest") generation.manifest = entry.target;
150
+ else if (entry.type === "temporary") await removeOrphanTemporary(entry.target);
151
+ else try {
152
+ const stats = await fs.stat(entry.target);
153
+ files.push({
154
+ target: entry.target,
155
+ size: stats.size,
156
+ mtimeMs: stats.mtimeMs,
157
+ generation
158
+ });
159
+ generation.derivatives += 1;
160
+ } catch (error) {
161
+ if (errorCode(error) !== "ENOENT") throw error;
162
+ }
163
+ }
164
+ state.sizes = new Map(files.map((file) => [file.target, file.size]));
165
+ state.bytes = files.reduce((sum, file) => sum + file.size, 0);
166
+ const cutoff = Date.now() - images.maxCacheAge * 1e3;
167
+ let oldestMtimeMs;
168
+ for (const file of files.sort((a, b) => a.mtimeMs - b.mtimeMs)) {
169
+ if (!state.keep.has(file.target) && (file.mtimeMs <= cutoff || state.bytes > images.maxCacheBytes)) {
170
+ await fs.rm(file.target, { force: true });
171
+ state.bytes -= file.size;
172
+ state.sizes.delete(file.target);
173
+ file.generation.derivatives -= 1;
174
+ continue;
175
+ }
176
+ oldestMtimeMs = Math.min(oldestMtimeMs ?? file.mtimeMs, file.mtimeMs);
177
+ }
178
+ for (const generation of generations) await removeEmptyGeneration(generation);
179
+ state.oldestMtimeMs = oldestMtimeMs;
180
+ state.reconciled = true;
181
+ })();
182
+ state.sweep = sweep;
183
+ try {
184
+ await sweep;
185
+ } finally {
186
+ state.sweep = void 0;
187
+ state.keep.clear();
188
+ }
189
+ }
190
+
191
+ //#endregion
192
+ export { freshImageVariant, scheduleImageVariantCacheTrim };
193
+ //# sourceMappingURL=trim-image-variant-cache.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trim-image-variant-cache.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/http/uploads/trim-image-variant-cache.ts"],"sourcesContent":["import { log } from \"@warlock.js/logger\";\nimport fs from \"node:fs/promises\";\nimport type { ResolvedImageVariantsConfig } from \"./image-variant-types\";\nimport { variantCacheGenerations } from \"./image-variant-cache-entries\";\n\nconst SWEEP_INTERVAL_MS = 60_000;\n\n/**\n * A writer's temporary file older than this is an orphan left by a crashed\n * write and is removed by the sweep. Younger ones are never touched: a writer\n * may still be renaming them into place.\n */\nexport const ORPHAN_TEMP_AGE_MS = 60 * 60_000;\n\n/**\n * A generation directory left without derivatives is removed only when it\n * was last changed longer ago than this, so a directory a writer has just\n * created for its first derivative survives. The writers also retry once when\n * their directory vanishes between `mkdir` and the rename.\n */\nexport const EMPTY_GENERATION_GRACE_MS = 5 * 60_000;\n\ntype CacheState = {\n sweep?: Promise<void>;\n timer?: ReturnType<typeof setTimeout>;\n /** When the last scheduled check ran, whether or not it walked the cache. */\n lastSweep: number;\n /** When the cache was last walked. */\n lastWalk: number;\n bytes: number;\n sizes: Map<string, number>;\n keep: Set<string>;\n /** True once a walk has reconciled `bytes` with the disk. */\n reconciled: boolean;\n /** The oldest retained derivative's mtime; undefined when none is known. */\n oldestMtimeMs?: number;\n};\nconst caches = new Map<string, CacheState>();\nfunction cacheState(directory: string): CacheState {\n let state = caches.get(directory);\n if (!state) {\n state = {\n lastSweep: 0,\n lastWalk: 0,\n bytes: 0,\n sizes: new Map(),\n keep: new Set(),\n reconciled: false,\n };\n caches.set(directory, state);\n }\n return state;\n}\n\nfunction errorCode(error: unknown): string | undefined {\n return (error as NodeJS.ErrnoException | undefined)?.code;\n}\n\n/** Age is measured from generation; hits do not extend a derivative's life. */\nexport async function freshImageVariant(target: string, maxAgeSeconds: number): Promise<boolean> {\n try {\n const stats = await fs.stat(target);\n return stats.isFile() && Date.now() - stats.mtimeMs < maxAgeSeconds * 1000;\n } catch (error) {\n if (errorCode(error) === \"ENOENT\") return false;\n throw error;\n }\n}\n\n/**\n * Whether a scheduled check must walk the cache. It may skip only when a walk\n * has already reconciled the byte estimate, the cache is within its byte\n * budget, nothing retained has expired (new writes are always younger than\n * the oldest retained derivative), and a walk ran within the last\n * `maxCacheAge`, which also bounds how long an orphaned temporary can linger.\n */\nfunction sweepIsDue(state: CacheState, images: ResolvedImageVariantsConfig): boolean {\n const maxAgeMs = images.maxCacheAge * 1000;\n const now = Date.now();\n return (\n !state.reconciled ||\n state.bytes > images.maxCacheBytes ||\n (state.oldestMtimeMs !== undefined && state.oldestMtimeMs <= now - maxAgeMs) ||\n now - state.lastWalk >= maxAgeMs\n );\n}\n\n/**\n * Schedule housekeeping only after a write. Hits do no cache-directory I/O.\n * Writes update a running byte estimate; at most one background check per\n * minute walks the cache, and only when it may have something to reclaim\n * (see {@link sweepIsDue}).\n */\nexport function scheduleImageVariantCacheTrim(\n images: ResolvedImageVariantsConfig,\n target: string,\n): void {\n const state = cacheState(images.cacheDirectory);\n void fs\n .stat(target)\n .then((stats) => {\n state.bytes += stats.size - (state.sizes.get(target) ?? 0);\n state.sizes.set(target, stats.size);\n state.oldestMtimeMs = Math.min(state.oldestMtimeMs ?? stats.mtimeMs, stats.mtimeMs);\n if (state.timer) return;\n const delay = Math.max(0, state.lastSweep + SWEEP_INTERVAL_MS - Date.now());\n state.timer = setTimeout(() => {\n state.timer = undefined;\n if (!sweepIsDue(state, images)) {\n state.lastSweep = Date.now();\n return;\n }\n void trimImageVariantCache(images, target).catch((error: unknown) => {\n log.warn(\"uploads\", \"image-variants\", error);\n });\n }, delay);\n state.timer.unref();\n })\n .catch((error: unknown) => {\n if (errorCode(error) !== \"ENOENT\") log.warn(\"uploads\", \"image-variants\", error);\n });\n}\n\ntype Derivative = { target: string; size: number; mtimeMs: number; generation: Generation };\ntype Generation = {\n directory: string;\n /** The directory's mtime before this sweep removed anything from it. */\n mtimeMs: number;\n derivatives: number;\n manifest?: string;\n};\n\n/** Remove a temporary only when it is a regular file older than {@link ORPHAN_TEMP_AGE_MS}. */\nasync function removeOrphanTemporary(target: string): Promise<void> {\n try {\n const stats = await fs.lstat(target);\n if (stats.isFile() && Date.now() - stats.mtimeMs > ORPHAN_TEMP_AGE_MS) {\n await fs.rm(target, { force: true });\n }\n } catch (error) {\n if (errorCode(error) !== \"ENOENT\") throw error;\n }\n}\n\n/**\n * Remove a generation left without derivatives: its manifest, then the\n * directory itself with a non-recursive `rmdir`, so anything a writer has put\n * there since the walk keeps the directory in place.\n */\nasync function removeEmptyGeneration(generation: Generation): Promise<void> {\n if (generation.derivatives > 0) return;\n if (Date.now() - generation.mtimeMs <= EMPTY_GENERATION_GRACE_MS) return;\n if (generation.manifest) await fs.rm(generation.manifest, { force: true });\n try {\n if (!(await fs.lstat(generation.directory)).isDirectory()) return;\n await fs.rmdir(generation.directory);\n } catch (error) {\n const code = errorCode(error);\n if (code !== \"ENOENT\" && code !== \"ENOTEMPTY\" && code !== \"EEXIST\") throw error;\n }\n}\n\n/**\n * Join an in-flight sweep rather than queueing another walk. Retain the file\n * about to be served, even if it alone exceeds the byte budget. Only paths in\n * the generated cache layout are touched and symlinks are never followed:\n * expired and oldest derivatives are evicted, writer temporaries older than\n * {@link ORPHAN_TEMP_AGE_MS} are removed, and a generation directory left\n * without derivatives loses its manifest and the directory itself once it is\n * older than {@link EMPTY_GENERATION_GRACE_MS}. The cache root, shard and\n * source directories, user folders and unrelated files stay in place.\n */\nexport async function trimImageVariantCache(\n images: ResolvedImageVariantsConfig,\n keep: string,\n): Promise<void> {\n const state = cacheState(images.cacheDirectory);\n state.keep.add(keep);\n if (state.sweep) return state.sweep;\n state.lastSweep = Date.now();\n state.lastWalk = state.lastSweep;\n const sweep = (async () => {\n const files: Derivative[] = [];\n const generations: Generation[] = [];\n for await (const found of variantCacheGenerations(images.cacheDirectory)) {\n let mtimeMs: number;\n try {\n mtimeMs = (await fs.lstat(found.directory)).mtimeMs;\n } catch (error) {\n if (errorCode(error) === \"ENOENT\") continue;\n throw error;\n }\n const generation: Generation = { directory: found.directory, mtimeMs, derivatives: 0 };\n generations.push(generation);\n for (const entry of found.entries) {\n if (entry.type === \"manifest\") {\n generation.manifest = entry.target;\n } else if (entry.type === \"temporary\") {\n await removeOrphanTemporary(entry.target);\n } else {\n try {\n const stats = await fs.stat(entry.target);\n files.push({\n target: entry.target,\n size: stats.size,\n mtimeMs: stats.mtimeMs,\n generation,\n });\n generation.derivatives += 1;\n } catch (error) {\n if (errorCode(error) !== \"ENOENT\") throw error;\n }\n }\n }\n }\n state.sizes = new Map(files.map((file) => [file.target, file.size]));\n state.bytes = files.reduce((sum, file) => sum + file.size, 0);\n const cutoff = Date.now() - images.maxCacheAge * 1000;\n let oldestMtimeMs: number | undefined;\n for (const file of files.sort((a, b) => a.mtimeMs - b.mtimeMs)) {\n if (\n !state.keep.has(file.target) &&\n (file.mtimeMs <= cutoff || state.bytes > images.maxCacheBytes)\n ) {\n await fs.rm(file.target, { force: true });\n state.bytes -= file.size;\n state.sizes.delete(file.target);\n file.generation.derivatives -= 1;\n continue;\n }\n oldestMtimeMs = Math.min(oldestMtimeMs ?? file.mtimeMs, file.mtimeMs);\n }\n for (const generation of generations) await removeEmptyGeneration(generation);\n state.oldestMtimeMs = oldestMtimeMs;\n state.reconciled = true;\n })();\n state.sweep = sweep;\n try {\n await sweep;\n } finally {\n state.sweep = undefined;\n state.keep.clear();\n }\n}\n"],"mappings":"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"}
@@ -6,13 +6,15 @@ import { RequestHandler } from "../../router/types.mjs";
6
6
  */
7
7
  type UploadedFileControllerOptions = {
8
8
  /**
9
- * Local disk to serve from; the default disk when omitted. A private disk
9
+ * Disk to serve from; remote disks support variants only. Uses the default
10
+ * disk when omitted. A private disk
10
11
  * should be served from a route that checks access first.
11
12
  */
12
- disk?: StorageDriverName;
13
+ disk?: StorageDriverName; /** Allow derivatives from the default cloud disk. Explicit disk selection also opts in. */
14
+ remote?: boolean;
13
15
  };
14
16
  /**
15
- * Serves local uploads, and bounded on-demand variants of the images among them.
17
+ * Serves local uploads and bounded on-demand image variants from any disk.
16
18
  *
17
19
  * ```ts
18
20
  * router.get("/uploads/*", uploadedFileController);
@@ -1 +1 @@
1
- {"version":3,"file":"uploaded-file.controller.d.mts","names":[],"sources":["../../../../../../../../../packages/core/src/http/uploads/uploaded-file.controller.ts"],"mappings":";;;;;;KA6FY,6BAAA;EAA6B;;;AAKf;EAAxB,IAAA,GAAO,iBAAiB;AAAA;;;;;;;AA0BT;AA8GjB;;;;AAAoF;;;;;;;;;;iBAhHpE,4BAAA,CACd,OAAA,GAAS,6BAAA,GACR,cAAc;;;;cA8GJ,sBAAA,EAAwB,cAA+C"}
1
+ {"version":3,"file":"uploaded-file.controller.d.mts","names":[],"sources":["../../../../../../../../../packages/core/src/http/uploads/uploaded-file.controller.ts"],"mappings":";;;;;;KA6FY,6BAAA;EAA6B;;;;;EAMvC,IAAA,GAAO,iBAAiB,EAGlB;EAAN,MAAA;AAAA;;;;;;;;AA0Be;AA+HjB;;;;AAAoF;;;;;;;;;iBAjIpE,4BAAA,CACd,OAAA,GAAS,6BAAA,GACR,cAAc;;;;cA+HJ,sBAAA,EAAwB,cAA+C"}