create-warlock 5.34.3 → 5.35.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 (68) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +8 -0
  3. package/esm/commands/create-new-app/index.mjs +4 -3
  4. package/esm/commands/create-new-app/index.mjs.map +1 -1
  5. package/esm/commands/create-warlock-app/index.mjs +7 -1
  6. package/esm/commands/create-warlock-app/index.mjs.map +1 -1
  7. package/esm/features/features-map.mjs +8 -2
  8. package/esm/features/features-map.mjs.map +1 -1
  9. package/esm/helpers/app.mjs +1 -0
  10. package/esm/helpers/app.mjs.map +1 -1
  11. package/esm/prompts/ask-stack.mjs +1 -1
  12. package/esm/prompts/ask-stack.mjs.map +1 -1
  13. package/llms-full.txt +58 -15
  14. package/package.json +2 -2
  15. package/skills/SKILL.md +58 -15
  16. package/templates/design-system/SOURCES.md +23 -0
  17. package/templates/design-system/package.json +14 -0
  18. package/templates/design-system/src/web/components/ui/LICENSE.md +21 -0
  19. package/templates/design-system/src/web/components/ui/button.tsx +61 -0
  20. package/templates/design-system/src/web/components/ui/calendar.tsx +178 -0
  21. package/templates/design-system/src/web/components/ui/checkbox.tsx +28 -0
  22. package/templates/design-system/src/web/components/ui/command.tsx +161 -0
  23. package/templates/design-system/src/web/components/ui/dialog.tsx +144 -0
  24. package/templates/design-system/src/web/components/ui/input.tsx +20 -0
  25. package/templates/design-system/src/web/components/ui/label.tsx +20 -0
  26. package/templates/design-system/src/web/components/ui/popover.tsx +73 -0
  27. package/templates/design-system/src/web/components/ui/radio-group.tsx +44 -0
  28. package/templates/design-system/src/web/components/ui/switch.tsx +34 -0
  29. package/templates/design-system/src/web/components/ui/textarea.tsx +17 -0
  30. package/templates/design-system/src/web/design-system/components/checkbox-input.tsx +19 -0
  31. package/templates/design-system/src/web/design-system/components/date-picker-input.tsx +59 -0
  32. package/templates/design-system/src/web/design-system/components/date-time-picker-input.tsx +67 -0
  33. package/templates/design-system/src/web/design-system/components/email-input.tsx +22 -0
  34. package/templates/design-system/src/web/design-system/components/field-frame.tsx +36 -0
  35. package/templates/design-system/src/web/design-system/components/file-input.tsx +24 -0
  36. package/templates/design-system/src/web/design-system/components/multi-select-input.tsx +94 -0
  37. package/templates/design-system/src/web/design-system/components/password-input.tsx +22 -0
  38. package/templates/design-system/src/web/design-system/components/radio-input.tsx +41 -0
  39. package/templates/design-system/src/web/design-system/components/select-input.tsx +94 -0
  40. package/templates/design-system/src/web/design-system/components/submit-button.tsx +20 -0
  41. package/templates/design-system/src/web/design-system/components/switch-input.tsx +19 -0
  42. package/templates/design-system/src/web/design-system/components/text-input.tsx +22 -0
  43. package/templates/design-system/src/web/design-system/components/textarea-input.tsx +22 -0
  44. package/templates/design-system/src/web/design-system/components/time-picker-input.tsx +22 -0
  45. package/templates/design-system/src/web/design-system/hooks/use-calendar-day-button.ts +9 -0
  46. package/templates/design-system/src/web/design-system/hooks/use-control.ts +29 -0
  47. package/templates/design-system/src/web/design-system/hooks/use-date-control.ts +42 -0
  48. package/templates/design-system/src/web/design-system/hooks/use-file-control.ts +24 -0
  49. package/templates/design-system/src/web/design-system/hooks/use-select-control.ts +78 -0
  50. package/templates/design-system/src/web/design-system/hooks/use-submit-control.ts +13 -0
  51. package/templates/design-system/src/web/design-system/types/control-props.ts +46 -0
  52. package/templates/warlock/e2e/home.e2e.ts +19 -0
  53. package/templates/warlock/package.json +1 -1
  54. package/templates/warlock/skills/api-design/SKILL.md +72 -9
  55. package/templates/warlock/skills/testing-strategy/SKILL.md +46 -5
  56. package/templates/warlock/skills/web-code-standards/SKILL.md +38 -0
  57. package/templates/warlock/src/app/auth/controllers/forgot-password.controller.ts +6 -0
  58. package/templates/warlock/src/app/auth/controllers/login.controller.ts +17 -0
  59. package/templates/warlock/src/app/auth/controllers/logout-all.controller.ts +6 -0
  60. package/templates/warlock/src/app/auth/controllers/logout.controller.ts +6 -0
  61. package/templates/warlock/src/app/auth/controllers/me.controller.ts +7 -0
  62. package/templates/warlock/src/app/auth/controllers/refresh-token.controller.ts +12 -0
  63. package/templates/warlock/src/app/auth/controllers/reset-password.controller.ts +6 -0
  64. package/templates/warlock/src/app/home/controllers/home-page.controller.ts +4 -0
  65. package/templates/warlock/src/app/posts/controllers/update-post.controller.ts +1 -0
  66. package/templates/warlock/src/app/shared/utils/format-http-error-response.ts +13 -1
  67. package/templates/warlock/src/app/users/controllers/create-new-user.controller.ts +1 -0
  68. package/templates/warlock/warlock.config.ts +1 -0
