@warlock.js/core 5.25.0 → 5.27.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 (163) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/esm/cli/commands/doctor/checks/index.mjs +2 -0
  3. package/esm/cli/commands/doctor/checks/index.mjs.map +1 -1
  4. package/esm/cli/commands/doctor/checks/locales-ts-deprecated.check.mjs +170 -0
  5. package/esm/cli/commands/doctor/checks/locales-ts-deprecated.check.mjs.map +1 -0
  6. package/esm/cli/commands/doctor/doctor-command.action.mjs +7 -2
  7. package/esm/cli/commands/doctor/doctor-command.action.mjs.map +1 -1
  8. package/esm/cli/commands/doctor.command.mjs +5 -0
  9. package/esm/cli/commands/doctor.command.mjs.map +1 -1
  10. package/esm/cli/commands/generate/generators/module.generator.mjs +1 -12
  11. package/esm/cli/commands/generate/generators/module.generator.mjs.map +1 -1
  12. package/esm/cli/commands/openapi-generator.action.mjs +43 -0
  13. package/esm/cli/commands/openapi-generator.action.mjs.map +1 -0
  14. package/esm/cli/commands/openapi-generator.command.mjs +44 -0
  15. package/esm/cli/commands/openapi-generator.command.mjs.map +1 -0
  16. package/esm/cli/commands/typings-generator.command.mjs +4 -1
  17. package/esm/cli/commands/typings-generator.command.mjs.map +1 -1
  18. package/esm/cli/framework-cli-commands.mjs +2 -0
  19. package/esm/cli/framework-cli-commands.mjs.map +1 -1
  20. package/esm/database/migrate-action.mjs +2 -2
  21. package/esm/database/migrate-action.mjs.map +1 -1
  22. package/esm/dev-server/build-development-openapi-document.mjs +36 -0
  23. package/esm/dev-server/build-development-openapi-document.mjs.map +1 -0
  24. package/esm/dev-server/collect-development-api-routes.mjs +27 -0
  25. package/esm/dev-server/collect-development-api-routes.mjs.map +1 -0
  26. package/esm/dev-server/dev-logger.mjs +9 -1
  27. package/esm/dev-server/dev-logger.mjs.map +1 -1
  28. package/esm/dev-server/development-server.mjs +6 -3
  29. package/esm/dev-server/development-server.mjs.map +1 -1
  30. package/esm/dev-server/files-orchestrator.mjs +17 -1
  31. package/esm/dev-server/files-orchestrator.mjs.map +1 -1
  32. package/esm/dev-server/legacy-locales-warning.mjs +16 -0
  33. package/esm/dev-server/legacy-locales-warning.mjs.map +1 -0
  34. package/esm/dev-server/model-resource-typings.mjs +246 -0
  35. package/esm/dev-server/model-resource-typings.mjs.map +1 -0
  36. package/esm/dev-server/module-loader.mjs +16 -0
  37. package/esm/dev-server/module-loader.mjs.map +1 -1
  38. package/esm/dev-server/special-file-patterns.mjs +2 -2
  39. package/esm/dev-server/special-file-patterns.mjs.map +1 -1
  40. package/esm/dev-server/translation-keys-sources.mjs +7 -3
  41. package/esm/dev-server/translation-keys-sources.mjs.map +1 -1
  42. package/esm/dev-server/type-generator.mjs +73 -6
  43. package/esm/dev-server/type-generator.mjs.map +1 -1
  44. package/esm/generations/features/access.feature.mjs +38 -18
  45. package/esm/generations/features/access.feature.mjs.map +1 -1
  46. package/esm/generations/features/bull-board.feature.mjs +2 -2
  47. package/esm/generations/features/bull-board.feature.mjs.map +1 -1
  48. package/esm/http/build-rate-limit-options.d.mts +8 -2
  49. package/esm/http/build-rate-limit-options.d.mts.map +1 -1
  50. package/esm/http/build-rate-limit-options.mjs +1 -0
  51. package/esm/http/build-rate-limit-options.mjs.map +1 -1
  52. package/esm/http/errors/errors.d.mts +1 -17
  53. package/esm/http/errors/errors.d.mts.map +1 -1
  54. package/esm/http/errors/errors.mjs +1 -20
  55. package/esm/http/errors/errors.mjs.map +1 -1
  56. package/esm/http/index.d.mts +4 -3
  57. package/esm/http/index.mjs +1 -1
  58. package/esm/http/middleware/inject-request-context.d.mts +1 -1
  59. package/esm/http/middleware/inject-request-context.d.mts.map +1 -1
  60. package/esm/http/middleware/inject-request-context.mjs.map +1 -1
  61. package/esm/http/middleware/rate-limit.middleware.d.mts.map +1 -1
  62. package/esm/http/middleware/rate-limit.middleware.mjs +5 -1
  63. package/esm/http/middleware/rate-limit.middleware.mjs.map +1 -1
  64. package/esm/http/request.d.mts +9 -26
  65. package/esm/http/request.d.mts.map +1 -1
  66. package/esm/http/request.mjs +1 -22
  67. package/esm/http/request.mjs.map +1 -1
  68. package/esm/http/response.d.mts.map +1 -1
  69. package/esm/http/response.mjs +4 -2
  70. package/esm/http/response.mjs.map +1 -1
  71. package/esm/http/serialized.d.mts +114 -0
  72. package/esm/http/serialized.d.mts.map +1 -0
  73. package/esm/index.d.mts +12 -5
  74. package/esm/index.mjs +8 -2
  75. package/esm/localization/index.mjs +4 -0
  76. package/esm/localization/parse-locale-dictionary.d.mts +23 -0
  77. package/esm/localization/parse-locale-dictionary.d.mts.map +1 -0
  78. package/esm/localization/parse-locale-dictionary.mjs +215 -0
  79. package/esm/localization/parse-locale-dictionary.mjs.map +1 -0
  80. package/esm/localization/register-locale-dictionary.d.mts +12 -0
  81. package/esm/localization/register-locale-dictionary.d.mts.map +1 -0
  82. package/esm/localization/register-locale-dictionary.mjs +58 -0
  83. package/esm/localization/register-locale-dictionary.mjs.map +1 -0
  84. package/esm/openapi/build-openapi-document.d.mts +17 -0
  85. package/esm/openapi/build-openapi-document.d.mts.map +1 -0
  86. package/esm/openapi/build-openapi-document.mjs +124 -0
  87. package/esm/openapi/build-openapi-document.mjs.map +1 -0
  88. package/esm/openapi/cast-schema.mjs +89 -0
  89. package/esm/openapi/cast-schema.mjs.map +1 -0
  90. package/esm/openapi/collect-openapi-document.mjs +40 -0
  91. package/esm/openapi/collect-openapi-document.mjs.map +1 -0
  92. package/esm/openapi/development-openapi-provider.d.mts +17 -0
  93. package/esm/openapi/development-openapi-provider.d.mts.map +1 -0
  94. package/esm/openapi/development-openapi-provider.mjs +21 -0
  95. package/esm/openapi/development-openapi-provider.mjs.map +1 -0
  96. package/esm/openapi/error-responses.mjs +67 -0
  97. package/esm/openapi/error-responses.mjs.map +1 -0
  98. package/esm/openapi/index.d.mts +3 -0
  99. package/esm/openapi/index.mjs +4 -0
  100. package/esm/openapi/openapi-child-output.mjs +57 -0
  101. package/esm/openapi/openapi-child-output.mjs.map +1 -0
  102. package/esm/openapi/openapi-protocol.mjs +30 -0
  103. package/esm/openapi/openapi-protocol.mjs.map +1 -0
  104. package/esm/openapi/paths.mjs +63 -0
  105. package/esm/openapi/paths.mjs.map +1 -0
  106. package/esm/openapi/request-schemas.mjs +138 -0
  107. package/esm/openapi/request-schemas.mjs.map +1 -0
  108. package/esm/openapi/response-schemas.mjs +85 -0
  109. package/esm/openapi/response-schemas.mjs.map +1 -0
  110. package/esm/openapi/schema-registry.mjs +143 -0
  111. package/esm/openapi/schema-registry.mjs.map +1 -0
  112. package/esm/openapi/security.mjs +98 -0
  113. package/esm/openapi/security.mjs.map +1 -0
  114. package/esm/openapi/types.d.mts +119 -0
  115. package/esm/openapi/types.d.mts.map +1 -0
  116. package/esm/production/production-builder.mjs +11 -2
  117. package/esm/production/production-builder.mjs.map +1 -1
  118. package/esm/production/registration-child-runner.mjs +139 -0
  119. package/esm/production/registration-child-runner.mjs.map +1 -0
  120. package/esm/production/route-registration-child.mjs +30 -2
  121. package/esm/production/route-registration-child.mjs.map +1 -1
  122. package/esm/production/route-registration-snapshot.mjs +22 -119
  123. package/esm/production/route-registration-snapshot.mjs.map +1 -1
  124. package/esm/resource/define-resource.d.mts +44 -14
  125. package/esm/resource/define-resource.d.mts.map +1 -1
  126. package/esm/resource/define-resource.mjs +0 -40
  127. package/esm/resource/define-resource.mjs.map +1 -1
  128. package/esm/resource/index.d.mts +1 -1
  129. package/esm/resource/resource.d.mts +4 -4
  130. package/esm/resource/resource.d.mts.map +1 -1
  131. package/esm/resource/resource.mjs.map +1 -1
  132. package/esm/resource/types.d.mts +89 -4
  133. package/esm/resource/types.d.mts.map +1 -1
  134. package/esm/restful/restful.d.mts +5 -5
  135. package/esm/restful/restful.d.mts.map +1 -1
  136. package/esm/router/index.d.mts +1 -1
  137. package/esm/router/named-api-routes-with-responses.mjs +59 -0
  138. package/esm/router/named-api-routes-with-responses.mjs.map +1 -0
  139. package/esm/router/response-schema-types.mjs +100 -0
  140. package/esm/router/response-schema-types.mjs.map +1 -0
  141. package/esm/router/router.d.mts +2 -2
  142. package/esm/router/router.d.mts.map +1 -1
  143. package/esm/router/router.mjs +38 -11
  144. package/esm/router/router.mjs.map +1 -1
  145. package/esm/router/types.d.mts +24 -1
  146. package/esm/router/types.d.mts.map +1 -1
  147. package/esm/tests/start-http-development-server.d.mts +10 -0
  148. package/esm/tests/start-http-development-server.d.mts.map +1 -1
  149. package/esm/tests/start-http-development-server.mjs +16 -2
  150. package/esm/tests/start-http-development-server.mjs.map +1 -1
  151. package/esm/validation/validateAll.mjs +48 -1
  152. package/esm/validation/validateAll.mjs.map +1 -1
  153. package/llms-full.txt +383 -80
  154. package/llms.txt +4 -3
  155. package/package.json +21 -21
  156. package/skills/README.md +4 -3
  157. package/skills/create-controller/SKILL.md +43 -2
  158. package/skills/define-resource/SKILL.md +100 -1
  159. package/skills/generate-openapi/SKILL.md +120 -0
  160. package/skills/index.md +3 -3
  161. package/skills/test-http/SKILL.md +9 -1
  162. package/skills/use-localization/SKILL.md +39 -26
  163. package/skills/validate-input/SKILL.md +2 -2
