@warlock.js/core 5.2.3 → 5.3.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 (261) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/esm/application/application.d.mts.map +1 -1
  3. package/esm/application/application.mjs.map +1 -1
  4. package/esm/benchmark/benchmark-snapshots.d.mts.map +1 -1
  5. package/esm/benchmark/benchmark.d.mts.map +1 -1
  6. package/esm/benchmark/channels/console.channel.mjs.map +1 -1
  7. package/esm/benchmark/channels/noop.channel.d.mts.map +1 -1
  8. package/esm/benchmark/profiler.d.mts.map +1 -1
  9. package/esm/benchmark/profiler.mjs.map +1 -1
  10. package/esm/bootstrap/setup.d.mts.map +1 -1
  11. package/esm/bootstrap/setup.mjs.map +1 -1
  12. package/esm/bootstrap.d.mts.map +1 -1
  13. package/esm/cache/database-cache-driver.d.mts.map +1 -1
  14. package/esm/cache/database-cache-driver.mjs.map +1 -1
  15. package/esm/cli/cli-commands.manager.mjs.map +1 -1
  16. package/esm/cli/cli-commands.utils.mjs.map +1 -1
  17. package/esm/cli/commands/doctor/boot-for-diagnostics.mjs.map +1 -1
  18. package/esm/cli/commands/doctor/checks/connectors.check.mjs.map +1 -1
  19. package/esm/cli/commands/doctor/checks/handler-signature.check.mjs.map +1 -1
  20. package/esm/cli/commands/doctor/checks/health.check.mjs.map +1 -1
  21. package/esm/cli/commands/doctor/checks/optional-peers.check.mjs.map +1 -1
  22. package/esm/cli/commands/doctor/checks/routes.check.mjs.map +1 -1
  23. package/esm/cli/commands/generate/generate.command.mjs.map +1 -1
  24. package/esm/cli/commands/generate/generators/column-dsl-parser.mjs.map +1 -1
  25. package/esm/cli/commands/generate/generators/migration.generator.mjs.map +1 -1
  26. package/esm/cli/commands/generate/generators/model.generator.mjs.map +1 -1
  27. package/esm/cli/commands/generate/templates/stubs.mjs +13 -14
  28. package/esm/cli/commands/generate/templates/stubs.mjs.map +1 -1
  29. package/esm/cli/commands/generate/utils/name-parser.mjs.map +1 -1
  30. package/esm/cli/commands/generate/utils/prompt.mjs.map +1 -1
  31. package/esm/cli/commands/routes/diff-page-routes.mjs.map +1 -1
  32. package/esm/cli/commands/routes/format-routes-table.mjs.map +1 -1
  33. package/esm/cli/commands/routes/route-row.mjs.map +1 -1
  34. package/esm/cli/commands/routes/routes-command.action.mjs.map +1 -1
  35. package/esm/cli/commands/routes/routes-diff.action.mjs.map +1 -1
  36. package/esm/cli/commands/storage-put.action.mjs.map +1 -1
  37. package/esm/cli/commands/typings-generator.command.mjs.map +1 -1
  38. package/esm/cli/commands-loader.mjs.map +1 -1
  39. package/esm/cli/start.mjs.map +1 -1
  40. package/esm/cli/string-similarity.mjs.map +1 -1
  41. package/esm/commands/cli-command.mjs.map +1 -1
  42. package/esm/config/config-loader.mjs.map +1 -1
  43. package/esm/config/config-setter.d.mts.map +1 -1
  44. package/esm/config/load-config-files.mjs.map +1 -1
  45. package/esm/connectors/access-connector.d.mts.map +1 -1
  46. package/esm/connectors/ai-connector.d.mts.map +1 -1
  47. package/esm/connectors/assert-no-reserved-connector-names.mjs.map +1 -1
  48. package/esm/connectors/assert-unique-connector-names.mjs.map +1 -1
  49. package/esm/connectors/base-connector.d.mts.map +1 -1
  50. package/esm/connectors/base-connector.mjs.map +1 -1
  51. package/esm/connectors/cache-connector.d.mts.map +1 -1
  52. package/esm/connectors/connectors-manager.d.mts.map +1 -1
  53. package/esm/connectors/connectors-manager.mjs.map +1 -1
  54. package/esm/connectors/database-connector.d.mts.map +1 -1
  55. package/esm/connectors/describe-server-address.mjs.map +1 -1
  56. package/esm/connectors/herald-connector.d.mts.map +1 -1
  57. package/esm/connectors/http-connector.d.mts.map +1 -1
  58. package/esm/connectors/http-connector.mjs.map +1 -1
  59. package/esm/connectors/logger-connector.d.mts.map +1 -1
  60. package/esm/connectors/mail-connector.d.mts.map +1 -1
  61. package/esm/connectors/notifications-connector.d.mts.map +1 -1
  62. package/esm/connectors/register-configured-connectors.mjs.map +1 -1
  63. package/esm/connectors/socket-connector.d.mts.map +1 -1
  64. package/esm/connectors/socket-connector.mjs.map +1 -1
  65. package/esm/connectors/storage.connector.d.mts.map +1 -1
  66. package/esm/container/index.d.mts.map +1 -1
  67. package/esm/database/create-database-action.mjs.map +1 -1
  68. package/esm/database/drop-tables-action.mjs.map +1 -1
  69. package/esm/database/migrate-action.mjs.map +1 -1
  70. package/esm/database/seed-command-action.mjs.map +1 -1
  71. package/esm/database/seeds/seed-records-table-migration.mjs.map +1 -1
  72. package/esm/database/seeds/seeders.manager.mjs.map +1 -1
  73. package/esm/database/seeds/seeds-table-migration.mjs.map +1 -1
  74. package/esm/dev-server/check-for-updates.mjs.map +1 -1
  75. package/esm/dev-server/dependency-graph.d.mts.map +1 -1
  76. package/esm/dev-server/dependency-graph.mjs.map +1 -1
  77. package/esm/dev-server/dev-logger.mjs.map +1 -1
  78. package/esm/dev-server/development-server.mjs.map +1 -1
  79. package/esm/dev-server/file-event-handler.mjs.map +1 -1
  80. package/esm/dev-server/file-manager.d.mts.map +1 -1
  81. package/esm/dev-server/file-manager.mjs.map +1 -1
  82. package/esm/dev-server/file-operations.d.mts.map +1 -1
  83. package/esm/dev-server/files-orchestrator.mjs.map +1 -1
  84. package/esm/dev-server/files-watcher.mjs.map +1 -1
  85. package/esm/dev-server/health-checker/checkers/eslint-health-checker.mjs.map +1 -1
  86. package/esm/dev-server/health-checker/checkers/typescript-health-checker.mjs.map +1 -1
  87. package/esm/dev-server/health-checker/file-health-result.d.mts.map +1 -1
  88. package/esm/dev-server/health-checker/file-health-result.mjs.map +1 -1
  89. package/esm/dev-server/health-checker/files-healthcare.manager.mjs.map +1 -1
  90. package/esm/dev-server/layer-executor.mjs.map +1 -1
  91. package/esm/dev-server/loader/hook-thread.d.mts.map +1 -1
  92. package/esm/dev-server/loader/own-resolver.mjs.map +1 -1
  93. package/esm/dev-server/loader/register-loader.mjs.map +1 -1
  94. package/esm/dev-server/loader/resolve-hook.mjs.map +1 -1
  95. package/esm/dev-server/loader/source-slug.mjs.map +1 -1
  96. package/esm/dev-server/loader/transpile-cache.mjs.map +1 -1
  97. package/esm/dev-server/loader/version-registry.mjs.map +1 -1
  98. package/esm/dev-server/manifest-manager.d.mts.map +1 -1
  99. package/esm/dev-server/manifest-manager.mjs.map +1 -1
  100. package/esm/dev-server/module-loader.mjs.map +1 -1
  101. package/esm/dev-server/package-json-manager.mjs.map +1 -1
  102. package/esm/dev-server/parse-imports.mjs.map +1 -1
  103. package/esm/dev-server/ready-block.mjs.map +1 -1
  104. package/esm/dev-server/special-files-collector.d.mts.map +1 -1
  105. package/esm/dev-server/special-files-collector.mjs.map +1 -1
  106. package/esm/dev-server/tsconfig-manager.mjs.map +1 -1
  107. package/esm/dev-server/type-generator.mjs.map +1 -1
  108. package/esm/dev-server/utils.mjs.map +1 -1
  109. package/esm/encryption/encrypt.mjs.map +1 -1
  110. package/esm/encryption/hash.mjs.map +1 -1
  111. package/esm/generations/add-command.action.mjs.map +1 -1
  112. package/esm/generations/features/shadcn.feature.mjs.map +1 -1
  113. package/esm/generations/features/shared/migration-timestamp.mjs.map +1 -1
  114. package/esm/generations/features/tailwind.feature.mjs.map +1 -1
  115. package/esm/generations/features/web.feature.mjs +2 -1
  116. package/esm/generations/features/web.feature.mjs.map +1 -1
  117. package/esm/generations/stubs.mjs +53 -54
  118. package/esm/generations/stubs.mjs.map +1 -1
  119. package/esm/http/boot-port-preflight.d.mts.map +1 -1
  120. package/esm/http/context/request-context.d.mts.map +1 -1
  121. package/esm/http/context/request-context.mjs.map +1 -1
  122. package/esm/http/context/request-memo.d.mts.map +1 -1
  123. package/esm/http/context/request-memo.mjs.map +1 -1
  124. package/esm/http/createHttpApplication.d.mts.map +1 -1
  125. package/esm/http/createHttpApplication.mjs.map +1 -1
  126. package/esm/http/health.d.mts.map +1 -1
  127. package/esm/http/index.d.mts +1 -1
  128. package/esm/http/middleware/cache-response-middleware.mjs.map +1 -1
  129. package/esm/http/middleware/idempotency.middleware.mjs.map +1 -1
  130. package/esm/http/middleware/inject-request-context.d.mts.map +1 -1
  131. package/esm/http/middleware/inject-request-context.mjs +2 -21
  132. package/esm/http/middleware/inject-request-context.mjs.map +1 -1
  133. package/esm/http/middleware/maintenance.middleware.mjs.map +1 -1
  134. package/esm/http/middleware/utils/idempotency-key.mjs.map +1 -1
  135. package/esm/http/ready-report.d.mts.map +1 -1
  136. package/esm/http/request-controller.d.mts.map +1 -1
  137. package/esm/http/request.d.mts +25 -6
  138. package/esm/http/request.d.mts.map +1 -1
  139. package/esm/http/request.mjs +48 -0
  140. package/esm/http/request.mjs.map +1 -1
  141. package/esm/http/response.d.mts +3 -3
  142. package/esm/http/response.d.mts.map +1 -1
  143. package/esm/http/response.mjs.map +1 -1
  144. package/esm/http/server.d.mts.map +1 -1
  145. package/esm/http/server.mjs.map +1 -1
  146. package/esm/http/types.d.mts +36 -5
  147. package/esm/http/types.d.mts.map +1 -1
  148. package/esm/http/uploaded-file.d.mts.map +1 -1
  149. package/esm/http/uploaded-file.mjs.map +1 -1
  150. package/esm/http/uploads-config.d.mts.map +1 -1
  151. package/esm/image/image.d.mts.map +1 -1
  152. package/esm/image/image.mjs.map +1 -1
  153. package/esm/index.d.mts +2 -2
  154. package/esm/logger/logger.mjs.map +1 -1
  155. package/esm/mail/config.d.mts.map +1 -1
  156. package/esm/mail/events.d.mts.map +1 -1
  157. package/esm/mail/mail.d.mts.map +1 -1
  158. package/esm/mail/mail.mjs.map +1 -1
  159. package/esm/mail/mailer-pool.d.mts.map +1 -1
  160. package/esm/mail/mailer-pool.mjs.map +1 -1
  161. package/esm/mail/send-mail.mjs.map +1 -1
  162. package/esm/mail/test-mailbox.d.mts.map +1 -1
  163. package/esm/mail/test-mailbox.mjs.map +1 -1
  164. package/esm/production/assert-generated-imports.mjs.map +1 -1
  165. package/esm/production/build-app-production.mjs.map +1 -1
  166. package/esm/production/dist-build-manifest.mjs.map +1 -1
  167. package/esm/production/production-builder.mjs.map +1 -1
  168. package/esm/production/production-supervisor.mjs.map +1 -1
  169. package/esm/production/promote-dist.mjs.map +1 -1
  170. package/esm/production/resolve-build-config.mjs.map +1 -1
  171. package/esm/repositories/adapters/cascade/cascade-adapter.d.mts.map +1 -1
  172. package/esm/repositories/adapters/cascade/cascade-adapter.mjs.map +1 -1
  173. package/esm/repositories/adapters/cascade/cascade-query-builder.d.mts.map +1 -1
  174. package/esm/repositories/adapters/cascade/cascade-query-builder.mjs.map +1 -1
  175. package/esm/repositories/adapters/cascade/filter-applicator.mjs.map +1 -1
  176. package/esm/repositories/repository.manager.d.mts.map +1 -1
  177. package/esm/repositories/repository.manager.mjs.map +1 -1
  178. package/esm/resource/register-resource.d.mts.map +1 -1
  179. package/esm/resource/resource-field-builder.d.mts.map +1 -1
  180. package/esm/resource/resource-field-builder.mjs.map +1 -1
  181. package/esm/resource/resource.d.mts.map +1 -1
  182. package/esm/resource/resource.mjs.map +1 -1
  183. package/esm/restful/restful.d.mts.map +1 -1
  184. package/esm/restful/restful.mjs.map +1 -1
  185. package/esm/router/log-request-lifecycle.mjs.map +1 -1
  186. package/esm/router/normalize-route-path.d.mts.map +1 -1
  187. package/esm/router/positional-handler-diagnostics.d.mts.map +1 -1
  188. package/esm/router/positional-handler-diagnostics.mjs.map +1 -1
  189. package/esm/router/route-registry.d.mts.map +1 -1
  190. package/esm/router/route-registry.mjs.map +1 -1
  191. package/esm/router/router.d.mts.map +1 -1
  192. package/esm/router/router.mjs.map +1 -1
  193. package/esm/socket/utils.d.mts.map +1 -1
  194. package/esm/storage/config.d.mts.map +1 -1
  195. package/esm/storage/context/storage-driver-context.d.mts.map +1 -1
  196. package/esm/storage/drivers/cloud-driver.d.mts.map +1 -1
  197. package/esm/storage/drivers/cloud-driver.mjs.map +1 -1
  198. package/esm/storage/drivers/do-spaces-driver.d.mts.map +1 -1
  199. package/esm/storage/drivers/local-driver.d.mts.map +1 -1
  200. package/esm/storage/drivers/local-driver.mjs.map +1 -1
  201. package/esm/storage/drivers/r2-driver.d.mts.map +1 -1
  202. package/esm/storage/scoped-storage.d.mts.map +1 -1
  203. package/esm/storage/scoped-storage.mjs.map +1 -1
  204. package/esm/storage/storage-file.d.mts.map +1 -1
  205. package/esm/storage/storage-file.mjs.map +1 -1
  206. package/esm/storage/storage.d.mts.map +1 -1
  207. package/esm/storage/utils/safe-fetch.mjs.map +1 -1
  208. package/esm/tests/start-http-development-server.d.mts.map +1 -1
  209. package/esm/tests/test-connectors-selection.mjs.map +1 -1
  210. package/esm/tests/test-helpers.d.mts.map +1 -1
  211. package/esm/tests/test-setup-timeout.mjs.map +1 -1
  212. package/esm/tests/vitest-setup.d.mts.map +1 -1
  213. package/esm/tests/vitest-setup.mjs.map +1 -1
  214. package/esm/updater/update-warlock-packages.mjs.map +1 -1
  215. package/esm/use-cases/use-case-broadcast.d.mts.map +1 -1
  216. package/esm/use-cases/use-case-events.d.mts.map +1 -1
  217. package/esm/use-cases/use-case-pipeline.d.mts.map +1 -1
  218. package/esm/use-cases/use-case.d.mts.map +1 -1
  219. package/esm/use-cases/use-cases-registry.d.mts.map +1 -1
  220. package/esm/use-cases/use-cases-registry.mjs.map +1 -1
  221. package/esm/utils/database-log.d.mts.map +1 -1
  222. package/esm/utils/database-log.mjs.map +1 -1
  223. package/esm/utils/environment.d.mts.map +1 -1
  224. package/esm/utils/framework-vesion.mjs.map +1 -1
  225. package/esm/utils/get-localized.mjs.map +1 -1
  226. package/esm/utils/load-environment.mjs.map +1 -1
  227. package/esm/utils/normalized-path.d.mts.map +1 -1
  228. package/esm/utils/paths.d.mts.map +1 -1
  229. package/esm/utils/promise-all-object.d.mts.map +1 -1
  230. package/esm/utils/promise-all-object.mjs.map +1 -1
  231. package/esm/utils/sluggable.mjs.map +1 -1
  232. package/esm/utils/version-compare.mjs.map +1 -1
  233. package/esm/validation/database/exists-except-current-id.mjs.map +1 -1
  234. package/esm/validation/database/exists-except-current-user.mjs.map +1 -1
  235. package/esm/validation/database/unique-except-current-id.mjs.map +1 -1
  236. package/esm/validation/plugins/file-plugin.mjs.map +1 -1
  237. package/esm/validation/plugins/localized-plugin.mjs +2 -2
  238. package/esm/validation/plugins/localized-plugin.mjs.map +1 -1
  239. package/esm/validation/types.d.mts +17 -4
  240. package/esm/validation/types.d.mts.map +1 -1
  241. package/esm/validation/validators/file-validator.mjs.map +1 -1
  242. package/esm/vite/lower-stage3-decorators.d.mts.map +1 -1
  243. package/esm/warlock-config/warlock-config.manager.d.mts.map +1 -1
  244. package/esm/warlock-config/warlock-config.manager.mjs.map +1 -1
  245. package/llms-full.txt +113 -87
  246. package/llms.txt +2 -2
  247. package/package.json +12 -12
  248. package/skills/README.md +2 -2
  249. package/skills/create-controller/SKILL.md +9 -9
  250. package/skills/send-response/SKILL.md +51 -37
  251. package/skills/store-file/SKILL.md +8 -3
  252. package/skills/upload-file/SKILL.md +9 -7
  253. package/skills/use-app-context/SKILL.md +2 -2
  254. package/skills/use-localization/SKILL.md +6 -2
  255. package/skills/use-repository/SKILL.md +2 -2
  256. package/skills/use-request-locals/SKILL.md +3 -3
  257. package/skills/validate-input/SKILL.md +2 -2
  258. package/skills/warlock-conventions/SKILL.md +2 -2
  259. package/skills/wire-socket/SKILL.md +2 -2
  260. package/skills/write-cli-command/SKILL.md +4 -1
  261. package/skills/write-middleware/SKILL.md +13 -15
