@warlock.js/core 5.25.0 → 5.26.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 (80) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/esm/cli/commands/doctor/checks/index.mjs +2 -0
  3. package/esm/cli/commands/doctor/checks/index.mjs.map +1 -1
  4. package/esm/cli/commands/doctor/checks/locales-ts-deprecated.check.mjs +170 -0
  5. package/esm/cli/commands/doctor/checks/locales-ts-deprecated.check.mjs.map +1 -0
  6. package/esm/cli/commands/doctor/doctor-command.action.mjs +7 -2
  7. package/esm/cli/commands/doctor/doctor-command.action.mjs.map +1 -1
  8. package/esm/cli/commands/doctor.command.mjs +5 -0
  9. package/esm/cli/commands/doctor.command.mjs.map +1 -1
  10. package/esm/cli/commands/generate/generators/module.generator.mjs +1 -12
  11. package/esm/cli/commands/generate/generators/module.generator.mjs.map +1 -1
  12. package/esm/cli/commands/typings-generator.command.mjs +4 -1
  13. package/esm/cli/commands/typings-generator.command.mjs.map +1 -1
  14. package/esm/database/migrate-action.mjs +2 -2
  15. package/esm/database/migrate-action.mjs.map +1 -1
  16. package/esm/dev-server/legacy-locales-warning.mjs +16 -0
  17. package/esm/dev-server/legacy-locales-warning.mjs.map +1 -0
  18. package/esm/dev-server/module-loader.mjs +16 -0
  19. package/esm/dev-server/module-loader.mjs.map +1 -1
  20. package/esm/dev-server/special-file-patterns.mjs +2 -2
  21. package/esm/dev-server/special-file-patterns.mjs.map +1 -1
  22. package/esm/dev-server/translation-keys-sources.mjs +7 -3
  23. package/esm/dev-server/translation-keys-sources.mjs.map +1 -1
  24. package/esm/dev-server/type-generator.mjs +27 -4
  25. package/esm/dev-server/type-generator.mjs.map +1 -1
  26. package/esm/generations/features/access.feature.mjs +38 -18
  27. package/esm/generations/features/access.feature.mjs.map +1 -1
  28. package/esm/generations/features/bull-board.feature.mjs +2 -2
  29. package/esm/generations/features/bull-board.feature.mjs.map +1 -1
  30. package/esm/http/build-rate-limit-options.d.mts +8 -2
  31. package/esm/http/build-rate-limit-options.d.mts.map +1 -1
  32. package/esm/http/build-rate-limit-options.mjs +1 -0
  33. package/esm/http/build-rate-limit-options.mjs.map +1 -1
  34. package/esm/http/errors/errors.d.mts +1 -17
  35. package/esm/http/errors/errors.d.mts.map +1 -1
  36. package/esm/http/errors/errors.mjs +1 -20
  37. package/esm/http/errors/errors.mjs.map +1 -1
  38. package/esm/http/index.d.mts +2 -2
  39. package/esm/http/index.mjs +1 -1
  40. package/esm/http/middleware/inject-request-context.d.mts +1 -1
  41. package/esm/http/middleware/inject-request-context.d.mts.map +1 -1
  42. package/esm/http/middleware/inject-request-context.mjs.map +1 -1
  43. package/esm/http/middleware/rate-limit.middleware.d.mts.map +1 -1
  44. package/esm/http/middleware/rate-limit.middleware.mjs +5 -1
  45. package/esm/http/middleware/rate-limit.middleware.mjs.map +1 -1
  46. package/esm/http/request.d.mts +7 -24
  47. package/esm/http/request.d.mts.map +1 -1
  48. package/esm/http/request.mjs +1 -22
  49. package/esm/http/request.mjs.map +1 -1
  50. package/esm/http/response.d.mts.map +1 -1
  51. package/esm/http/response.mjs +4 -2
  52. package/esm/http/response.mjs.map +1 -1
  53. package/esm/index.d.mts +5 -3
  54. package/esm/index.mjs +5 -2
  55. package/esm/localization/index.mjs +4 -0
  56. package/esm/localization/parse-locale-dictionary.d.mts +23 -0
  57. package/esm/localization/parse-locale-dictionary.d.mts.map +1 -0
  58. package/esm/localization/parse-locale-dictionary.mjs +215 -0
  59. package/esm/localization/parse-locale-dictionary.mjs.map +1 -0
  60. package/esm/localization/register-locale-dictionary.d.mts +12 -0
  61. package/esm/localization/register-locale-dictionary.d.mts.map +1 -0
  62. package/esm/localization/register-locale-dictionary.mjs +58 -0
  63. package/esm/localization/register-locale-dictionary.mjs.map +1 -0
  64. package/esm/production/production-builder.mjs +11 -2
  65. package/esm/production/production-builder.mjs.map +1 -1
  66. package/esm/router/types.d.mts +8 -0
  67. package/esm/router/types.d.mts.map +1 -1
  68. package/esm/tests/start-http-development-server.d.mts +10 -0
  69. package/esm/tests/start-http-development-server.d.mts.map +1 -1
  70. package/esm/tests/start-http-development-server.mjs +16 -2
  71. package/esm/tests/start-http-development-server.mjs.map +1 -1
  72. package/esm/validation/validateAll.mjs +48 -1
  73. package/esm/validation/validateAll.mjs.map +1 -1
  74. package/llms-full.txt +118 -75
  75. package/llms.txt +1 -1
  76. package/package.json +20 -20
  77. package/skills/README.md +1 -1
  78. package/skills/create-controller/SKILL.md +4 -0
  79. package/skills/test-http/SKILL.md +9 -1
  80. package/skills/use-localization/SKILL.md +39 -26