package/llms.txt CHANGED
@@ -11,10 +11,11 @@
11
11
  - [build-restful](@warlock.js/core/build-restful/SKILL.md): Generate standard CRUD endpoints — via `router.route(path).list().show().create().update().destroy()` chain or the `Restful` base class. Pick the chain by default; reach for `Restful` when you want repository-bound defaults. Triggers: `router.route`, `Restful`, `router.restfulResource`, `RouteResource`, `.crud`, `.nest`, `beforeCreate`, `onCreate`; "build a CRUD API", "register list/show/create/update/destroy", "repository-bound default handlers", "override a single REST action"; typical import `import { router, Restful } from "@warlock.js/core"`. Skip: wider router surface — `@warlock.js/core/register-route/SKILL.md`; per-action controllers — `@warlock.js/core/create-controller/SKILL.md`; wire mapping — `@warlock.js/core/define-resource/SKILL.md`; competing pattern: hand-rolled controllers, `@nestjs/swagger` decorator-driven CRUD.
12
12
  - [build-url](@warlock.js/core/build-url/SKILL.md): HTTP URL helpers — `url`, `publicUrl`, `assetsUrl`, `uploadsUrl`, anchored at `app.baseUrl`. Use to render `src` / `href` / API URLs in resources and responses. `setBaseUrl` is wired by the HTTP connector from `config.get("app.baseUrl")`. Triggers: `url`, `publicUrl`, `assetsUrl`, `uploadsUrl`, `setBaseUrl`, `BASE_URL`; "render an avatar src URL", "absolute download link", "embed asset URL in email", "URL helpers vs path helpers"; typical import `import { url, publicUrl, uploadsUrl } from "@warlock.js/core"`. Skip: filesystem paths — `@warlock.js/core/resolve-path/SKILL.md`; signed CDN URLs — `@warlock.js/core/store-file/SKILL.md`; resource output — `@warlock.js/core/define-resource/SKILL.md`; competing patterns: hand-rolled `${baseUrl}/...` template strings.