package/llms.txt CHANGED
@@ -42,7 +42,7 @@
42
42
  - [warlock-doctor](@warlock.js/core/warlock-doctor/SKILL.md): Run `warlock doctor` — a read-only diagnostics command that checks routes / config / connectors / optional-peers / health endpoints / release hygiene and prints a pass/warn/fail report, exiting non-zero on any failure. Add your own probe with the `DoctorCheck` contract and `runChecks` / `formatReportLines`. Triggers: `warlock doctor`, `doctorCommand`, `DoctorCheck`, `CheckResult`, `CheckStatus`, `DoctorReport`, `runChecks`, `formatReportLines`, `printReport`, `defaultDoctorChecks`; "diagnose my app", "preflight / preflight check", "is the app healthy", "why are there 0 routes", "pre-release sanity check", "CI smoke check"; run as `pnpm warlock doctor`. Skip: the live `/health` + `/ready` HTTP probes — `@warlock.js/core/health-checks/SKILL.md`; authoring a general CLI command — `@warlock.js/core/write-cli-command/SKILL.md`; releasing the package — `releasing-warlock-monorepo`; competing tools `npm doctor`, `nest info`, hand-rolled preflight scripts.
43
43
  - [warlock-routes](@warlock.js/core/warlock-routes/SKILL.md): Run `warlock routes` — a read-only command that lists the registered HTTP routes as a verb-colored table (method / path / name / action / middleware-count / source), a sibling of `warlock doctor`. Filter with `--method` / `--path` / `--name`, or emit normalized rows as JSON with `--json`. Also covers `warlock routes:diff`, which compares live page routes against the last `warlock build`'s route snapshot and exits non-zero on drift. Triggers: `warlock routes`, `routesCommand`, `warlock routes:diff`, `routesDiffCommand`, "list my routes", "show all routes", "route table", "what endpoints does my app expose", "dump routes as JSON", "which routes have middleware", "route map for CI", "did my page routes drift from the last build"; run as `pnpm warlock routes` / `pnpm warlock routes:diff`. Skip: read-only health/preflight checks — `@warlock.js/core/warlock-doctor/SKILL.md`; defining/naming/grouping routes — `@warlock.js/core/register-route/SKILL.md`; authoring a general CLI command — `@warlock.js/core/write-cli-command/SKILL.md`; competing tools `nest`/`express` route listers, `php artisan route:list`.