@@ -0,0 +1,46 @@
1
+ import type { FormControlProps } from "@mongez/react-form";
2
+ import type { ReactNode } from "react";
3
+
4
+ export type ControlProps = Pick<
5
+ FormControlProps,
6
+ | "name"
7
+ | "rules"
8
+ | "schema"
9
+ | "validate"
10
+ | "validateOn"
11
+ | "onChange"
12
+ | "onError"
13
+ | "errors"
14
+ | "errorKeys"
15
+ | "disabled"
16
+ | "readOnly"
17
+ | "required"
18
+ > & {
19
+ id: string;
20
+ label: ReactNode;
21
+ hint?: ReactNode;
22
+ error?: ReactNode;
23
+ value?: unknown;
24
+ defaultValue?: unknown;
25
+ placeholder?: string;
26
+ className?: string;
27
+ dir?: "ltr" | "rtl";
28
+ autoComplete?: string;
29
+ rows?: number;
30
+ accept?: string;
31
+ multiple?: boolean;
32
+ };
33
+ export type Choice = { value: string; label: string; disabled?: boolean };
34
+ export type SelectProps = ControlProps & {
35
+ options: readonly Choice[];
36
+ searchLabel?: string;
37
+ emptyLabel?: string;
38
+ clearLabel?: string;
39
+ };
40
+ export type RadioProps = ControlProps & { options: readonly Choice[] };
41
+ export type CheckProps = ControlProps & { checked?: boolean; defaultChecked?: boolean };
42
+ export type DateProps = ControlProps & {
43
+ clearLabel?: string;
44
+ closeLabel?: string;
45
+ timeLabel?: string;
46
+ };
@@ -0,0 +1,19 @@
1
+ import { defineE2E, defaultViewports } from "@warlock.js/core/e2e";
2
+
3
+ export default defineE2E({
4
+ app: {
5
+ build: [process.execPath, "node_modules/@warlock.js/core/bin/warlock.js", "build"],
6
+ start: [process.execPath, "dist/app.js"],
7
+ exclude: ["storage", "data"],
8
+ },
9
+ run: async ({ page }) => {
10
+ for (const viewport of defaultViewports) {
11
+ await page.setViewport(viewport);
12
+ await page.goto("/");
13
+ await page.waitFor({ selector: "main" });
14
+ await page.assertAccessible();
15
+ await page.assertViewport();
16
+ await page.screenshot("home");
17
+ }
18
+ },
19
+ });
@@ -46,7 +46,7 @@
46
46
  "@mongez/agent-kit": "^1.3.1",
