@warlock.js/core 5.13.0 → 5.15.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 (59) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/esm/application/application-config-types.d.mts +9 -0
  3. package/esm/application/application-config-types.d.mts.map +1 -1
  4. package/esm/application/index.d.mts +2 -1
  5. package/esm/application/index.mjs +1 -0
  6. package/esm/application/public-url.d.mts +20 -0
  7. package/esm/application/public-url.d.mts.map +1 -0
  8. package/esm/application/public-url.mjs +26 -0
  9. package/esm/application/public-url.mjs.map +1 -0
  10. package/esm/cli/commands/build.command.mjs.map +1 -1
  11. package/esm/cli/commands/dev-server.command.mjs +2 -0
  12. package/esm/cli/commands/dev-server.command.mjs.map +1 -1
  13. package/esm/dev-server/files-watcher.mjs +6 -3
  14. package/esm/dev-server/files-watcher.mjs.map +1 -1
  15. package/esm/errors/esbuild-binary-missing-error.mjs +20 -0
  16. package/esm/errors/esbuild-binary-missing-error.mjs.map +1 -0
  17. package/esm/generations/features/bull-board.feature.mjs +65 -0
  18. package/esm/generations/features/bull-board.feature.mjs.map +1 -0
  19. package/esm/generations/features/index.mjs +4 -0
  20. package/esm/generations/features/index.mjs.map +1 -1
  21. package/esm/generations/features/queue.feature.mjs +4 -1
  22. package/esm/generations/features/queue.feature.mjs.map +1 -1
  23. package/esm/generations/features/shared/insert-connector-entry.mjs +68 -0
  24. package/esm/generations/features/shared/insert-connector-entry.mjs.map +1 -0
  25. package/esm/generations/features/shared/insert-queue-dashboard-block.mjs +55 -0
  26. package/esm/generations/features/shared/insert-queue-dashboard-block.mjs.map +1 -0
  27. package/esm/generations/features/sitemap.feature.mjs +74 -0
  28. package/esm/generations/features/sitemap.feature.mjs.map +1 -0
  29. package/esm/generations/features/web.feature.mjs +4 -1
  30. package/esm/generations/features/web.feature.mjs.map +1 -1
  31. package/esm/generations/stubs.mjs +4 -4
  32. package/esm/generations/stubs.mjs.map +1 -1
  33. package/esm/http/errors/errors.d.mts +16 -1
  34. package/esm/http/errors/errors.d.mts.map +1 -1
  35. package/esm/http/errors/errors.mjs +19 -1
  36. package/esm/http/errors/errors.mjs.map +1 -1
  37. package/esm/http/index.d.mts +1 -1
  38. package/esm/http/index.mjs +1 -1
  39. package/esm/http/middleware/cache-response-middleware.d.mts +12 -0
  40. package/esm/http/middleware/cache-response-middleware.d.mts.map +1 -1
  41. package/esm/http/middleware/cache-response-middleware.mjs +15 -3
  42. package/esm/http/middleware/cache-response-middleware.mjs.map +1 -1
  43. package/esm/http/request.d.mts +8 -0
  44. package/esm/http/request.d.mts.map +1 -1
  45. package/esm/http/request.mjs +13 -1
  46. package/esm/http/request.mjs.map +1 -1
  47. package/esm/index.d.mts +3 -2
  48. package/esm/index.mjs +3 -2
  49. package/esm/production/esbuild-preflight.mjs +23 -13
  50. package/esm/production/esbuild-preflight.mjs.map +1 -1
  51. package/llms-full.txt +82 -6
  52. package/llms.txt +4 -4
  53. package/package.json +11 -11
  54. package/skills/configure-app/SKILL.md +29 -2
  55. package/skills/run-app/SKILL.md +6 -2
  56. package/skills/send-response/SKILL.md +15 -1
  57. package/skills/use-localization/SKILL.md +6 -0
  58. package/skills/use-middleware/SKILL.md +24 -0
  59. package/skills/write-cli-command/SKILL.md +2 -1
package/llms-full.txt CHANGED
@@ -964,7 +964,7 @@ Don't call `setBaseUrl` per request — it's process-global and races every othe
964
964
 
965
965
  ---
966
966
  name: configure-app
967
- description: 'Configure a Warlock app — the two layers (`warlock.config.ts` for framework-level wiring, `src/config/*.ts` for subsystems), `.env` + `env()`, and the `config()` getter for runtime reads. Triggers: `defineConfig`, `config.get`, `config.key`, `env`, `ConfigRegistry`, `HttpConfigurations`, `AppConfigurations`; "add a new config file", "warlock.config.ts vs src/config", "read env values", "runtime config lookup"; typical import `import { defineConfig, config, env } from "@warlock.js/core"`. Skip: cache driver registration — `@warlock.js/cache/cache-basics/SKILL.md`; mail config — `@warlock.js/core/send-mail/SKILL.md`; storage config — `@warlock.js/core/store-file/SKILL.md`; competing libs `dotenv` direct, `convict`, `node-config`.'
967
+ description: 'Configure a Warlock app — the two layers (`warlock.config.ts` for framework-level wiring, `src/config/*.ts` for subsystems), `.env` + `env()`, the `config()` getter for runtime reads, and `app.publicUrl`/`PUBLIC_APP_URL` (the app''s public origin). Triggers: `defineConfig`, `config.get`, `config.key`, `env`, `ConfigRegistry`, `HttpConfigurations`, `AppConfigurations`, `publicUrl`, `PUBLIC_APP_URL`, `getPublicUrl`; "add a new config file", "warlock.config.ts vs src/config", "read env values", "runtime config lookup", "app public origin/URL"; typical import `import { defineConfig, config, env } from "@warlock.js/core"`. Skip: cache driver registration — `@warlock.js/cache/cache-basics/SKILL.md`; mail config — `@warlock.js/core/send-mail/SKILL.md`; storage config — `@warlock.js/core/store-file/SKILL.md`; sitemap boot-time refusal on a missing origin — `@warlock.js/sitemap/sitemap-overview/SKILL.md`; competing libs `dotenv` direct, `convict`, `node-config`.'
968
968
  ---
969
969
 
970
970
  # Warlock — configure the app
@@ -1069,7 +1069,7 @@ same nonce reaches the page's `<script>` tags.
1069
1069
  | CLI commands (registered via `warlock <cmd>`) | `warlock.config.ts > cli` |
1070
1070
  | HTTP server tuning per env (port, host, retry) | `warlock.config.ts > server` |
1071
1071
  | HTTP runtime (CORS, cookies, rate limits, upload size) | `src/config/http.ts` |
1072
- | App identity (name, baseUrl, timezone, locales) | `src/config/app.ts` |
1072
+ | App identity (name, baseUrl, publicUrl, timezone, locales) | `src/config/app.ts` |
1073
1073
  | Subsystem configs (auth, mail, storage, cache, ai, …) | `src/config/<name>.ts` |
1074
1074
 
1075
1075
  Heuristic: if the setting changes how the framework **boots, builds, or scaffolds**, it goes in `warlock.config.ts`. If it changes how a **subsystem behaves at runtime**, it goes in `src/config/`.