44
44
  - [wire-socket](@warlock.js/core/wire-socket/SKILL.md): Configure Socket.IO via `src/config/socket.ts`, reach the live server through `getSocketServer()` (or `app.socket` post-bootstrap), register `connection` handlers once the late-phase socket connector has booted, emit from controllers/services, use rooms and namespaces. Triggers: `app.socket`, `getSocketServer`, `SocketOptions`, `socket.io` `Server`, `socket.join`, `socket.to`, `io.of`, `io.use`; "add realtime chat", "emit socket events from a service", "use rooms and namespaces", "per-socket JWT auth". Skip: connector lifecycle — `@warlock.js/core/add-connector/SKILL.md`; app context accessors — `@warlock.js/core/use-app-context/SKILL.md`; competing libs `ws`, `socket.io` direct without Warlock connector, `uWebSockets.js`.
45
- - [write-cli-command](@warlock.js/core/write-cli-command/SKILL.md): Author a custom `warlock <my-cmd>` command via the `command()` factory — name, description, action, options, preload, then register in `warlock.config.ts > cli.commands` or drop in `src/app/<module>/commands/`. Triggers: `command`, `CLICommand`, `CLICommandPreload`, `CLICommandOption`, `preload`, `preAction`, `persistent`, `colors`; "write a custom warlock command", "one-off maintenance task", "ship a CLI from a package", "framework built-in commands"; typical import `import { command } from "@warlock.js/core"`. Skip: framework dev/build/start — `@warlock.js/core/run-app/SKILL.md`; warlock.config.ts wiring — `@warlock.js/core/configure-app/SKILL.md`; competing libs `commander`, `yargs`, `oclif`.
46
- - [write-middleware](@warlock.js/core/write-middleware/SKILL.md): Author HTTP middleware for @warlock.js/core — the `(request, response)` signature, short-circuit by returning a response, enrich the request with extra fields, register per-route, per-group, or app-wide. Triggers: `Middleware`, `MiddlewareResponse`, `router.group`, `guarded`, `request.detectIp`, `authMiddleware`; "write a custom middleware", "short-circuit a request", "enrich the request with extra fields", "per-route vs per-group middleware"; typical import `import type { Middleware } from "@warlock.js/core"`. Skip: built-in middleware catalog — `@warlock.js/core/use-middleware/SKILL.md`; route attachment — `@warlock.js/core/register-route/SKILL.md`; response helpers — `@warlock.js/core/send-response/SKILL.md`; competing patterns: `express` `(req, res, next)` middleware, Fastify `preHandler` hooks.
45
+ - [write-cli-command](@warlock.js/core/write-cli-command/SKILL.md): Author a custom `warlock <my-cmd>` command via the `command()` factory — name, description, action, options, preload, then register in `warlock.config.ts > cli.commands` or drop in `src/app/<module>/commands/`. Also covers built-in `warlock add` feature scaffolding, including the Web starter and `index.register.ts`. Triggers: `command`, `CLICommand`, `CLICommandPreload`, `CLICommandOption`, `preload`, `preAction`, `persistent`, `colors`, `warlock add`, `index.register.ts`; "write a custom warlock command", "one-off maintenance task", "ship a CLI from a package", "framework built-in commands"; typical import `import { command } from "@warlock.js/core"`. Skip: framework dev/build/start — `@warlock.js/core/run-app/SKILL.md`; warlock.config.ts wiring — `@warlock.js/core/configure-app/SKILL.md`; competing libs `commander`, `yargs`, `oclif`.
46
+ - [write-middleware](@warlock.js/core/write-middleware/SKILL.md): Author HTTP middleware for @warlock.js/core — the `({ request, response })` signature, short-circuit by returning a response, enrich the request with extra fields, register per-route, per-group, or app-wide. Triggers: `Middleware`, `MiddlewareResponse`, `router.group`, `guarded`, `request.detectIp`, `authMiddleware`; "write a custom middleware", "short-circuit a request", "enrich the request with extra fields", "per-route vs per-group middleware"; typical import `import type { Middleware } from "@warlock.js/core"`. Skip: built-in middleware catalog — `@warlock.js/core/use-middleware/SKILL.md`; route attachment — `@warlock.js/core/register-route/SKILL.md`; response helpers — `@warlock.js/core/send-response/SKILL.md`; competing patterns: `express` `(req, res, next)` middleware, Fastify `preHandler` hooks.
47
47
  - [write-seeder](@warlock.js/core/write-seeder/SKILL.md): Author a seed file under `src/app/<module>/seeds/<name>.ts` using the `seeder()` factory — `name`, `dependsOn`, `once`, `order`, `batchSize`, `run({ track, now, batchSize })`. Auto-discovered by `warlock seed`; tracked in a `seeds` table; per-record refs in `seed_records` so `warlock seed --drop` can undo a seed. Triggers: `seeder`, `Seeder`, `SeedResult`, `SeedContext`, `SeedClock`, `track`, `now`, `batchSize`, `SeedersManager`, `warlock seed`, `--fresh`, `--drop`, `--list`, `--path`; "seed default roles", "undo a seed", "one-time data migration", "auto-discovered seeds", "order seeds by dependency", "deterministic seed timestamps", "inject a seed clock"; typical import `import { seeder } from "@warlock.js/core"`. Skip: module folder layout — `@warlock.js/core/create-module/SKILL.md`; repository CRUD — `@warlock.js/core/use-repository/SKILL.md`; CLI flags — `@warlock.js/core/write-cli-command/SKILL.md`; competing patterns: hand-rolled `node scripts/seed.js`, `typeorm-seeding`.