@@ -50,7 +50,7 @@ import { startHttpTestServer, stopHttpTestServer } from "@warlock.js/core/tests"
50
50
  - Loads `warlock.config.ts` + bootstrap + `filesOrchestrator` (without file watching).
51
51
  - Loads `src/config/*.ts`.
52
52
  - Loads every module (`routes.ts`, `main.ts`, `events/*.ts`).
53
- - Starts **ALL** connectors, including HTTP.
53
+ - Starts **ALL** connectors, including HTTP, by default.
54
54
 
55
55
  Unlike the dev server, it doesn't watch files, doesn't do HMR, doesn't run health checkers. Just a working HTTP endpoint listening on the configured port.
56
56
 
@@ -70,6 +70,14 @@ The explicit port wins over `http.port` — including over `HTTP_PORT` in `.env`
70
70
 
71
71
  The request helpers follow with no extra wiring: the bound port is published as `WARLOCK_TEST_SERVER_PORT`, which the workers inherit, and `getTestServerUrl()` prefers it over their own `.env`-resolved `http.port`. `stopHttpTestServer()` withdraws it.
72
72
 
73
+ ### API-only suites — `startHttpTestServer({ web: false })`
74
+
75
+ ```ts
76
+ await startHttpTestServer({ web: false });
77
+ ```
78
+
79
+ Use this in `globalSetup` for a suite that exercises API routes only. It still boots the HTTP connector and every other configured connector, but excludes the optional `web` connector before its lifecycle begins. That means web's lazy delegate never constructs `WebConnector`, and Vite is never created. Omitting `web` keeps the default full-stack behavior.
80
+
73
81
  ### The port is preflighted
74
82
 
75
83
  Before binding, the test server checks the resolved port is free and fails with an instruction if it isn't:
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: use-localization
3
- description: 'Multi-locale translations via `groupedTranslations` (declare keys), `t()` / `request.t()` / `request.trans()` (look up), `request.getLocaleCode()` (detect locale from headers/query), `getLocalized` (pick the right value from a localized-array column). Triggers: `groupedTranslations`, `t`, `request.t`, `request.trans`, `request.transFrom`, `request.getLocaleCode`, `request.setLocaleCode`, `getLocalized`; "add a translation key", "resolve a localized error message", "detect request locale", "pick the right per-locale column value"; typical import `import { t, getLocalized } from "@warlock.js/core"`. Skip: resource output — `@warlock.js/core/define-resource/SKILL.md`; module scaffold — `@warlock.js/core/create-module/SKILL.md`; competing libs `i18next`, `react-intl`, raw `@mongez/localization`.'
3
+ description: 'Multi-locale translations via `groupedTranslations` (declare keys), `t()` (look up; always use it in app code, never `request.t()`), `request.getLocaleCode()` (detect locale from headers/query), `getLocalized` (pick the right value from a localized-array column). Triggers: `groupedTranslations`, `t`, `request.t`, `request.trans`, `request.transFrom`, `request.getLocaleCode`, `request.setLocaleCode`, `getLocalized`; "add a translation key", "resolve a localized error message", "detect request locale", "pick the right per-locale column value"; typical import `import { t, getLocalized } from "@warlock.js/core"`. Skip: resource output — `@warlock.js/core/define-resource/SKILL.md`; module scaffold — `@warlock.js/core/create-module/SKILL.md`; competing libs `i18next`, `react-intl`, raw `@mongez/localization`.'
4
4
  ---