13
13
  - [configure-app](@warlock.js/core/configure-app/SKILL.md): Configure a Warlock app — the two layers (`warlock.config.ts` for framework-level wiring, `src/config/*.ts` for subsystems), `.env` + `env()`, the `config()` getter for runtime reads, and `app.publicUrl`/`PUBLIC_APP_URL` (the app's public origin). Triggers: `defineConfig`, `config.get`, `config.key`, `env`, `ConfigRegistry`, `HttpConfigurations`, `AppConfigurations`, `publicUrl`, `PUBLIC_APP_URL`, `getPublicUrl`; "add a new config file", "warlock.config.ts vs src/config", "read env values", "runtime config lookup", "app public origin/URL"; typical import `import { defineConfig, config, env } from "@warlock.js/core"`. Skip: cache driver registration — `@warlock.js/cache/cache-basics/SKILL.md`; mail config — `@warlock.js/core/send-mail/SKILL.md`; storage config — `@warlock.js/core/store-file/SKILL.md`; sitemap refusal on a missing origin — `@warlock.js/web/generate-sitemap/SKILL.md`; competing libs `dotenv` direct, `convict`, `node-config`.
14
- - [create-controller](@warlock.js/core/create-controller/SKILL.md): Author HTTP controllers in @warlock.js/core — RequestHandler signature, validated input via seal schemas, response helpers, attaching metadata. Controllers are thin functions; business logic moves to services or use-cases. Triggers: `RequestHandler`, `Request<TSchema>`, `GuardedRequestHandler`, `request.validated`, `request.input`, `controller.validation`, `response.success`, `response.successCreate`; "write a controller", "attach a schema to a handler", "thin controller pattern", "guarded request type"; typical import `import { type RequestHandler } from "@warlock.js/core"`. Skip: response helper menu — `@warlock.js/core/send-response/SKILL.md`; schema authoring — `@warlock.js/core/validate-input/SKILL.md`; URL wiring — `@warlock.js/core/register-route/SKILL.md`; competing patterns: `express` middleware functions, `@nestjs/common` `@Controller`/`@Get` decorators.
14
+ - [create-controller](@warlock.js/core/create-controller/SKILL.md): Author HTTP controllers in @warlock.js/core — RequestHandler signature, validated input via seal schemas, response helpers, attaching metadata. Controllers are thin functions; business logic moves to services or use-cases. Triggers: `RequestHandler`, `Request<TSchema>`, `GuardedRequestHandler`, `request.validated`, `request.input`, `controller.validation`, `controller.responseSchema`, `response.success`, `response.successCreate`; "write a controller", "attach a schema to a handler", "thin controller pattern", "guarded request type", "type the API response"; typical import `import { type RequestHandler } from "@warlock.js/core"`. Skip: response helper menu — `@warlock.js/core/send-response/SKILL.md`; schema authoring — `@warlock.js/core/validate-input/SKILL.md`; URL wiring — `@warlock.js/core/register-route/SKILL.md`; competing patterns: `express` middleware functions, `@nestjs/common` `@Controller`/`@Get` decorators.
15
15
  - [create-module](@warlock.js/core/create-module/SKILL.md): Scaffold a new feature module under `src/app/<name>/` via `warlock generate.module` and the follow-up generators for controllers, models, repositories, resources, and validation schemas. Triggers: `warlock generate.module`, `generate.controller`, `generate.service`, `generate.model`, `generate.repository`, `generate.resource`, `generate.migration`, `--minimal`, `gen.m`; "scaffold a new module", "create CRUD bootstrap", "add a controller to a module", "generate a model"; typical CLI `npx warlock generate.module <name>`. Skip: framework-wide layout rules — `@warlock.js/core/warlock-conventions/SKILL.md`; routes file shape — `@warlock.js/core/register-route/SKILL.md`; controller shape — `@warlock.js/core/create-controller/SKILL.md`; competing tooling: `@nestjs/cli`, `hygen`, hand-rolled folder layouts.
16
- - [define-resource](@warlock.js/core/define-resource/SKILL.md): Map model fields to wire-shape via `defineResource()` or `Resource` subclasses. Output-only — never put business logic, hydration, or reconciliation in a resource. Triggers: `defineResource`, `Resource`, `RegisterResource`, `toJSON`, `"self"`, `"localized"`, `"uploadsUrl"`; "shape an API response", "nest related resources", "rename a field on output", "self-referential tree resource"; typical import `import { defineResource } from "@warlock.js/core"`. Skip: localized columns — `@warlock.js/core/use-localization/SKILL.md`; URL casting — `@warlock.js/core/build-url/SKILL.md`; controller side — `@warlock.js/core/create-controller/SKILL.md`; competing libs `@nestjs/swagger` `@ApiProperty`, `class-transformer`, hand-rolled DTO mappers.
16
+ - [define-resource](@warlock.js/core/define-resource/SKILL.md): Map model fields to wire-shape via `defineResource()` or `Resource` subclasses. Output-only — never put business logic, hydration, or reconciliation in a resource. Also covers the typed output: `ResourceOutput<typeof R>`, `defineResource<Out>()`, `Serialized<T, W>` and `ModelResourceRegistry`. Triggers: `defineResource`, `Resource`, `RegisterResource`, `toJSON`, `ResourceOutput`, `Serialized`, `ModelResourceRegistry`, `"self"`, `"localized"`, `"uploadsUrl"`; "shape an API response", "type a resource's output", "what does the client receive", "nest related resources", "rename a field on output", "self-referential tree resource"; typical import `import { defineResource } from "@warlock.js/core"`. Skip: localized columns — `@warlock.js/core/use-localization/SKILL.md`; URL casting — `@warlock.js/core/build-url/SKILL.md`; controller side — `@warlock.js/core/create-controller/SKILL.md`; competing libs `@nestjs/swagger` `@ApiProperty`, `class-transformer`, hand-rolled DTO mappers.
17
17
  - [encrypt-data](@warlock.js/core/encrypt-data/SKILL.md): Reversible AES-256-GCM `encrypt` / `decrypt` for secrets you need to read back; one-way HMAC-SHA256 `hmacHash` for deterministic fingerprints (lookup/dedup of encrypted columns). Keys come from `src/config/encryption.ts`. Triggers: `encrypt`, `decrypt`, `hmacHash`, `EncryptionConfigurations`, `APP_ENCRYPTION_KEY`, `APP_HMAC_KEY`; "store an API key reversibly", "fingerprint an encrypted column for lookup", "AES-256-GCM secret", "HMAC-SHA256 dedup key"; typical import `import { encrypt, decrypt, hmacHash } from "@warlock.js/core"`. Skip: password hashing — `@warlock.js/core/hash-password/SKILL.md`; config wiring — `@warlock.js/core/configure-app/SKILL.md`; competing libs Node `crypto` direct, `crypto-js`, `libsodium-wrappers`.
18
+ - [generate-openapi](@warlock.js/core/generate-openapi/SKILL.md): Generate an OpenAPI 3.1 document from the registered routes with `warlock generate.openapi` (default `storage/openapi/openapi.json`) and read it live in the dev-only API docs page at `/__warlock/docs`. Documents paths, path/query parameters, request bodies from Seal validation, responses from `handler.responseSchema`, 401/422 and bearer/cookie security from `authMiddleware()`. Triggers: `generate.openapi`, `openApiGeneratorCommand`, `buildOpenApiDocument`, `getDevelopmentOpenApiDocument`, `responseSchema`, `--include-pages`, `/__warlock/docs`; "OpenAPI", "Swagger", "API docs", "import my API into Postman or Insomnia", "document my routes". Skip: declaring response types on a handler — `@warlock.js/core/create-controller/SKILL.md`; the devtools dashboard — `@warlock.js/devtools/devtools-overview/SKILL.md`; listing routes — `@warlock.js/core/warlock-routes/SKILL.md`.
18
19
  - [hash-password](@warlock.js/core/hash-password/SKILL.md): One-way bcrypt password hashing — `hashPassword` / `verifyPassword`, plus the declarative `useHashedPassword()` schema transformer that auto-hashes a model's password field on save. Salt rounds come from `src/config/encryption.ts`. Triggers: `hashPassword`, `verifyPassword`, `useHashedPassword`, `password.salt`, `bcryptjs`; "hash a user password", "verify login credentials", "auto-hash on save", "rotate a password"; typical import `import { hashPassword, verifyPassword } from "@warlock.js/core"`. Skip: reversible secrets — `@warlock.js/core/encrypt-data/SKILL.md`; the other transformers — `@warlock.js/core/use-model-transformers/SKILL.md`; config wiring — `@warlock.js/core/configure-app/SKILL.md`; competing libs `bcrypt` native, `argon2`, `scrypt`.
19
20
  - [health-checks](@warlock.js/core/health-checks/SKILL.md): Built-in liveness (`/health`) and readiness (`/ready`) endpoints plus graceful HTTP request draining for zero-downtime deploys — the `health` registry (`health.addCheck`/`removeCheck`), the `http.health.*` and `http.gracefulShutdown.*` config, and how readiness ties into `Application.isShuttingDown`. Triggers: `health`, `health.addCheck`, `health.removeCheck`, `HealthCheck`, `/health`, `/ready`, `http.health`, `http.gracefulShutdown`, `forceCloseConnections`, "liveness probe", "readiness probe", "graceful shutdown", "drain in-flight requests", "zero-downtime deploy", "kubernetes health check", "503 until ready"; typical import `import { health } from "@warlock.js/core"`. Skip: the `Application.onShutdown` / `onceBooted` lifecycle hooks — `@warlock.js/core/use-app-context/SKILL.md`; maintenance-mode 503s — `@warlock.js/core/use-middleware/SKILL.md`; connector lifecycle — `@warlock.js/core/add-connector/SKILL.md`; competing libs `@fastify/under-pressure`, `terminus`, hand-rolled `/health` controllers.
20
21
  - [lower-stage3-decorators](@warlock.js/core/lower-stage3-decorators/SKILL.md): Vite/Vitest plugin `lowerStage3Decorators()` that lowers TC39 Stage-3 (native) decorators with esbuild before oxc / the SSR rewrite mangles them — so `@RegisterModel`-decorated Cascade models load under Vitest 4 / Vite 8. Triggers: `lowerStage3Decorators`, "SyntaxError: Invalid or unexpected token" on a decorated class, "@(0, __vite_ssr_import__.X)()", "decorator crashes vitest", "models won't load in tests", "Vite 8 oxc decorators"; typical import `import { lowerStage3Decorators } from "@warlock.js/core/vite"` in `vite.config.ts` / `vitest.config.ts`. Skip: writing the tests themselves — `@warlock.js/core/test-service/SKILL.md` / `@warlock.js/core/test-http/SKILL.md`; runtime migrations — `@warlock.js/cascade/write-migration/SKILL.md`.
@@ -33,7 +34,7 @@
33
34
  - [update-packages](@warlock.js/core/update-packages/SKILL.md): Keep a project current with `warlock update` — bump every `@warlock.js/*` dependency in package.json to its latest published version (range operator preserved), then run the lockfile-detected package manager install. Also covers the `warlock dev` update notice, its `u` update-and-restart keyboard shortcut, and the `devServer.checkForUpdates` toggle. Triggers: `warlock update`, `--no-install`, `--dry-run`, `--check`, `checkForUpdates`, `fetchLatestVersion`, `isNewerVersion`; "update warlock packages", "upgrade the framework", "is there a new warlock version", "update notice in the dev server", "press u to update", "dev server keyboard shortcut", "update check offline", "bump @warlock.js/* to latest"; typical CLI `warlock update`. Skip: dev/build/start runtime — `@warlock.js/core/run-app/SKILL.md`; writing a custom command — `@warlock.js/core/write-cli-command/SKILL.md`; installing a NEW feature package (auth, mail, storage) — that is `warlock add`; releasing/publishing the framework — workspace release tooling, not this command.
34
35
  - [upload-file](@warlock.js/core/upload-file/SKILL.md): Handle multipart file uploads — read via `request.file()` or `request.validated()`, validate with `v.file()`, save via `UploadedFile.save()` or the storage layer, transform images inline. Triggers: `UploadedFile`, `request.file`, `v.file`, `.save`, `.saveAs`, `.resize`, `.format`, `.quality`, `.image`, `.mimeType`, `.maxSize`; "accept a file upload", "validate file size and mime", "save to S3 or local disk", "resize an uploaded image on save"; typical import `import type { UploadedFile, RequestHandler } from "@warlock.js/core"`. Skip: storage drivers + presigned URLs — `@warlock.js/core/store-file/SKILL.md`; image-only transforms — `@warlock.js/core/process-image/SKILL.md`; schema rules — `@warlock.js/core/validate-input/SKILL.md`; competing libs `multer`, `formidable`, `busboy`.
35
36
  - [use-app-context](@warlock.js/core/use-app-context/SKILL.md): Read app-wide context — the `Application` static class (env, version, uptime, runtime strategy, boot lifecycle) plus the `app` runtime accessor (live Fastify, socket.io, router, database via the DI container). Triggers: `Application.isProduction`, `Application.environment`, `Application.runtimeStrategy`, `Application.uptime`, `Application.version`, `Application.onceBooted`, `Application.whenBooted`, `Application.isBooted`, `Application.onShutdown`, `Application.isShuttingDown`, `app.http`, `app.socket`, `app.database`, `app.router`; "branch on environment", "reach the live Fastify instance", "framework version in health endpoint", "dev vs production runtime check", "run code once the app is fully booted", "after all connectors started", "app booted hook", "run cleanup before shutdown", "graceful shutdown hook"; typical import `import { Application, app } from "@warlock.js/core"`. Skip: path helpers — `@warlock.js/core/resolve-path/SKILL.md`; connector start order — `@warlock.js/core/add-connector/SKILL.md`; competing patterns: bare `process.env.NODE_ENV`, ad-hoc Fastify imports.
36
- - [use-localization](@warlock.js/core/use-localization/SKILL.md): Multi-locale translations via `groupedTranslations` (declare keys), `t()` / `request.t()` / `request.trans()` (look up), `request.getLocaleCode()` (detect locale from headers/query), `getLocalized` (pick the right value from a localized-array column). Triggers: `groupedTranslations`, `t`, `request.t`, `request.trans`, `request.transFrom`, `request.getLocaleCode`, `request.setLocaleCode`, `getLocalized`; "add a translation key", "resolve a localized error message", "detect request locale", "pick the right per-locale column value"; typical import `import { t, getLocalized } from "@warlock.js/core"`. Skip: resource output — `@warlock.js/core/define-resource/SKILL.md`; module scaffold — `@warlock.js/core/create-module/SKILL.md`; competing libs `i18next`, `react-intl`, raw `@mongez/localization`.
37
+ - [use-localization](@warlock.js/core/use-localization/SKILL.md): Multi-locale translations via `groupedTranslations` (declare keys), `t()` (look up; always use it in app code, never `request.t()`), `request.getLocaleCode()` (detect locale from headers/query), `getLocalized` (pick the right value from a localized-array column). Triggers: `groupedTranslations`, `t`, `request.t`, `request.trans`, `request.transFrom`, `request.getLocaleCode`, `request.setLocaleCode`, `getLocalized`; "add a translation key", "resolve a localized error message", "detect request locale", "pick the right per-locale column value"; typical import `import { t, getLocalized } from "@warlock.js/core"`. Skip: resource output — `@warlock.js/core/define-resource/SKILL.md`; module scaffold — `@warlock.js/core/create-module/SKILL.md`; competing libs `i18next`, `react-intl`, raw `@mongez/localization`.
37
38
  - [use-middleware](@warlock.js/core/use-middleware/SKILL.md): Attach built-in HTTP middleware to routes via the `middleware` namespace from `@warlock.js/core` — rateLimit, concurrencyLimit, maxBodySize, idempotency, maintenance, ipFilter, cache. Plus `X-Request-Id` correlation, wired automatically. Triggers: `middleware.rateLimit`, `middleware.concurrencyLimit`, `middleware.maxBodySize`, `middleware.idempotency`, `middleware.maintenance`, `middleware.ipFilter`, `middleware.cache`, `X-Request-Id`, `Idempotency-Key`; "add rate limiting", "dedupe writes by idempotency key", "cap concurrent requests", "block IPs", "cache a GET response"; typical import `import { middleware } from "@warlock.js/core"`. Skip: author custom middleware — `@warlock.js/core/write-middleware/SKILL.md`; cache singleton — `@warlock.js/cache/cache-basics/SKILL.md`; competing libs `@fastify/rate-limit` direct, `express-rate-limit`, `helmet`.
38
39
  - [use-model-transformers](@warlock.js/core/use-model-transformers/SKILL.md): Three schema-side helpers — `useHashedPassword()` (bcrypt on save) attaches via `.addTransformer(...)`; `useComputedSlug(field?, scope?)` (auto-slug from another field) and `useComputedModel(callback)` (arbitrary computed-on-save value) attach via `v.computed(...)`. Triggers: `useHashedPassword`, `useComputedSlug`, `useComputedModel`, `.addTransformer`, `v.computed`, `ComputedCallback`; "auto-hash a password field", "auto-slug from title on save", "derive a value at write time", "declarative model transformers"; typical import `import { useHashedPassword, useComputedSlug } from "@warlock.js/core"`. Skip: bcrypt setup details — `@warlock.js/core/hash-password/SKILL.md`; repository writes — `@warlock.js/core/use-repository/SKILL.md`; output filtering — `@warlock.js/core/define-resource/SKILL.md`; competing patterns: manual `await hashPassword(input)` in services, ORM lifecycle hooks.
39
40
  - [use-repository](@warlock.js/core/use-repository/SKILL.md): Subclass `RepositoryManager` for data access — declare `source`, `filterBy`, `defaultOptions`, then call `list()`/`listCached()`/`find()`/`create()`/`update()`/`delete()`, the active/cached/cursor variants, and the `filterBy`-aware aggregates `sum()`/`avg()`/`min()`/`max()`/`groupBy()`/`aggregate()`. Triggers: `RepositoryManager`, `FilterRules`, `RepositoryOptions`, `.list`, `.listCached`, `.find`, `.findCached`, `.create`, `.update`, `.delete`, `.sum`, `.avg`, `.min`, `.max`, `.groupBy`, `.aggregate`, `simpleSelectColumns`; "create a repository", "filter rules for a list endpoint", "cursor vs page pagination", "cached vs uncached read", "sum/avg/group-by with filters"; typical import `import { RepositoryManager } from "@warlock.js/core"`. Skip: cache singleton — `@warlock.js/cache/cache-basics/SKILL.md`; use-case pipelines — `@warlock.js/core/write-use-case/SKILL.md`; wire mapping — `@warlock.js/core/define-resource/SKILL.md`; competing libs `typeorm` Repository, `prisma.client.<model>`, `@nestjs/typeorm`.
package/package.json CHANGED
@@ -8,12 +8,12 @@
8
8
  "warlock": "bin/warlock.js"
9
9
  },