48
48
  - [write-use-case](@warlock.js/core/write-use-case/SKILL.md): Author `useCase()` pipelines for business logic — guards, schema, before/after middleware, retry, benchmark, broadcast, lifecycle callbacks; transport-agnostic and observable by default. Input is inferred from the `schema`. Triggers: `useCase`, `UseCaseContext`, `UseCaseResult`, `retry`, `benchmark`, `broadcast`, `description`, `globalUseCasesEvents`, `UseCaseBroadcastChannel`; "encapsulate a business operation", "share logic between HTTP and CLI", "add guards and lifecycle hooks", "broadcast a use case result", "transport-agnostic pipeline"; typical import `import { useCase } from "@warlock.js/core"`. Skip: thin handler shape — `@warlock.js/core/create-controller/SKILL.md`; schema details — `@warlock.js/core/validate-input/SKILL.md`; the standalone retry util — `@warlock.js/core/retry-operation/SKILL.md`; competing libs `@nestjs/cqrs`, `inversify`, hand-rolled service classes.
package/package.json CHANGED
@@ -25,13 +25,13 @@
25
25
  "@mongez/slug": "^1.0.7",
26
26
  "@mongez/supportive-is": "^2.1.4",
27
27
  "@mongez/time-wizard": "^1.0.6",
28
- "@warlock.js/auth": "5.2.3",
29
- "@warlock.js/cache": "5.2.3",
30
- "@warlock.js/cascade": "5.2.3",
31
- "@warlock.js/context": "5.2.3",
32
- "@warlock.js/logger": "5.2.3",
33
- "@warlock.js/seal": "5.2.3",
34
- "@warlock.js/fs": "5.2.3",
28
+ "@warlock.js/auth": "5.3.0",
29
+ "@warlock.js/cache": "5.3.0",
30
+ "@warlock.js/cascade": "5.3.0",
31
+ "@warlock.js/context": "5.3.0",
32
+ "@warlock.js/logger": "5.3.0",
33
+ "@warlock.js/seal": "5.3.0",
34
+ "@warlock.js/fs": "5.3.0",
35
35
  "chokidar": "^5.0.0",