5
5
 
6
6
  # Warlock — translate keys + pick localized values
@@ -15,7 +15,7 @@ Both pivot on **the request's locale** — auto-detected from headers / query st
15
15
  ## The shape
16
16
 
17
17
  ```ts
18
- // 1. Declare keys (auto-loaded from src/app/<module>/utils/locales.ts)
18
+ // 1. Declare keys (auto-loaded from src/app/<module>/utils/locales.json)
19
19
  import { groupedTranslations } from "@mongez/localization";
20
20
 
21
21
  groupedTranslations("products", {
@@ -27,8 +27,7 @@ groupedTranslations("products", {
27
27
  import { t } from "@warlock.js/core";
28
28
 
29
29
  throw new ResourceNotFoundError(t("products.notFound"));
30
- // or via the request:
31
- return response.success({ message: request.t("products.created") });
30
+ return response.success({ message: t("products.created") });
32
31
 
33
32
  // 3. Pick a value from a localized-array column
34
33
  import { getLocalized } from "@warlock.js/core";
@@ -37,7 +36,26 @@ const name = getLocalized(product.get("name_translations"));
37
36
  // → reads the current request's locale, returns the matching value
38
37
  ```
39
38
 
40
- ## Declaring translations — `groupedTranslations`
39
+ ## Declaring translations — module `locales.json`
40
+
41
+ Prefer a data-only dictionary in `src/app/<module>/utils/locales.json`. The
42
+ module folder is its default group, so this example registers
43
+ `products.notFound`; a root `$group` string replaces that group when a module
44
+ needs a different namespace. The module loader validates the strict JSON
45
+ shape, flattens nested keys, and rejects duplicate ownership. Do not import the
46
+ file yourself.
47
+
48
+ ```json title="src/app/products/utils/locales.json"
49
+ {
50
+ "notFound": { "en": "Product not found", "ar": "المنتج غير موجود" },
51
+ "errors": { "outOfStock": { "en": "Product out of stock", "ar": "المنتج غير متوفر" } }
52
+ }
53
+ ```
54
+
55
+ `utils/locales.ts` with `groupedTranslations` remains supported but is
56
+ deprecated and will be removed in v6. Migrate it with `warlock doctor --fix`.
57
+
58
+ ### Legacy `groupedTranslations`
41
59
 
42
60
  Every module owns its translation namespace under `src/app/<module>/utils/locales.ts`. The file is auto-loaded at boot — you don't import it from anywhere; the framework picks it up via the module loader.
43
61
 
@@ -74,31 +92,25 @@ t("products.outOfStock", { name: "Pen", count: 0 });
74
92
 
75
93
  Placeholders use the `:name` syntax. The lookup helper substitutes them from the second argument.
76
94
 
77
- ## Looking up translations — `t()`, `request.t()`, `request.trans()`
95
+ ## Looking up translations — always `t()`
78
96
 
79
- Three calls, same job, slightly different reachability:
97
+ **Rule: app code translates with `t()` from `@warlock.js/core`.** It works everywhere: controllers, services, page and layout loaders, `metadata`, events and jobs. It returns a `string`.
80
98
 