10
10
  "dependencies": {
11
- "@fastify/cookie": "^10.0.0",
12
- "@fastify/cors": "^11.2.0",
13
- "@fastify/http-proxy": "^11.4.1",
14
- "@fastify/multipart": "^9.3.0",
15
- "@fastify/rate-limit": "^10.3.0",
16
- "@fastify/static": "^8.3.0",
11
+ "@fastify/cookie": "^11.1.2",
12
+ "@fastify/cors": "^11.3.0",
13
+ "@fastify/http-proxy": "^11.6.3",
14
+ "@fastify/multipart": "^10.1.2",
15
+ "@fastify/rate-limit": "^11.2.0",
16
+ "@fastify/static": "^10.1.5",
17
17
  "@mongez/concat-route": "^1.2.1",
18
18
  "@mongez/config": "^1.2.2",
19
19
  "@mongez/copper": "^2.1.3",
@@ -23,14 +23,14 @@
23
23
  "@mongez/localization": "^3.5.1",
24
24
  "@mongez/reinforcements": "^4.0.2",
25
25
  "@mongez/slug": "^1.0.7",
26
- "@mongez/supportive-is": "^2.1.5",
26
+ "@mongez/supportive-is": "^3.0.0",
27
27
  "@mongez/time-wizard": "^1.0.6",
28
- "@warlock.js/cache": "5.25.0",
29
- "@warlock.js/cascade": "5.25.0",
30
- "@warlock.js/context": "5.25.0",
31
- "@warlock.js/fs": "5.25.0",
32
- "@warlock.js/logger": "5.25.0",
33
- "@warlock.js/seal": "5.25.0",
28
+ "@warlock.js/cache": "5.27.0",
29
+ "@warlock.js/cascade": "5.27.0",
30
+ "@warlock.js/context": "5.27.0",
31
+ "@warlock.js/fs": "5.27.0",
32
+ "@warlock.js/logger": "5.27.0",
33
+ "@warlock.js/seal": "5.27.0",
34
34
  "bcryptjs": "^3.0.3",
35
35
  "chokidar": "^5.0.0",
36
36
  "dayjs": "^1.11.19",
@@ -38,8 +38,8 @@
38
38
  "esbuild": "^0.27.2",
39
39
  "eslint": "^9.18.0",
40
40
  "fast-glob": "^3.3.3",
41
- "fastify": "^5.6.2",
42
- "find-my-way": "^9.3.0",
41
+ "fastify": "^5.12.5",
42
+ "find-my-way": "^9.9.0",
43
43
  "get-tsconfig": "^5.0.0-beta.5",
44
44
  "import-meta-resolve": "^4.2.0",
45
45
  "mime": "^4.1.0",
@@ -52,11 +52,11 @@
52
52
  "@aws-sdk/lib-storage": "^3.955.0",
53
53
  "@aws-sdk/s3-request-presigner": "^3.955.0",
54
54
  "@react-email/render": "^2.0.5",
55
- "@warlock.js/access": "5.25.0",
56
- "@warlock.js/ai": "5.25.0",
57
- "@warlock.js/herald": "5.25.0",
58
- "@warlock.js/notifications": "5.25.0",
59
- "@warlock.js/devtools": "5.25.0",
55
+ "@warlock.js/access": "5.27.0",
56
+ "@warlock.js/ai": "5.27.0",
57
+ "@warlock.js/herald": "5.27.0",
58
+ "@warlock.js/notifications": "5.27.0",
59
+ "@warlock.js/devtools": "5.27.0",
60
60
  "nodemailer": "^8.0.5",
61
61
  "react": "^19.2.3",
62
62
  "react-dom": "^19.2.3",
@@ -127,7 +127,7 @@
127
127
  ],