36
36
  "dayjs": "^1.11.19",
37
37
  "es-module-lexer": "^2.0.0",
@@ -57,10 +57,10 @@
57
57
  "react": "^19.2.3",
58
58
  "react-dom": "^19.2.3",
59
59
  "@react-email/render": "^2.0.5",
60
- "@warlock.js/herald": "5.2.3",
61
- "@warlock.js/ai": "5.2.3",
62
- "@warlock.js/access": "5.2.3",
63
- "@warlock.js/notifications": "5.2.3"
60
+ "@warlock.js/herald": "5.3.0",
61
+ "@warlock.js/ai": "5.3.0",
62
+ "@warlock.js/access": "5.3.0",
63
+ "@warlock.js/notifications": "5.3.0"
64
64
  },
65
65
  "peerDependenciesMeta": {
66
66
  "sharp": {
@@ -123,7 +123,7 @@
123
123
  ],
124
124
  "author": "hassanzohdy",
125
125
  "license": "MIT",
126
- "version": "5.2.3",
126
+ "version": "5.3.0",
127
127
  "type": "module",
128
128
  "main": "./esm/index.mjs",
129
129
  "module": "./esm/index.mjs",
package/skills/README.md CHANGED
@@ -10,7 +10,7 @@ Each folder holds one `SKILL.md` (an agent-facing how-to for a single task). The
10
10
  - [send-response](send-response/SKILL.md) — `Response` helpers: success/error variants, status helpers, redirects, files, streams, SSE.
11
11
  - [validate-input](validate-input/SKILL.md) — author seal schemas, attach them to controllers, infer types, layer DTOs.
12
12
  - [use-middleware](use-middleware/SKILL.md) — attach built-in HTTP middleware (rateLimit, concurrencyLimit, maxBodySize, …) via the `middleware` namespace.
13
- - [write-middleware](write-middleware/SKILL.md) — author HTTP middleware: the `(request, response)` signature, short-circuit, request enrichment.
13
+ - [write-middleware](write-middleware/SKILL.md) — author HTTP middleware: the `({ request, response })` signature, short-circuit, request enrichment.
14
14
  - [build-url](build-url/SKILL.md) — HTTP URL helpers (`url`, `publicUrl`, `assetsUrl`, `uploadsUrl`) anchored at `app.baseUrl`.
15
15
  - [upload-file](upload-file/SKILL.md) — handle multipart uploads: `request.file()`, `v.file()` validation, `UploadedFile.save()`.
16
16
  - [health-checks](health-checks/SKILL.md) — built-in `/health` + `/ready` endpoints, the `health` registry, and graceful request draining.
@@ -45,7 +45,7 @@ Each folder holds one `SKILL.md` (an agent-facing how-to for a single task). The
45
45
 
46
46
  - [warlock-doctor](warlock-doctor/SKILL.md) — `warlock doctor`: read-only diagnostics (routes/config/connectors/optional-peers/health/release-hygiene) with a pass/warn/fail report and non-zero exit on failure.
47
47
  - [warlock-routes](warlock-routes/SKILL.md) — `warlock routes`: list the registered HTTP routes as a verb-colored table (method/path/name/action/middleware/source); filter by method/path/name or emit JSON. Read-only, no connectors.
48
- - [write-cli-command](write-cli-command/SKILL.md) — author a custom `warlock <cmd>` via the `command()` factory (name, action, options, preload).
48
+ - [write-cli-command](write-cli-command/SKILL.md) — author a custom `warlock <cmd>` via the `command()` factory, or inspect built-in `warlock add` feature scaffolding such as the Web starter.
49
49
  - [run-app](run-app/SKILL.md) — `warlock dev` / `warlock build` / `warlock start` operational commands.
50
50
  - [update-packages](update-packages/SKILL.md) — bump every `@warlock.js/*` dependency with `warlock update`.
51
51
 
@@ -12,7 +12,7 @@ A controller is a thin function: pull inputs from `request`, call work, return t
12
12
  ```ts title="src/app/<module>/controllers/<action>.controller.ts"
13
13
  import { type RequestHandler } from "@warlock.js/core";
14
14
 
15
- export const listProductsController: RequestHandler = async (request, response) => {
15
+ export const listProductsController: RequestHandler = async ({ request, response }) => {
16
16
  return response.success({ products: [] });
17
17
  };
18
18
  ```
@@ -67,10 +67,10 @@ import { type Request, type RequestHandler } from "@warlock.js/core";
67
67
  import { type CreateProductSchema, createProductSchema } from "../schema/create-product.schema";
68
68
  import { createProductService } from "../services/create-product.service";
69
69
 
70
- export const createProductController: RequestHandler<Request<CreateProductSchema>> = async (
70
+ export const createProductController: RequestHandler<Request<CreateProductSchema>> = async ({
71
71
  request,
72
72
  response,
73
- ) => {
73
+ }) => {
74
74
  const product = await createProductService(request.validated());
75
75
 
76
76
  return response.successCreate({ product });
@@ -96,10 +96,10 @@ Routes behind `authMiddleware` need `request.user` typed. Project conventions ad
96
96
  import { type GuardedRequestHandler } from "app/auth/requests/guarded.request";
97
97
  import { type CreateProductSchema, createProductSchema } from "../schema/create-product.schema";
98
98
 
99
- export const createProductController: GuardedRequestHandler<CreateProductSchema> = async (
99
+ export const createProductController: GuardedRequestHandler<CreateProductSchema> = async ({
100
100
  request,
101
101
  response,
102
- ) => {
102
+ }) => {
103
103
  // request.user is typed
104
104
  const product = await createProductService(request.validated());
105
105
  return response.successCreate({ product });
@@ -146,7 +146,7 @@ If your controller is over ~30 lines, the work probably belongs in a service.
146
146
  import { type RequestHandler } from "@warlock.js/core";
147
147
  import { listProductsService } from "../services/list-products.service";
148
148
 
149
- export const listProductsController: RequestHandler = async (request, response) => {
149
+ export const listProductsController: RequestHandler = async ({ request, response }) => {
150
150
  const { data: products, pagination } = await listProductsService({
151
151
  ...request.all(),
152
152
  organization_id: request.user.organizationId,
@@ -163,10 +163,10 @@ import { type Request, type RequestHandler } from "@warlock.js/core";
163
163
  import { type CreateProductSchema, createProductSchema } from "../schema/create-product.schema";
164
164
  import { createProductService } from "../services/create-product.service";
165
165
 
166
- export const createProductController: RequestHandler<Request<CreateProductSchema>> = async (
166
+ export const createProductController: RequestHandler<Request<CreateProductSchema>> = async ({
167
167
  request,
168
168
  response,
169
- ) => {
169
+ }) => {
170
170
  const product = await createProductService(request.validated());
171
171
 
172
172
  return response.successCreate({ product });
@@ -200,7 +200,7 @@ export async function getProductService(id: string) {
200
200
  import type { RequestHandler } from "@warlock.js/core";
201
201
  import { getProductService } from "../services/get-product.service";
202
202
 
203
- export const getProductController: RequestHandler = async (request, response) => {
203
+ export const getProductController: RequestHandler = async ({ request, response }) => {
204
204
  const product = await getProductService(request.input("id"));
205
205
 
206
206
  return response.success({ product });
@@ -10,9 +10,9 @@ description: 'Send HTTP responses via @warlock.js/core''s Response helpers — s
10
10
  ## The shape
11
11
 
12
12
  ```ts
13
- import type { RequestHandler, Response } from "@warlock.js/core";
13
+ import type { RequestHandler } from "@warlock.js/core";
14
14
 
15
- export const myController: RequestHandler = async (request, response: Response) => {
15
+ export const myController: RequestHandler = async ({ request, response }) => {
16
16
  // …choose a helper and return it
17
17
  return response.success({ data: "…" });
18
18
  };
@@ -22,11 +22,11 @@ Always `return response.<helper>(...)`. The return value drives Fastify's send.
22
22
 
23
23
  ## Success helpers
24
24
 
25
- | Method | Status | When |
26
- | ----------------------------------- | ------ | ----------------------------------------- |
27
- | `response.success(data?)` | 200 | normal read / update |
28
- | `response.successCreate(data)` | 201 | resource created (POST) |
29
- | `response.noContent()` | 204 | delete succeeded, no body needed |
25
+ | Method | Status | When |
26
+ | ------------------------------ | ------ | -------------------------------- |
27
+ | `response.success(data?)` | 200 | normal read / update |
28
+ | `response.successCreate(data)` | 201 | resource created (POST) |
29
+ | `response.noContent()` | 204 | delete succeeded, no body needed |
30
30
 
31
31
  ```ts
32
32
  return response.success({ products: [...] });
@@ -40,13 +40,13 @@ return response.noContent();
40
40
 
41
41
  ## Client-error helpers
42
42
 
43
- | Method | Status | When |
44
- | --------------------------------------------------- | ------ | ------------------------------------- |
45
- | `response.badRequest(data)` | 400 | malformed or invalid input |
46
- | `response.unauthorized(data?)` | 401 | missing/invalid auth token |
47
- | `response.forbidden(data?)` | 403 | authenticated but not allowed |
48
- | `response.notFound(data?)` | 404 | record missing |
49
- | `response.conflict(data?)` | 409 | uniqueness violation, state conflict |
43
+ | Method | Status | When |
44
+ | ------------------------------ | ------ | ------------------------------------ |
45
+ | `response.badRequest(data)` | 400 | malformed or invalid input |
46
+ | `response.unauthorized(data?)` | 401 | missing/invalid auth token |
47
+ | `response.forbidden(data?)` | 403 | authenticated but not allowed |
48
+ | `response.notFound(data?)` | 404 | record missing |
49
+ | `response.conflict(data?)` | 409 | uniqueness violation, state conflict |
50
50
 
51
51
  ```ts
52
52
  return response.badRequest({ error: t("validation.invalid") });
@@ -65,8 +65,8 @@ Most error helpers accept an optional payload — if you omit it, they send a de
65
65
  ## Redirects
66
66
 
67
67
  ```ts
68
- return response.redirect("/login"); // 302
69
- return response.redirect("/new-home", 301); // permanent
68
+ return response.redirect("/login"); // 302
69
+ return response.redirect("/new-home", 301); // permanent
70
70
  ```
71
71
 
72
72
  ## Files
@@ -98,19 +98,19 @@ stream.end();
98
98
 
99
99
  ## Throwing HTTP errors
100
100
 
101
- Most of the time, controllers don't need to *choose* an error helper — they throw from the service layer instead. The request middleware (`http/middleware/inject-request-context.ts`) catches every `HttpError` subclass and produces the matching response. The error classes mirror the helpers above:
101
+ Most of the time, controllers don't need to _choose_ an error helper — they throw from the service layer instead. The request middleware (`http/middleware/inject-request-context.ts`) catches every `HttpError` subclass and produces the matching response. The error classes mirror the helpers above:
102
102
 
103
103
  ```ts
104
104
  import {
105
- ResourceNotFoundError, // 404
106
- UnAuthorizedError, // 401
107
- ForbiddenError, // 403
108
- BadRequestError, // 400
109
- ConflictError, // 409
110
- NotAcceptableError, // 406
111
- NotAllowedError, // 405
112
- ServerError, // 500
113
- HttpError, // base class — `new HttpError(status, message, payload?)` for arbitrary codes
105
+ ResourceNotFoundError, // 404
106
+ UnAuthorizedError, // 401
107
+ ForbiddenError, // 403
108
+ BadRequestError, // 400
109
+ ConflictError, // 409
110
+ NotAcceptableError, // 406
111
+ NotAllowedError, // 405
112
+ ServerError, // 500
113
+ HttpError, // base class — `new HttpError(status, message, payload?)` for arbitrary codes
114
114
  } from "@warlock.js/core";
115
115
 
116
116
  throw new ResourceNotFoundError("product.notFound");
@@ -118,13 +118,27 @@ throw new ForbiddenError("permission.denied", { resource: "product", id });
118
118
  throw new ConflictError("user.duplicateEmail");
119
119
  ```
120
120
 
121
- Each class takes `(message, payload?)`. The payload merges into the response body alongside `error`. In development mode, the stack trace is included too.
121
+ Each class takes `(message, payload?)`. The framework keeps the optional
122
+ detail nested under `payload`:
123
+
124
+ ```json
125
+ {
126
+ "error": "Product not found",
127
+ "payload": { "id": 42 }
128
+ }
129
+ ```
130
+
131
+ Every unhandled error response also carries
132
+ `Cache-Control: private, no-store`, regardless of status. The floor is
133
+ applied once at the shared error funnel, so API-route errors cannot be stored
134
+ and replayed across users. In development mode, the stack trace is included
135
+ too.
122
136
 
123
137
  Pick the class, throw from the service or use-case, and forget about response shaping at the call site. The controller stays focused on the success path:
124
138
 
125
139
  ```ts
126
- export const getProductController: RequestHandler = async (request, response) => {
127
- const product = await getProductService(request.input("id")); // throws ResourceNotFoundError on miss
140
+ export const getProductController: RequestHandler = async ({ request, response }) => {
141
+ const product = await getProductService(request.input("id")); // throws ResourceNotFoundError on miss
128
142
  return response.success({ product });
129
143
  };
130
144
  ```
@@ -136,9 +150,9 @@ See [`create-controller`](../create-controller/SKILL.md) for the "throw from ser
136
150
  ```ts
137
151
  const sse = response.sse();
138
152
 
139
- sse.send("tick", { count: 1 }); // event name, data, optional id
140
- sse.send("tick", { count: 2 }, "msg-2"); // third arg is the SSE event id
141
- sse.comment("keep-alive"); // invisible to the client, prevents timeout
153
+ sse.send("tick", { count: 1 }); // event name, data, optional id
154
+ sse.send("tick", { count: 2 }, "msg-2"); // third arg is the SSE event id
155
+ sse.comment("keep-alive"); // invisible to the client, prevents timeout
142
156
  sse.end();
143
157
  ```
144
158
 
@@ -178,11 +192,11 @@ response.clearCookie("session_id");
178
192
 
179
193
  Every response cookie gets `httpOnly: true`, `sameSite: "lax"`, and `secure: true` outside development, unless you override them:
180
194
 
181
- | Flag | Default | Why it's the default |
182
- |---|---|---|
183
- | `httpOnly` | `true` | without it, any injected script can read the cookie |
184
- | `sameSite` | `"lax"` | without it, the cookie rides along on cross-site requests |
185
- | `secure` | `true`, except in development | without it, the cookie travels in cleartext |
195
+ | Flag | Default | Why it's the default |
196
+ | ---------- | ----------------------------- | --------------------------------------------------------- |
197
+ | `httpOnly` | `true` | without it, any injected script can read the cookie |
198
+ | `sameSite` | `"lax"` | without it, the cookie rides along on cross-site requests |
199
+ | `secure` | `true`, except in development | without it, the cookie travels in cleartext |
186
200
 
187
201
  `secure` is relaxed in development only — browsers drop a `Secure` cookie over plain http, which would silently break every local login. It stays on in test and staging.
188
202
 
@@ -345,12 +345,17 @@ Set `STORAGE_DRIVER=r2` in production, leave unset in dev — same code uses loc
345
345
  ### Uploading a request file
346
346
 
347
347
  ```ts
348
- import type { RequestHandler, Response } from "@warlock.js/core";
348
+ import { type GuardedRequestHandler } from "app/auth/requests/guarded.request";
349
349
  import { storage } from "@warlock.js/core";
350
350
 
351
- export const uploadAvatarController: RequestHandler = async (request, response: Response) => {
351
+ export const uploadAvatarController: GuardedRequestHandler = async ({ request, response }) => {
352
352
  const upload = request.file("avatar");
353
- const file = await storage.put(upload, `avatars/${request.user.id}/${upload.fileName}`);
353
+
354
+ if (!upload) {
355
+ return response.badRequest({ error: "missing file" });
356
+ }
357
+
358
+ const file = await storage.put(upload, `avatars/${request.user.id}/${upload.name}`);
354
359
 
355
360
  return response.successCreate({ url: file.url, hash: file.hash });
356
361
  };
@@ -9,8 +9,8 @@ Multipart uploads come in as `UploadedFile` instances. The class wraps Fastify's
9
9
 
10
10
  ## The shape
11
11
 
12
- ```ts title="src/app/uploads/schema/index.ts"
13
- import { v } from "@warlock.js/seal";
12
+ ```ts title="src/app/uploads/schema/upload-avatar.schema.ts"
13
+ import { v, type Infer } from "@warlock.js/seal";
14
14
 
15
15
  export const uploadAvatarSchema = v.object({
16
16
  avatar: v
@@ -19,16 +19,18 @@ export const uploadAvatarSchema = v.object({
19
19
  .maxSize({ unit: "MB", size: 5 })
20
20
  .mimeType(["image/jpeg", "image/png", "image/webp"]),
21
21
  });
22
+
23
+ export type UploadAvatarSchema = Infer<typeof uploadAvatarSchema>;
22
24
  ```
23
25
 
24
26
  ```ts title="src/app/uploads/controllers/upload-avatar.controller.ts"
25
- import { type GuardedRequestHandler } from "app/auth/types/guarded-request.type";
27
+ import { type GuardedRequestHandler } from "app/auth/requests/guarded.request";
26
28
  import { type UploadAvatarSchema, uploadAvatarSchema } from "../schema/upload-avatar.schema";
27
29
 
28
- export const uploadAvatarController: GuardedRequestHandler<UploadAvatarSchema> = async (
30
+ export const uploadAvatarController: GuardedRequestHandler<UploadAvatarSchema> = async ({
29
31
  request,
30
32
  response,
31
- ) => {
33
+ }) => {
32
34
  const { avatar } = request.validated();
33
35
 
34
36
  const file = await avatar
@@ -54,7 +56,7 @@ Inside a controller:
54
56
  ```ts
55
57
  import type { RequestHandler, UploadedFile } from "@warlock.js/core";
56
58
 
57
- export const uploadController: RequestHandler = async (request, response) => {
59
+ export const uploadController: RequestHandler = async ({ request, response }) => {
58
60
  // option A — direct from request, no validation
59
61
  const file: UploadedFile | undefined = request.file("avatar");
60
62
 
@@ -236,7 +238,7 @@ export const uploadFilesSchema = v.object({
236
238
  ```ts title="src/app/uploads/controllers/create-upload.controller.ts"
237
239
  import type { RequestHandler } from "@warlock.js/core";
238
240
 
239
- export const createUploadController: RequestHandler = async (request, response) => {
241
+ export const createUploadController: RequestHandler = async ({ request, response }) => {
240
242
  const { files } = request.validated();
241
243
 
242
244
  const saved = await Promise.all(
@@ -232,9 +232,9 @@ const template = await readFile(appPath("mailers/templates/welcome.html"), "utf-
232
232
  ### Health endpoint
233
233
 
234
234
  ```ts title="src/app/system/controllers/health.controller.ts"
235
- import { Application, type RequestHandler, type Response } from "@warlock.js/core";
235
+ import { Application, type RequestHandler } from "@warlock.js/core";
236
236
 
237
- export const healthController: RequestHandler = async (_request, response: Response) => {
237
+ export const healthController: RequestHandler = async ({ response }) => {
238
238
  return response.success({
239
239
  status: "ok",
240
240
  environment: Application.environment,
@@ -229,10 +229,14 @@ The error's message is locale-aware — the `inject-request-context` middleware
229
229
  ### Translated response message in a controller
230
230
 
231
231
  ```ts
232
- export const createProductController: GuardedRequestHandler<CreateProductSchema> = async (
232
+ import { type GuardedRequestHandler } from "app/auth/requests/guarded.request";
233
+ import { type CreateProductSchema } from "../schema/create-product.schema";
234
+ import { createProductService } from "../services/create-product.service";
235
+
236
+ export const createProductController: GuardedRequestHandler<CreateProductSchema> = async ({
233
237
  request,
234
238
  response,
235
- ) => {
239
+ }) => {
236
240
  const product = await createProductService(request.validated());
237
241
  return response.success({
238
242
  message: request.t("products.created"),
@@ -276,10 +276,10 @@ export async function listFaqsService(filters: FaqListOptions) {
276
276
  ```
277
277
 
278
278
  ```ts title="src/app/faqs/controllers/list-faqs.controller.ts"
279
- import type { RequestHandler, Response } from "@warlock.js/core";
279
+ import { type GuardedRequestHandler } from "app/auth/requests/guarded.request";
280
280
  import { listFaqsService } from "../services/list-faqs.service";
281
281
 
282
- export const listFaqsController: RequestHandler = async (request, response: Response) => {
282
+ export const listFaqsController: GuardedRequestHandler = async ({ request, response }) => {
283
283
  const { data, pagination } = await listFaqsService({
284
284
  ...request.all(),
285
285
  organization_id: request.user.organizationId,
@@ -30,7 +30,7 @@ public locals: RequestLocals = {};
30
30
  ```ts title="src/app/observability/middleware/request-timing.middleware.ts"
31
31
  import type { Middleware } from "@warlock.js/core";
32
32
 
33
- export const requestTimingMiddleware: Middleware = request => {
33
+ export const requestTimingMiddleware: Middleware = (request) => {
34
34
  request.startedAt = Date.now();
35
35
  };
36
36
  ```
@@ -38,7 +38,7 @@ export const requestTimingMiddleware: Middleware = request => {
38
38
  ```ts title="src/app/observability/controllers/timing.controller.ts"
39
39
  import type { RequestHandler } from "@warlock.js/core";
40
40
 
41
- export const timingController: RequestHandler = async (request, response) => {
41
+ export const timingController: RequestHandler = async ({ request, response }) => {
42
42
  return response.success({ elapsedMs: Date.now() - request.startedAt });
43
43
  };
44
44
  ```
@@ -123,7 +123,7 @@ Feature-local files such as `src/app/organizations/request-locals.d.ts` are equa
123
123
 
124
124
  Two rules the scaffold's own comments spell out, and both bite silently:
125
125
 
126
- - **Keep the trailing `export {}`.** `declare module "x"` inside a file with no top-level import or export declares an *ambient* module, which REPLACES `@warlock.js/core`'s real typings instead of merging into them — every framework export vanishes. The `export {}` is what makes the file a module and the block an augmentation. It is not an unused statement to clean up.
126
+ - **Keep the trailing `export {}`.** `declare module "x"` inside a file with no top-level import or export declares an _ambient_ module, which REPLACES `@warlock.js/core`'s real typings instead of merging into them — every framework export vanishes. The `export {}` is what makes the file a module and the block an augmentation. It is not an unused statement to clean up.
127
127
  - **Keep them `interface`, not `type`.** This project otherwise prefers `type`; these are the named exception, because declaration merging is interface-only. `type RequestUser = { ... }` is a duplicate-identifier error, not an augmentation.
128
128
 
129
129
  On a project scaffolded before 5.1 there is no `src/typings.d.ts`, and `tsconfig.json` carries `"typeRoots": ["./src/typings.d.ts"]` — wrong twice, since `typeRoots` takes directories of `@types` packages rather than files, and that file did not exist. Drop the `typeRoots` entry, create the file, and list it under `include`.
@@ -26,10 +26,10 @@ import type { Request, RequestHandler } from "@warlock.js/core";
26
26
  import { type CreateProductSchema, createProductSchema } from "../schema/create-product.schema";
27
27
  import { createProductService } from "../services/create-product.service";
28
28
 
29
- export const createProductController: RequestHandler<Request<CreateProductSchema>> = async (
29
+ export const createProductController: RequestHandler<Request<CreateProductSchema>> = async ({
30
30
  request,
31
31
  response,
32
- ) => {
32
+ }) => {
33
33
  const product = await createProductService(request.validated());
34
34
 
35
35
  return response.successCreate({ product });
@@ -38,11 +38,11 @@ This skill is the foundation. Every other warlock skill (`register-route`, `crea
38
38
  ```
39
39
 
40
40
  ```ts title="src/app/<module>/controllers/create-<thing>.controller.ts"
41
- import { type GuardedRequestHandler } from "app/auth/types/guarded-request.type";
41
+ import { type GuardedRequestHandler } from "app/auth/requests/guarded.request";
42
42
  import { type CreateThingSchema, createThingSchema } from "../schema/create-thing.schema";
43
43
  import { createThingService } from "../services/create-thing.service";
44
44
 
45
- export const createThingController: GuardedRequestHandler<CreateThingSchema> = async (request, response) => {
45
+ export const createThingController: GuardedRequestHandler<CreateThingSchema> = async ({ request, response }) => {
46
46
  const thing = await createThingService(request.validated());
47
47
  return response.success({ thing });
48
48
  };
@@ -123,10 +123,10 @@ export async function notifyUserService(user: User, payload: unknown) {
123
123
  Then from a controller:
124
124
 
125
125
  ```ts
126
- import type { GuardedRequestHandler } from "app/auth/types/guarded-request.type";
126
+ import type { GuardedRequestHandler } from "app/auth/requests/guarded.request";
127
127
  import { notifyUserService } from "../services/notify-user.service";
128
128
 
129
- export const sendNotificationController: GuardedRequestHandler = async (request, response) => {
129
+ export const sendNotificationController: GuardedRequestHandler = async ({ request, response }) => {
130
130
  await notifyUserService(request.user, request.input("payload"));
131
131
  return response.success({ delivered: true });
132
132
  };
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: write-cli-command
3
- description: 'Author a custom `warlock <my-cmd>` command via the `command()` factory — name, description, action, options, preload, then register in `warlock.config.ts > cli.commands` or drop in `src/app/<module>/commands/`. Triggers: `command`, `CLICommand`, `CLICommandPreload`, `CLICommandOption`, `preload`, `preAction`, `persistent`, `colors`; "write a custom warlock command", "one-off maintenance task", "ship a CLI from a package", "framework built-in commands"; typical import `import { command } from "@warlock.js/core"`. Skip: framework dev/build/start — `@warlock.js/core/run-app/SKILL.md`; warlock.config.ts wiring — `@warlock.js/core/configure-app/SKILL.md`; competing libs `commander`, `yargs`, `oclif`.'
3
+ description: 'Author a custom `warlock <my-cmd>` command via the `command()` factory — name, description, action, options, preload, then register in `warlock.config.ts > cli.commands` or drop in `src/app/<module>/commands/`. Also covers built-in `warlock add` feature scaffolding, including the Web starter and `index.register.ts`. Triggers: `command`, `CLICommand`, `CLICommandPreload`, `CLICommandOption`, `preload`, `preAction`, `persistent`, `colors`, `warlock add`, `index.register.ts`; "write a custom warlock command", "one-off maintenance task", "ship a CLI from a package", "framework built-in commands"; typical import `import { command } from "@warlock.js/core"`. Skip: framework dev/build/start — `@warlock.js/core/run-app/SKILL.md`; warlock.config.ts wiring — `@warlock.js/core/configure-app/SKILL.md`; competing libs `commander`, `yargs`, `oclif`.'
4
4
  ---
5
5
 
6
6
  # Warlock — write a CLI command
@@ -205,6 +205,7 @@ warlock generate (alias: g) — interactive picker
205
205
  | Feature | Installs |
206
206
  | ------------- | --------------------------------------------------------------------------------------------------- |
207
207
  | `react-email` | `react-email` + `@react-email/components` + `@react-email/render` + `@react-email/tailwind`; drops a `welcome-email.tsx` sample; patches `tsconfig.json` |
208
+ | `web` | Warlock Web + React stack; scaffolds the application page, localization, contact endpoint, and Web configuration |
208
209
  | `react` | `react` + `react-dom` + types |
209
210
  | `image` | `sharp` (for the `Image` class) |
210
211
  | `mail` | `nodemailer` + types |
@@ -222,6 +223,8 @@ warlock generate (alias: g) — interactive picker
222
223
 
223
224
  Run `warlock add --list` to see what's currently registered. Pass `--packageManager <yarn\|pnpm\|npm>` to override auto-detection (defaults to whichever lockfile is present).
224
225
 
226
+ The Web starter keeps the page's universal non-component setup in `src/web/index.register.ts`, while `src/web/index.page.tsx` re-exports `register`. Warlock still discovers and runs the hook from the page namespace in both SSR and the browser, but editing the page component remains inside React's Fast Refresh boundary. Do not move the `register()` declaration inline beside the component: React Refresh treats that non-component export as incompatible and reloads the page, losing component state.
227
+
225
228
  ### Misc
226
229
 
227
230
  | Command | Purpose |