47
47
  "@types/node": "^25.0.3",
48
48
  "typescript": "7.0.2",
49
- "vitest": "4.0.5",
49
+ "vitest": "4.1.10",
50
50
  "oxlint": "1.88.0",
51
51
  "oxfmt": "0.73.0"
52
52
  }
@@ -20,17 +20,26 @@ The project uses **three envelope shapes**, one per response kind. Each success
20
20
 
21
21
  ### 1.1 List endpoints — `{ <pluralResourceName>, pagination }`
22
22
 
23
- The list lands under the **plural name of the module's resource** — `users`, `aiModels`, `orders` — paired with `pagination`. The repository returns the framework's `{ data, pagination }`; the controller re-keys `data` to the plural name so the response is self-describing.
23
+ The list lands under the **plural name of the module's resource** — `users`, `aiModels`, `orders` — paired with `pagination`. The repository returns the framework's `{ data, pagination }`; `listEndpoint` re-keys `data` to the plural name and enforces bounded page controls.
24
24
 
25
25
  ```typescript
26
26
  // ✅ list-users.controller.ts
27
- export const listUsersController: RequestHandler = async ({ request, response }) => {
28
- const { data, pagination } = await listUsersService(request.validated());
27
+ import { listEndpoint, type Request, type RequestHandler } from "@warlock.js/core";
28
+ import type { ListUsersQuery } from "../schema/list-users.schema";
29
+ import { usersRepository } from "../repositories/users.repository";
29
30
 
30
- return response.success({
31
- users: data,
32
- pagination,
33
- });
31
+ export const listUsersController: RequestHandler<Request<ListUsersQuery>> = async ({
32
+ request,
33
+ response,
34
+ }) => {
35
+ return response.success(
36
+ await listEndpoint(request, (options) => usersRepository.list(options), {
37
+ pluralKey: "users",
38
+ maxLimit: 100,
39
+ orderBy: ["createdAt"],
40
+ filters: ["status"],
41
+ }),
42
+ );
34
43
  };
35
44
  ```
36
45
 
@@ -113,7 +122,7 @@ Every JSON error response has this shape, with optional `details` for safe field
113
122
  }
114
123
  ```
115
124
 
116
- Use the matching status helper or throw a framework error from the service. The starter's `http.errorResponse` formatter normalizes helper bodies and framework-generated JSON errors, including unmatched-route 404, validation 422, rate-limit 429, and 500. It removes stacks and masks all 5xx messages. `requestId` is the real Warlock `request.id`, read from the framework's correlation header at the transport boundary; a rejection before Warlock creates a Request uses Fastify's request ID and echoes that same value in the header. Do not generate a new ID inside a controller or use a user ID. See the `observability-and-resilience` skill for trace propagation.
125
+ Use the matching status helper or throw a framework error from the service. The starter's `http.errorResponse` formatter normalizes helper bodies and framework-generated JSON errors, including unmatched-route 404, validation 422, rate-limit 429, and 500. It removes stacks and masks ordinary 5xx messages. Typed `AppError` messages and details are explicitly public and are preserved, including for 5xx responses. `requestId` is the real Warlock `request.id`, read from the framework's correlation header at the transport boundary; a rejection before Warlock creates a Request uses Fastify's request ID and echoes that same value in the header. Do not generate a new ID inside a controller or use a user ID. See the `observability-and-resilience` skill for trace propagation.
117
126
 
118
127
  ```typescript
119
128
  return response.notFound(); // formatter supplies { error: { code, message }, requestId }
