@warlock.js/core 5.26.0 → 5.28.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.
- package/CHANGELOG.md +30 -0
- package/esm/cli/commands/openapi-generator.action.mjs +43 -0
- package/esm/cli/commands/openapi-generator.action.mjs.map +1 -0
- package/esm/cli/commands/openapi-generator.command.mjs +44 -0
- package/esm/cli/commands/openapi-generator.command.mjs.map +1 -0
- package/esm/cli/commands/postman-generator.action.mjs +47 -0
- package/esm/cli/commands/postman-generator.action.mjs.map +1 -0
- package/esm/cli/commands/postman-generator.command.mjs +43 -0
- package/esm/cli/commands/postman-generator.command.mjs.map +1 -0
- package/esm/cli/framework-cli-commands.mjs +4 -0
- package/esm/cli/framework-cli-commands.mjs.map +1 -1
- package/esm/dev-server/build-development-openapi-document.mjs +36 -0
- package/esm/dev-server/build-development-openapi-document.mjs.map +1 -0
- package/esm/dev-server/collect-development-api-routes.mjs +27 -0
- package/esm/dev-server/collect-development-api-routes.mjs.map +1 -0
- package/esm/dev-server/dev-logger.mjs +9 -1
- package/esm/dev-server/dev-logger.mjs.map +1 -1
- package/esm/dev-server/development-server.mjs +6 -3
- package/esm/dev-server/development-server.mjs.map +1 -1
- package/esm/dev-server/files-orchestrator.mjs +17 -1
- package/esm/dev-server/files-orchestrator.mjs.map +1 -1
- package/esm/dev-server/model-resource-typings.mjs +246 -0
- package/esm/dev-server/model-resource-typings.mjs.map +1 -0
- package/esm/dev-server/type-generator.mjs +46 -2
- package/esm/dev-server/type-generator.mjs.map +1 -1
- package/esm/http/index.d.mts +2 -1
- package/esm/http/middleware/inject-request-context.d.mts.map +1 -1
- package/esm/http/middleware/inject-request-context.mjs +29 -3
- package/esm/http/middleware/inject-request-context.mjs.map +1 -1
- package/esm/http/serialized.d.mts +114 -0
- package/esm/http/serialized.d.mts.map +1 -0
- package/esm/index.d.mts +11 -3
- package/esm/index.mjs +6 -1
- package/esm/openapi/build-openapi-document.d.mts +17 -0
- package/esm/openapi/build-openapi-document.d.mts.map +1 -0
- package/esm/openapi/build-openapi-document.mjs +124 -0
- package/esm/openapi/build-openapi-document.mjs.map +1 -0
- package/esm/openapi/cast-schema.mjs +89 -0
- package/esm/openapi/cast-schema.mjs.map +1 -0
- package/esm/openapi/collect-openapi-document.mjs +40 -0
- package/esm/openapi/collect-openapi-document.mjs.map +1 -0
- package/esm/openapi/development-openapi-provider.d.mts +28 -0
- package/esm/openapi/development-openapi-provider.d.mts.map +1 -0
- package/esm/openapi/development-openapi-provider.mjs +35 -0
- package/esm/openapi/development-openapi-provider.mjs.map +1 -0
- package/esm/openapi/error-responses.mjs +67 -0
- package/esm/openapi/error-responses.mjs.map +1 -0
- package/esm/openapi/index.d.mts +6 -0
- package/esm/openapi/index.mjs +6 -0
- package/esm/openapi/openapi-child-output.mjs +57 -0
- package/esm/openapi/openapi-child-output.mjs.map +1 -0
- package/esm/openapi/openapi-protocol.mjs +30 -0
- package/esm/openapi/openapi-protocol.mjs.map +1 -0
- package/esm/openapi/paths.mjs +63 -0
- package/esm/openapi/paths.mjs.map +1 -0
- package/esm/openapi/postman/build-request-body.mjs +86 -0
- package/esm/openapi/postman/build-request-body.mjs.map +1 -0
- package/esm/openapi/postman/build-request-url.mjs +85 -0
- package/esm/openapi/postman/build-request-url.mjs.map +1 -0
- package/esm/openapi/postman/build-saved-responses.mjs +60 -0
- package/esm/openapi/postman/build-saved-responses.mjs.map +1 -0
- package/esm/openapi/postman/example-from-schema.d.mts +17 -0
- package/esm/openapi/postman/example-from-schema.d.mts.map +1 -0
- package/esm/openapi/postman/example-from-schema.mjs +92 -0
- package/esm/openapi/postman/example-from-schema.mjs.map +1 -0
- package/esm/openapi/postman/http-status-text.mjs +31 -0
- package/esm/openapi/postman/http-status-text.mjs.map +1 -0
- package/esm/openapi/postman/index.d.mts +3 -0
- package/esm/openapi/postman/openapi-to-postman-collection.d.mts +22 -0
- package/esm/openapi/postman/openapi-to-postman-collection.d.mts.map +1 -0
- package/esm/openapi/postman/openapi-to-postman-collection.mjs +109 -0
- package/esm/openapi/postman/openapi-to-postman-collection.mjs.map +1 -0
- package/esm/openapi/postman/postman-types.d.mts +119 -0
- package/esm/openapi/postman/postman-types.d.mts.map +1 -0
- package/esm/openapi/postman/resolve-operation-auth.mjs +51 -0
- package/esm/openapi/postman/resolve-operation-auth.mjs.map +1 -0
- package/esm/openapi/postman/schema-refs.d.mts +7 -0
- package/esm/openapi/postman/schema-refs.d.mts.map +1 -0
- package/esm/openapi/postman/schema-refs.mjs +41 -0
- package/esm/openapi/postman/schema-refs.mjs.map +1 -0
- package/esm/openapi/postman/uuid-v5.mjs +29 -0
- package/esm/openapi/postman/uuid-v5.mjs.map +1 -0
- package/esm/openapi/request-schemas.mjs +138 -0
- package/esm/openapi/request-schemas.mjs.map +1 -0
- package/esm/openapi/response-schemas.mjs +85 -0
- package/esm/openapi/response-schemas.mjs.map +1 -0
- package/esm/openapi/schema-registry.mjs +143 -0
- package/esm/openapi/schema-registry.mjs.map +1 -0
- package/esm/openapi/security.mjs +98 -0
- package/esm/openapi/security.mjs.map +1 -0
- package/esm/openapi/types.d.mts +119 -0
- package/esm/openapi/types.d.mts.map +1 -0
- package/esm/production/registration-child-runner.mjs +139 -0
- package/esm/production/registration-child-runner.mjs.map +1 -0
- package/esm/production/route-registration-child.mjs +30 -2
- package/esm/production/route-registration-child.mjs.map +1 -1
- package/esm/production/route-registration-snapshot.mjs +22 -119
- package/esm/production/route-registration-snapshot.mjs.map +1 -1
- package/esm/resource/define-resource.d.mts +44 -14
- package/esm/resource/define-resource.d.mts.map +1 -1
- package/esm/resource/define-resource.mjs +0 -40
- package/esm/resource/define-resource.mjs.map +1 -1
- package/esm/resource/index.d.mts +1 -1
- package/esm/resource/resource.d.mts +4 -4
- package/esm/resource/resource.d.mts.map +1 -1
- package/esm/resource/resource.mjs +2 -4
- package/esm/resource/resource.mjs.map +1 -1
- package/esm/resource/types.d.mts +89 -4
- package/esm/resource/types.d.mts.map +1 -1
- package/esm/router/index.d.mts +1 -1
- package/esm/router/named-api-routes-with-responses.mjs +59 -0
- package/esm/router/named-api-routes-with-responses.mjs.map +1 -0
- package/esm/router/response-schema-types.mjs +100 -0
- package/esm/router/response-schema-types.mjs.map +1 -0
- package/esm/router/router.d.mts +2 -2
- package/esm/router/router.d.mts.map +1 -1
- package/esm/router/router.mjs +38 -11
- package/esm/router/router.mjs.map +1 -1
- package/esm/router/types.d.mts +16 -1
- package/esm/router/types.d.mts.map +1 -1
- package/llms-full.txt +293 -5
- package/llms.txt +3 -2
- package/package.json +13 -13
- package/skills/README.md +3 -2
- package/skills/create-controller/SKILL.md +39 -2
- package/skills/define-resource/SKILL.md +100 -1
- package/skills/generate-openapi/SKILL.md +148 -0
- package/skills/index.md +3 -3
- package/skills/validate-input/SKILL.md +2 -2
package/llms-full.txt
CHANGED
|
@@ -1280,7 +1280,7 @@ Internal `@warlock.js/*` packages read from `config.get("<name>")` the same way
|
|
|
1280
1280
|
|
|
1281
1281
|
---
|
|
1282
1282
|
name: create-controller
|
|
1283
|
-
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.'
|
|
1283
|
+
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.'
|
|
1284
1284
|
---
|
|
1285
1285
|
|
|
1286
1286
|
# Warlock — create a controller
|
|
@@ -1414,7 +1414,44 @@ Use `RequestHandler<Request<TSchema>>` for public routes, `GuardedRequestHandler
|
|
|
1414
1414
|
createProductController.description = "Create a new product (admin only)";
|
|
1415
1415
|
```
|
|
1416
1416
|
|
|
1417
|
-
|
|
1417
|
+
`description` feeds the OpenAPI document (`warlock generate.openapi`, see [`generate-openapi`](../generate-openapi/SKILL.md)) and surfaces in dev-server logs.
|
|
1418
|
+
|
|
1419
|
+
## Declaring response types with `responseSchema`
|
|
1420
|
+
|
|
1421
|
+
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.
|
|
1422
|
+
|
|
1423
|
+
```ts title="src/app/auth/controllers/login.controller.ts"
|
|
1424
|
+
import type { RequestHandler } from "@warlock.js/core";
|
|
1425
|
+
import { loginUseCase } from "app/auth/use-cases/login.use-case";
|
|
1426
|
+
import { UserResource } from "app/users/resources/user.resource";
|
|
1427
|
+
|
|
1428
|
+
export const loginController: RequestHandler = async ({ request, response }) => {
|
|
1429
|
+
const { user, token } = await loginUseCase(request.all());
|
|
1430
|
+
|
|
1431
|
+
return response.success({ user, token });
|
|
1432
|
+
};
|
|
1433
|
+
|
|
1434
|
+
loginController.responseSchema = {
|
|
1435
|
+
200: { body: { user: UserResource, token: "string" } },
|
|
1436
|
+
400: { body: { error: "string" } },
|
|
1437
|
+
};
|
|
1438
|
+
```
|
|
1439
|
+
|
|
1440
|
+
A body is an object whose values are one of:
|
|
1441
|
+
|
|
1442
|
+
| Value | Meaning |
|
|
1443
|
+
| ----------------------------------------------- | ------------------------------------------- |
|
|
1444
|
+
| a cast string, with the resource suffixes | `"string"`, `"number?"`, `"string[]"`, `"date[]?"` (`?` is `T \| null`, key always present) |
|
|
1445
|
+
| a resource | one `ResourceOutput` object |
|
|
1446
|
+
| `[Resource]` | an array of that resource |
|
|
1447
|
+
| a nested plain object of any of the above | recursed |
|
|
1448
|
+
|
|
1449
|
+
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).
|
|
1450
|
+
|
|
1451
|
+
- 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.
|
|
1452
|
+
- 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.
|
|
1453
|
+
- `responseSchema` is kept when a route is bound from an array handler (`[controller, "action"]`) or a restful resource method.
|
|
1454
|
+
- Nothing checks the response against the schema at runtime: it is documentation and types.
|
|
1418
1455
|
|
|
1419
1456
|
## What belongs in a controller (and what doesn't)
|
|
1420
1457
|
|
|
@@ -1719,7 +1756,7 @@ await warmupProductCache();
|
|
|
1719
1756
|
|
|
1720
1757
|
---
|
|
1721
1758
|
name: define-resource
|
|
1722
|
-
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.'
|
|
1759
|
+
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.'
|
|
1723
1760
|
---
|
|
1724
1761
|
|
|
1725
1762
|
# Warlock — define a resource
|
|
@@ -1915,6 +1952,8 @@ export const ChatResource = defineResource({
|
|
|
1915
1952
|
transform(data, resource) {
|
|
1916
1953
|
// final pass — mutate `data` in place; the return value is ignored
|
|
1917
1954
|
data.slug = String(data.title).toLowerCase();
|
|
1955
|
+
|
|
1956
|
+
return data;
|
|
1918
1957
|
},
|
|
1919
1958
|
});
|
|
1920
1959
|
```
|
|
@@ -1923,6 +1962,103 @@ export const ChatResource = defineResource({
|
|
|
1923
1962
|
|
|
1924
1963
|
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.
|
|
1925
1964
|
|
|
1965
|
+
## Typed output
|
|
1966
|
+
|
|
1967
|
+
`defineResource()` reads the literal cast strings in `schema` and types `toJSON()` with them. Nothing is annotated by hand.
|
|
1968
|
+
|
|
1969
|
+
```ts title="src/app/users/resources/user.resource.ts"
|
|
1970
|
+
import { defineResource, type ResourceOutput } from "@warlock.js/core";
|
|
1971
|
+
|
|
1972
|
+
export const UserResource = defineResource({
|
|
1973
|
+
schema: {
|
|
1974
|
+
id: "number",
|
|
1975
|
+
name: "string?",
|
|
1976
|
+
tags: "string[]",
|
|
1977
|
+
createdAt: "date",
|
|
1978
|
+
},
|
|
1979
|
+
});
|
|
1980
|
+
|
|
1981
|
+
export type UserJson = ResourceOutput<typeof UserResource>;
|
|
1982
|
+
// { id: number; name: string | null; tags: string[]; createdAt: { iso: string; format: string; timestamp: number; humanTime: string } }
|
|
1983
|
+
|
|
1984
|
+
const json = new UserResource(user).toJSON(); // typed as UserJson
|
|
1985
|
+
```
|
|
1986
|
+
|
|
1987
|
+
`ResourceOutput<R>` takes the resource class or an instance (`InstanceType<typeof UserResource>`).
|
|
1988
|
+
|
|
1989
|
+
How each schema entry is typed:
|
|
1990
|
+
|
|
1991
|
+
| Schema entry | Output type |
|
|
1992
|
+
| ----------------------------------------------------------------- | --------------------------------------------------------------------- |
|
|
1993
|
+
| `"string"`, `"localized"`, `"url"`, `"uploadsUrl"`, `"storageUrl"` | `string` |
|
|
1994
|
+
| `"number"`, `"float"`, `"int"` | `number` |
|
|
1995
|
+
| `"boolean"` | `boolean` |
|
|
1996
|
+
| `"date"` | `{ iso: string; format: string; timestamp: number; humanTime: string }` (the default date output) |
|
|
1997
|
+
| `"object"` / `"array"` | `Record<string, unknown>` / `unknown[]` |
|
|
1998
|
+
| `"x[]"` | array of the base type |
|
|
1999
|
+
| `"x?"` | `T \| null`; the key is always present, never optional |
|
|
2000
|
+
| a nested resource, `lazy(() => R)` | that resource's output (one object, even if the model holds an array) |
|
|
2001
|
+
| `"self"` / `"self[]"` | this resource's own output / an array of it |
|
|
2002
|
+
| `["inputKey", "cast"]` tuple | the cast's type |
|
|
2003
|
+
| a resolver function | its return type (an `any` return becomes `unknown`) |
|
|
2004
|
+
| a field builder (`this.date()...`, `new ResourceFieldBuilder(...)`) | `unknown` |
|
|
2005
|
+
|
|
2006
|
+
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:
|
|
2007
|
+
|
|
2008
|
+
```ts title="src/app/products/resources/product.resource.ts"
|
|
2009
|
+
import { defineResource } from "@warlock.js/core";
|
|
2010
|
+
|
|
2011
|
+
type ProductJson = { id: number; title: string; priceLabel: string };
|
|
2012
|
+
|
|
2013
|
+
export const ProductResource = defineResource<ProductJson>({
|
|
2014
|
+
schema: { id: "number", title: "string", price: "number" },
|
|
2015
|
+
transform: (data) => {
|
|
2016
|
+
data.priceLabel = `$${data.price}`;
|
|
2017
|
+
delete data.price;
|
|
2018
|
+
|
|
2019
|
+
return data;
|
|
2020
|
+
},
|
|
2021
|
+
});
|
|
2022
|
+
```
|
|
2023
|
+
|
|
2024
|
+
`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.
|
|
2025
|
+
|
|
2026
|
+
### `Serialized<T, W>` — what the client receives
|
|
2027
|
+
|
|
2028
|
+
`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`.
|
|
2029
|
+
|
|
2030
|
+
```ts
|
|
2031
|
+
import type { Serialized } from "@warlock.js/core";
|
|
2032
|
+
|
|
2033
|
+
type Payload = Serialized<{ createdAt: Date; note?: string }>;
|
|
2034
|
+
// { createdAt: string; note?: string }
|
|
2035
|
+
```
|
|
2036
|
+
|
|
2037
|
+
Rules, in the order the runtime applies them:
|
|
2038
|
+
|
|
2039
|
+
- Primitives, `Buffer` and `Uint8Array` pass through. `any` and `unknown` stay as they are.
|
|
2040
|
+
- On `"devalue"`, `Date`, `RegExp`, `URL`, `Map` and `Set` stay native. On `"json"`, a `Map` becomes a record and a `Set` an array.
|
|
2041
|
+
- A cascade model becomes its resource's output (next section); with no registry entry it becomes its serialized `data` type.
|
|
2042
|
+
- Anything with `toJSON()` becomes the (awaited) serialized result, which is how a `Date` becomes a `string` on `"json"`.
|
|
2043
|
+
- Arrays map item by item. Functions are dropped from objects.
|
|
2044
|
+
- On `"json"`, a key whose value can be `undefined` becomes optional, because `JSON.stringify` omits it.
|
|
2045
|
+
- 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"`.
|
|
2046
|
+
- Nesting is walked to a fixed depth (8), then the type is `unknown`.
|
|
2047
|
+
|
|
2048
|
+
### `ModelResourceRegistry` — map a model to its resource
|
|
2049
|
+
|
|
2050
|
+
`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`:
|
|
2051
|
+
|
|
2052
|
+
```ts title=".warlock/typings/model-resources.d.ts (generated)"
|
|
2053
|
+
declare module "@warlock.js/core" {
|
|
2054
|
+
interface ModelResourceRegistry {
|
|
2055
|
+
"User": { model: import("../../src/app/users/models/user.model").User; resource: typeof import("../../src/app/users/resources/user.resource").UserResource };
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
```
|
|
2059
|
+
|
|
2060
|
+
`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.
|
|
2061
|
+
|
|
1926
2062
|
## Attaching to a model
|
|
1927
2063
|
|
|
1928
2064
|
Models reference their default resource via a static `resource` field. Repositories and other helpers can then serialize without importing the resource:
|
|
@@ -2232,6 +2368,158 @@ Password hashing (`hashPassword`) needs `bcryptjs`, which is its own install —
|
|
|
2232
2368
|
- [`warlock-conventions/SKILL.md`](../warlock-conventions/SKILL.md) — service layering, where the encryption boundary should sit.
|
|
2233
2369
|
|
|
2234
2370
|
|
|
2371
|
+
## generate-openapi `@warlock.js/core/generate-openapi/SKILL.md`
|
|
2372
|
+
|
|
2373
|
+
---
|
|
2374
|
+
name: generate-openapi
|
|
2375
|
+
description: 'Generate an OpenAPI 3.1 document from the registered routes with `warlock generate.openapi` (default `storage/openapi/openapi.json`), write a Postman collection from it with `warlock generate.postman` (default `storage/postman/collection.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`, `generate.postman`, `openApiToPostmanCollection`, `getDevelopmentPostmanCollection`, `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`.'
|
|
2376
|
+
---
|
|
2377
|
+
|
|
2378
|
+
# Warlock — generate an OpenAPI document
|
|
2379
|
+
|
|
2380
|
+
`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.
|
|
2381
|
+
|
|
2382
|
+
```bash
|
|
2383
|
+
npx warlock generate.openapi
|
|
2384
|
+
# OpenAPI 3.1.0 written to <root>/storage/openapi/openapi.json (12 paths, 17 operations).
|
|
2385
|
+
```
|
|
2386
|
+
|
|
2387
|
+
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`.
|
|
2388
|
+
|
|
2389
|
+
## Options
|
|
2390
|
+
|
|
2391
|
+
| Option | Default | Meaning |
|
|
2392
|
+
| ----------------- | ---------------------------------------------- | ------------------------------------------------------------ |
|
|
2393
|
+
| `--out`, `-o` | `storage/openapi/openapi.json` | Output file, relative to the project root. Parent folders are created. |
|
|
2394
|
+
| `--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`. |
|
|
2395
|
+
| `--server` | `http://<host>:<port>` from the `http` config | `servers[0].url`. A wildcard host (`0.0.0.0`, `::`) becomes `localhost`. |
|
|
2396
|
+
| `--include-pages` | off | Also document page (SSR) routes; each is a `200` with `text/html`. |
|
|
2397
|
+
|
|
2398
|
+
```bash
|
|
2399
|
+
npx warlock generate.openapi --out docs/openapi.json --title "Shop API" --server https://api.shop.test
|
|
2400
|
+
```
|
|
2401
|
+
|
|
2402
|
+
## What is documented
|
|
2403
|
+
|
|
2404
|
+
| Part | Source |
|
|
2405
|
+
| ----------------------- | ----------------------------------------------------------------------------------------------- |
|
|
2406
|
+
| Paths | `:id` becomes `{id}`. An `all` route expands into one operation per verb (get, post, put, patch, delete, options, head). |
|
|
2407
|
+
| `operationId` | The route `name` (`name.<verb>` when an `all` route expands), else `<verb>_<path_slug>`. Duplicates are renamed with a warning. |
|
|
2408
|
+
| Tag | The first static path segment (`/users/:id` is tagged `users`). |
|
|
2409
|
+
| 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. |
|
|
2410
|
+
| Path parameters | `validation.params` gives typed ones; a path segment with no schema is a required `string`. |
|
|
2411
|
+
| 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. |
|
|
2412
|
+
| Responses | `handler.responseSchema`, one response per declared status (see below). A route with no 2xx declared gets a `200` description only. |
|
|
2413
|
+
| `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. |
|
|
2414
|
+
| `401` | Added when the route is guarded, as `{ error: string }` (`components.schemas.Unauthorized`). A status you declare yourself is kept. |
|
|
2415
|
+
| 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. |
|
|
2416
|
+
|
|
2417
|
+
### Responses from `responseSchema`
|
|
2418
|
+
|
|
2419
|
+
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.
|
|
2420
|
+
|
|
2421
|
+
```ts
|
|
2422
|
+
loginController.responseSchema = {
|
|
2423
|
+
200: { body: { user: UserResource, token: "string" } },
|
|
2424
|
+
400: { body: { error: "string" } },
|
|
2425
|
+
};
|
|
2426
|
+
```
|
|
2427
|
+
|
|
2428
|
+
becomes
|
|
2429
|
+
|
|
2430
|
+
```json
|
|
2431
|
+
"200": {
|
|
2432
|
+
"description": "Successful response",
|
|
2433
|
+
"content": {
|
|
2434
|
+
"application/json": {
|
|
2435
|
+
"schema": {
|
|
2436
|
+
"type": "object",
|
|
2437
|
+
"properties": {
|
|
2438
|
+
"user": { "$ref": "#/components/schemas/UserResource" },
|
|
2439
|
+
"token": { "type": "string" }
|
|
2440
|
+
},
|
|
2441
|
+
"required": ["user", "token"]
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
```
|
|
2447
|
+
|
|
2448
|
+
- 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"]`.
|
|
2449
|
+
- 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.
|
|
2450
|
+
- Every listed key is marked `required`.
|
|
2451
|
+
|
|
2452
|
+
## Warnings
|
|
2453
|
+
|
|
2454
|
+
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.
|
|
2455
|
+
|
|
2456
|
+
## Live in development
|
|
2457
|
+
|
|
2458
|
+
With `@warlock.js/devtools` installed (`warlock add devtools`), `warlock dev` serves the same document:
|
|
2459
|
+
|
|
2460
|
+
- `http://localhost:<port>/__warlock/docs` renders it with Scalar. The dashboard has an **API** tab that embeds the same page.
|
|
2461
|
+
- `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.
|
|
2462
|
+
- 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.
|
|
2463
|
+
- Devtools is dev-only and loopback-only, and the page makes no outside request (Scalar is vendored, no CDN).
|
|
2464
|
+
|
|
2465
|
+
## Use the file in a client
|
|
2466
|
+
|
|
2467
|
+
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.
|
|
2468
|
+
|
|
2469
|
+
## Postman
|
|
2470
|
+
|
|
2471
|
+
`warlock generate.postman` writes a Postman Collection v2.1.0. It is derived from the OpenAPI document (the same registration child and options as `generate.openapi`), never from the routes directly, so the collection and the OpenAPI file always agree.
|
|
2472
|
+
|
|
2473
|
+
```bash
|
|
2474
|
+
npx warlock generate.postman
|
|
2475
|
+
# Postman collection written to <root>/storage/postman/collection.json (3 folders, 17 requests). Import it in Postman and set the {{token}} variable.
|
|
2476
|
+
```
|
|
2477
|
+
|
|
2478
|
+
| Option | Default | Meaning |
|
|
2479
|
+
| ----------------- | ----------------------------------------- | ------------------------------------------------------------------ |
|
|
2480
|
+
| `--out`, `-o` | `storage/postman/collection.json` | Output file, relative to the project root. |
|
|
2481
|
+
| `--title` | `name` in `package.json` | Collection name; the collection id is a stable hash of it. |
|
|
2482
|
+
| `--server` | `http://<host>:<port>` from the `http` config | Value of the `{{baseUrl}}` variable. |
|
|
2483
|
+
| `--include-pages` | off | Also include page (SSR) routes. |
|
|
2484
|
+
|
|
2485
|
+
What the collection contains:
|
|
2486
|
+
|
|
2487
|
+
- One folder per tag (the first path segment), in order of first appearance; untagged operations sit at the root.
|
|
2488
|
+
- One request per operation, named by its summary (the route `label`) or `operationId`. `{id}` becomes the `:id` path variable; query parameters are rows (optional ones disabled); the operation description and parameter descriptions carry over.
|
|
2489
|
+
- A JSON body with a `Content-Type` header and an **example generated from the schema** (strings by format such as `email` or `date-time`, `0`, `false`, the first `enum` value, one array item, resources resolved with cycles cut). A multipart body becomes form-data with file rows.
|
|
2490
|
+
- A saved response for every declared status, with an example body.
|
|
2491
|
+
- Collection variables `baseUrl` and `token`. When any route is guarded by `authMiddleware()`, the collection uses bearer auth with `{{token}}`; public routes are set to no auth so the token is not sent. A cookie-only route has a description note: Postman keeps cookies per domain, so sign in against `{{baseUrl}}` first.
|
|
2492
|
+
|
|
2493
|
+
To use it: in Postman choose **Import** and pick the file, open the collection's **Variables** tab, paste a token into `token` (for example the `token` from your login response), and adjust `baseUrl` if needed. Regenerate and re-import when routes change.
|
|
2494
|
+
|
|
2495
|
+
Programmatic use: `openApiToPostmanCollection(document, { title?, baseUrl? })` is exported from `@warlock.js/core` (pure and deterministic), and `getDevelopmentPostmanCollection()` returns `{ collection, warnings }` for the running dev process (it throws outside `warlock dev`, like `getDevelopmentOpenApiDocument`).
|
|
2496
|
+
|
|
2497
|
+
## Programmatic use
|
|
2498
|
+
|
|
2499
|
+
`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.
|
|
2500
|
+
|
|
2501
|
+
```ts
|
|
2502
|
+
import { buildOpenApiDocument, router } from "@warlock.js/core";
|
|
2503
|
+
|
|
2504
|
+
const { document, warnings } = buildOpenApiDocument(router.list(), {
|
|
2505
|
+
info: { title: "Shop API", version: "1.0.0" },
|
|
2506
|
+
});
|
|
2507
|
+
```
|
|
2508
|
+
|
|
2509
|
+
## Gotchas
|
|
2510
|
+
|
|
2511
|
+
- **The document describes what you declare.** A route with no `responseSchema` documents only a `200` description; nothing is inferred from the controller body.
|
|
2512
|
+
- **Export each resource from its own `*.resource.ts` file** so the schema component is named after it.
|
|
2513
|
+
- **Seal validators are converted in the child process**, so the document always reflects the schema the app actually registers.
|
|
2514
|
+
- **The 401 body is `{ error: string }`.** The auth middleware's real rejection also carries an `errorCode`; the document does not list it.
|
|
2515
|
+
|
|
2516
|
+
## See also
|
|
2517
|
+
|
|
2518
|
+
- [`create-controller/SKILL.md`](../create-controller/SKILL.md) — `validation`, `description` and `responseSchema` on a handler.
|
|
2519
|
+
- [`define-resource/SKILL.md`](../define-resource/SKILL.md) — resources, which become `components.schemas`.
|
|
2520
|
+
- [`warlock-routes/SKILL.md`](../warlock-routes/SKILL.md) — list the routes the document is built from.
|
|
2521
|
+
|
|
2522
|
+
|
|
2235
2523
|
## hash-password `@warlock.js/core/hash-password/SKILL.md`
|
|
2236
2524
|
|
|
2237
2525
|
---
|
|
@@ -8428,10 +8716,10 @@ const updateAvatarSchema = v.object({
|
|
|
8428
8716
|
|
|
8429
8717
|
## What the framework sends on failure
|
|
8430
8718
|
|
|
8431
|
-
The framework calls `response.failedSchema(result)` which sends `
|
|
8719
|
+
The framework calls `response.failedSchema(result)` which sends `422` by default (set `validation.response.status` to change it) with the shape configured under `validation.response` (defaults shown):
|
|
8432
8720
|
|
|
8433
8721
|
```jsonc
|
|
8434
|
-
//
|
|
8722
|
+
// 422 Unprocessable Entity
|
|
8435
8723
|
{
|
|
8436
8724
|
"errors": [
|
|
8437
8725
|
{ "input": "email", "error": "The email must be a valid email" },
|
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`), write a Postman collection from it with `warlock generate.postman` (default `storage/postman/collection.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`, `generate.postman`, `openApiToPostmanCollection`, `getDevelopmentPostmanCollection`, `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`.
|
package/package.json
CHANGED
|
@@ -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": "^
|
|
26
|
+
"@mongez/supportive-is": "^3.0.0",
|
|
27
27
|
"@mongez/time-wizard": "^1.0.6",
|
|
28
|
-
"@warlock.js/cache": "5.
|
|
29
|
-
"@warlock.js/cascade": "5.
|
|
30
|
-
"@warlock.js/context": "5.
|
|
31
|
-
"@warlock.js/fs": "5.
|
|
32
|
-
"@warlock.js/logger": "5.
|
|
33
|
-
"@warlock.js/seal": "5.
|
|
28
|
+
"@warlock.js/cache": "5.28.0",
|
|
29
|
+
"@warlock.js/cascade": "5.28.0",
|
|
30
|
+
"@warlock.js/context": "5.28.0",
|
|
31
|
+
"@warlock.js/fs": "5.28.0",
|
|
32
|
+
"@warlock.js/logger": "5.28.0",
|
|
33
|
+
"@warlock.js/seal": "5.28.0",
|
|
34
34
|
"bcryptjs": "^3.0.3",
|
|
35
35
|
"chokidar": "^5.0.0",
|
|
36
36
|
"dayjs": "^1.11.19",
|
|
@@ -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.
|
|
56
|
-
"@warlock.js/ai": "5.
|
|
57
|
-
"@warlock.js/herald": "5.
|
|
58
|
-
"@warlock.js/notifications": "5.
|
|
59
|
-
"@warlock.js/devtools": "5.
|
|
55
|
+
"@warlock.js/access": "5.28.0",
|
|
56
|
+
"@warlock.js/ai": "5.28.0",
|
|
57
|
+
"@warlock.js/herald": "5.28.0",
|
|
58
|
+
"@warlock.js/notifications": "5.28.0",
|
|
59
|
+
"@warlock.js/devtools": "5.28.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.
|
|
130
|
+
"version": "5.28.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
|
|
@@ -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
|
|
@@ -134,7 +134,44 @@ Use `RequestHandler<Request<TSchema>>` for public routes, `GuardedRequestHandler
|
|
|
134
134
|
createProductController.description = "Create a new product (admin only)";
|
|
135
135
|
```
|
|
136
136
|
|
|
137
|
-
|
|
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.
|
|
138
175
|
|
|
139
176
|
## What belongs in a controller (and what doesn't)
|
|
140
177
|
|