@warlock.js/core 5.2.4 → 5.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/esm/cli/commands/generate/templates/stubs.mjs +13 -14
  2. package/esm/cli/commands/generate/templates/stubs.mjs.map +1 -1
  3. package/esm/generations/stubs.mjs +13 -25
  4. package/esm/generations/stubs.mjs.map +1 -1
  5. package/esm/http/index.d.mts +1 -1
  6. package/esm/http/middleware/inject-request-context.d.mts.map +1 -1
  7. package/esm/http/middleware/inject-request-context.mjs +2 -21
  8. package/esm/http/middleware/inject-request-context.mjs.map +1 -1
  9. package/esm/http/request.d.mts +25 -6
  10. package/esm/http/request.d.mts.map +1 -1
  11. package/esm/http/request.mjs +48 -0
  12. package/esm/http/request.mjs.map +1 -1
  13. package/esm/http/response.d.mts +3 -3
  14. package/esm/http/response.d.mts.map +1 -1
  15. package/esm/http/types.d.mts +36 -5
  16. package/esm/http/types.d.mts.map +1 -1
  17. package/esm/index.d.mts +2 -2
  18. package/esm/validation/plugins/file-plugin.mjs.map +1 -1
  19. package/esm/validation/plugins/localized-plugin.mjs +2 -2
  20. package/esm/validation/plugins/localized-plugin.mjs.map +1 -1
  21. package/esm/validation/types.d.mts +17 -4
  22. package/esm/validation/types.d.mts.map +1 -1
  23. package/llms-full.txt +109 -86
  24. package/llms.txt +1 -1
  25. package/package.json +12 -12
  26. package/skills/README.md +1 -1
  27. package/skills/create-controller/SKILL.md +9 -9
  28. package/skills/send-response/SKILL.md +51 -37
  29. package/skills/store-file/SKILL.md +8 -3
  30. package/skills/upload-file/SKILL.md +9 -7
  31. package/skills/use-app-context/SKILL.md +2 -2
  32. package/skills/use-localization/SKILL.md +6 -2
  33. package/skills/use-repository/SKILL.md +2 -2
  34. package/skills/use-request-locals/SKILL.md +3 -3
  35. package/skills/validate-input/SKILL.md +2 -2
  36. package/skills/warlock-conventions/SKILL.md +2 -2
  37. package/skills/wire-socket/SKILL.md +2 -2
  38. package/skills/write-middleware/SKILL.md +13 -15
@@ -1,14 +1,27 @@
1
+ import { UploadedFile } from "../http/uploaded-file.mjs";
1
2
  import { ExistsExceptCurrentIdRuleOptions, ExistsExceptCurrentUserRuleOptions, UniqueExceptCurrentIdRuleOptions, UniqueExceptCurrentUserRuleOptions } from "./database/types.mjs";
2
3
  import { FileValidator } from "./validators/file-validator.mjs";
3
- import { ArrayValidator, BaseValidator, TranslateAttributeCallback, TranslateRuleCallback } from "@warlock.js/seal";
4
+ import { ArrayValidator, BaseValidator, Infer, ObjectValidator, StandardSchemaV1, StringValidator, TranslateAttributeCallback, TranslateRuleCallback } from "@warlock.js/seal";
4
5
  import { ChildModel, Model } from "@warlock.js/cascade";
5
6
 
6
7
  //#region ../core/src/validation/types.d.ts