81
99
  ```ts
82
- // 1. Top-level helper — works inside or outside a request.
83
- // Inside a request: uses request's locale.
84
- // Outside: uses the global default locale.
85
100
  import { t } from "@warlock.js/core";
86
101
 
87
102
  t("products.notFound");
88
103
  t("products.outOfStock", { name: "Pen", count: 0 });
89
-
90
- // 2. On the request object — explicit, scoped to that request.
91
- request.t("products.notFound");
92
-
93
- // 3. Alias for request.t — same behavior.
94
- request.trans("products.notFound");
95
104
  ```
96
105
 
97
- All three lookups go through `@mongez/localization`'s `trans()` under the hood, with the locale pulled from the request context (or the global default).
106
+ - Inside a request (a controller or a page loader) it uses the request's locale. In a page it first checks the route's `locales.json` snapshot, then falls back to module translations (`groupedTranslations` or module `locales.json`).
107
+ - Outside a request (jobs, CLI) it uses the configured default locale.
108
+ - `request.t()` / `request.trans()` are the per-request binding that `t()` delegates to. Don't call them in app code: `t()` gives the same result without threading `request` through services.
109
+ - Don't wrap it in `String(...)`; it is already typed `string`.
98
110
 
99
111
  ### Web `useTrans()` key checking
100
112
 
101
- When an app uses `@warlock.js/web`, `warlock dev` writes `.warlock/typings/translations.d.ts` from literal `groupedTranslations("group", { key: ... })` registrations. It augments web's `TranslationKeyRegistry`, so `useTrans()("products.notFound")` is checked against registered keys and a typo fails TypeScript. Before the generated file exists, `useTrans()` accepts `string` for a non-breaking first boot. Dynamic groups/keys and placeholders are not inferred.
113
+ When an app uses `@warlock.js/web`, `warlock dev` writes `.warlock/typings/translations.d.ts` from module `locales.json` dictionaries and literal `groupedTranslations("group", { key: ... })` registrations. It augments web's `TranslationKeyRegistry`, so `useTrans()("products.notFound")` is checked against registered keys and a typo fails TypeScript. Before the generated file exists, `useTrans()` accepts `string` for a non-breaking first boot. Dynamic TypeScript groups/keys and placeholders are not inferred.
102
114
 
103
115
  `useTrans()` now works correctly across hydration (5.15.0): the hydration
104
116
  payload ships a `translations` key with the active locale's keywords, and