@@ -1203,6 +1203,33 @@ const config = {
1203
1203
 
1204
1204
  Avoid scattering `process.env.NODE_ENV === "production"` checks — they don't get the same default-handling.
1205
1205
 
1206
+ ### `app.publicUrl` — the app's public origin (5.15.0)
1207
+
1208
+ ```ts title="src/config/app.ts"
1209
+ import type { AppConfigurations } from "@warlock.js/core";
1210
+
1211
+ const appConfigurations: AppConfigurations = {
1212
+ appName: "My App",
1213
+ publicUrl: "https://example.com",
1214
+ };
1215
+
1216
+ export default appConfigurations;
1217
+ ```
1218
+
1219
+ The one absolute-URL source every consumer that needs one — the sitemap
1220
+ route, canonical links, OG tags, absolute URLs in mail — reads instead of
1221
+ keeping its own copy. Optional in general (most apps have no consumer that
1222
+ needs it yet); read it with `getPublicUrl()`, which returns `app.publicUrl`,
1223
+ falling back to the `PUBLIC_APP_URL` env var, or `undefined` when neither is
1224
+ set.
1225
+
1226
+ `getPublicUrl()` never throws — it is a consumer's job to fail loudly when it
1227
+ requires the value. `@warlock.js/sitemap` is the first such consumer: with
1228
+ `sitemap.enabled: true` and no `app.publicUrl`/`PUBLIC_APP_URL` set, boot
1229
+ refuses to start (`MissingPublicUrlError`, naming both) rather than falling
1230
+ back to a request-derived host — a sitemap served from the wrong host is
1231
+ worse than one that never boots.
1232
+
1206
1233
  ## Common patterns
1207
1234
 
1208
1235
  ### Adding a new subsystem
@@ -3520,7 +3547,7 @@ const result = await measure("publish-event", () =>
3520
3547
 
3521
3548
  ---
3522
3549
  name: run-app
3523
- description: 'Three operational commands — `warlock dev` (HMR + type-gen + health checks), `warlock build` (esbuild bundle), `warlock start` (spawn the production bundle). All flags, all `warlock.config.ts` knobs that shape them. Triggers: `warlock dev`, `warlock build`, `warlock start`, `devServer`, `--fresh`, `--skip-typings`, `--skip-health`, `outdir`, `outFile`, `sourcemap`, `PortInUseError`, `assertPortIsAvailable`, `EADDRINUSE`; "start the dev server", "build for production", "run the bundle", "skip type generation", "tune watch globs", "dev server keyboard shortcuts", "press r to restart", "press q to quit", "restart the dev server", "port already in use"; typical config `warlock.config.ts > devServer / build`. Skip: writing a custom CLI — `@warlock.js/core/write-cli-command/SKILL.md`; config shape — `@warlock.js/core/configure-app/SKILL.md`; competing tooling `nodemon`, `tsx`, `ts-node-dev`, `esbuild` direct.'
3550
+ description: 'Three operational commands — `warlock dev` (HMR + type-gen + health checks), `warlock build` (esbuild bundle), `warlock start` (spawn the production bundle). All flags, all `warlock.config.ts` knobs that shape them. Triggers: `warlock dev`, `warlock build`, `warlock start`, `devServer`, `--fresh`, `--skip-typings`, `--skip-health`, `outdir`, `outFile`, `sourcemap`, `PortInUseError`, `assertPortIsAvailable`, `EADDRINUSE`, `EsbuildBinaryMissingError`; "start the dev server", "build for production", "run the bundle", "skip type generation", "tune watch globs", "dev server keyboard shortcuts", "press r to restart", "press q to quit", "restart the dev server", "port already in use"; typical config `warlock.config.ts > devServer / build`. Skip: writing a custom CLI — `@warlock.js/core/write-cli-command/SKILL.md`; config shape — `@warlock.js/core/configure-app/SKILL.md`; competing tooling `nodemon`, `tsx`, `ts-node-dev`, `esbuild` direct.'
3524
3551
  ---
3525
3552
 
3526
3553
  # Warlock — run the app
@@ -3663,6 +3690,7 @@ export default defineConfig({
3663
3690
  restartOnConfigChange: true, // restart when warlock.config.ts / .env* changes
3664
3691
  healthCheckers: [...] /* or false */,
3665
3692
  transpileCacheDebug: false, // name cache files <slug>.<hash>.js w/ // @source markers
3693
+ timings: false, // print a per-phase reload timing breakdown
3666
3694
  },
3667
3695
  });
3668
3696
  ```
@@ -3673,6 +3701,7 @@ export default defineConfig({
3673
3701
  - **`transpileCacheDebug`** — diagnostic only. Names `.warlock/transpile/*.js` files `<slug>.<hash>.js` and appends `// @source <path>` markers so you can eyeball which cache entry came from which source. Leave off in normal use.
3674
3702
  - **`checkForUpdates`** — on `warlock dev` start, check npm for a newer `@warlock.js/core` and print a one-line notice if one exists. Best-effort and non-blocking; auto-skipped in CI and non-TTY shells. In an interactive terminal the notice arms a **`u` shortcut** that updates every `@warlock.js/*` package, installs, and restarts the server; elsewhere it prints `npx warlock update` instead. The registry answer is cached for 24h in `.warlock/update-check.json`, so a day of restarts costs one lookup. See [`update-packages/SKILL.md`](../update-packages/SKILL.md).
3675
3703
  - **`restartOnConfigChange`** — restart the dev server when `warlock.config.ts` or any `.env*` changes (default `true`). Set `false` to get a warning instead and restart by hand. Neither file can be hot-reloaded, so without a restart the running services keep the old values.
3704
+ - **`timings`** — print a one-line, per-phase breakdown next to the `hmr update` line on every hot reload: `watcher`, `debounce`, `graph`, `reimport`, `connectors`. `watcher` is the raw-fs-notification-to-stabilised-event gap (chokidar's `awaitWriteFinish` window); `debounce` is the handler's own adaptive wait; the rest are self-explanatory. Opt-in, off by default — a disabled flag costs one boolean check per reload, since the watcher-settle bookkeeping only runs when this is on. Use it to see which phase a slow reload is actually spending time in.
3676
3705
 
3677
3706
  ## `warlock build` — production bundle
3678
3707
 
@@ -3778,7 +3807,7 @@ Means `docker stop` / `kubectl delete pod` works as expected: SIGTERM reaches th
3778
3807
 
3779
3808
  ### pnpm needs esbuild's install script allowed
3780
3809
 
3781
- pnpm 10+ will not run a dependency's install script unless the app names it. esbuild's script links its platform-native binary, and `warlock build` shells out to that binary — so the app installs cleanly and then cannot build:
3810
+ pnpm 10+ will not run a dependency's install script unless the app names it. esbuild's script links its platform-native binary, and both `warlock build` and `warlock dev` shell out to that binary — so the app installs cleanly and then cannot build or start dev:
3782
3811
 
3783
3812
  ```
3784
3813
  [ERR_PNPM_IGNORED_BUILDS] Ignored build scripts: esbuild@0.27.7
@@ -3793,6 +3822,8 @@ allowBuilds:
3793
3822
 
3794
3823
  Note pnpm reads this from `pnpm-workspace.yaml`, **not** from `package.json`'s `pnpm` field — pnpm 11 warns that the field is ignored and then carries on, so settings left there fail silently.
3795
3824
 
3825
+ `warlock dev` checks for esbuild's native binary before starting (as `warlock build` already did) and fails fast with `EsbuildBinaryMissingError`, naming the same fix, instead of surfacing an opaque low-level error later from inside the bundler.
3826
+
3796
3827
  Nothing else is needed for pnpm. Warlock never requires an app to declare a package it does not import: generated code is checked at build time against the app's own `dependencies`, so `warlock build` failing over an unfamiliar package name is a framework bug, not a missing dependency.
3797
3828
 
3798
3829
  ### Output streams — what a supervisor may trust
@@ -4374,7 +4405,7 @@ await Mail.to(user.email)
4374
4405
 
4375
4406
  ---
4376
4407
  name: send-response
4377
- description: 'Send HTTP responses via @warlock.js/core''s Response helpers — success/error variants, status helpers, redirects, files, streams, and SSE. Picking the right helper carries the HTTP semantic without manual status codes. Triggers: `response.success`, `response.successCreate`, `response.notFound`, `response.forbidden`, `response.badRequest`, `response.sendFile`, `response.stream`, `response.sse`, `response.replay`, `ResourceNotFoundError`, `ForbiddenError`; "return a 201 from a controller", "send a file", "stream Server-Sent Events", "throw HTTP-shaped errors from services"; typical import `import type { RequestHandler, Response } from "@warlock.js/core"`. Skip: controller shape — `@warlock.js/core/create-controller/SKILL.md`; route registration — `@warlock.js/core/register-route/SKILL.md`; competing patterns: hand-rolled status codes via `reply.code(404).send(...)`, raw Fastify reply.'
4408
+ description: 'Send HTTP responses via @warlock.js/core''s Response helpers — success/error variants, status helpers, redirects, files, streams, and SSE. Picking the right helper carries the HTTP semantic without manual status codes. Triggers: `response.success`, `response.successCreate`, `response.notFound`, `response.forbidden`, `response.badRequest`, `response.sendFile`, `response.stream`, `response.sse`, `response.replay`, `ResourceNotFoundError`, `ForbiddenError`, `request.cookie`, `request.hasCookie`, `CookieJarUnavailableError`; "return a 201 from a controller", "send a file", "stream Server-Sent Events", "throw HTTP-shaped errors from services"; typical import `import type { RequestHandler, Response } from "@warlock.js/core"`. Skip: controller shape — `@warlock.js/core/create-controller/SKILL.md`; route registration — `@warlock.js/core/register-route/SKILL.md`; competing patterns: hand-rolled status codes via `reply.code(404).send(...)`, raw Fastify reply.'
4378
4409
  ---
4379
4410
 
4380
4411
  # Warlock — send a response
@@ -4576,6 +4607,20 @@ Every response cookie gets `httpOnly: true`, `sameSite: "lax"`, and `secure: tru
4576
4607
 
4577
4608
  Precedence, lowest to highest: **framework defaults → `http.cookies.options` → the per-call `options` argument.** Set an app-wide policy in config, override per cookie when a specific one genuinely needs different treatment.
4578
4609
 
4610
+ ### Reading cookies back — `request.cookie()` / `request.hasCookie()` throw when the jar is unavailable (5.15.0)
4611
+
4612
+ `request.cookie(name)` and `request.hasCookie(name)` are a deliberate
4613
+ by-name assertion — "this cookie should be readable here" — so when
4614
+ `@fastify/cookie` is not registered on the Fastify instance, both now
4615
+ **throw `CookieJarUnavailableError`** naming the missing cookie, instead of
4616
+ silently returning `undefined` / `false`. Register the plugin (see core's
4617
+ `http/plugins.ts`) before reading cookies by name.
4618
+
4619
+ `request.cookies` (the plain getter, no by-name assertion) is unchanged and
4620
+ stays lenient — it returns `{}` when the jar is unavailable, because the
4621
+ framework's own opportunistic reads (e.g. locale resolution) must not throw
4622
+ on a request that simply has no jar.
4623
+
4579
4624
  These are the flags whose absence never fails a test and is fatal in production: the app works perfectly and is simply insecure. Opting out is now explicit and visible in review.
4580
4625
 
4581
4626
  These mutate the response in place; chain or call before the final `return response.<helper>()`.
@@ -6652,6 +6697,12 @@ All three lookups go through `@mongez/localization`'s `trans()` under the hood,
6652
6697
 
6653
6698
  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.
6654
6699
 
6700
+ `useTrans()` now works correctly across hydration (5.15.0): the hydration
6701
+ payload ships a `translations` key with the active locale's keywords, and
6702
+ `@warlock.js/web` registers them into this same lookup table before the
6703
+ client hydrates — see `@warlock.js/web/write-the-root/SKILL.md`'s "`useTrans()`
6704
+ survives hydration" section for the failure this fixed.
6705
+
6655
6706
  ### Locale on a specific lookup
6656
6707
 
6657
6708
  ```ts
@@ -7033,6 +7084,30 @@ router.get("/analytics/summary", summaryController, {
7033
7084
 
7034
7085
  `cacheKey` can be a string OR a function `(request) => string | Promise<string>` for per-request keys. Excludes failures and omits `["user", "settings"]` from the cached body by default.
7035
7086
 
7087
+ ### Tag-based invalidation
7088
+
7089
+ Give it `tags` — a static list, or a function of the request — to evict the entry early with `cache.tags([...]).invalidate()` (`@warlock.js/cache`), instead of waiting out `ttl`. Mirrors `route.cache.tags` on `@warlock.js/web`'s page cache, so an API response and a page can share the same tag and be invalidated together:
7090
+
7091
+ ```ts
7092
+ import { middleware } from "@warlock.js/core";
7093
+ import { cache } from "@warlock.js/cache";
7094
+
7095
+ router.get("/orders/:id", getOrderController, {
7096
+ middleware: [
7097
+ middleware.cache({
7098
+ cacheKey: (request) => `orders.${request.params.id}`,
7099
+ ttl: 300,
7100
+ tags: (request) => [`order.${request.params.id}`],
7101
+ }),
7102
+ ],
7103
+ });
7104
+
7105
+ // Elsewhere, after the order changes:
7106
+ await cache.tags([`order.${orderId}`]).invalidate();
7107
+ ```
7108
+
7109
+ Untagged entries (no `tags` given) behave exactly as before — they only expire via `ttl`.
7110
+
7036
7111
  ## Composed example
7037
7112
 
7038
7113
  Tight cap on logins, concurrency + idempotency on AI calls:
@@ -8947,7 +9022,7 @@ CLI commands, scheduled jobs, queue workers — anything running outside an HTTP
8947
9022
 
8948
9023
  ---
8949
9024
  name: write-cli-command
8950
- description: '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`.'
9025
+ description: '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`, `warlock add bull-board`, `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`.'
8951
9026
  ---
8952
9027
 
8953
9028
  # Warlock — write a CLI command
@@ -9154,6 +9229,7 @@ warlock generate (alias: g) — interactive picker
9154
9229
  | `react-email` | `react-email` + `@react-email/components` + `@react-email/render` + `@react-email/tailwind`; drops a `welcome-email.tsx` sample; patches `tsconfig.json` |
9155
9230
  | `web` | Warlock Web + React stack; scaffolds the application page, localization, contact endpoint, and Web configuration |
9156
9231
  | `react` | `react` + `react-dom` + types |
9232
+ | `bull-board` | `@bull-board/api` + `@bull-board/fastify`; writes a `dashboard` block to `src/config/queue.ts`. `requires: ["queue"]` — adds the `queue` feature first automatically when it's missing |
9157
9233
  | `image` | `sharp` (for the `Image` class) |
9158
9234
  | `mail` | `nodemailer` + types |
9159
9235
  | `ses` | `@aws-sdk/client-sesv2` |
package/llms.txt CHANGED
@@ -10,7 +10,7 @@
10
10
  - [benchmark-code](@warlock.js/core/benchmark-code/SKILL.md): Wrap a function with `measure(name, fn, options?)` to time it and classify the latency — onComplete/onError/onFinish hooks, `latencyRange` thresholds, `BenchmarkProfiler` for percentiles, `BenchmarkSnapshots` for raw captures. Triggers: `measure`, `BenchmarkProfiler`, `BenchmarkSnapshots`, `BenchmarkChannel`, `ConsoleChannel`, `latencyRange`, `shouldBenchmarkError`; "time this operation", "profile a slow service", "emit p50/p95/p99 metrics", "classify latency against thresholds"; typical import `import { measure, BenchmarkProfiler } from "@warlock.js/core"`. Skip: retry composition — `@warlock.js/core/retry-operation/SKILL.md`; benchmark config wiring — `@warlock.js/core/configure-app/SKILL.md`; competing libs `prom-client`, `pino`, `perf_hooks`, `console.time`.
11
11
  - [build-restful](@warlock.js/core/build-restful/SKILL.md): Generate standard CRUD endpoints — via `router.route(path).list().show().create().update().destroy()` chain or the `Restful` base class. Pick the chain by default; reach for `Restful` when you want repository-bound defaults. Triggers: `router.route`, `Restful`, `router.restfulResource`, `RouteResource`, `.crud`, `.nest`, `beforeCreate`, `onCreate`; "build a CRUD API", "register list/show/create/update/destroy", "repository-bound default handlers", "override a single REST action"; typical import `import { router, Restful } from "@warlock.js/core"`. Skip: wider router surface — `@warlock.js/core/register-route/SKILL.md`; per-action controllers — `@warlock.js/core/create-controller/SKILL.md`; wire mapping — `@warlock.js/core/define-resource/SKILL.md`; competing pattern: hand-rolled controllers, `@nestjs/swagger` decorator-driven CRUD.
12
12
  - [build-url](@warlock.js/core/build-url/SKILL.md): HTTP URL helpers — `url`, `publicUrl`, `assetsUrl`, `uploadsUrl`, anchored at `app.baseUrl`. Use to render `src` / `href` / API URLs in resources and responses. `setBaseUrl` is wired by the HTTP connector from `config.get("app.baseUrl")`. Triggers: `url`, `publicUrl`, `assetsUrl`, `uploadsUrl`, `setBaseUrl`, `BASE_URL`; "render an avatar src URL", "absolute download link", "embed asset URL in email", "URL helpers vs path helpers"; typical import `import { url, publicUrl, uploadsUrl } from "@warlock.js/core"`. Skip: filesystem paths — `@warlock.js/core/resolve-path/SKILL.md`; signed CDN URLs — `@warlock.js/core/store-file/SKILL.md`; resource output — `@warlock.js/core/define-resource/SKILL.md`; competing patterns: hand-rolled `${baseUrl}/...` template strings.
13
- - [configure-app](@warlock.js/core/configure-app/SKILL.md): Configure a Warlock app — the two layers (`warlock.config.ts` for framework-level wiring, `src/config/*.ts` for subsystems), `.env` + `env()`, and the `config()` getter for runtime reads. Triggers: `defineConfig`, `config.get`, `config.key`, `env`, `ConfigRegistry`, `HttpConfigurations`, `AppConfigurations`; "add a new config file", "warlock.config.ts vs src/config", "read env values", "runtime config lookup"; typical import `import { defineConfig, config, env } from "@warlock.js/core"`. Skip: cache driver registration — `@warlock.js/cache/cache-basics/SKILL.md`; mail config — `@warlock.js/core/send-mail/SKILL.md`; storage config — `@warlock.js/core/store-file/SKILL.md`; competing libs `dotenv` direct, `convict`, `node-config`.
13
+ - [configure-app](@warlock.js/core/configure-app/SKILL.md): Configure a Warlock app — the two layers (`warlock.config.ts` for framework-level wiring, `src/config/*.ts` for subsystems), `.env` + `env()`, the `config()` getter for runtime reads, and `app.publicUrl`/`PUBLIC_APP_URL` (the app's public origin). Triggers: `defineConfig`, `config.get`, `config.key`, `env`, `ConfigRegistry`, `HttpConfigurations`, `AppConfigurations`, `publicUrl`, `PUBLIC_APP_URL`, `getPublicUrl`; "add a new config file", "warlock.config.ts vs src/config", "read env values", "runtime config lookup", "app public origin/URL"; typical import `import { defineConfig, config, env } from "@warlock.js/core"`. Skip: cache driver registration — `@warlock.js/cache/cache-basics/SKILL.md`; mail config — `@warlock.js/core/send-mail/SKILL.md`; storage config — `@warlock.js/core/store-file/SKILL.md`; sitemap boot-time refusal on a missing origin — `@warlock.js/sitemap/sitemap-overview/SKILL.md`; competing libs `dotenv` direct, `convict`, `node-config`.
14
14
  - [create-controller](@warlock.js/core/create-controller/SKILL.md): Author HTTP controllers in @warlock.js/core — RequestHandler signature, validated input via seal schemas, response helpers, attaching metadata. Controllers are thin functions; business logic moves to services or use-cases. Triggers: `RequestHandler`, `Request<TSchema>`, `GuardedRequestHandler`, `request.validated`, `request.input`, `controller.validation`, `response.success`, `response.successCreate`; "write a controller", "attach a schema to a handler", "thin controller pattern", "guarded request type"; typical import `import { type RequestHandler } from "@warlock.js/core"`. Skip: response helper menu — `@warlock.js/core/send-response/SKILL.md`; schema authoring — `@warlock.js/core/validate-input/SKILL.md`; URL wiring — `@warlock.js/core/register-route/SKILL.md`; competing patterns: `express` middleware functions, `@nestjs/common` `@Controller`/`@Get` decorators.
15
15
  - [create-module](@warlock.js/core/create-module/SKILL.md): Scaffold a new feature module under `src/app/<name>/` via `warlock generate.module` and the follow-up generators for controllers, models, repositories, resources, and validation schemas. Triggers: `warlock generate.module`, `generate.controller`, `generate.service`, `generate.model`, `generate.repository`, `generate.resource`, `generate.migration`, `--minimal`, `gen.m`; "scaffold a new module", "create CRUD bootstrap", "add a controller to a module", "generate a model"; typical CLI `npx warlock generate.module <name>`. Skip: framework-wide layout rules — `@warlock.js/core/warlock-conventions/SKILL.md`; routes file shape — `@warlock.js/core/register-route/SKILL.md`; controller shape — `@warlock.js/core/create-controller/SKILL.md`; competing tooling: `@nestjs/cli`, `hygen`, hand-rolled folder layouts.
16
16
  - [define-resource](@warlock.js/core/define-resource/SKILL.md): Map model fields to wire-shape via `defineResource()` or `Resource` subclasses. Output-only — never put business logic, hydration, or reconciliation in a resource. Triggers: `defineResource`, `Resource`, `RegisterResource`, `toJSON`, `"self"`, `"localized"`, `"uploadsUrl"`; "shape an API response", "nest related resources", "rename a field on output", "self-referential tree resource"; typical import `import { defineResource } from "@warlock.js/core"`. Skip: localized columns — `@warlock.js/core/use-localization/SKILL.md`; URL casting — `@warlock.js/core/build-url/SKILL.md`; controller side — `@warlock.js/core/create-controller/SKILL.md`; competing libs `@nestjs/swagger` `@ApiProperty`, `class-transformer`, hand-rolled DTO mappers.
@@ -24,9 +24,9 @@
24
24
  - [request-tracing](@warlock.js/core/request-tracing/SKILL.md): Vendor-neutral request tracing hooks (`http.tracing`) — observe request start/end and named phase spans (`route.match`, `middleware`, `validation`, `handler`, `response.write`) without adopting an OTel/vendor dependency in `core`. Covers the `TracingHooks` shape, trace id derivation from an inbound W3C `traceparent` vs `request.id`, that a throwing hook is caught and reported once (never breaks the request), and zero overhead when disabled. Triggers: `http.tracing`, `TracingHooks`, `onRequestStart`, `onRequestEnd`, `onPhase`, `traceparent`, `traceId`, `dispatchPhase`, "instrument requests", "trace a request", "slow phase logging", "APM / OTel bridge for Warlock". Skip: request-id header echo/inheritance (`X-Request-Id`) — `@warlock.js/core/use-middleware/SKILL.md`; app-level structured logging — `@warlock.js/logger/logger-basics/SKILL.md`; competing libs `@opentelemetry/api` direct instrumentation, `express-request-id`, hand-rolled `X-Trace-Id` middleware.
25
25
  - [resolve-path](@warlock.js/core/resolve-path/SKILL.md): Path helpers anchored at `process.cwd()` — `rootPath`, `srcPath`, `appPath`, `configPath`, `publicPath`, `storagePath`, `uploadsPath`, `cachePath`, `logsPath`, `tempPath`, `warlockPath`, `sanitizePath`. Optional `uploads.root` config overrides the uploads anchor. Triggers: `appPath`, `configPath`, `uploadsPath`, `storagePath`, `publicPath`, `cachePath`, `logsPath`, `tempPath`, `sanitizePath`, `paths`; "resolve a path inside src/app", "absolute upload destination", "sanitize a user filename", "ship uploads to a mounted volume"; typical import `import { appPath, uploadsPath } from "@warlock.js/core"`. Skip: HTTP URL helpers — `@warlock.js/core/build-url/SKILL.md`; app metadata — `@warlock.js/core/use-app-context/SKILL.md`; storage abstraction — `@warlock.js/core/store-file/SKILL.md`; competing patterns: `path.join(process.cwd(), ...)`, hand-rolled directory constants.
26
26
  - [retry-operation](@warlock.js/core/retry-operation/SKILL.md): Wrap a flaky operation with `retry(fn, options)` — now provided by `@mongez/reinforcements` (not `@warlock.js/core`). `attempts` total tries, `delay` + `backoff` (linear/exponential/fn), `maxDelay`, `jitter`, `shouldRetry` to bail on permanent errors, `signal` to cancel, plus `retryable()` to pre-bind options. Triggers: `retry`, `retryable`, `RetryOptions`, `attempts`, `backoff`, `jitter`, `maxDelay`, `shouldRetry`, `signal`; "retry a flaky API call", "handle transient errors", "exponential backoff with jitter", "wrap an external request"; typical import `import { retry } from "@mongez/reinforcements"`. Skip: timing the retried op — `@warlock.js/core/benchmark-code/SKILL.md`; use-case-level `retry` option — `@warlock.js/core/write-use-case/SKILL.md`; competing libs `p-retry`, `async-retry`, `cockatiel`.
27
- - [run-app](@warlock.js/core/run-app/SKILL.md): Three operational commands — `warlock dev` (HMR + type-gen + health checks), `warlock build` (esbuild bundle), `warlock start` (spawn the production bundle). All flags, all `warlock.config.ts` knobs that shape them. Triggers: `warlock dev`, `warlock build`, `warlock start`, `devServer`, `--fresh`, `--skip-typings`, `--skip-health`, `outdir`, `outFile`, `sourcemap`, `PortInUseError`, `assertPortIsAvailable`, `EADDRINUSE`; "start the dev server", "build for production", "run the bundle", "skip type generation", "tune watch globs", "dev server keyboard shortcuts", "press r to restart", "press q to quit", "restart the dev server", "port already in use"; typical config `warlock.config.ts > devServer / build`. Skip: writing a custom CLI — `@warlock.js/core/write-cli-command/SKILL.md`; config shape — `@warlock.js/core/configure-app/SKILL.md`; competing tooling `nodemon`, `tsx`, `ts-node-dev`, `esbuild` direct.
27
+ - [run-app](@warlock.js/core/run-app/SKILL.md): Three operational commands — `warlock dev` (HMR + type-gen + health checks), `warlock build` (esbuild bundle), `warlock start` (spawn the production bundle). All flags, all `warlock.config.ts` knobs that shape them. Triggers: `warlock dev`, `warlock build`, `warlock start`, `devServer`, `--fresh`, `--skip-typings`, `--skip-health`, `outdir`, `outFile`, `sourcemap`, `PortInUseError`, `assertPortIsAvailable`, `EADDRINUSE`, `EsbuildBinaryMissingError`; "start the dev server", "build for production", "run the bundle", "skip type generation", "tune watch globs", "dev server keyboard shortcuts", "press r to restart", "press q to quit", "restart the dev server", "port already in use"; typical config `warlock.config.ts > devServer / build`. Skip: writing a custom CLI — `@warlock.js/core/write-cli-command/SKILL.md`; config shape — `@warlock.js/core/configure-app/SKILL.md`; competing tooling `nodemon`, `tsx`, `ts-node-dev`, `esbuild` direct.
28
28
  - [send-mail](@warlock.js/core/send-mail/SKILL.md): Send transactional email — `Mail` fluent builder, `sendMail()` direct call, React Email components. Test mode auto-captures into an in-memory mailbox; dev mode logs. Triggers: `Mail.to`, `sendMail`, `setMailMode`, `mailEvents`, `assertMailSent`, `getTestMailbox`, `wasMailSentTo`, `closeAllMailers`; "send a transactional email", "build a React Email template", "configure SMTP or SES", "assert an email was sent in tests"; typical import `import { Mail, sendMail } from "@warlock.js/core"`. Skip: per-config wiring — `@warlock.js/core/configure-app/SKILL.md`; layered service patterns — `@warlock.js/core/warlock-conventions/SKILL.md`; competing libs `nodemailer` direct, `@sendgrid/mail`, `resend`, `mailgun.js`.
29
- - [send-response](@warlock.js/core/send-response/SKILL.md): Send HTTP responses via @warlock.js/core's Response helpers — success/error variants, status helpers, redirects, files, streams, and SSE. Picking the right helper carries the HTTP semantic without manual status codes. Triggers: `response.success`, `response.successCreate`, `response.notFound`, `response.forbidden`, `response.badRequest`, `response.sendFile`, `response.stream`, `response.sse`, `response.replay`, `ResourceNotFoundError`, `ForbiddenError`; "return a 201 from a controller", "send a file", "stream Server-Sent Events", "throw HTTP-shaped errors from services"; typical import `import type { RequestHandler, Response } from "@warlock.js/core"`. Skip: controller shape — `@warlock.js/core/create-controller/SKILL.md`; route registration — `@warlock.js/core/register-route/SKILL.md`; competing patterns: hand-rolled status codes via `reply.code(404).send(...)`, raw Fastify reply.
29
+ - [send-response](@warlock.js/core/send-response/SKILL.md): Send HTTP responses via @warlock.js/core's Response helpers — success/error variants, status helpers, redirects, files, streams, and SSE. Picking the right helper carries the HTTP semantic without manual status codes. Triggers: `response.success`, `response.successCreate`, `response.notFound`, `response.forbidden`, `response.badRequest`, `response.sendFile`, `response.stream`, `response.sse`, `response.replay`, `ResourceNotFoundError`, `ForbiddenError`, `request.cookie`, `request.hasCookie`, `CookieJarUnavailableError`; "return a 201 from a controller", "send a file", "stream Server-Sent Events", "throw HTTP-shaped errors from services"; typical import `import type { RequestHandler, Response } from "@warlock.js/core"`. Skip: controller shape — `@warlock.js/core/create-controller/SKILL.md`; route registration — `@warlock.js/core/register-route/SKILL.md`; competing patterns: hand-rolled status codes via `reply.code(404).send(...)`, raw Fastify reply.
30
30
  - [store-file](@warlock.js/core/store-file/SKILL.md): Read/write/delete files via the `storage` singleton — disks, drivers (local/S3/R2/DO Spaces), `storage.use(name)`, `StorageFile` handles, presigned URLs. Triggers: `storage.put`, `storage.get`, `storage.use`, `StorageFile`, `storageConfigurations`, `getPresignedUrl`, `getPresignedUploadUrl`; "save an uploaded file", "switch between local and S3", "generate a presigned URL", "read file metadata"; typical import `import { storage } from "@warlock.js/core"`. Skip: multipart parsing + image chain — `@warlock.js/core/upload-file/SKILL.md`; image transforms — `@warlock.js/core/process-image/SKILL.md`; storage config shape — `@warlock.js/core/configure-app/SKILL.md`; competing libs `@aws-sdk/client-s3`, `multer`, `formidable`.
31
31
  - [test-http](@warlock.js/core/test-http/SKILL.md): Integration tests against a real HTTP server — `startHttpTestServer()` boots one shared server in globalSetup, then `testGet` / `testPost` / `expectJson` make typed requests against it. Triggers: `startHttpTestServer`, `startHttpTestServer({ port })`, `stopHttpTestServer`, `testGet`, `testPost`, `testPut`, `testPatch`, `testDelete`, `expectJson`, `getTestServerUrl`, `testRequest`, `PortInUseError`, `assertPortIsAvailable`, `isPortAvailable`; "integration-test a controller", "end-to-end HTTP test", "globalSetup HTTP server", "assert status and body shape", "test server port already in use", "EADDRINUSE while running tests", "run tests while the dev server is up"; typical import `import { testGet, testPost, expectJson } from "@warlock.js/core/tests"`. Skip: pure unit tests — `@warlock.js/core/test-service/SKILL.md`; controller shape — `@warlock.js/core/create-controller/SKILL.md`; competing libs `supertest`, `light-my-request`, `nock`.
32
32
  - [test-service](@warlock.js/core/test-service/SKILL.md): Pure unit tests against services, repositories, models, and use-cases — `setupTest({ connectors })` bootstraps the framework with its own DB/cache connections so you can call your code directly, and `teardownTest()` closes it. Triggers: `setupTest`, `teardownTest`, `src/test-setup.ts`, `tests.connectors`, `tests.setupTimeout`, `Application.setEnvironment`; "unit-test a service", "test a repository query", "vitest setupFiles", "skip connectors for pure-logic tests"; typical import `import { setupTest, teardownTest } from "@warlock.js/core/tests"`. Skip: HTTP integration — `@warlock.js/core/test-http/SKILL.md`; warlock add test scaffold — `@warlock.js/core/write-cli-command/SKILL.md`; competing tooling: jest direct, `supertest`, `nock`.
@@ -43,7 +43,7 @@
43
43
  - [warlock-doctor](@warlock.js/core/warlock-doctor/SKILL.md): Run `warlock doctor` — a read-only diagnostics command that checks routes / config / connectors / optional-peers / health endpoints / release hygiene and prints a pass/warn/fail report, exiting non-zero on any failure. Add your own probe with the `DoctorCheck` contract and `runChecks` / `formatReportLines`. Triggers: `warlock doctor`, `doctorCommand`, `DoctorCheck`, `CheckResult`, `CheckStatus`, `DoctorReport`, `runChecks`, `formatReportLines`, `printReport`, `defaultDoctorChecks`; "diagnose my app", "preflight / preflight check", "is the app healthy", "why are there 0 routes", "pre-release sanity check", "CI smoke check"; run as `npx warlock doctor`. Skip: the live `/health` + `/ready` HTTP probes — `@warlock.js/core/health-checks/SKILL.md`; authoring a general CLI command — `@warlock.js/core/write-cli-command/SKILL.md`; releasing the package — `releasing-warlock-monorepo`; competing tools `npm doctor`, `nest info`, hand-rolled preflight scripts.
44
44
  - [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 `npx warlock routes` / `npx 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`.
45
45
  - [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`.
46
- - [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-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`, `warlock add bull-board`, `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`.
47
47
  - [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.
48
48
  - [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`.
49
49
  - [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,12 +25,12 @@
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/cache": "5.13.0",
29
- "@warlock.js/cascade": "5.13.0",
30
- "@warlock.js/context": "5.13.0",
31
- "@warlock.js/logger": "5.13.0",
32
- "@warlock.js/seal": "5.13.0",
33
- "@warlock.js/fs": "5.13.0",
28
+ "@warlock.js/cache": "5.15.0",
29
+ "@warlock.js/cascade": "5.15.0",
30
+ "@warlock.js/context": "5.15.0",
31
+ "@warlock.js/logger": "5.15.0",
32
+ "@warlock.js/seal": "5.15.0",
33
+ "@warlock.js/fs": "5.15.0",
34
34
  "chokidar": "^5.0.0",
35
35
  "dayjs": "^1.11.19",
36
36
  "es-module-lexer": "^2.0.0",
@@ -56,10 +56,10 @@
56
56
  "react": "^19.2.3",
57
57
  "react-dom": "^19.2.3",
58
58
  "@react-email/render": "^2.0.5",
59
- "@warlock.js/herald": "5.13.0",
60
- "@warlock.js/ai": "5.13.0",
61
- "@warlock.js/access": "5.13.0",
62
- "@warlock.js/notifications": "5.13.0"
59
+ "@warlock.js/herald": "5.15.0",
60
+ "@warlock.js/ai": "5.15.0",
61
+ "@warlock.js/access": "5.15.0",
62
+ "@warlock.js/notifications": "5.15.0"
63
63
  },
64
64
  "peerDependenciesMeta": {
65
65
  "sharp": {
@@ -122,7 +122,7 @@
122
122
  ],
123
123
  "author": "hassanzohdy",
124
124
  "license": "MIT",
125
- "version": "5.13.0",
125
+ "version": "5.15.0",
126
126
  "type": "module",
127
127
  "main": "./esm/index.mjs",
128
128
  "module": "./esm/index.mjs",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: configure-app
3
- description: 'Configure a Warlock app — the two layers (`warlock.config.ts` for framework-level wiring, `src/config/*.ts` for subsystems), `.env` + `env()`, and the `config()` getter for runtime reads. Triggers: `defineConfig`, `config.get`, `config.key`, `env`, `ConfigRegistry`, `HttpConfigurations`, `AppConfigurations`; "add a new config file", "warlock.config.ts vs src/config", "read env values", "runtime config lookup"; typical import `import { defineConfig, config, env } from "@warlock.js/core"`. Skip: cache driver registration — `@warlock.js/cache/cache-basics/SKILL.md`; mail config — `@warlock.js/core/send-mail/SKILL.md`; storage config — `@warlock.js/core/store-file/SKILL.md`; competing libs `dotenv` direct, `convict`, `node-config`.'
3
+ description: 'Configure a Warlock app — the two layers (`warlock.config.ts` for framework-level wiring, `src/config/*.ts` for subsystems), `.env` + `env()`, the `config()` getter for runtime reads, and `app.publicUrl`/`PUBLIC_APP_URL` (the app''s public origin). Triggers: `defineConfig`, `config.get`, `config.key`, `env`, `ConfigRegistry`, `HttpConfigurations`, `AppConfigurations`, `publicUrl`, `PUBLIC_APP_URL`, `getPublicUrl`; "add a new config file", "warlock.config.ts vs src/config", "read env values", "runtime config lookup", "app public origin/URL"; typical import `import { defineConfig, config, env } from "@warlock.js/core"`. Skip: cache driver registration — `@warlock.js/cache/cache-basics/SKILL.md`; mail config — `@warlock.js/core/send-mail/SKILL.md`; storage config — `@warlock.js/core/store-file/SKILL.md`; sitemap boot-time refusal on a missing origin — `@warlock.js/sitemap/sitemap-overview/SKILL.md`; competing libs `dotenv` direct, `convict`, `node-config`.'
4
4
  ---
5
5
 
6
6
  # Warlock — configure the app
@@ -105,7 +105,7 @@ same nonce reaches the page's `<script>` tags.
105
105
  | CLI commands (registered via `warlock <cmd>`) | `warlock.config.ts > cli` |
106
106
  | HTTP server tuning per env (port, host, retry) | `warlock.config.ts > server` |
107
107
  | HTTP runtime (CORS, cookies, rate limits, upload size) | `src/config/http.ts` |
108
- | App identity (name, baseUrl, timezone, locales) | `src/config/app.ts` |
108
+ | App identity (name, baseUrl, publicUrl, timezone, locales) | `src/config/app.ts` |
109
109
  | Subsystem configs (auth, mail, storage, cache, ai, …) | `src/config/<name>.ts` |
110
110
 
111
111
  Heuristic: if the setting changes how the framework **boots, builds, or scaffolds**, it goes in `warlock.config.ts`. If it changes how a **subsystem behaves at runtime**, it goes in `src/config/`.
@@ -239,6 +239,33 @@ const config = {
239
239
 
240
240
  Avoid scattering `process.env.NODE_ENV === "production"` checks — they don't get the same default-handling.
241
241
 
242
+ ### `app.publicUrl` — the app's public origin (5.15.0)
243
+
244
+ ```ts title="src/config/app.ts"
245
+ import type { AppConfigurations } from "@warlock.js/core";
246
+
247
+ const appConfigurations: AppConfigurations = {
248
+ appName: "My App",
249
+ publicUrl: "https://example.com",
250
+ };
251
+
252
+ export default appConfigurations;
253
+ ```
254
+
255
+ The one absolute-URL source every consumer that needs one — the sitemap
256
+ route, canonical links, OG tags, absolute URLs in mail — reads instead of
257
+ keeping its own copy. Optional in general (most apps have no consumer that
258
+ needs it yet); read it with `getPublicUrl()`, which returns `app.publicUrl`,
259
+ falling back to the `PUBLIC_APP_URL` env var, or `undefined` when neither is
260
+ set.
261
+
262
+ `getPublicUrl()` never throws — it is a consumer's job to fail loudly when it
263
+ requires the value. `@warlock.js/sitemap` is the first such consumer: with
264
+ `sitemap.enabled: true` and no `app.publicUrl`/`PUBLIC_APP_URL` set, boot
265
+ refuses to start (`MissingPublicUrlError`, naming both) rather than falling
266
+ back to a request-derived host — a sitemap served from the wrong host is
267
+ worse than one that never boots.
268
+
242
269
  ## Common patterns
243
270
 
244
271
  ### Adding a new subsystem
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: run-app
3
- description: 'Three operational commands — `warlock dev` (HMR + type-gen + health checks), `warlock build` (esbuild bundle), `warlock start` (spawn the production bundle). All flags, all `warlock.config.ts` knobs that shape them. Triggers: `warlock dev`, `warlock build`, `warlock start`, `devServer`, `--fresh`, `--skip-typings`, `--skip-health`, `outdir`, `outFile`, `sourcemap`, `PortInUseError`, `assertPortIsAvailable`, `EADDRINUSE`; "start the dev server", "build for production", "run the bundle", "skip type generation", "tune watch globs", "dev server keyboard shortcuts", "press r to restart", "press q to quit", "restart the dev server", "port already in use"; typical config `warlock.config.ts > devServer / build`. Skip: writing a custom CLI — `@warlock.js/core/write-cli-command/SKILL.md`; config shape — `@warlock.js/core/configure-app/SKILL.md`; competing tooling `nodemon`, `tsx`, `ts-node-dev`, `esbuild` direct.'
3
+ description: 'Three operational commands — `warlock dev` (HMR + type-gen + health checks), `warlock build` (esbuild bundle), `warlock start` (spawn the production bundle). All flags, all `warlock.config.ts` knobs that shape them. Triggers: `warlock dev`, `warlock build`, `warlock start`, `devServer`, `--fresh`, `--skip-typings`, `--skip-health`, `outdir`, `outFile`, `sourcemap`, `PortInUseError`, `assertPortIsAvailable`, `EADDRINUSE`, `EsbuildBinaryMissingError`; "start the dev server", "build for production", "run the bundle", "skip type generation", "tune watch globs", "dev server keyboard shortcuts", "press r to restart", "press q to quit", "restart the dev server", "port already in use"; typical config `warlock.config.ts > devServer / build`. Skip: writing a custom CLI — `@warlock.js/core/write-cli-command/SKILL.md`; config shape — `@warlock.js/core/configure-app/SKILL.md`; competing tooling `nodemon`, `tsx`, `ts-node-dev`, `esbuild` direct.'
4
4
  ---
5
5
 
6
6
  # Warlock — run the app
@@ -143,6 +143,7 @@ export default defineConfig({
143
143
  restartOnConfigChange: true, // restart when warlock.config.ts / .env* changes
144
144
  healthCheckers: [...] /* or false */,
145
145
  transpileCacheDebug: false, // name cache files <slug>.<hash>.js w/ // @source markers
146
+ timings: false, // print a per-phase reload timing breakdown
146
147
  },