@@ -123,7 +132,7 @@ return response.conflict({
123
132
  return response.badRequest({ error: { code: "invalidRequest", message: "Invalid request." } });
124
133
  ```
125
134
 
126
- `http.errorMapper` translates thrown non-`HttpError` domain errors to `{ status, message?, payload? }`; it does **not** format response bodies. It skips `HttpError` subclasses, returned validation/helper responses, and Fastify's unmatched-route and global rate-limit responses. The starter config uses it to keep unknown thrown errors opaque; `http.errorResponse` handles the final JSON shape for all these paths. HTML error pages, files, and already-started streams retain their own contracts; neither hook rewrites their bodies.
135
+ `http.errorMapper` translates thrown non-`HttpError` domain errors to `{ status, message?, payload? }`; it does **not** format response bodies. It skips `AppError` and `HttpError` subclasses, returned validation/helper responses, and Fastify's unmatched-route and global rate-limit responses. The starter config uses it to keep unknown thrown errors opaque; `http.errorResponse` handles the final JSON shape for all these paths. HTML error pages, files, and already-started streams retain their own contracts; neither hook rewrites their bodies.
127
136
 
128
137
  Never send a `200 OK` with an `error` field. Never put secrets, stack traces, raw request bodies, or internal database IDs in `details`.
129
138
 
@@ -519,3 +528,57 @@ Before merging any controller / route / resource change:
519
528
  - [ ] `Cache-Control` set explicitly on `GET` endpoints
520
529
  - [ ] Versioning: additive-only by default; explicit `/vN` prefix only when a real breaking change forces it
521
530
  - [ ] Deprecated fields announced via `Deprecation` / `Sunset` headers, not silently changed
531
+
532
+ ## Framework helpers for API contracts
533
+
534
+ Throw `AppError` for new coded domain failures. Its positional constructor is
535
+ `(code, status, publicMessage, details?)`; status must be 400–599. Details are
536
+ typed and must be safe to disclose. Existing `HttpError` subclasses remain
537
+ separate and retain their constructors and payload behavior. Neither needs
538
+ `http.errorMapper`. The starter formatter reads `context.appError` natively and
539
+ keeps this project's `{ error: { code, message }, details?, requestId }` contract.
540
+ Core's unformatted default places details inside `error`; declare the actual
541
+ formatted shape in controller response schemas.
542
+
543
+ ```ts
544
+ import { AppError } from "@warlock.js/core/errors";
545
+
546
+ throw new AppError("quotaExceeded", 429, "Try again later.", { retryAfter: 30 });
547
+ ```
548
+
549
+ For lists, use the `listEndpoint` example in § 1.1. Type the controller as
550
+ `RequestHandler<Request<ListUsersQuery>>`, attach the query schema with
551
+ `validating: ["query"]`, and configure repository `filterBy`. The helper reads
552
+ only `request.validated()`, defaults to page 1 / limit 20, clamps limits to
553
+ `maxLimit` (100 by default), rejects unlisted sort columns, and forwards only
554
+ named filters as flat repository options. Both `(options) => repo.list(options)`
555
+ and `(options) => repo.listCached(options)` preserve the repository receiver.
556
+ Page pagination is forced; cursor endpoints use a separate controller.
557
+
558
+ Test the declared response contract through actual HTTP, not just generated
559
+ types. Keep the existing `startHttpTestServer({ port: 0 })` / `setupTest` setup
560
+ and import routes in the worker so its registry contains handler schemas:
561
+
562
+ ```ts
563
+ import { it } from "vitest";
564
+ import { router } from "@warlock.js/core";
565
+ import { assertResponseContracts } from "@warlock.js/core/tests";
566
+ import "../routes";
567
+
568
+ it("matches user response declarations", async () => {
569
+ await assertResponseContracts({
570
+ routes: router.list().filter((route) => route.path === "/users"),
571
+ cases: () => [{ path: "/users?page=1&limit=10", status: 200 }],
572
+ requireAllStatuses: true,
573
+ });
574
+ });
575
+ ```
576
+
577
+ Omit `routes` to discover all registered routes with schemas. Supply fixtures
578
+ for every declared status when using `requireAllStatuses`; parameterized routes
579
+ and methods other than GET always require fixtures. Cases support a concrete
580
+ `path`, `init` (headers/serialized body), and expected `status`. Use isolated
581
+ seed data and cleanup for mutations. Pass `client` for an authenticated client
582
+ or `createTestHttpServer()` instance. Checks reject undeclared statuses, missing
583
+ fields and type mismatches; unsupported computed/builder shapes fail explicitly.
584
+ See core's `test-http` topic for lifecycle, fixture and schema limitations.
@@ -21,7 +21,7 @@ This skill governs projects that have **opted into testing**. Testing is **enabl
21
21
  - `package.json` has a `test` (or `test:*`) script, **or**
22
22
  - a `vitest.config.*` / `vite.config.*` file exists at the project root.
23
23
 
24
- When the `test` feature is selected at scaffold time, both land automatically (Vitest, `@vitest/coverage-v8`, testcontainers, and a per-worker DB/cache test setup).
24
+ When the `test` feature is selected at scaffold time, both land automatically (Vitest, `@vitest/coverage-v8`, and separate unit/integration projects with a per-file runtime by default).
25
25
 
26
26
  ### 0.1 If testing is enabled, tests are mandatory — not optional
27
27
 
@@ -119,7 +119,7 @@ Each test sets up and tears down its own world. No `let users = []` at the top o
119
119
 
120
120
  Use transactions that roll back, or use testcontainers with a fresh container per file. Never share rows across tests.
121
121
 
122
- The project's testcontainers setup makes "fresh DB per test file" cheap — use it.
122
+ Provide the database fixture in global setup; the framework does not create testcontainers automatically. Give each worker its own database/schema before resetting rows.
123
123
 
124
124
  ### 3.4 Time and randomness
125
125
 
@@ -309,12 +309,12 @@ Co-located with the integration: `src/app/payments/clients/stripe-client.contrac
309
309
  src/app/orders/services/
310
310
  place-order.service.ts
311
311
  place-order.service.spec.ts ← unit test, mocks repository
312
- place-order.service.integration.spec.ts ← integration, real DB
312
+ place-order.service.test.ts ← integration, real DB
313
313
  ```
314
314
 
315
315
  - `.spec.ts` — default suffix; covers unit tests
316
- - `.integration.spec.ts` — when the test needs real I/O (DB, queue)
317
- - `.e2e.spec.ts` — full-system journey tests (sparse; one per critical flow)
316
+ - `.test.ts` or a spec inside `tests/` — integration, real I/O (DB, queue)
317
+ - Full-system journeys also use `.test.ts` in the integration project.
318
318
 
319
319
  Tests live next to the file under test, always. Never in a separate `__tests__/` or `test/` folder — colocation makes the link obvious and survives refactors.
320
320
 
@@ -338,3 +338,44 @@ Before merging a change that should include tests (testing enabled — see § 0)
338
338
  - [ ] No `.only`, no `.skip` without a tracking ticket
339
339
  - [ ] Snapshots reviewed line-by-line if updated
340
340
  - [ ] `npm run test --run` passes; `npm run test:coverage` doesn't drop the floor
341
+
342
+ ## Runtime cost and release gates
343
+
344
+ With `--features=test`, create-warlock delegates to `warlock add test` to generate
345
+ `vite.config.ts`: the `unit` project runs `src/**/*.spec.ts` outside `tests/`
346
+ without framework or HTTP setup; `integration` runs `*.test.ts` and specs under
347
+ `tests/` with `src/test-setup.ts`, global HTTP setup and `src/test-runner.ts`.
348
+ Prefer `npx vitest run --project unit` during the pure service loop.
349
+
350
+ When a unit needs config values, call
351
+ `setupTest({ mode: "unit", importModule: (file) => import(file) })` from
352
+ `@warlock.js/core/tests` and pair it with `afterAll(teardownTest)`. Config loads
353
+ once per module graph, without hooks, connectors, native loader or fakes.
354
+ Most unit specs need no setup call.
355
+
356
+ Integration setup defaults to `tests.lifetime: "file"`. For reuse, set
357
+ `lifetime: "worker"` in `src/config/tests.ts`, change the integration project to
358
+ `pool: "forks", isolate: false`, keep the generated runner, and use `vitest run`.
359
+ Watch, threads and isolated graphs are refused. Keep `afterAll(teardownTest)`:
360
+ it marks file boundaries while the runner closes connectors at worker exit.
361
+ Set `tests.resetBetweenFiles` to an async application-owned cleanup callback;
362
+ it runs before every file, including the first, after selected fakes, clock and
363
+ IDs reset. Each worker needs a separate database/schema. Module singletons and
364
+ mocks survive; reset application state and restore mocks explicitly. Worker
365
+ storage is private and does not mirror the shared HTTP server's fake files.
366
+
367
+ Use `tests.profileSetup: true` or
368
+ `WARLOCK_TEST_PROFILE=1 npx vitest run --project integration` for file timings and
369
+ summaries per worker. Pass `file: expect.getState().testPath` in the setup call.
370
+ `getTestRuntimeStats()` exposes bootstrap and file counts. Generated configs use
371
+ `recommendedTestWorkers()` from `@warlock.js/core/vite` to respect visible cgroup
372
+ v2 CPU quotas; reduce `maxWorkers` further for database or memory contention.
373
+
374
+ Release gate:
375
+
376
+ ```bash
377
+ npx warlock test --changed --since "$LAST_RELEASE_REF"
378
+ npx vitest run --project integration
379
+ # Nightly:
380
+ npx vitest run
381
+ ```
@@ -229,6 +229,11 @@ Avoid copying props into hook state without a plan for refresh and client
229
229
  navigation. Avoid setting another state variable inside a state-updater
230
230
  callback. Prefer deriving display values from one authoritative state.
231
231
 
232
+ **State ownership.** Loader data is server truth. UI interaction state (open,
233
+ selection, draft input) lives in the component's own hook. Shared client state
234
+ goes only through a named store module, never a global variable or a copy
235
+ spread across components. Never copy props into state without a refresh plan.
236
+
232
237
  Effects own their subscriptions and timers: unsubscribe, cancel, or ignore
233
238
  stale async work on unmount or entity change. A failed script/socket load
234
239
  must not leave a permanently rejected shared promise or silently disable a
@@ -326,6 +331,39 @@ feature hook. With generated route declarations and a handler's `responseSchema`
326
331
  `@warlock.js/web` reads a declared status body. Do not replace this inference
327
332
  with casts. Server endpoints must still validate input independently.
328
333
 
334
+ ### Use the shared design-system controls
335
+
336
+ For Tailwind + shadcn projects, use the controls in `src/web/design-system/`:
337
+ TextInput, EmailInput, PasswordInput, TextareaInput, SelectInput (search),
338
+ MultiSelectInput, DatePickerInput, TimePickerInput, DateTimePickerInput,
339
+ CheckboxInput, RadioInput, SwitchInput, FileInput, and SubmitButton.
340
+ Import each component directly, for example
341
+ `import TextInput from "web/design-system/components/text-input";` — no barrels.
342
+ Use `Form` from `@mongez/react-form` directly. Every control requires a stable
343
+ explicit `id`, `name`, and visible `label`, and accepts `hint`, `error`,
344
+ Mongez `schema`/`rules`, and `validateOn`. Put behavior in design-system hooks
345
+ and props in design-system types. See the Web `build-accessible-controls`
346
+ topic for keyboard/focus, RTL, reset and calendar-date semantics.
347
+
348
+ ```tsx
349
+ import { Form } from "@mongez/react-form";
350
+ import TextInput from "web/design-system/components/text-input";
351
+ import SubmitButton from "web/design-system/components/submit-button";
352
+
353
+ export default function ProfileForm() {
354
+ return (
355
+ <Form>
356
+ <TextInput id="profile-name" name="name" label="Name" hint="Display name" />
357
+ <SubmitButton id="profile-save" name="save" label="Save" />
358
+ </Form>
359
+ );
360
+ }
361
+ ```
362
+
363
+ The full-stack preset and shadcn selection scaffold these controls. Existing
364
+ Tailwind apps can run `npx warlock add design-system`; install Tailwind first
365
+ with `npx warlock add tailwind` when missing. Existing files are preserved.
366
+
329
367
  ### Build custom controls with the project's UI kit
330
368
 
331
369
  Do not render native `<select>`, `<input type="checkbox">`,
@@ -23,3 +23,9 @@ forgotPasswordController.validation = {
23
23
  email: v.email(),
24
24
  }),
25
25
  };
26
+
27
+ forgotPasswordController.responseSchema = {
28
+ 200: { body: { message: "string" } },
29
+ 422: { body: { error: { code: "string", message: "string" }, requestId: "string" } },
30
+ 500: { body: { error: { code: "string", message: "string" }, requestId: "string" } },
31
+ };
@@ -1,4 +1,5 @@
1
1
  import { type Request, type RequestHandler } from "@warlock.js/core";
2
+ import { UserResource } from "app/users/resources/user.resource";
2
3
  import { loginSchema, type LoginSchema } from "../schema/login.schema";
3
4
  import { loginService } from "../services/auth.service";
4
5
 
@@ -23,3 +24,19 @@ loginController.description = "User Login";
23
24
  loginController.validation = {
24
25
  schema: loginSchema,
25
26
  };
27
+
28
+ // The starter disables refresh tokens; add tokens.refreshToken when enabling them.
29
+ loginController.responseSchema = {
30
+ 200: {
31
+ body: {
32
+ user: UserResource,
33
+ tokens: {
34
+ accessToken: { token: "string", expiresAt: "string" },
35
+ },
36
+ },
37
+ },
38
+ 401: { body: { error: { code: "string", message: "string" }, requestId: "string" } },
39
+ 403: { body: { error: { code: "string", message: "string" }, requestId: "string" } },
40
+ 422: { body: { error: { code: "string", message: "string" }, requestId: "string" } },
41
+ 500: { body: { error: { code: "string", message: "string" }, requestId: "string" } },
42
+ };
@@ -17,3 +17,9 @@ export const logoutAllController: GuardedRequestHandler = async ({ request, resp
17
17
  };
18
18
 
19
19
  logoutAllController.description = "Logout from all devices";
20
+
21
+ logoutAllController.responseSchema = {
22
+ 200: { body: { message: "string" } },
23
+ 401: { body: { error: { code: "string", message: "string" }, requestId: "string" } },
24
+ 500: { body: { error: { code: "string", message: "string" }, requestId: "string" } },
25
+ };
@@ -19,3 +19,9 @@ export const logoutController: GuardedRequestHandler = async ({ request, respons
19
19
  };
20
20
 
21
21
  logoutController.description = "User Logout";
22
+
23
+ logoutController.responseSchema = {
24
+ 200: { body: { message: "string" } },
25
+ 401: { body: { error: { code: "string", message: "string" }, requestId: "string" } },
26
+ 500: { body: { error: { code: "string", message: "string" }, requestId: "string" } },
27
+ };
@@ -1,4 +1,5 @@
1
1
  import { type GuardedRequestHandler } from "app/auth/requests/guarded.request";
2
+ import { UserResource } from "app/users/resources/user.resource";
2
3
 
3
4
  /**
4
5
  * Get current user controller
@@ -16,3 +17,9 @@ export const meController: GuardedRequestHandler = async ({ request, response })
16
17
  };
17
18
 
18
19
  meController.description = "Get Current User";
20
+
21
+ meController.responseSchema = {
22
+ 200: { body: { user: UserResource } },
23
+ 401: { body: { error: { code: "string", message: "string" }, requestId: "string" } },
24
+ 500: { body: { error: { code: "string", message: "string" }, requestId: "string" } },
25
+ };
@@ -24,3 +24,15 @@ refreshTokenController.validation = {
24
24
  refreshToken: v.string(),
25
25
  }),
26
26
  };
27
+
28
+ refreshTokenController.responseSchema = {
29
+ 200: {
30
+ body: {
31
+ accessToken: { token: "string", expiresAt: "string" },
32
+ refreshToken: { token: "string", expiresAt: "string" },
33
+ },
34
+ },
35
+ 401: { body: { error: { code: "string", message: "string" }, requestId: "string" } },
36
+ 422: { body: { error: { code: "string", message: "string" }, requestId: "string" } },
37
+ 500: { body: { error: { code: "string", message: "string" }, requestId: "string" } },
38
+ };
@@ -21,3 +21,9 @@ resetPasswordController.description = "Reset password with OTP";
21
21
  resetPasswordController.validation = {
22
22
  schema: resetPasswordSchema,
23
23
  };
24
+
25
+ resetPasswordController.responseSchema = {
26
+ 200: { body: { message: "string" } },
27
+ 422: { body: { error: { code: "string", message: "string" }, requestId: "string" } },
28
+ 500: { body: { error: { code: "string", message: "string" }, requestId: "string" } },
29
+ };
@@ -10,3 +10,7 @@ export const homePageController: RequestHandler = async ({ response }) => {
10
10
  };
11
11
 
12
12
  homePageController.description = "Welcome Home Page";
13
+
14
+ homePageController.responseSchema = {
15
+ 200: { body: { message: "string", version: "string", docs: "string" } },
16
+ };
@@ -32,4 +32,5 @@ updatePostController.responseSchema = {
32
32
  200: { body: { post: PostResource } },
33
33
  422: { body: { error: { code: "string", message: "string" }, requestId: "string" } },
34
34
  500: { body: { error: { code: "string", message: "string" }, requestId: "string" } },
35
+ 404: { body: { error: { code: "string", message: "string" }, requestId: "string" } },
35
36
  };
@@ -11,7 +11,19 @@ const statusCodes: Record<number, string> = {
11
11
  };
12
12
 
13
13
  /** Convert framework JSON errors to the project's public error contract. */
14
- export function formatHttpErrorResponse({ status, body, requestId }: HttpErrorResponseContext) {
14
+ export function formatHttpErrorResponse({
15
+ status,
16
+ body,
17
+ requestId,
18
+ appError,
19
+ }: HttpErrorResponseContext) {
20
+ if (appError) {
21
+ return {
22
+ error: { code: appError.code, message: appError.publicMessage },
23
+ ...(appError.details === undefined ? {} : { details: appError.details }),
24
+ requestId,
25
+ };
26
+ }
15
27
  const source = isRecord(body) ? body : {};
16
28
  const error = isRecord(source.error) ? source.error : {};
17
29
  const serverFault = status >= 500;
@@ -35,4 +35,5 @@ createNewUserController.responseSchema = {
35
35
  201: { body: { message: "string", user: UserResource } },
36
36
  422: { body: { error: { code: "string", message: "string" }, requestId: "string" } },
37
37
  500: { body: { error: { code: "string", message: "string" }, requestId: "string" } },
38
+ 400: { body: { error: { code: "string", message: "string" }, requestId: "string" } },
38
39
  };
@@ -6,6 +6,7 @@ import {
6
6
  import { defineConfig } from "@warlock.js/core";
7
7
 
8
8
  export default defineConfig({
9
+ build: { budgets: { page: { js: "150kb", css: "50kb" }, mode: "error" } },
9
10
  cli: {
10
11
  commands: [registerJWTSecretGeneratorCommand(), registerAuthCleanupCommand()],
11
12
  },