@@ -144,7 +156,7 @@ export default {
144
156
  request.setLocaleCode("ar");
145
157
  ```
146
158
 
147
- After this, every `request.t(...)` and `getLocalized(...)` in the same request returns Arabic. Useful for user-preference overrides (e.g. "this user has set their language to ar in their profile — switch the request's locale on auth").
159
+ After this, every `t(...)` and `getLocalized(...)` in the same request returns Arabic. Useful for user-preference overrides (e.g. "this user has set their language to ar in their profile — switch the request's locale on auth").
148
160
 
149
161
  ## Localized columns — `getLocalized`
150
162
 
@@ -241,6 +253,7 @@ The error's message is locale-aware — the `inject-request-context` middleware
241
253
  import { type GuardedRequestHandler } from "app/auth/requests/guarded.request";
242
254
  import { type CreateProductSchema } from "../schema/create-product.schema";
243
255
  import { createProductService } from "../services/create-product.service";
256
+ import { t } from "@warlock.js/core";
244
257
 
245
258
  export const createProductController: GuardedRequestHandler<CreateProductSchema> = async ({
246
259
  request,
@@ -248,7 +261,7 @@ export const createProductController: GuardedRequestHandler<CreateProductSchema>
248
261
  }) => {
249
262
  const product = await createProductService(request.validated());
250
263
  return response.success({
251
- message: request.t("products.created"),
264
+ message: t("products.created"),
252
265
  product,
253
266
  });
254
267
  };
@@ -288,11 +301,11 @@ await Product.create({
288
301
  GET /products/42?locale=ar
289
302
  ```
290
303
 
291
- `request.getLocaleCode()` returns `"ar"` — every `request.t(...)` and resource-output `getLocalized` returns Arabic. No app code changes required.
304
+ `request.getLocaleCode()` returns `"ar"` — every `t(...)` and resource-output `getLocalized` returns Arabic. No app code changes required.
292
305
 
293
306
  ## Gotchas
294
307
 
295
- - **Locales files are auto-loaded — don't import them.** `src/app/<module>/utils/locales.ts` is picked up by the module loader on boot. Importing it manually causes double-registration warnings.
308
+ - **Locales files are auto-loaded — don't import them.** `src/app/<module>/utils/locales.json` is picked up by the module loader on boot. Importing legacy `locales.ts` manually causes double-registration warnings.
296
309
  - **Group names should match module URL slugs.** `groupedTranslations("products", ...)` for the `/products` module. Mismatch breaks the convention but doesn't throw.
297
310
  - **Every key needs every locale.** If a key is missing for the active locale, the lookup falls back to the key string itself (`"products.notFound"` literally appears in the response). Always ship all locales together when adding a key.
298
311
  - **`t()` outside a request uses the global default.** `Application.environment === "test"` or background jobs without a request context get the configured `app.localeCode`, not whatever the most recent request used.
@@ -305,8 +318,8 @@ GET /products/42?locale=ar
305
318
 
306
319
  - [`send-response/SKILL.md`](../send-response/SKILL.md) — error helpers that pair with translated messages (`response.notFound({ error: t("...") })`).
307
320
  - [`define-resource/SKILL.md`](../define-resource/SKILL.md) — using `getLocalized` inside resource output for clean per-locale responses.
308
- - [`create-module/SKILL.md`](../create-module/SKILL.md) — the `utils/locales.ts` file is part of the generated module scaffold.
309
- - [`warlock-conventions/SKILL.md`](../warlock-conventions/SKILL.md) — `utils/locales.ts` is auto-loaded; the suffix is mandatory.
321
+ - [`create-module/SKILL.md`](../create-module/SKILL.md) — the `utils/locales.json` file is part of the generated module scaffold.
322
+ - [`warlock-conventions/SKILL.md`](../warlock-conventions/SKILL.md) — `utils/locales.json` is auto-loaded; the suffix is mandatory.
310
323
 
311
324
  ## Route-owned web translations (5.17)
312
325
 
@@ -320,6 +333,6 @@ GET /products/42?locale=ar
320
333
  }
321
334
  ```
322
335
 
323
- This produces `account.title` and `account.actions.save`. Keep every leaf complete for the configured locale list. The page pipeline selects an immutable snapshot for its route source and locale. `useTrans()`, `request.t()`, and `request.trans()` use that snapshot; a missing scoped key returns the key itself. With no route-locales manifest, the existing global `@mongez/localization` registry remains the legacy fallback path.
336
+ This produces `account.title` and `account.actions.save`. Keep every leaf complete for the configured locale list. The page pipeline selects an immutable snapshot for its route source and locale. `useTrans()` and `t()` use that snapshot; a missing scoped key returns the key itself. With no route-locales manifest, the existing global `@mongez/localization` registry remains the legacy fallback path.
324
337
 
325
- When a route-locales snapshot applies, `request.t()`, `request.trans()`, and `request.transFrom(locale, key, placeholders?)` resolve through it; without one, they retain the existing global-registry behavior. The underlying `transFromKeywords(locale, keywords, key, placeholders?, converter?)` is supplied by `@mongez/localization` 3.5.0. Error pages rebind to their own snapshot, while framework fallback uses the app/root snapshot. Development HMR add/edit/delete publishes a new snapshot, and the snapshot revision keeps page-cache entries from reusing old JSON. Generated `.warlock` typings contain the flattened route keys; do not maintain a second handwritten key union.
338
+ When a route-locales snapshot applies, `t()` (through the request binding) and `request.transFrom(locale, key, placeholders?)` resolve through it; without one, they retain the existing global-registry behavior. The underlying `transFromKeywords(locale, keywords, key, placeholders?, converter?)` is supplied by `@mongez/localization` 3.5.0. Error pages rebind to their own snapshot, while framework fallback uses the app/root snapshot. Development HMR add/edit/delete publishes a new snapshot, and the snapshot revision keeps page-cache entries from reusing old JSON. Generated `.warlock` typings contain the flattened route keys; do not maintain a second handwritten key union.