128
128
  "author": "hassanzohdy",
129
129
  "license": "MIT",
130
- "version": "5.25.0",
130
+ "version": "5.27.0",
131
131
  "type": "module",
132
132
  "main": "./esm/index.mjs",
133
133
  "module": "./esm/index.mjs",
package/skills/README.md CHANGED
@@ -6,7 +6,7 @@ Each folder holds one `SKILL.md` (an agent-facing how-to for a single task). The
6
6
 
7
7
  - [register-route](register-route/SKILL.md) — register single routes, prefix groups, middleware-guarded blocks, and RESTful resource chains.
8
8
  - [build-restful](build-restful/SKILL.md) — generate standard CRUD endpoints via the `router.route(...)` chain or the `Restful` base class.
9
- - [create-controller](create-controller/SKILL.md) — author HTTP controllers: `RequestHandler` signature, validated input, response helpers, middleware.
9
+ - [create-controller](create-controller/SKILL.md) — author HTTP controllers: `RequestHandler` signature, validated input, response helpers, middleware, `responseSchema` response types.
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.
@@ -19,7 +19,7 @@ Each folder holds one `SKILL.md` (an agent-facing how-to for a single task). The
19
19
 
20
20
  - [use-repository](use-repository/SKILL.md) — subclass `RepositoryManager`: `source`/`filterBy`/`defaultOptions`, list/find/CRUD, cached/cursor variants, and the `filterBy`-aware aggregates (`sum`/`avg`/`min`/`max`/`groupBy`/`aggregate`).
21
21
  - [use-model-transformers](use-model-transformers/SKILL.md) — schema-side helpers: `useHashedPassword()`, `useComputedSlug()`, and friends.
22
- - [define-resource](define-resource/SKILL.md) — map model fields to wire-shape via `defineResource()` / `Resource` subclasses (output-only).
22
+ - [define-resource](define-resource/SKILL.md) — map model fields to wire-shape via `defineResource()` / `Resource` subclasses (output-only); typed output with `ResourceOutput`, `Serialized<T, W>` and `ModelResourceRegistry`.
23
23
  - [write-seeder](write-seeder/SKILL.md) — author a seed file with `seeder()` — `name`/`dependsOn`/`once`/`order`/`batchSize`, `run({ track, now, batchSize })`, `warlock seed --drop`.
24
24
 
25
25
  ## Files, media, mail
@@ -34,7 +34,7 @@ Each folder holds one `SKILL.md` (an agent-facing how-to for a single task). The
34
34
  - [use-app-context](use-app-context/SKILL.md) — read app-wide context via the `Application` static class and the `app` runtime accessor.
35
35
  - [add-connector](add-connector/SKILL.md) — extend the lifecycle with a `BaseConnector` subclass (`start`/`shutdown`/`watchedFiles`).
36
36
  - [wire-socket](wire-socket/SKILL.md) — configure Socket.IO, reach the live server via `getSocketServer()` / `app.socket`.
37
- - [use-localization](use-localization/SKILL.md) — multi-locale translations: `groupedTranslations`, `t()` / `request.t()`, locale resolution.
37
+ - [use-localization](use-localization/SKILL.md) — multi-locale translations: `groupedTranslations`, `t()` (always, never `request.t()`), locale resolution.
38
38
 
39
39
  ## Use-cases & services
40
40
 
@@ -46,6 +46,7 @@ Each folder holds one `SKILL.md` (an agent-facing how-to for a single task). The
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
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
+ - [generate-openapi](generate-openapi/SKILL.md) — `warlock generate.openapi`: an OpenAPI 3.1 document from the routes (validation, `responseSchema`, auth), plus the dev-only API docs page.
49
50
  - [run-app](run-app/SKILL.md) — `warlock dev` / `warlock build` / `warlock start` operational commands.
50
51
  - [update-packages](update-packages/SKILL.md) — bump every `@warlock.js/*` dependency with `warlock update`.
51
52
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: create-controller
3
- description: 'Author HTTP controllers in @warlock.js/core — RequestHandler signature, validated input via seal schemas, response helpers, attaching metadata. Controllers are thin functions; business logic moves to services or use-cases. Triggers: `RequestHandler`, `Request<TSchema>`, `GuardedRequestHandler`, `request.validated`, `request.input`, `controller.validation`, `response.success`, `response.successCreate`; "write a controller", "attach a schema to a handler", "thin controller pattern", "guarded request type"; typical import `import { type RequestHandler } from "@warlock.js/core"`. Skip: response helper menu — `@warlock.js/core/send-response/SKILL.md`; schema authoring — `@warlock.js/core/validate-input/SKILL.md`; URL wiring — `@warlock.js/core/register-route/SKILL.md`; competing patterns: `express` middleware functions, `@nestjs/common` `@Controller`/`@Get` decorators.'
3
+ description: 'Author HTTP controllers in @warlock.js/core — RequestHandler signature, validated input via seal schemas, response helpers, attaching metadata. Controllers are thin functions; business logic moves to services or use-cases. Triggers: `RequestHandler`, `Request<TSchema>`, `GuardedRequestHandler`, `request.validated`, `request.input`, `controller.validation`, `controller.responseSchema`, `response.success`, `response.successCreate`; "write a controller", "attach a schema to a handler", "thin controller pattern", "guarded request type", "type the API response"; typical import `import { type RequestHandler } from "@warlock.js/core"`. Skip: response helper menu — `@warlock.js/core/send-response/SKILL.md`; schema authoring — `@warlock.js/core/validate-input/SKILL.md`; URL wiring — `@warlock.js/core/register-route/SKILL.md`; competing patterns: `express` middleware functions, `@nestjs/common` `@Controller`/`@Get` decorators.'
4
4
  ---
5
5
 
6
6
  # Warlock — create a controller
@@ -102,6 +102,10 @@ Two pieces:
102
102
 
103
103
  If validation fails, the framework returns a 400 with an `errors` payload and your handler never runs.
104
104
 
105
+ ### Validating route params
106
+
107
+ `handler.validation = { params: v.object({ id: v.int().min(1) }) }` validates path segments (e.g. `/posts/:id`). Number and boolean validators convert the string segments; the app's schema object is never changed. Parsed values replace `request.params` (`request.input("id")` is a number) and merge into `request.validated()`, with params winning over body fields. Failures answer like a `schema` failure.
108
+
105
109
  ### Typing a guarded handler
106
110
 
107
111
  Routes behind `authMiddleware` need `request.locals.user` typed. Project conventions add a `GuardedRequest<TSchema>` (overriding `locals` to `RequestLocals & { user: User }`) and a paired `GuardedRequestHandler<TSchema>` alias in `app/auth/requests/guarded.request`:
@@ -130,7 +134,44 @@ Use `RequestHandler<Request<TSchema>>` for public routes, `GuardedRequestHandler
130
134
  createProductController.description = "Create a new product (admin only)";
131
135
  ```
132
136
 