147
148
  });
148
149
  ```
@@ -153,6 +154,7 @@ export default defineConfig({
153
154
  - **`transpileCacheDebug`** — diagnostic only. Names `.warlock/transpile/*.js` files `<slug>.<hash>.js` and appends `// @source <path>` markers so you can eyeball which cache entry came from which source. Leave off in normal use.
154
155
  - **`checkForUpdates`** — on `warlock dev` start, check npm for a newer `@warlock.js/core` and print a one-line notice if one exists. Best-effort and non-blocking; auto-skipped in CI and non-TTY shells. In an interactive terminal the notice arms a **`u` shortcut** that updates every `@warlock.js/*` package, installs, and restarts the server; elsewhere it prints `npx warlock update` instead. The registry answer is cached for 24h in `.warlock/update-check.json`, so a day of restarts costs one lookup. See [`update-packages/SKILL.md`](../update-packages/SKILL.md).
155
156
  - **`restartOnConfigChange`** — restart the dev server when `warlock.config.ts` or any `.env*` changes (default `true`). Set `false` to get a warning instead and restart by hand. Neither file can be hot-reloaded, so without a restart the running services keep the old values.
157
+ - **`timings`** — print a one-line, per-phase breakdown next to the `hmr update` line on every hot reload: `watcher`, `debounce`, `graph`, `reimport`, `connectors`. `watcher` is the raw-fs-notification-to-stabilised-event gap (chokidar's `awaitWriteFinish` window); `debounce` is the handler's own adaptive wait; the rest are self-explanatory. Opt-in, off by default — a disabled flag costs one boolean check per reload, since the watcher-settle bookkeeping only runs when this is on. Use it to see which phase a slow reload is actually spending time in.
156
158
 
157
159
  ## `warlock build` — production bundle
158
160
 
@@ -258,7 +260,7 @@ Means `docker stop` / `kubectl delete pod` works as expected: SIGTERM reaches th
258
260
 
259
261
  ### pnpm needs esbuild's install script allowed
260
262
 
261
- pnpm 10+ will not run a dependency's install script unless the app names it. esbuild's script links its platform-native binary, and `warlock build` shells out to that binary — so the app installs cleanly and then cannot build:
263
+ pnpm 10+ will not run a dependency's install script unless the app names it. esbuild's script links its platform-native binary, and both `warlock build` and `warlock dev` shell out to that binary — so the app installs cleanly and then cannot build or start dev:
262
264
 
263
265
  ```
264
266
  [ERR_PNPM_IGNORED_BUILDS] Ignored build scripts: esbuild@0.27.7
@@ -273,6 +275,8 @@ allowBuilds:
273
275
 
274
276
  Note pnpm reads this from `pnpm-workspace.yaml`, **not** from `package.json`'s `pnpm` field — pnpm 11 warns that the field is ignored and then carries on, so settings left there fail silently.
275
277
 
278
+ `warlock dev` checks for esbuild's native binary before starting (as `warlock build` already did) and fails fast with `EsbuildBinaryMissingError`, naming the same fix, instead of surfacing an opaque low-level error later from inside the bundler.
279
+
276
280
  Nothing else is needed for pnpm. Warlock never requires an app to declare a package it does not import: generated code is checked at build time against the app's own `dependencies`, so `warlock build` failing over an unfamiliar package name is a framework bug, not a missing dependency.
277
281
 
278
282
  ### Output streams — what a supervisor may trust
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: send-response
3
- description: 'Send HTTP responses via @warlock.js/core''s Response helpers — success/error variants, status helpers, redirects, files, streams, and SSE. Picking the right helper carries the HTTP semantic without manual status codes. Triggers: `response.success`, `response.successCreate`, `response.notFound`, `response.forbidden`, `response.badRequest`, `response.sendFile`, `response.stream`, `response.sse`, `response.replay`, `ResourceNotFoundError`, `ForbiddenError`; "return a 201 from a controller", "send a file", "stream Server-Sent Events", "throw HTTP-shaped errors from services"; typical import `import type { RequestHandler, Response } from "@warlock.js/core"`. Skip: controller shape — `@warlock.js/core/create-controller/SKILL.md`; route registration — `@warlock.js/core/register-route/SKILL.md`; competing patterns: hand-rolled status codes via `reply.code(404).send(...)`, raw Fastify reply.'
3
+ description: 'Send HTTP responses via @warlock.js/core''s Response helpers — success/error variants, status helpers, redirects, files, streams, and SSE. Picking the right helper carries the HTTP semantic without manual status codes. Triggers: `response.success`, `response.successCreate`, `response.notFound`, `response.forbidden`, `response.badRequest`, `response.sendFile`, `response.stream`, `response.sse`, `response.replay`, `ResourceNotFoundError`, `ForbiddenError`, `request.cookie`, `request.hasCookie`, `CookieJarUnavailableError`; "return a 201 from a controller", "send a file", "stream Server-Sent Events", "throw HTTP-shaped errors from services"; typical import `import type { RequestHandler, Response } from "@warlock.js/core"`. Skip: controller shape — `@warlock.js/core/create-controller/SKILL.md`; route registration — `@warlock.js/core/register-route/SKILL.md`; competing patterns: hand-rolled status codes via `reply.code(404).send(...)`, raw Fastify reply.'
4
4
  ---
5
5
 
6
6
  # Warlock — send a response
@@ -202,6 +202,20 @@ Every response cookie gets `httpOnly: true`, `sameSite: "lax"`, and `secure: tru
202
202
 
203
203
  Precedence, lowest to highest: **framework defaults → `http.cookies.options` → the per-call `options` argument.** Set an app-wide policy in config, override per cookie when a specific one genuinely needs different treatment.
204
204
 
205
+ ### Reading cookies back — `request.cookie()` / `request.hasCookie()` throw when the jar is unavailable (5.15.0)
206
+
207
+ `request.cookie(name)` and `request.hasCookie(name)` are a deliberate
208
+ by-name assertion — "this cookie should be readable here" — so when
209
+ `@fastify/cookie` is not registered on the Fastify instance, both now
210
+ **throw `CookieJarUnavailableError`** naming the missing cookie, instead of
211
+ silently returning `undefined` / `false`. Register the plugin (see core's
212
+ `http/plugins.ts`) before reading cookies by name.
213
+
214
+ `request.cookies` (the plain getter, no by-name assertion) is unchanged and
215
+ stays lenient — it returns `{}` when the jar is unavailable, because the
216
+ framework's own opportunistic reads (e.g. locale resolution) must not throw
217
+ on a request that simply has no jar.
218
+
205
219
  These are the flags whose absence never fails a test and is fatal in production: the app works perfectly and is simply insecure. Opting out is now explicit and visible in review.
206
220
 
207
221
  These mutate the response in place; chain or call before the final `return response.<helper>()`.
@@ -100,6 +100,12 @@ All three lookups go through `@mongez/localization`'s `trans()` under the hood,
100
100
 
101
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.
102
102
 
103
+ `useTrans()` now works correctly across hydration (5.15.0): the hydration
104
+ payload ships a `translations` key with the active locale's keywords, and
105
+ `@warlock.js/web` registers them into this same lookup table before the
106
+ client hydrates — see `@warlock.js/web/write-the-root/SKILL.md`'s "`useTrans()`
107
+ survives hydration" section for the failure this fixed.
108
+
103
109
  ### Locale on a specific lookup
104
110
 
105
111
  ```ts
@@ -174,6 +174,30 @@ router.get("/analytics/summary", summaryController, {
174
174
 
175
175
  `cacheKey` can be a string OR a function `(request) => string | Promise<string>` for per-request keys. Excludes failures and omits `["user", "settings"]` from the cached body by default.
176
176
 
177
+ ### Tag-based invalidation
178
+
179
+ Give it `tags` — a static list, or a function of the request — to evict the entry early with `cache.tags([...]).invalidate()` (`@warlock.js/cache`), instead of waiting out `ttl`. Mirrors `route.cache.tags` on `@warlock.js/web`'s page cache, so an API response and a page can share the same tag and be invalidated together:
180
+
181
+ ```ts
182
+ import { middleware } from "@warlock.js/core";
183
+ import { cache } from "@warlock.js/cache";
184
+
185
+ router.get("/orders/:id", getOrderController, {
186
+ middleware: [
187
+ middleware.cache({
188
+ cacheKey: (request) => `orders.${request.params.id}`,
189
+ ttl: 300,
190
+ tags: (request) => [`order.${request.params.id}`],
191
+ }),
192
+ ],
193
+ });
194
+
195
+ // Elsewhere, after the order changes:
196
+ await cache.tags([`order.${orderId}`]).invalidate();
197
+ ```
198
+
199
+ Untagged entries (no `tags` given) behave exactly as before — they only expire via `ttl`.
200
+
177
201
  ## Composed example
178
202
 
179
203
  Tight cap on logins, concurrency + idempotency on AI calls:
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: write-cli-command
3
- description: '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`.'
3
+ description: '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`, `warlock add bull-board`, `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`.'
4
4
  ---
5
5
 
6
6
  # Warlock — write a CLI command
@@ -207,6 +207,7 @@ warlock generate (alias: g) — interactive picker
207
207
  | `react-email` | `react-email` + `@react-email/components` + `@react-email/render` + `@react-email/tailwind`; drops a `welcome-email.tsx` sample; patches `tsconfig.json` |
208
208
  | `web` | Warlock Web + React stack; scaffolds the application page, localization, contact endpoint, and Web configuration |
209
209
  | `react` | `react` + `react-dom` + types |
210
+ | `bull-board` | `@bull-board/api` + `@bull-board/fastify`; writes a `dashboard` block to `src/config/queue.ts`. `requires: ["queue"]` — adds the `queue` feature first automatically when it's missing |
210
211
  | `image` | `sharp` (for the `Image` class) |
211
212
  | `mail` | `nodemailer` + types |
212
213
  | `ses` | `@aws-sdk/client-sesv2` |