8
+ /**
9
+ * The object shape produced by `v.localized()`'s inner `v.object({
10
+ * localeCode, value })` — kept in sync with `localizedPlugin`'s runtime
11
+ * implementation (`core/src/validation/plugins/localized-plugin.ts`).
12
+ */
13
+ type LocalizedEntryValidator<T extends BaseValidator> = ObjectValidator<{
14
+ localeCode: StringValidator & StandardSchemaV1<string>;
15
+ value: T;
16
+ }> & StandardSchemaV1<{
17
+ localeCode: string;
18
+ value: Infer<T>;
19
+ }>;
7
20
  declare module "@warlock.js/seal" {
8
21
  interface ValidatorV {
9
- file: (errorMessage?: string) => FileValidator;
10
- localized: (valueValidator?: BaseValidator, errorMessage?: string) => ArrayValidator & {
11
- validator: BaseValidator;
22
+ file: (errorMessage?: string) => FileValidator & StandardSchemaV1<UploadedFile>;
23
+ localized: <T extends BaseValidator = ScalarValidator & StandardSchemaV1<string | number | boolean>>(valueValidator?: T, errorMessage?: string) => ArrayValidator & {
24
+ validator: LocalizedEntryValidator<T>;
12
25
  };
13
26
  }
14
27
  interface ScalarValidator {
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../core/src/validation/types.ts"],"mappings":";;;;;;;YA2BmB,UAAA;IACf,IAAA,GAAO,YAAA,cAA0B,aAAA;IACjC,SAAA,GACE,cAAA,GAAiB,aAAA,EACjB,YAAA,cACG,cAAA;MACH,SAAA,EAAW,aAAA;IAAA;EAAA;EAAA,UAIL,eAAA;IAWC;IATT,uBAAA,CACE,KAAA,EAAO,UAAA,CAAW,KAAA,YAClB,WAAA,GAAc,OAAA,CAAQ,kCAAA;MACpB,YAAA;IAAA;IAcK;IATT,qBAAA,CACE,KAAA,EAAO,UAAA,CAAW,KAAA,YAClB,WAAA,GAAc,OAAA,CAAQ,gCAAA;MACpB,YAAA;IAAA;IAcK;IATT,uBAAA,CACE,KAAA,EAAO,UAAA,CAAW,KAAA,YAClB,WAAA,GAAc,OAAA,CAAQ,kCAAA;MACpB,YAAA;IAAA;IAgBmB;IAXvB,qBAAA,CACE,KAAA,EAAO,UAAA,CAAW,KAAA,YAClB,WAAA,GAAc,OAAA,CAAQ,gCAAA;MACpB,YAAA;IAAA;EAAA;EAAA,UAMI,eAAA;IACR,uBAAA,EAAyB,eAAA;IACzB,qBAAA,EAAuB,eAAA;IACvB,uBAAA,EAAyB,eAAA;IACzB,qBAAA,EAAuB,eAAA;EAAA;AAAA;AAAA,KAYf,uBAAA;EAvDN;;;;;;EA+DJ,gBAAA;EAvDkB;;;;;;EA8DlB,cAAA;EAvDI;;;;EA4DJ,cAAA;EArDE;;;;EA0DF,aAAA,GAAgB,qBAAA;EAxDU;;;EA4D1B,kBAAA,GAAqB,0BAA0B;AAAA"}
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../core/src/validation/types.ts"],"mappings":";;;;;;;;;;;;KAmCK,uBAAA,WAAkC,aAAA,IAAiB,eAAA;EACtD,UAAA,EAAY,eAAA,GAAkB,gBAAA;EAC9B,KAAA,EAAO,CAAA;AAAA,KAEP,gBAAA;EACE,UAAA;EACA,KAAA,EAAO,KAAA,CAAM,CAAA;AAAA;AAAA;EAAA,UAME,UAAA;IACf,IAAA,GAAO,YAAA,cAA0B,aAAA,GAAgB,gBAAA,CAAiB,YAAA;IAClE,SAAA,aACY,aAAA,GAAgB,eAAA,GAAkB,gBAAA,6BAE5C,cAAA,GAAiB,CAAA,EACjB,YAAA,cACG,cAAA;MACH,SAAA,EAAW,uBAAA,CAAwB,CAAA;IAAA;EAAA;EAAA,UAI7B,eAAA;IAlBR;IAoBA,uBAAA,CACE,KAAA,EAAO,UAAA,CAAW,KAAA,YAClB,WAAA,GAAc,OAAA,CAAQ,kCAAA;MACpB,YAAA;IAAA;IAvBU;IA4Bd,qBAAA,CACE,KAAA,EAAO,UAAA,CAAW,KAAA,YAClB,WAAA,GAAc,OAAA,CAAQ,gCAAA;MACpB,YAAA;IAAA;IAxB6B;IA6BjC,uBAAA,CACE,KAAA,EAAO,UAAA,CAAW,KAAA,YAClB,WAAA,GAAc,OAAA,CAAQ,kCAAA;MACpB,YAAA;IAAA;IA9BwB;IAmC5B,qBAAA,CACE,KAAA,EAAO,UAAA,CAAW,KAAA,YAClB,WAAA,GAAc,OAAA,CAAQ,gCAAA;MACpB,YAAA;IAAA;EAAA;EAAA,UAMI,eAAA;IACR,uBAAA,EAAyB,eAAA;IACzB,qBAAA,EAAuB,eAAA;IACvB,uBAAA,EAAyB,eAAA;IACzB,qBAAA,EAAuB,eAAA;EAAA;AAAA;AAAA,KAYf,uBAAA;EAhCY;;;;;;EAwCtB,gBAAA;EA/BkB;;;;;;EAsClB,cAAA;EA9EiB;;;;EAmFjB,cAAA;EAlFoE;;;;EAuFpE,aAAA,GAAgB,qBAAA;EArFgC;;;EAyFhD,kBAAA,GAAqB,0BAA0B;AAAA"}
package/llms-full.txt CHANGED
@@ -1222,7 +1222,7 @@ A controller is a thin function: pull inputs from `request`, call work, return t
1222
1222
  ```ts title="src/app/<module>/controllers/<action>.controller.ts"
1223
1223
  import { type RequestHandler } from "@warlock.js/core";
1224
1224
 
1225
- export const listProductsController: RequestHandler = async (request, response) => {
1225
+ export const listProductsController: RequestHandler = async ({ request, response }) => {
1226
1226
  return response.success({ products: [] });
1227
1227
  };
1228
1228
  ```
@@ -1277,10 +1277,10 @@ import { type Request, type RequestHandler } from "@warlock.js/core";
1277
1277
  import { type CreateProductSchema, createProductSchema } from "../schema/create-product.schema";
1278
1278
  import { createProductService } from "../services/create-product.service";
1279
1279
 
1280
- export const createProductController: RequestHandler<Request<CreateProductSchema>> = async (
1280
+ export const createProductController: RequestHandler<Request<CreateProductSchema>> = async ({
1281
1281
  request,
1282
1282
  response,
1283
- ) => {
1283
+ }) => {
1284
1284
  const product = await createProductService(request.validated());
1285
1285
 
1286
1286
  return response.successCreate({ product });
@@ -1306,10 +1306,10 @@ Routes behind `authMiddleware` need `request.user` typed. Project conventions ad
1306
1306
  import { type GuardedRequestHandler } from "app/auth/requests/guarded.request";
1307
1307
  import { type CreateProductSchema, createProductSchema } from "../schema/create-product.schema";
1308
1308
 
1309
- export const createProductController: GuardedRequestHandler<CreateProductSchema> = async (
1309
+ export const createProductController: GuardedRequestHandler<CreateProductSchema> = async ({
1310
1310
  request,
1311
1311
  response,
1312
- ) => {
1312
+ }) => {
1313
1313
  // request.user is typed
1314
1314
  const product = await createProductService(request.validated());
1315
1315
  return response.successCreate({ product });
@@ -1356,7 +1356,7 @@ If your controller is over ~30 lines, the work probably belongs in a service.
1356
1356
  import { type RequestHandler } from "@warlock.js/core";
1357
1357
  import { listProductsService } from "../services/list-products.service";
1358
1358
 
1359
- export const listProductsController: RequestHandler = async (request, response) => {
1359
+ export const listProductsController: RequestHandler = async ({ request, response }) => {
1360
1360
  const { data: products, pagination } = await listProductsService({
1361
1361
  ...request.all(),
1362
1362
  organization_id: request.user.organizationId,
@@ -1373,10 +1373,10 @@ import { type Request, type RequestHandler } from "@warlock.js/core";
1373
1373
  import { type CreateProductSchema, createProductSchema } from "../schema/create-product.schema";
1374
1374
  import { createProductService } from "../services/create-product.service";
1375
1375
 
1376
- export const createProductController: RequestHandler<Request<CreateProductSchema>> = async (
1376
+ export const createProductController: RequestHandler<Request<CreateProductSchema>> = async ({
1377
1377
  request,
1378
1378
  response,
1379
- ) => {
1379
+ }) => {
1380
1380
  const product = await createProductService(request.validated());
1381
1381
 
1382
1382
  return response.successCreate({ product });
@@ -1410,7 +1410,7 @@ export async function getProductService(id: string) {
1410
1410
  import type { RequestHandler } from "@warlock.js/core";
1411
1411
  import { getProductService } from "../services/get-product.service";
1412
1412
 
1413
- export const getProductController: RequestHandler = async (request, response) => {
1413
+ export const getProductController: RequestHandler = async ({ request, response }) => {
1414
1414
  const product = await getProductService(request.input("id"));
1415
1415
 
1416
1416
  return response.success({ product });
@@ -4133,9 +4133,9 @@ description: 'Send HTTP responses via @warlock.js/core''s Response helpers — s
4133
4133
  ## The shape
4134
4134
 
4135
4135
  ```ts
4136
- import type { RequestHandler, Response } from "@warlock.js/core";
4136
+ import type { RequestHandler } from "@warlock.js/core";
4137
4137
 
4138
- export const myController: RequestHandler = async (request, response: Response) => {
4138
+ export const myController: RequestHandler = async ({ request, response }) => {
4139
4139
  // …choose a helper and return it
4140
4140
  return response.success({ data: "…" });
4141
4141
  };
@@ -4145,11 +4145,11 @@ Always `return response.<helper>(...)`. The return value drives Fastify's send.
4145
4145
 
4146
4146
  ## Success helpers
4147
4147
 
4148
- | Method | Status | When |
4149
- | ----------------------------------- | ------ | ----------------------------------------- |
4150
- | `response.success(data?)` | 200 | normal read / update |
4151
- | `response.successCreate(data)` | 201 | resource created (POST) |
4152
- | `response.noContent()` | 204 | delete succeeded, no body needed |
4148
+ | Method | Status | When |
4149
+ | ------------------------------ | ------ | -------------------------------- |
4150
+ | `response.success(data?)` | 200 | normal read / update |
4151
+ | `response.successCreate(data)` | 201 | resource created (POST) |
4152
+ | `response.noContent()` | 204 | delete succeeded, no body needed |
4153
4153
 
4154
4154
  ```ts
4155
4155
  return response.success({ products: [...] });
@@ -4163,13 +4163,13 @@ return response.noContent();
4163
4163
 
4164
4164
  ## Client-error helpers
4165
4165
 
4166
- | Method | Status | When |
4167
- | --------------------------------------------------- | ------ | ------------------------------------- |
4168
- | `response.badRequest(data)` | 400 | malformed or invalid input |
4169
- | `response.unauthorized(data?)` | 401 | missing/invalid auth token |
4170
- | `response.forbidden(data?)` | 403 | authenticated but not allowed |
4171
- | `response.notFound(data?)` | 404 | record missing |
4172
- | `response.conflict(data?)` | 409 | uniqueness violation, state conflict |
4166
+ | Method | Status | When |
4167
+ | ------------------------------ | ------ | ------------------------------------ |
4168
+ | `response.badRequest(data)` | 400 | malformed or invalid input |
4169
+ | `response.unauthorized(data?)` | 401 | missing/invalid auth token |
4170
+ | `response.forbidden(data?)` | 403 | authenticated but not allowed |
4171
+ | `response.notFound(data?)` | 404 | record missing |
4172
+ | `response.conflict(data?)` | 409 | uniqueness violation, state conflict |
4173
4173
 
4174
4174
  ```ts
4175
4175
  return response.badRequest({ error: t("validation.invalid") });
@@ -4188,8 +4188,8 @@ Most error helpers accept an optional payload — if you omit it, they send a de
4188
4188
  ## Redirects
4189
4189
 
4190
4190
  ```ts
4191
- return response.redirect("/login"); // 302
4192
- return response.redirect("/new-home", 301); // permanent
4191
+ return response.redirect("/login"); // 302
4192
+ return response.redirect("/new-home", 301); // permanent
4193
4193
  ```
4194
4194
 
4195
4195
  ## Files
@@ -4221,19 +4221,19 @@ stream.end();
4221
4221
 
4222
4222
  ## Throwing HTTP errors
4223
4223
 
4224
- Most of the time, controllers don't need to *choose* an error helper — they throw from the service layer instead. The request middleware (`http/middleware/inject-request-context.ts`) catches every `HttpError` subclass and produces the matching response. The error classes mirror the helpers above:
4224
+ Most of the time, controllers don't need to _choose_ an error helper — they throw from the service layer instead. The request middleware (`http/middleware/inject-request-context.ts`) catches every `HttpError` subclass and produces the matching response. The error classes mirror the helpers above:
4225
4225
 
4226
4226
  ```ts
4227
4227
  import {
4228
- ResourceNotFoundError, // 404
4229
- UnAuthorizedError, // 401
4230
- ForbiddenError, // 403
4231
- BadRequestError, // 400
4232
- ConflictError, // 409
4233
- NotAcceptableError, // 406
4234
- NotAllowedError, // 405
4235
- ServerError, // 500
4236
- HttpError, // base class — `new HttpError(status, message, payload?)` for arbitrary codes
4228
+ ResourceNotFoundError, // 404
4229
+ UnAuthorizedError, // 401
4230
+ ForbiddenError, // 403
4231
+ BadRequestError, // 400
4232
+ ConflictError, // 409
4233
+ NotAcceptableError, // 406
4234
+ NotAllowedError, // 405
4235
+ ServerError, // 500
4236
+ HttpError, // base class — `new HttpError(status, message, payload?)` for arbitrary codes
4237
4237
  } from "@warlock.js/core";
4238
4238
 
4239
4239
  throw new ResourceNotFoundError("product.notFound");
@@ -4241,13 +4241,27 @@ throw new ForbiddenError("permission.denied", { resource: "product", id });
4241
4241
  throw new ConflictError("user.duplicateEmail");
4242
4242
  ```
4243
4243
 
4244
- Each class takes `(message, payload?)`. The payload merges into the response body alongside `error`. In development mode, the stack trace is included too.
4244
+ Each class takes `(message, payload?)`. The framework keeps the optional
4245
+ detail nested under `payload`:
4246
+
4247
+ ```json
4248
+ {
4249
+ "error": "Product not found",
4250
+ "payload": { "id": 42 }
4251
+ }
4252
+ ```
4253
+
4254
+ Every unhandled error response also carries
4255
+ `Cache-Control: private, no-store`, regardless of status. The floor is
4256
+ applied once at the shared error funnel, so API-route errors cannot be stored
4257
+ and replayed across users. In development mode, the stack trace is included
4258
+ too.
4245
4259
 
4246
4260
  Pick the class, throw from the service or use-case, and forget about response shaping at the call site. The controller stays focused on the success path:
4247
4261
 
4248
4262
  ```ts
4249
- export const getProductController: RequestHandler = async (request, response) => {
4250
- const product = await getProductService(request.input("id")); // throws ResourceNotFoundError on miss
4263
+ export const getProductController: RequestHandler = async ({ request, response }) => {
4264
+ const product = await getProductService(request.input("id")); // throws ResourceNotFoundError on miss
4251
4265
  return response.success({ product });
4252
4266
  };
4253
4267
  ```
@@ -4259,9 +4273,9 @@ See [`create-controller`](../create-controller/SKILL.md) for the "throw from ser
4259
4273
  ```ts
4260
4274
  const sse = response.sse();
4261
4275
 
4262
- sse.send("tick", { count: 1 }); // event name, data, optional id
4263
- sse.send("tick", { count: 2 }, "msg-2"); // third arg is the SSE event id
4264
- sse.comment("keep-alive"); // invisible to the client, prevents timeout
4276
+ sse.send("tick", { count: 1 }); // event name, data, optional id
4277
+ sse.send("tick", { count: 2 }, "msg-2"); // third arg is the SSE event id
4278
+ sse.comment("keep-alive"); // invisible to the client, prevents timeout
4265
4279
  sse.end();
4266
4280
  ```
4267
4281
 
@@ -4301,11 +4315,11 @@ response.clearCookie("session_id");
4301
4315
 
4302
4316
  Every response cookie gets `httpOnly: true`, `sameSite: "lax"`, and `secure: true` outside development, unless you override them:
4303
4317
 
4304
- | Flag | Default | Why it's the default |
4305
- |---|---|---|
4306
- | `httpOnly` | `true` | without it, any injected script can read the cookie |
4307
- | `sameSite` | `"lax"` | without it, the cookie rides along on cross-site requests |
4308
- | `secure` | `true`, except in development | without it, the cookie travels in cleartext |
4318
+ | Flag | Default | Why it's the default |
4319
+ | ---------- | ----------------------------- | --------------------------------------------------------- |
4320
+ | `httpOnly` | `true` | without it, any injected script can read the cookie |
4321
+ | `sameSite` | `"lax"` | without it, the cookie rides along on cross-site requests |
4322
+ | `secure` | `true`, except in development | without it, the cookie travels in cleartext |
4309
4323
 
4310
4324
  `secure` is relaxed in development only — browsers drop a `Secure` cookie over plain http, which would silently break every local login. It stays on in test and staging.
4311
4325
 
@@ -4736,12 +4750,17 @@ Set `STORAGE_DRIVER=r2` in production, leave unset in dev — same code uses loc
4736
4750
  ### Uploading a request file
4737
4751
 
4738
4752
  ```ts
4739
- import type { RequestHandler, Response } from "@warlock.js/core";
4753
+ import { type GuardedRequestHandler } from "app/auth/requests/guarded.request";
4740
4754
  import { storage } from "@warlock.js/core";
4741
4755
 
4742
- export const uploadAvatarController: RequestHandler = async (request, response: Response) => {
4756
+ export const uploadAvatarController: GuardedRequestHandler = async ({ request, response }) => {
4743
4757
  const upload = request.file("avatar");
4744
- const file = await storage.put(upload, `avatars/${request.user.id}/${upload.fileName}`);
4758
+
4759
+ if (!upload) {
4760
+ return response.badRequest({ error: "missing file" });
4761
+ }
4762
+
4763
+ const file = await storage.put(upload, `avatars/${request.user.id}/${upload.name}`);
4745
4764
 
4746
4765
  return response.successCreate({ url: file.url, hash: file.hash });
4747
4766
  };
@@ -5656,8 +5675,8 @@ Multipart uploads come in as `UploadedFile` instances. The class wraps Fastify's
5656
5675
 
5657
5676
  ## The shape
5658
5677
 
5659
- ```ts title="src/app/uploads/schema/index.ts"
5660
- import { v } from "@warlock.js/seal";
5678
+ ```ts title="src/app/uploads/schema/upload-avatar.schema.ts"
5679
+ import { v, type Infer } from "@warlock.js/seal";
5661
5680
 
5662
5681
  export const uploadAvatarSchema = v.object({
5663
5682
  avatar: v
@@ -5666,16 +5685,18 @@ export const uploadAvatarSchema = v.object({
5666
5685
  .maxSize({ unit: "MB", size: 5 })
5667
5686
  .mimeType(["image/jpeg", "image/png", "image/webp"]),
5668
5687
  });
5688
+
5689
+ export type UploadAvatarSchema = Infer<typeof uploadAvatarSchema>;
5669
5690
  ```
5670
5691
 
5671
5692
  ```ts title="src/app/uploads/controllers/upload-avatar.controller.ts"
5672
- import { type GuardedRequestHandler } from "app/auth/types/guarded-request.type";
5693
+ import { type GuardedRequestHandler } from "app/auth/requests/guarded.request";
5673
5694
  import { type UploadAvatarSchema, uploadAvatarSchema } from "../schema/upload-avatar.schema";
5674
5695
 
5675
- export const uploadAvatarController: GuardedRequestHandler<UploadAvatarSchema> = async (
5696
+ export const uploadAvatarController: GuardedRequestHandler<UploadAvatarSchema> = async ({
5676
5697
  request,
5677
5698
  response,
5678
- ) => {
5699
+ }) => {
5679
5700
  const { avatar } = request.validated();
5680
5701
 
5681
5702
  const file = await avatar
@@ -5701,7 +5722,7 @@ Inside a controller:
5701
5722
  ```ts
5702
5723
  import type { RequestHandler, UploadedFile } from "@warlock.js/core";
5703
5724
 
5704
- export const uploadController: RequestHandler = async (request, response) => {
5725
+ export const uploadController: RequestHandler = async ({ request, response }) => {
5705
5726
  // option A — direct from request, no validation
5706
5727
  const file: UploadedFile | undefined = request.file("avatar");
5707
5728
 
@@ -5883,7 +5904,7 @@ export const uploadFilesSchema = v.object({
5883
5904
  ```ts title="src/app/uploads/controllers/create-upload.controller.ts"
5884
5905
  import type { RequestHandler } from "@warlock.js/core";
5885
5906
 
5886
- export const createUploadController: RequestHandler = async (request, response) => {
5907
+ export const createUploadController: RequestHandler = async ({ request, response }) => {
5887
5908
  const { files } = request.validated();
5888
5909
 
5889
5910
  const saved = await Promise.all(
@@ -6180,9 +6201,9 @@ const template = await readFile(appPath("mailers/templates/welcome.html"), "utf-
6180
6201
  ### Health endpoint
6181
6202
 
6182
6203
  ```ts title="src/app/system/controllers/health.controller.ts"
6183
- import { Application, type RequestHandler, type Response } from "@warlock.js/core";
6204
+ import { Application, type RequestHandler } from "@warlock.js/core";
6184
6205
 
6185
- export const healthController: RequestHandler = async (_request, response: Response) => {
6206
+ export const healthController: RequestHandler = async ({ response }) => {
6186
6207
  return response.success({
6187
6208
  status: "ok",
6188
6209
  environment: Application.environment,
@@ -6481,10 +6502,14 @@ The error's message is locale-aware — the `inject-request-context` middleware
6481
6502
  ### Translated response message in a controller
6482
6503
 
6483
6504
  ```ts
6484
- export const createProductController: GuardedRequestHandler<CreateProductSchema> = async (
6505
+ import { type GuardedRequestHandler } from "app/auth/requests/guarded.request";
6506
+ import { type CreateProductSchema } from "../schema/create-product.schema";
6507
+ import { createProductService } from "../services/create-product.service";
6508
+
6509
+ export const createProductController: GuardedRequestHandler<CreateProductSchema> = async ({
6485
6510
  request,
6486
6511
  response,
6487
- ) => {
6512
+ }) => {
6488
6513
  const product = await createProductService(request.validated());
6489
6514
  return response.success({
6490
6515
  message: request.t("products.created"),
@@ -7301,10 +7326,10 @@ export async function listFaqsService(filters: FaqListOptions) {
7301
7326
  ```
7302
7327
 
7303
7328
  ```ts title="src/app/faqs/controllers/list-faqs.controller.ts"
7304
- import type { RequestHandler, Response } from "@warlock.js/core";
7329
+ import { type GuardedRequestHandler } from "app/auth/requests/guarded.request";
7305
7330
  import { listFaqsService } from "../services/list-faqs.service";
7306
7331
 
7307
- export const listFaqsController: RequestHandler = async (request, response: Response) => {
7332
+ export const listFaqsController: GuardedRequestHandler = async ({ request, response }) => {
7308
7333
  const { data, pagination } = await listFaqsService({
7309
7334
  ...request.all(),
7310
7335
  organization_id: request.user.organizationId,
@@ -7489,7 +7514,7 @@ public locals: RequestLocals = {};
7489
7514
  ```ts title="src/app/observability/middleware/request-timing.middleware.ts"
7490
7515
  import type { Middleware } from "@warlock.js/core";
7491
7516
 
7492
- export const requestTimingMiddleware: Middleware = request => {
7517
+ export const requestTimingMiddleware: Middleware = (request) => {
7493
7518
  request.startedAt = Date.now();
7494
7519
  };
7495
7520
  ```
@@ -7497,7 +7522,7 @@ export const requestTimingMiddleware: Middleware = request => {
7497
7522
  ```ts title="src/app/observability/controllers/timing.controller.ts"
7498
7523
  import type { RequestHandler } from "@warlock.js/core";
7499
7524
 
7500
- export const timingController: RequestHandler = async (request, response) => {
7525
+ export const timingController: RequestHandler = async ({ request, response }) => {
7501
7526
  return response.success({ elapsedMs: Date.now() - request.startedAt });
7502
7527
  };
7503
7528
  ```
@@ -7582,7 +7607,7 @@ Feature-local files such as `src/app/organizations/request-locals.d.ts` are equa
7582
7607
 
7583
7608
  Two rules the scaffold's own comments spell out, and both bite silently:
7584
7609
 
7585
- - **Keep the trailing `export {}`.** `declare module "x"` inside a file with no top-level import or export declares an *ambient* module, which REPLACES `@warlock.js/core`'s real typings instead of merging into them — every framework export vanishes. The `export {}` is what makes the file a module and the block an augmentation. It is not an unused statement to clean up.
7610
+ - **Keep the trailing `export {}`.** `declare module "x"` inside a file with no top-level import or export declares an _ambient_ module, which REPLACES `@warlock.js/core`'s real typings instead of merging into them — every framework export vanishes. The `export {}` is what makes the file a module and the block an augmentation. It is not an unused statement to clean up.
7586
7611
  - **Keep them `interface`, not `type`.** This project otherwise prefers `type`; these are the named exception, because declaration merging is interface-only. `type RequestUser = { ... }` is a duplicate-identifier error, not an augmentation.
7587
7612
 
7588
7613
  On a project scaffolded before 5.1 there is no `src/typings.d.ts`, and `tsconfig.json` carries `"typeRoots": ["./src/typings.d.ts"]` — wrong twice, since `typeRoots` takes directories of `@types` packages rather than files, and that file did not exist. Drop the `typeRoots` entry, create the file, and list it under `include`.
@@ -7656,10 +7681,10 @@ import type { Request, RequestHandler } from "@warlock.js/core";
7656
7681
  import { type CreateProductSchema, createProductSchema } from "../schema/create-product.schema";
7657
7682
  import { createProductService } from "../services/create-product.service";
7658
7683
 
7659
- export const createProductController: RequestHandler<Request<CreateProductSchema>> = async (
7684
+ export const createProductController: RequestHandler<Request<CreateProductSchema>> = async ({
7660
7685
  request,
7661
7686
  response,
7662
- ) => {
7687
+ }) => {
7663
7688
  const product = await createProductService(request.validated());
7664
7689
 
7665
7690
  return response.successCreate({ product });
@@ -7969,11 +7994,11 @@ This skill is the foundation. Every other warlock skill (`register-route`, `crea
7969
7994
  ```
7970
7995
 
7971
7996
  ```ts title="src/app/<module>/controllers/create-<thing>.controller.ts"
7972
- import { type GuardedRequestHandler } from "app/auth/types/guarded-request.type";
7997
+ import { type GuardedRequestHandler } from "app/auth/requests/guarded.request";
7973
7998
  import { type CreateThingSchema, createThingSchema } from "../schema/create-thing.schema";
7974
7999
  import { createThingService } from "../services/create-thing.service";
7975
8000
 
7976
- export const createThingController: GuardedRequestHandler<CreateThingSchema> = async (request, response) => {
8001
+ export const createThingController: GuardedRequestHandler<CreateThingSchema> = async ({ request, response }) => {
7977
8002
  const thing = await createThingService(request.validated());
7978
8003
  return response.success({ thing });
7979
8004
  };
@@ -8459,10 +8484,10 @@ export async function notifyUserService(user: User, payload: unknown) {
8459
8484
  Then from a controller:
8460
8485
 
8461
8486
  ```ts
8462
- import type { GuardedRequestHandler } from "app/auth/types/guarded-request.type";
8487
+ import type { GuardedRequestHandler } from "app/auth/requests/guarded.request";
8463
8488
  import { notifyUserService } from "../services/notify-user.service";
8464
8489
 
8465
- export const sendNotificationController: GuardedRequestHandler = async (request, response) => {
8490
+ export const sendNotificationController: GuardedRequestHandler = async ({ request, response }) => {
8466
8491
  await notifyUserService(request.user, request.input("payload"));
8467
8492
  return response.success({ delivered: true });
8468
8493
  };
@@ -8938,7 +8963,7 @@ export default command({
8938
8963
 
8939
8964
  ---
8940
8965
  name: write-middleware
8941
- description: 'Author HTTP middleware for @warlock.js/core — the `(request, response)` signature, short-circuit by returning a response, enrich the request with extra fields, register per-route, per-group, or app-wide. Triggers: `Middleware`, `MiddlewareResponse`, `router.group`, `guarded`, `request.detectIp`, `authMiddleware`; "write a custom middleware", "short-circuit a request", "enrich the request with extra fields", "per-route vs per-group middleware"; typical import `import type { Middleware } from "@warlock.js/core"`. Skip: built-in middleware catalog — `@warlock.js/core/use-middleware/SKILL.md`; route attachment — `@warlock.js/core/register-route/SKILL.md`; response helpers — `@warlock.js/core/send-response/SKILL.md`; competing patterns: `express` `(req, res, next)` middleware, Fastify `preHandler` hooks.'
8966
+ description: 'Author HTTP middleware for @warlock.js/core — the `({ request, response })` signature, short-circuit by returning a response, enrich the request with extra fields, register per-route, per-group, or app-wide. Triggers: `Middleware`, `MiddlewareResponse`, `router.group`, `guarded`, `request.detectIp`, `authMiddleware`; "write a custom middleware", "short-circuit a request", "enrich the request with extra fields", "per-route vs per-group middleware"; typical import `import type { Middleware } from "@warlock.js/core"`. Skip: built-in middleware catalog — `@warlock.js/core/use-middleware/SKILL.md`; route attachment — `@warlock.js/core/register-route/SKILL.md`; response helpers — `@warlock.js/core/send-response/SKILL.md`; competing patterns: `express` `(req, res, next)` middleware, Fastify `preHandler` hooks.'
8942
8967
  ---
8943
8968
 
8944
8969
  # Warlock — write a middleware
@@ -8950,7 +8975,7 @@ Middleware is a plain function that runs before the controller. Two outcomes: re
8950
8975
  ```ts title="src/app/<module>/utils/<name>.middleware.ts"
8951
8976
  import type { Middleware } from "@warlock.js/core";
8952
8977
 
8953
- export const requireApiKey: Middleware = (request, response) => {
8978
+ export const requireApiKey: Middleware = ({ request, response }) => {
8954
8979
  const key = request.header("X-API-Key");
8955
8980
 
8956
8981
  if (!key || key !== process.env.API_KEY) {
@@ -8961,16 +8986,14 @@ export const requireApiKey: Middleware = (request, response) => {
8961
8986
  };
8962
8987
  ```
8963
8988
 
8964
- That's the contract: `(request: Request, response: Response) => Response | undefined | void`. Async is fine — return a `Promise<Response | undefined | void>`.
8989
+ That's the contract: `(context: HttpContext<Request>) => Response | undefined | void`, where `context` is `{ request, response }`. Async is fine — return a `Promise<Response | undefined | void>`.
8965
8990
 
8966
8991
  The real type, from `@warlock.js/core/src/router/types.ts`:
8967
8992
 
8968
8993
  ```ts
8969
8994
  export type Middleware<MiddlewareRequest extends Request = Request> = {
8970
- (request: MiddlewareRequest, response: Response): MiddlewareResponse;
8995
+ (context: HttpContext<MiddlewareRequest>): MiddlewareResponse;
8971
8996
  };
8972
-
8973
- export type MiddlewareResponse = ReturnedResponse | undefined | void;
8974
8997
  ```
8975
8998
 
8976
8999
  ## Short-circuit vs continue
@@ -8980,7 +9003,7 @@ The pattern is "return a response to stop, return nothing to continue":
8980
9003
  ```ts
8981
9004
  import type { Middleware } from "@warlock.js/core";
8982
9005
 
8983
- export const requireFeatureFlag: Middleware = async (request, response) => {
9006
+ export const requireFeatureFlag: Middleware = async ({ request, response }) => {
8984
9007
  const flag = await loadFeatureFlag(request.input("organization_id"));
8985
9008
 
8986
9009
  if (!flag.enabled) {
@@ -8998,8 +9021,8 @@ If you short-circuit, the controller never runs. The response helper you pick (`
8998
9021
  You can attach arbitrary fields to `request` from a middleware, and they survive into the controller. The cleanest pattern is to extend `Request` via module augmentation in a `.d.ts` and assign in the middleware:
8999
9022
 
9000
9023
  ```ts title="src/app/feature-flags/middleware/load-feature-flag.middleware.ts"
9001
- import type { Middleware } from "@warlock.js/core";
9002
- import type { FeatureFlag } from "../models/feature-flag";
9024
+ import type { Middleware, Request, RequestUser } from "@warlock.js/core";
9025
+ import { FeatureFlag } from "../models/feature-flag";
9003
9026
 
9004
9027
  declare module "@warlock.js/core" {
9005
9028
  interface Request {
@@ -9007,7 +9030,7 @@ declare module "@warlock.js/core" {
9007
9030
  }
9008
9031
  }
9009
9032
 
9010
- export const loadFeatureFlag: Middleware = async (request) => {
9033
+ export const loadFeatureFlag: Middleware<Request & { user: RequestUser }> = async ({ request }) => {
9011
9034
  request.featureFlag = await FeatureFlag.findBy("organization_id", request.user.organizationId);
9012
9035
  };
9013
9036
  ```
@@ -9077,7 +9100,7 @@ export function guarded(callback: () => void) {
9077
9100
  }
9078
9101
 
9079
9102
  export function guardedAdmin(callback: () => void) {
9080
- router.group({ prefix: "/admin", middleware: [authMiddleware()] }, callback);
9103
+ router.group({ prefix: "/admin", middleware: [authMiddleware("admin")] }, callback);
9081
9104
  }
9082
9105
 
9083
9106
  export function publicRoutes(callback: () => void) {
@@ -9095,7 +9118,7 @@ guarded(() => {
9095
9118
  });
9096
9119
  ```
9097
9120
 
9098
- `authMiddleware(allowedUserType?)` accepts a user-type string or array. Without an arg it just verifies the token is present; with `"user"` / `"admin"` it also checks the decoded `userType` matches.
9121
+ `authMiddleware(allowedUserType, tokenFrom?)` requires a user-type string or array `[]` accepts any authenticated user without checking `userType`; `"user"` / `"admin"` also checks the decoded `userType` matches.
9099
9122
 
9100
9123
  ## Common patterns
9101
9124
 
@@ -9122,13 +9145,13 @@ router.group(
9122
9145
  ```ts
9123
9146
  import type { Middleware } from "@warlock.js/core";
9124
9147
 
9125
- export const optionalAuth: Middleware = async (request, response) => {
9148
+ export const optionalAuth: Middleware = async ({ request, response }) => {
9126
9149
  if (!request.authorizationValue) {
9127
9150
  return; // anonymous — let it through
9128
9151
  }
9129
9152
 
9130
9153
  // token present → enforce it
9131
- return authMiddleware("user")(request, response);
9154
+ return authMiddleware("user")({ request, response });
9132
9155
  };
9133
9156
  ```
9134
9157
 
@@ -9144,7 +9167,7 @@ export const optionalAuth: Middleware = async (request, response) => {
9144
9167
  - **Group middleware runs before per-route middleware**, in array order. The full chain is `app.all → group → per-route → controller`. Mind the order if you stack auth + rate-limit + audit.
9145
9168
  - **Middleware can be async.** Returning `Promise<undefined>` continues the chain. Returning `Promise<Response>` short-circuits. The framework awaits the result.
9146
9169
  - **Don't mutate `request.payload` directly.** Use `request.setValidatedData(...)` or attach a new named field (`request.featureFlag = ...`). The internals expect `payload.all` shapes to come from the validator pipeline.
9147
- - **`Middleware` is generic over the request type.** For middleware that assumes a validated schema, narrow it: `const m: Middleware<CreateProductRequest> = (request) => { ... }`. But most middlewares run before validation, so the default `Middleware` is right.
9170
+ - **`Middleware` is generic over the request type.** For middleware that assumes a validated schema, narrow it: `const m: Middleware<CreateProductRequest> = ({ request }) => { ... }`. But most middlewares run before validation, so the default `Middleware` is right.
9148
9171
  - **No `next()` parameter.** Express-style `next()` doesn't apply here. The framework chains based on return value.
9149
9172
  - **`request.baseRequest` / `response.baseResponse` are escape hatches, not API.** They expose the underlying Fastify primitives for cases the framework hasn't covered yet (streaming was the historical precedent). Prefer framework helpers first — `response.send()`, `response.header()`, `response.replay()`, `request.input()`, `request.detectIp()`, etc. If you find yourself reaching for `baseResponse` or `baseRequest` for non-streaming work, that's a missing helper — file an issue. The cache and idempotency middlewares both shipped with a quietly-broken FastifyReply-return bug because they bypassed the helper layer; the framework now guards `Response.send()` against double-send, but the right answer is "use the helper."
9150
9173
  - **Behind any proxy, use `request.detectIp()` not `request.ip`.** `request.ip` is the immediate peer (likely your load balancer); `request.detectIp()` honors `X-Real-IP` / `X-Forwarded-For`. Either way, only trust the result as far as you trust the upstream chain — those headers are client-settable; verify the request came through your trusted edge before treating the value as authoritative.
package/llms.txt CHANGED
@@ -43,6 +43,6 @@
43
43
  - [warlock-routes](@warlock.js/core/warlock-routes/SKILL.md): Run `warlock routes` — a read-only command that lists the registered HTTP routes as a verb-colored table (method / path / name / action / middleware-count / source), a sibling of `warlock doctor`. Filter with `--method` / `--path` / `--name`, or emit normalized rows as JSON with `--json`. Also covers `warlock routes:diff`, which compares live page routes against the last `warlock build`'s route snapshot and exits non-zero on drift. Triggers: `warlock routes`, `routesCommand`, `warlock routes:diff`, `routesDiffCommand`, "list my routes", "show all routes", "route table", "what endpoints does my app expose", "dump routes as JSON", "which routes have middleware", "route map for CI", "did my page routes drift from the last build"; run as `pnpm warlock routes` / `pnpm warlock routes:diff`. Skip: read-only health/preflight checks — `@warlock.js/core/warlock-doctor/SKILL.md`; defining/naming/grouping routes — `@warlock.js/core/register-route/SKILL.md`; authoring a general CLI command — `@warlock.js/core/write-cli-command/SKILL.md`; competing tools `nest`/`express` route listers, `php artisan route:list`.
44
44
  - [wire-socket](@warlock.js/core/wire-socket/SKILL.md): Configure Socket.IO via `src/config/socket.ts`, reach the live server through `getSocketServer()` (or `app.socket` post-bootstrap), register `connection` handlers once the late-phase socket connector has booted, emit from controllers/services, use rooms and namespaces. Triggers: `app.socket`, `getSocketServer`, `SocketOptions`, `socket.io` `Server`, `socket.join`, `socket.to`, `io.of`, `io.use`; "add realtime chat", "emit socket events from a service", "use rooms and namespaces", "per-socket JWT auth". Skip: connector lifecycle — `@warlock.js/core/add-connector/SKILL.md`; app context accessors — `@warlock.js/core/use-app-context/SKILL.md`; competing libs `ws`, `socket.io` direct without Warlock connector, `uWebSockets.js`.
45
45
  - [write-cli-command](@warlock.js/core/write-cli-command/SKILL.md): Author a custom `warlock <my-cmd>` command via the `command()` factory — name, description, action, options, preload, then register in `warlock.config.ts > cli.commands` or drop in `src/app/<module>/commands/`. Also covers built-in `warlock add` feature scaffolding, including the Web starter and `index.register.ts`. Triggers: `command`, `CLICommand`, `CLICommandPreload`, `CLICommandOption`, `preload`, `preAction`, `persistent`, `colors`, `warlock add`, `index.register.ts`; "write a custom warlock command", "one-off maintenance task", "ship a CLI from a package", "framework built-in commands"; typical import `import { command } from "@warlock.js/core"`. Skip: framework dev/build/start — `@warlock.js/core/run-app/SKILL.md`; warlock.config.ts wiring — `@warlock.js/core/configure-app/SKILL.md`; competing libs `commander`, `yargs`, `oclif`.
46
- - [write-middleware](@warlock.js/core/write-middleware/SKILL.md): Author HTTP middleware for @warlock.js/core — the `(request, response)` signature, short-circuit by returning a response, enrich the request with extra fields, register per-route, per-group, or app-wide. Triggers: `Middleware`, `MiddlewareResponse`, `router.group`, `guarded`, `request.detectIp`, `authMiddleware`; "write a custom middleware", "short-circuit a request", "enrich the request with extra fields", "per-route vs per-group middleware"; typical import `import type { Middleware } from "@warlock.js/core"`. Skip: built-in middleware catalog — `@warlock.js/core/use-middleware/SKILL.md`; route attachment — `@warlock.js/core/register-route/SKILL.md`; response helpers — `@warlock.js/core/send-response/SKILL.md`; competing patterns: `express` `(req, res, next)` middleware, Fastify `preHandler` hooks.
46
+ - [write-middleware](@warlock.js/core/write-middleware/SKILL.md): Author HTTP middleware for @warlock.js/core — the `({ request, response })` signature, short-circuit by returning a response, enrich the request with extra fields, register per-route, per-group, or app-wide. Triggers: `Middleware`, `MiddlewareResponse`, `router.group`, `guarded`, `request.detectIp`, `authMiddleware`; "write a custom middleware", "short-circuit a request", "enrich the request with extra fields", "per-route vs per-group middleware"; typical import `import type { Middleware } from "@warlock.js/core"`. Skip: built-in middleware catalog — `@warlock.js/core/use-middleware/SKILL.md`; route attachment — `@warlock.js/core/register-route/SKILL.md`; response helpers — `@warlock.js/core/send-response/SKILL.md`; competing patterns: `express` `(req, res, next)` middleware, Fastify `preHandler` hooks.
47
47
  - [write-seeder](@warlock.js/core/write-seeder/SKILL.md): Author a seed file under `src/app/<module>/seeds/<name>.ts` using the `seeder()` factory — `name`, `dependsOn`, `once`, `order`, `batchSize`, `run({ track, now, batchSize })`. Auto-discovered by `warlock seed`; tracked in a `seeds` table; per-record refs in `seed_records` so `warlock seed --drop` can undo a seed. Triggers: `seeder`, `Seeder`, `SeedResult`, `SeedContext`, `SeedClock`, `track`, `now`, `batchSize`, `SeedersManager`, `warlock seed`, `--fresh`, `--drop`, `--list`, `--path`; "seed default roles", "undo a seed", "one-time data migration", "auto-discovered seeds", "order seeds by dependency", "deterministic seed timestamps", "inject a seed clock"; typical import `import { seeder } from "@warlock.js/core"`. Skip: module folder layout — `@warlock.js/core/create-module/SKILL.md`; repository CRUD — `@warlock.js/core/use-repository/SKILL.md`; CLI flags — `@warlock.js/core/write-cli-command/SKILL.md`; competing patterns: hand-rolled `node scripts/seed.js`, `typeorm-seeding`.
48
48
  - [write-use-case](@warlock.js/core/write-use-case/SKILL.md): Author `useCase()` pipelines for business logic — guards, schema, before/after middleware, retry, benchmark, broadcast, lifecycle callbacks; transport-agnostic and observable by default. Input is inferred from the `schema`. Triggers: `useCase`, `UseCaseContext`, `UseCaseResult`, `retry`, `benchmark`, `broadcast`, `description`, `globalUseCasesEvents`, `UseCaseBroadcastChannel`; "encapsulate a business operation", "share logic between HTTP and CLI", "add guards and lifecycle hooks", "broadcast a use case result", "transport-agnostic pipeline"; typical import `import { useCase } from "@warlock.js/core"`. Skip: thin handler shape — `@warlock.js/core/create-controller/SKILL.md`; schema details — `@warlock.js/core/validate-input/SKILL.md`; the standalone retry util — `@warlock.js/core/retry-operation/SKILL.md`; competing libs `@nestjs/cqrs`, `inversify`, hand-rolled service classes.
package/package.json CHANGED
@@ -25,13 +25,13 @@
25
25
  "@mongez/slug": "^1.0.7",
26
26
  "@mongez/supportive-is": "^2.1.4",
27
27
  "@mongez/time-wizard": "^1.0.6",
28
- "@warlock.js/auth": "5.2.4",
29
- "@warlock.js/cache": "5.2.4",
30
- "@warlock.js/cascade": "5.2.4",
31
- "@warlock.js/context": "5.2.4",
32
- "@warlock.js/logger": "5.2.4",
33
- "@warlock.js/seal": "5.2.4",
34
- "@warlock.js/fs": "5.2.4",
28
+ "@warlock.js/auth": "5.3.0",
29
+ "@warlock.js/cache": "5.3.0",
30
+ "@warlock.js/cascade": "5.3.0",
31
+ "@warlock.js/context": "5.3.0",
32
+ "@warlock.js/logger": "5.3.0",
33
+ "@warlock.js/seal": "5.3.0",
34
+ "@warlock.js/fs": "5.3.0",
35
35
  "chokidar": "^5.0.0",
36
36
  "dayjs": "^1.11.19",
37
37
  "es-module-lexer": "^2.0.0",
@@ -57,10 +57,10 @@
57
57
  "react": "^19.2.3",
58
58
  "react-dom": "^19.2.3",
59
59
  "@react-email/render": "^2.0.5",
60
- "@warlock.js/herald": "5.2.4",
61
- "@warlock.js/ai": "5.2.4",
62
- "@warlock.js/access": "5.2.4",
63
- "@warlock.js/notifications": "5.2.4"
60
+ "@warlock.js/herald": "5.3.0",
61
+ "@warlock.js/ai": "5.3.0",
62
+ "@warlock.js/access": "5.3.0",
63
+ "@warlock.js/notifications": "5.3.0"
64
64
  },
65
65
  "peerDependenciesMeta": {
66
66
  "sharp": {
@@ -123,7 +123,7 @@
123
123
  ],
124
124
  "author": "hassanzohdy",
125
125
  "license": "MIT",
126
- "version": "5.2.4",
126
+ "version": "5.3.0",
127
127
  "type": "module",
128
128
  "main": "./esm/index.mjs",
129
129
  "module": "./esm/index.mjs",
package/skills/README.md CHANGED
@@ -10,7 +10,7 @@ Each folder holds one `SKILL.md` (an agent-facing how-to for a single task). The
10
10
  - [send-response](send-response/SKILL.md) — `Response` helpers: success/error variants, status helpers, redirects, files, streams, SSE.
11
11
  - [validate-input](validate-input/SKILL.md) — author seal schemas, attach them to controllers, infer types, layer DTOs.
12
12
  - [use-middleware](use-middleware/SKILL.md) — attach built-in HTTP middleware (rateLimit, concurrencyLimit, maxBodySize, …) via the `middleware` namespace.
13
- - [write-middleware](write-middleware/SKILL.md) — author HTTP middleware: the `(request, response)` signature, short-circuit, request enrichment.
13
+ - [write-middleware](write-middleware/SKILL.md) — author HTTP middleware: the `({ request, response })` signature, short-circuit, request enrichment.
14
14
  - [build-url](build-url/SKILL.md) — HTTP URL helpers (`url`, `publicUrl`, `assetsUrl`, `uploadsUrl`) anchored at `app.baseUrl`.
15
15
  - [upload-file](upload-file/SKILL.md) — handle multipart uploads: `request.file()`, `v.file()` validation, `UploadedFile.save()`.
16
16
  - [health-checks](health-checks/SKILL.md) — built-in `/health` + `/ready` endpoints, the `health` registry, and graceful request draining.