133
- Used by OpenAPI/Swagger generation (planned per `domains/core/backlog.md`) and surfaces in dev-server logs.
137
+ `description` feeds the OpenAPI document (`warlock generate.openapi`, see [`generate-openapi`](../generate-openapi/SKILL.md)) and surfaces in dev-server logs.
138
+
139
+ ## Declaring response types with `responseSchema`
140
+
141
+ Set `responseSchema` on the handler, next to `validation`, to declare the body per status code. The same declaration types the client (web's `ApiRouteRegistry`, `useSubmitForm`) and fills the `responses` of the OpenAPI document.
142
+
143
+ ```ts title="src/app/auth/controllers/login.controller.ts"
144
+ import type { RequestHandler } from "@warlock.js/core";
145
+ import { loginUseCase } from "app/auth/use-cases/login.use-case";
146
+ import { UserResource } from "app/users/resources/user.resource";
147
+
148
+ export const loginController: RequestHandler = async ({ request, response }) => {
149
+ const { user, token } = await loginUseCase(request.all());
150
+
151
+ return response.success({ user, token });
152
+ };
153
+
154
+ loginController.responseSchema = {
155
+ 200: { body: { user: UserResource, token: "string" } },
156
+ 400: { body: { error: "string" } },
157
+ };
158
+ ```
159
+
160
+ A body is an object whose values are one of:
161
+
162
+ | Value | Meaning |
163
+ | ----------------------------------------------- | ------------------------------------------- |
164
+ | a cast string, with the resource suffixes | `"string"`, `"number?"`, `"string[]"`, `"date[]?"` (`?` is `T \| null`, key always present) |
165
+ | a resource | one `ResourceOutput` object |
166
+ | `[Resource]` | an array of that resource |
167
+ | a nested plain object of any of the above | recursed |
168
+
169
+ The output types follow the same rules as `defineResource` (`ResponseBodyOutput<B>` and `ResponseSchemaOutput<S>` are exported if you need the type of a body or of the whole schema).
170
+
171
+ - Types are generated, not annotated: `warlock generate.typings` and `warlock dev` load the routes, read each `responseSchema` and hand the response types of every named route to the web route-types generator. A route without `responseSchema` gets no `response` entry.
172
+ - A resource is mapped back to its `*.resource.ts(x)` export by identity. A resource defined inline or outside such a file is typed `unknown`, and a warning names the route and field. Export each resource from its own `*.resource.ts` file.
173
+ - `responseSchema` is kept when a route is bound from an array handler (`[controller, "action"]`) or a restful resource method.
174
+ - Nothing checks the response against the schema at runtime: it is documentation and types.
134
175
 
135
176
  ## What belongs in a controller (and what doesn't)
136
177
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: define-resource
3
- description: 'Map model fields to wire-shape via `defineResource()` or `Resource` subclasses. Output-only — never put business logic, hydration, or reconciliation in a resource. Triggers: `defineResource`, `Resource`, `RegisterResource`, `toJSON`, `"self"`, `"localized"`, `"uploadsUrl"`; "shape an API response", "nest related resources", "rename a field on output", "self-referential tree resource"; typical import `import { defineResource } from "@warlock.js/core"`. Skip: localized columns — `@warlock.js/core/use-localization/SKILL.md`; URL casting — `@warlock.js/core/build-url/SKILL.md`; controller side — `@warlock.js/core/create-controller/SKILL.md`; competing libs `@nestjs/swagger` `@ApiProperty`, `class-transformer`, hand-rolled DTO mappers.'
3
+ description: 'Map model fields to wire-shape via `defineResource()` or `Resource` subclasses. Output-only — never put business logic, hydration, or reconciliation in a resource. Also covers the typed output: `ResourceOutput<typeof R>`, `defineResource<Out>()`, `Serialized<T, W>` and `ModelResourceRegistry`. Triggers: `defineResource`, `Resource`, `RegisterResource`, `toJSON`, `ResourceOutput`, `Serialized`, `ModelResourceRegistry`, `"self"`, `"localized"`, `"uploadsUrl"`; "shape an API response", "type a resource''s output", "what does the client receive", "nest related resources", "rename a field on output", "self-referential tree resource"; typical import `import { defineResource } from "@warlock.js/core"`. Skip: localized columns — `@warlock.js/core/use-localization/SKILL.md`; URL casting — `@warlock.js/core/build-url/SKILL.md`; controller side — `@warlock.js/core/create-controller/SKILL.md`; competing libs `@nestjs/swagger` `@ApiProperty`, `class-transformer`, hand-rolled DTO mappers.'
4
4
  ---
5
5
 
6
6
  # Warlock — define a resource
@@ -196,6 +196,8 @@ export const ChatResource = defineResource({
196
196
  transform(data, resource) {
197
197
  // final pass — mutate `data` in place; the return value is ignored
198
198
  data.slug = String(data.title).toLowerCase();
199
+
200
+ return data;
199
201
  },
200
202
  });
201
203
  ```
@@ -204,6 +206,103 @@ export const ChatResource = defineResource({
204
206
 
205
207
  Hooks are an escape hatch. If you find yourself running queries or reconciling state inside a hook, stop — that work belongs in a service or in a `prepare-*` step before the resource is constructed.
206
208
 
209
+ ## Typed output
210
+
211
+ `defineResource()` reads the literal cast strings in `schema` and types `toJSON()` with them. Nothing is annotated by hand.
212
+
213
+ ```ts title="src/app/users/resources/user.resource.ts"
214
+ import { defineResource, type ResourceOutput } from "@warlock.js/core";
215
+
216
+ export const UserResource = defineResource({
217
+ schema: {
218
+ id: "number",
219
+ name: "string?",
220
+ tags: "string[]",
221
+ createdAt: "date",
222
+ },
223
+ });
224
+
225
+ export type UserJson = ResourceOutput<typeof UserResource>;
226
+ // { id: number; name: string | null; tags: string[]; createdAt: { iso: string; format: string; timestamp: number; humanTime: string } }
227
+
228
+ const json = new UserResource(user).toJSON(); // typed as UserJson
229
+ ```
230
+
231
+ `ResourceOutput<R>` takes the resource class or an instance (`InstanceType<typeof UserResource>`).
232
+
233
+ How each schema entry is typed:
234
+
235
+ | Schema entry | Output type |
236
+ | ----------------------------------------------------------------- | --------------------------------------------------------------------- |
237
+ | `"string"`, `"localized"`, `"url"`, `"uploadsUrl"`, `"storageUrl"` | `string` |
238
+ | `"number"`, `"float"`, `"int"` | `number` |
239
+ | `"boolean"` | `boolean` |
240
+ | `"date"` | `{ iso: string; format: string; timestamp: number; humanTime: string }` (the default date output) |
241
+ | `"object"` / `"array"` | `Record<string, unknown>` / `unknown[]` |
242
+ | `"x[]"` | array of the base type |
243
+ | `"x?"` | `T \| null`; the key is always present, never optional |
244
+ | a nested resource, `lazy(() => R)` | that resource's output (one object, even if the model holds an array) |
245
+ | `"self"` / `"self[]"` | this resource's own output / an array of it |
246
+ | `["inputKey", "cast"]` tuple | the cast's type |
247
+ | a resolver function | its return type (an `any` return becomes `unknown`) |
248
+ | a field builder (`this.date()...`, `new ResourceFieldBuilder(...)`) | `unknown` |
249
+
250
+ The type reflects the schema only, not the data: mark a field `?` when the model may not have a value. `boot`, `extend`, `transform` and builders can change the real shape; when they do, state the output with a type argument, which replaces the inferred type:
251
+
252
+ ```ts title="src/app/products/resources/product.resource.ts"
253
+ import { defineResource } from "@warlock.js/core";
254
+
255
+ type ProductJson = { id: number; title: string; priceLabel: string };
256
+
257
+ export const ProductResource = defineResource<ProductJson>({
258
+ schema: { id: "number", title: "string", price: "number" },
259
+ transform: (data) => {
260
+ data.priceLabel = `$${data.price}`;
261
+ delete data.price;
262
+
263
+ return data;
264
+ },
265
+ });
266
+ ```
267
+
268
+ `transform` still mutates `data` in place (see Hooks); the returned value is ignored at runtime, but the option type requires a return. `ProductJson` is what consumers of `ResourceOutput<typeof ProductResource>` see.
269
+
270
+ ### `Serialized<T, W>` — what the client receives
271
+
272
+ `Serialized<T, W>` is the type of a value after Warlock serializes it. `W` is the wire: `"json"` (default) for API responses, `"devalue"` for web page loader data. It is exported from `@warlock.js/core`.
273
+
274
+ ```ts
275
+ import type { Serialized } from "@warlock.js/core";
276
+
277
+ type Payload = Serialized<{ createdAt: Date; note?: string }>;
278
+ // { createdAt: string; note?: string }
279
+ ```
280
+
281
+ Rules, in the order the runtime applies them:
282
+
283
+ - Primitives, `Buffer` and `Uint8Array` pass through. `any` and `unknown` stay as they are.
284
+ - On `"devalue"`, `Date`, `RegExp`, `URL`, `Map` and `Set` stay native. On `"json"`, a `Map` becomes a record and a `Set` an array.
285
+ - A cascade model becomes its resource's output (next section); with no registry entry it becomes its serialized `data` type.
286
+ - Anything with `toJSON()` becomes the (awaited) serialized result, which is how a `Date` becomes a `string` on `"json"`.
287
+ - Arrays map item by item. Functions are dropped from objects.
288
+ - On `"json"`, a key whose value can be `undefined` becomes optional, because `JSON.stringify` omits it.
289
+ - A class with private or protected members and no `toJSON()` maps to its public data on `"json"` and to `never` on `"devalue"` (devalue throws on it at render). `Error` is `{}` on `"json"`.
290
+ - Nesting is walked to a fixed depth (8), then the type is `unknown`.
291
+
292
+ ### `ModelResourceRegistry` — map a model to its resource
293
+
294
+ `Model.resource` is typed loosely, so a model type cannot reveal its resource by itself. You don't write the mapping: `warlock generate.typings` and `warlock dev` generate `.warlock/typings/model-resources.d.ts` from every model that declares `static resource = SomeResource`:
295
+
296
+ ```ts title=".warlock/typings/model-resources.d.ts (generated)"
297
+ declare module "@warlock.js/core" {
298
+ interface ModelResourceRegistry {
299
+ "User": { model: import("../../src/app/users/models/user.model").User; resource: typeof import("../../src/app/users/resources/user.resource").UserResource };
300
+ }
301
+ }
302
+ ```
303
+
304
+ `Serialized<User>` then resolves to `ResourceOutput<typeof UserResource>` instead of the raw model data (so hidden columns such as a password never appear in the type). The generator reads the model file statically and resolves the identifier through its imports (named, aliased or default, or a resource declared in the same file). It skips anything it cannot resolve statically (a computed value, `lazy(...)`, a namespace import): that model keeps its raw data type. Your `tsconfig.json` must include `.warlock/typings/*.d.ts` (new apps do). `@warlock.js/web` uses the same registry to type page loader data.
305
+
207
306
  ## Attaching to a model
208
307
 
209
308
  Models reference their default resource via a static `resource` field. Repositories and other helpers can then serialize without importing the resource:
@@ -0,0 +1,120 @@
1
+ ---
2
+ name: generate-openapi
3
+ description: 'Generate an OpenAPI 3.1 document from the registered routes with `warlock generate.openapi` (default `storage/openapi/openapi.json`) and read it live in the dev-only API docs page at `/__warlock/docs`. Documents paths, path/query parameters, request bodies from Seal validation, responses from `handler.responseSchema`, 401/422 and bearer/cookie security from `authMiddleware()`. Triggers: `generate.openapi`, `openApiGeneratorCommand`, `buildOpenApiDocument`, `getDevelopmentOpenApiDocument`, `responseSchema`, `--include-pages`, `/__warlock/docs`; "OpenAPI", "Swagger", "API docs", "import my API into Postman or Insomnia", "document my routes". Skip: declaring response types on a handler — `@warlock.js/core/create-controller/SKILL.md`; the devtools dashboard — `@warlock.js/devtools/devtools-overview/SKILL.md`; listing routes — `@warlock.js/core/warlock-routes/SKILL.md`.'
4
+ ---
5
+
6
+ # Warlock — generate an OpenAPI document
7
+
8
+ `warlock generate.openapi` writes an OpenAPI 3.1 JSON document derived from the routes your app registers. Nothing is annotated by hand: the document reads the route table, each handler's `validation`, `description` and `responseSchema`, and the auth middleware on the route.
9
+
10
+ ```bash
11
+ npx warlock generate.openapi
12
+ # OpenAPI 3.1.0 written to <root>/storage/openapi/openapi.json (12 paths, 17 operations).
13
+ ```
14
+
15
+ Routes are read in the same isolated registration child process `warlock build` uses: your route modules are imported, **no connector starts** (no database, cache or socket), and the running dev server is untouched. If the app cannot be loaded the command fails; gaps in a single route never fail it, they are printed as warnings. The child honours `WARLOCK_ROUTE_REGISTRATION_TIMEOUT_MS`.
16
+
17
+ ## Options
18
+
19
+ | Option | Default | Meaning |
20
+ | ----------------- | ---------------------------------------------- | ------------------------------------------------------------ |
21
+ | `--out`, `-o` | `storage/openapi/openapi.json` | Output file, relative to the project root. Parent folders are created. |
22
+ | `--title` | `name` in `package.json` (else `Warlock API`) | `info.title`. `info.version` is the `package.json` version (else `0.0.0`); `info.description` is its `description`. |
23
+ | `--server` | `http://<host>:<port>` from the `http` config | `servers[0].url`. A wildcard host (`0.0.0.0`, `::`) becomes `localhost`. |
24
+ | `--include-pages` | off | Also document page (SSR) routes; each is a `200` with `text/html`. |
25
+
26
+ ```bash
27
+ npx warlock generate.openapi --out docs/openapi.json --title "Shop API" --server https://api.shop.test
28
+ ```
29
+
30
+ ## What is documented
31
+
32
+ | Part | Source |
33
+ | ----------------------- | ----------------------------------------------------------------------------------------------- |
34
+ | Paths | `:id` becomes `{id}`. An `all` route expands into one operation per verb (get, post, put, patch, delete, options, head). |
35
+ | `operationId` | The route `name` (`name.<verb>` when an `all` route expands), else `<verb>_<path_slug>`. Duplicates are renamed with a warning. |
36
+ | Tag | The first static path segment (`/users/:id` is tagged `users`). |
37
+ | Summary / description | The route `label`; the route or handler `description`, plus a line about required user types and the cookie CSRF rule when the route is guarded. |
38
+ | Path parameters | `validation.params` gives typed ones; a path segment with no schema is a required `string`. |
39
+ | Request body / query | `validation.schema`, converted with the Seal schema's JSON Schema output. It is the JSON request body for POST/PUT/PATCH and query parameters for GET/HEAD/DELETE, following `validating` the way validation reads the request. |
40
+ | Responses | `handler.responseSchema`, one response per declared status (see below). A route with no 2xx declared gets a `200` description only. |
41
+ | `422` | Added when the route has `validation.schema`, shaped by `validation.response` (`errors`, `inputKey`, `inputError`, `status`; `Response.failedSchema` defaults to 422). Stored once as `components.schemas.ValidationFailed`. A route with only `validation.params` gets none. |
42
+ | `401` | Added when the route is guarded, as `{ error: string }` (`components.schemas.Unauthorized`). A status you declare yourself is kept. |
43
+ | Security | `authMiddleware()` tags the middleware it returns with a `Symbol.for("warlock.auth")` descriptor (`{ sources, userTypes }`); core reads it without importing auth. A header source is `bearerAuth` (HTTP bearer), a cookie source is `cookieAuth` (API key in that cookie); header plus cookie is an OR. Only the schemes a route uses are emitted. Middleware without a descriptor adds nothing. |
44
+
45
+ ### Responses from `responseSchema`
46
+
47
+ The grammar is the one described in [`create-controller`](../create-controller/SKILL.md#declaring-response-types-with-responseschema): cast strings with suffixes, a resource, `[Resource]`, nested objects.
48
+
49
+ ```ts
50
+ loginController.responseSchema = {
51
+ 200: { body: { user: UserResource, token: "string" } },
52
+ 400: { body: { error: "string" } },
53
+ };
54
+ ```
55
+
56
+ becomes
57
+
58
+ ```json
59
+ "200": {
60
+ "description": "Successful response",
61
+ "content": {
62
+ "application/json": {
63
+ "schema": {
64
+ "type": "object",
65
+ "properties": {
66
+ "user": { "$ref": "#/components/schemas/UserResource" },
67
+ "token": { "type": "string" }
68
+ },
69
+ "required": ["user", "token"]
70
+ }
71
+ }
72
+ }
73
+ }
74
+ ```
75
+
76
+ - A cast maps to JSON Schema: `string` and `localized` to `string`, `url`/`uploadsUrl`/`storageUrl` to `string` with `format: uri`, `number`/`float` to `number`, `int` to `integer`, `boolean`, `object`, `array`, and `date` to the default date object (`iso`, `format`, `timestamp`, `humanTime`). `x[]` is an array; `x?` is `type: [T, "null"]`.
77
+ - A resource becomes a `components.schemas` entry named after its export (`UserResource`) and every use is a `$ref`. Nested, lazy and `"self"` fields reference the same entry. A resource that is not exported from a `*.resource.ts(x)` file gets a generic name.
78
+ - Every listed key is marked `required`.
79
+
80
+ ## Warnings
81
+
82
+ What cannot be described statically is documented as `{}` and listed after the run. Common ones: a field builder or resolver function in a resource, an unknown cast, a `validation.validate` hook (custom middleware is not documented), a `validation.schema` that is not an object schema, an already-documented method+path, and a malformed auth descriptor. Fix them by declaring the field with a cast string.
83
+
84
+ ## Live in development
85
+
86
+ With `@warlock.js/devtools` installed (`warlock add devtools`), `warlock dev` serves the same document:
87
+
88
+ - `http://localhost:<port>/__warlock/docs` renders it with Scalar. The dashboard has an **API** tab that embeds the same page.
89
+ - `GET /__warlock/api/openapi.json` returns the raw document, rebuilt from the routes registered in the running process on every request; it answers `503` when the host provides no generator.
90
+ - It is built in-process by `getDevelopmentOpenApiDocument()` (exported from `@warlock.js/core`; it throws outside `warlock dev`), so it follows your edits with no CLI run. Page routes are not included; each distinct warning is logged once per process.
91
+ - Devtools is dev-only and loopback-only, and the page makes no outside request (Scalar is vendored, no CDN).
92
+
93
+ ## Use the file in a client
94
+
95
+ The output is plain OpenAPI 3.1 JSON, so any tool that reads an OpenAPI file can use it: import `storage/openapi/openapi.json` into Postman or Insomnia (their Import action), or feed it to a client generator. Support for 3.1 is the tool's; if an old version refuses it, update the tool. Regenerate the file whenever routes change, or commit it if you publish it.
96
+
97
+ ## Programmatic use
98
+
99
+ `buildOpenApiDocument(routes, context)` is exported from `@warlock.js/core`: a pure function from `router.list()` routes to `{ document, warnings }`. `context` carries `info`, `servers`, `includePages`, `resolveResourceName` and `validationResponse`. Use it to build the document inside your own tooling; the CLI uses the same function inside the registration child.
100
+
101
+ ```ts
102
+ import { buildOpenApiDocument, router } from "@warlock.js/core";
103
+
104
+ const { document, warnings } = buildOpenApiDocument(router.list(), {
105
+ info: { title: "Shop API", version: "1.0.0" },
106
+ });
107
+ ```
108
+
109
+ ## Gotchas
110
+
111
+ - **The document describes what you declare.** A route with no `responseSchema` documents only a `200` description; nothing is inferred from the controller body.
112
+ - **Export each resource from its own `*.resource.ts` file** so the schema component is named after it.
113
+ - **Seal validators are converted in the child process**, so the document always reflects the schema the app actually registers.
114
+ - **The 401 body is `{ error: string }`.** The auth middleware's real rejection also carries an `errorCode`; the document does not list it.
115
+
116
+ ## See also
117
+
118
+ - [`create-controller/SKILL.md`](../create-controller/SKILL.md) — `validation`, `description` and `responseSchema` on a handler.
119
+ - [`define-resource/SKILL.md`](../define-resource/SKILL.md) — resources, which become `components.schemas`.
120
+ - [`warlock-routes/SKILL.md`](../warlock-routes/SKILL.md) — list the routes the document is built from.
package/skills/index.md CHANGED
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: "The Warlock server framework: HTTP routes, controllers, responses, resources, repositories, CLI, mail, storage, security helpers. Exports `router`, `Request`, `Response`, `RequestHandler`, `useCase`, `RepositoryManager`, `defineResource`, `defineConfig`, `storage`, `sendMail`, `hashPassword`, `command`. Use for: add a route, write a controller, return a JSON response, hide a field from the response, upload a file, send an email, hash a password, add a CLI command, wire a socket, run or configure the app. Not this package: database models/queries → @warlock.js/cascade; input schemas → @warlock.js/seal; SSR pages → @warlock.js/web."
2
+ description: "The Warlock server framework: HTTP routes, controllers, responses, resources, repositories, CLI, mail, storage, security helpers. Exports `router`, `Request`, `Response`, `RequestHandler`, `useCase`, `RepositoryManager`, `defineResource`, `defineConfig`, `storage`, `sendMail`, `hashPassword`, `command`. Use for: add a route, write a controller, return a JSON response, hide a field from the response, upload a file, send an email, hash a password, add a CLI command, generate an OpenAPI document, type an API response, wire a socket, run or configure the app. Not this package: database models/queries → @warlock.js/cascade; input schemas → @warlock.js/seal; SSR pages → @warlock.js/web."
3
3
  ---
4
4
  # @warlock.js/core
5
5
 
@@ -9,7 +9,7 @@ Core is the application runtime: it boots the app, auto-loads each module's spec
9
9
  1. Scaffold a module (`create-module.md`), then declare routes in its `routes.ts` (`register-route.md`, `warlock-routes.md`).
10
10
  2. Write a thin controller (`create-controller.md`); validate input with a schema attached to the handler (`validate-input.md`).
11
11
  3. Put logic in a service or use case (`write-use-case.md`), and data access in a repository (`use-repository.md`).
12
- 4. Return via `response.*` (`send-response.md`) and shape output with a resource (`define-resource.md`).
12
+ 4. Return via `response.*` (`send-response.md`) and shape output with a resource (`define-resource.md`); declare `responseSchema` for typed clients and docs (`create-controller.md`, `generate-openapi.md`).
13
13
  5. Configure and run (`configure-app.md`, `run-app.md`); check setup with `warlock-doctor.md`.
14
14
 
15
15
  ## Topics by area
@@ -17,7 +17,7 @@ Core is the application runtime: it boots the app, auto-loads each module's spec
17
17
  - **HTTP & routing:** register-route, warlock-routes, create-controller, use-middleware, write-middleware, validate-input, use-request-locals, build-restful, wire-socket
18
18
  - **Responses & resources:** send-response, define-resource, use-model-transformers, use-localization
19
19
  - **Domain layers:** write-use-case, use-repository, write-seeder
20
- - **CLI & running the app:** run-app, write-cli-command, add-connector, health-checks, warlock-doctor
20
+ - **CLI & running the app:** run-app, write-cli-command, add-connector, health-checks, warlock-doctor, generate-openapi
21
21
  - **Storage, files & mail:** store-file, upload-file, process-image, send-mail
22
22
  - **Security & utilities:** hash-password, encrypt-data, retry-operation, benchmark-code, request-memo, request-tracing
23
23
  - **Testing:** test-http, test-service
@@ -50,7 +50,7 @@ import { startHttpTestServer, stopHttpTestServer } from "@warlock.js/core/tests"
50
50
  - Loads `warlock.config.ts` + bootstrap + `filesOrchestrator` (without file watching).
51
51
  - Loads `src/config/*.ts`.
52
52
  - Loads every module (`routes.ts`, `main.ts`, `events/*.ts`).
53
- - Starts **ALL** connectors, including HTTP.
53
+ - Starts **ALL** connectors, including HTTP, by default.
54
54
 
55
55
  Unlike the dev server, it doesn't watch files, doesn't do HMR, doesn't run health checkers. Just a working HTTP endpoint listening on the configured port.
56
56
 
@@ -70,6 +70,14 @@ The explicit port wins over `http.port` — including over `HTTP_PORT` in `.env`
70
70
 
71
71
  The request helpers follow with no extra wiring: the bound port is published as `WARLOCK_TEST_SERVER_PORT`, which the workers inherit, and `getTestServerUrl()` prefers it over their own `.env`-resolved `http.port`. `stopHttpTestServer()` withdraws it.
72
72
 
73
+ ### API-only suites — `startHttpTestServer({ web: false })`
74
+
75
+ ```ts
76
+ await startHttpTestServer({ web: false });
77
+ ```
78
+
79
+ Use this in `globalSetup` for a suite that exercises API routes only. It still boots the HTTP connector and every other configured connector, but excludes the optional `web` connector before its lifecycle begins. That means web's lazy delegate never constructs `WebConnector`, and Vite is never created. Omitting `web` keeps the default full-stack behavior.
80
+
73
81
  ### The port is preflighted
74
82
 
75
83
  Before binding, the test server checks the resolved port is free and fails with an instruction if it isn't: