@warlock.js/core 4.2.11 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -1
- package/esm/application/application.d.mts +126 -4
- package/esm/application/application.d.mts.map +1 -1
- package/esm/application/application.mjs +133 -0
- package/esm/application/application.mjs.map +1 -1
- package/esm/application/index.d.mts +1 -1
- package/esm/cli/commands/dev-server.command.mjs +4 -2
- package/esm/cli/commands/dev-server.command.mjs.map +1 -1
- package/esm/cli/commands/update.command.mjs +20 -0
- package/esm/cli/commands/update.command.mjs.map +1 -0
- package/esm/cli/framework-cli-commands.mjs +2 -0
- package/esm/cli/framework-cli-commands.mjs.map +1 -1
- package/esm/cli/types.d.mts +2 -1
- package/esm/cli/types.d.mts.map +1 -1
- package/esm/config/config-getter.mjs +5 -5
- package/esm/config/config-getter.mjs.map +1 -1
- package/esm/config/config-loader.mjs +2 -2
- package/esm/config/config-loader.mjs.map +1 -1
- package/esm/connectors/access-connector.mjs +2 -2
- package/esm/connectors/access-connector.mjs.map +1 -1
- package/esm/connectors/cache-connector.mjs +2 -2
- package/esm/connectors/cache-connector.mjs.map +1 -1
- package/esm/connectors/connectors-manager.d.mts.map +1 -1
- package/esm/connectors/connectors-manager.mjs +4 -1
- package/esm/connectors/connectors-manager.mjs.map +1 -1
- package/esm/connectors/database-connector.mjs +2 -2
- package/esm/connectors/database-connector.mjs.map +1 -1
- package/esm/connectors/herald-connector.mjs +2 -2
- package/esm/connectors/herald-connector.mjs.map +1 -1
- package/esm/connectors/http-connector.d.mts +8 -0
- package/esm/connectors/http-connector.d.mts.map +1 -1
- package/esm/connectors/http-connector.mjs +40 -7
- package/esm/connectors/http-connector.mjs.map +1 -1
- package/esm/connectors/logger-connector.mjs +2 -2
- package/esm/connectors/logger-connector.mjs.map +1 -1
- package/esm/connectors/mail-connector.mjs +2 -2
- package/esm/connectors/mail-connector.mjs.map +1 -1
- package/esm/connectors/notifications-connector.mjs +2 -2
- package/esm/connectors/notifications-connector.mjs.map +1 -1
- package/esm/connectors/socket-connector.mjs +3 -3
- package/esm/connectors/socket-connector.mjs.map +1 -1
- package/esm/dev-server/check-for-updates.mjs +59 -0
- package/esm/dev-server/check-for-updates.mjs.map +1 -0
- package/esm/dev-server/development-server.mjs +7 -0
- package/esm/dev-server/development-server.mjs.map +1 -1
- package/esm/dev-server/loader/transpile-cache.mjs +1 -1
- package/esm/http/config.mjs +2 -2
- package/esm/http/config.mjs.map +1 -1
- package/esm/http/createHttpApplication.mjs +2 -2
- package/esm/http/createHttpApplication.mjs.map +1 -1
- package/esm/http/health.d.mts +57 -0
- package/esm/http/health.d.mts.map +1 -0
- package/esm/http/health.mjs +75 -0
- package/esm/http/health.mjs.map +1 -0
- package/esm/http/index.d.mts +2 -1
- package/esm/http/index.mjs +2 -1
- package/esm/http/middleware/idempotency.middleware.mjs +5 -5
- package/esm/http/middleware/idempotency.middleware.mjs.map +1 -1
- package/esm/http/middleware/inject-request-context.mjs +2 -2
- package/esm/http/middleware/inject-request-context.mjs.map +1 -1
- package/esm/http/middleware/maintenance.middleware.mjs +4 -4
- package/esm/http/middleware/maintenance.middleware.mjs.map +1 -1
- package/esm/http/plugins.mjs +9 -9
- package/esm/http/plugins.mjs.map +1 -1
- package/esm/http/response.mjs +5 -5
- package/esm/http/response.mjs.map +1 -1
- package/esm/http/server.d.mts +16 -1
- package/esm/http/server.d.mts.map +1 -1
- package/esm/http/server.mjs +23 -3
- package/esm/http/server.mjs.map +1 -1
- package/esm/http/types.d.mts +45 -0
- package/esm/http/types.d.mts.map +1 -1
- package/esm/index.d.mts +7 -4
- package/esm/index.mjs +8 -5
- package/esm/production/production-builder.mjs +2 -2
- package/esm/production/production-builder.mjs.map +1 -1
- package/esm/updater/package-manager.mjs +32 -0
- package/esm/updater/package-manager.mjs.map +1 -0
- package/esm/updater/update-warlock-packages.mjs +134 -0
- package/esm/updater/update-warlock-packages.mjs.map +1 -0
- package/esm/utils/environment.d.mts +6 -1
- package/esm/utils/environment.d.mts.map +1 -1
- package/esm/utils/environment.mjs.map +1 -1
- package/esm/utils/index.d.mts +4 -2
- package/esm/utils/index.mjs +2 -0
- package/esm/utils/npm-registry.d.mts +24 -0
- package/esm/utils/npm-registry.d.mts.map +1 -0
- package/esm/utils/npm-registry.mjs +43 -0
- package/esm/utils/npm-registry.mjs.map +1 -0
- package/esm/utils/paths.mjs +2 -2
- package/esm/utils/paths.mjs.map +1 -1
- package/esm/utils/version-compare.d.mts +20 -0
- package/esm/utils/version-compare.d.mts.map +1 -0
- package/esm/utils/version-compare.mjs +70 -0
- package/esm/utils/version-compare.mjs.map +1 -0
- package/esm/validation/validateAll.mjs +2 -2
- package/esm/validation/validateAll.mjs.map +1 -1
- package/esm/warlock-config/types.d.mts +9 -0
- package/esm/warlock-config/types.d.mts.map +1 -1
- package/llms-full.txt +249 -2
- package/llms.txt +3 -1
- package/package.json +9 -9
- package/skills/health-checks/SKILL.md +98 -0
- package/skills/run-app/SKILL.md +3 -0
- package/skills/update-packages/SKILL.md +78 -0
- package/skills/use-app-context/SKILL.md +62 -2
|
@@ -82,6 +82,15 @@ type WarlockConfig = {
|
|
|
82
82
|
* @default true
|
|
83
83
|
*/
|
|
84
84
|
generateTypings?: boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Check npm for a newer `@warlock.js/core` release when the dev server
|
|
87
|
+
* starts and print a one-line notice if one is available. Because the
|
|
88
|
+
* family is versioned in lockstep, core stands in for every package.
|
|
89
|
+
* Best-effort and non-blocking; automatically skipped in CI and in
|
|
90
|
+
* non-interactive (non-TTY) shells.
|
|
91
|
+
* @default true
|
|
92
|
+
*/
|
|
93
|
+
checkForUpdates?: boolean;
|
|
85
94
|
/**
|
|
86
95
|
* Debug aid for the (always-on) persisted transpile cache. Names cache
|
|
87
96
|
* files `<slug>.<hash>.js` (last 3 source path segments) and appends a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../@warlock.js/core/src/warlock-config/types.ts"],"mappings":";;;;;;;AASA;;;KAAY,aAAA;EAuES;;;EAnEnB,MAAA;IACE,IAAA;IACA,IAAA;IACA,cAAA;EAAA;EAAA;;;EAMF,KAAA;IAkBE;;;;;IAZA,YAAA;IAmCA;;;;;IA7BA,OAAA;
|
|
1
|
+
{"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../@warlock.js/core/src/warlock-config/types.ts"],"mappings":";;;;;;;AASA;;;KAAY,aAAA;EAuES;;;EAnEnB,MAAA;IACE,IAAA;IACA,IAAA;IACA,cAAA;EAAA;EAAA;;;EAMF,KAAA;IAkBE;;;;;IAZA,YAAA;IAmCA;;;;;IA7BA,OAAA;IA4DA;;;;;IAtDA,MAAA;IAiGF;;;;AAcS;IAzGP,SAAA;EAAA;;;;EAMF,GAAA;IACE,QAAA,GAAW,UAAA;EAAA;;;;EAMb,SAAA;;;;IAIE,KAAA;;;;;;MAME,OAAA;;;;;;MAMA,OAAA;IAAA;;;;IAKF,cAAA,GAAiB,yBAAA;;;;;IAKjB,eAAA;;;;;;;;;IASA,eAAA;;;;;;;;;;IAUA,mBAAA;EAAA;;;;EAMF,QAAA;;;;;;;;;;;;;;;;;;IAkBE,UAAA,GAAa,KAAA,CAAM,oBAAA;EAAA;;;;;;;EASrB,OAAA;;;;;;;IAOE,OAAA;;;;;;IAOA,OAAA;EAAA;AAAA"}
|
package/llms-full.txt
CHANGED
|
@@ -2292,6 +2292,108 @@ There is no `warlock add` feature for password hashing — `bcryptjs` is a plain
|
|
|
2292
2292
|
- [`warlock-conventions/SKILL.md`](../warlock-conventions/SKILL.md) — service layering, where the password boundary should sit.
|
|
2293
2293
|
|
|
2294
2294
|
|
|
2295
|
+
## health-checks `@warlock.js/core/health-checks/SKILL.md`
|
|
2296
|
+
|
|
2297
|
+
---
|
|
2298
|
+
name: health-checks
|
|
2299
|
+
description: 'Built-in liveness (`/health`) and readiness (`/ready`) endpoints plus graceful HTTP request draining for zero-downtime deploys — the `health` registry (`health.addCheck`/`removeCheck`), the `http.health.*` and `http.gracefulShutdown.*` config, and how readiness ties into `Application.isShuttingDown`. Triggers: `health`, `health.addCheck`, `health.removeCheck`, `HealthCheck`, `/health`, `/ready`, `http.health`, `http.gracefulShutdown`, `forceCloseConnections`, "liveness probe", "readiness probe", "graceful shutdown", "drain in-flight requests", "zero-downtime deploy", "kubernetes health check", "503 until ready"; typical import `import { health } from "@warlock.js/core"`. Skip: the `Application.onShutdown` / `onceBooted` lifecycle hooks — `@warlock.js/core/use-app-context/SKILL.md`; maintenance-mode 503s — `@warlock.js/core/use-middleware/SKILL.md`; connector lifecycle — `@warlock.js/core/add-connector/SKILL.md`; competing libs `@fastify/under-pressure`, `terminus`, hand-rolled `/health` controllers.'
|
|
2300
|
+
---
|
|
2301
|
+
|
|
2302
|
+
# Warlock — health checks & graceful shutdown
|
|
2303
|
+
|
|
2304
|
+
Two endpoints and a drain, so a load balancer never routes to an instance that isn't ready and a deploy never kills an in-flight request. All built in; no controller to hand-roll.
|
|
2305
|
+
|
|
2306
|
+
## The two endpoints
|
|
2307
|
+
|
|
2308
|
+
The HTTP connector registers them on the Fastify server during boot (before route scanning), so they exist by the time the server listens:
|
|
2309
|
+
|
|
2310
|
+
| Path | Probe | 200 when | 503 when |
|
|
2311
|
+
| --- | --- | --- | --- |
|
|
2312
|
+
| `/health` | liveness | the process is up | shutdown has begun |
|
|
2313
|
+
| `/ready` | readiness | booted **and** not shutting down **and** every check passes | before boot, during shutdown, or any failing check |
|
|
2314
|
+
|
|
2315
|
+
**Liveness** answers "should the orchestrator RESTART me?" — it ignores dependency checks (a failing DB doesn't mean restart the pod). **Readiness** answers "should the load balancer ROUTE to me?" — it gates on boot completion, shutdown state, and your registered checks.
|
|
2316
|
+
|
|
2317
|
+
```
|
|
2318
|
+
GET /health → 200 {"status":"ok"}
|
|
2319
|
+
GET /ready → 200 {"status":"ok","checks":{"db":true}}
|
|
2320
|
+
→ 503 {"status":"error","checks":{"db":false}}
|
|
2321
|
+
```
|
|
2322
|
+
|
|
2323
|
+
### Config
|
|
2324
|
+
|
|
2325
|
+
```ts title="src/config/http.ts"
|
|
2326
|
+
const httpConfigurations: HttpConfigurations = {
|
|
2327
|
+
health: {
|
|
2328
|
+
enabled: true, // default; set false to remove both endpoints
|
|
2329
|
+
path: "/health", // liveness path
|
|
2330
|
+
readinessPath: "/ready", // readiness path
|
|
2331
|
+
},
|
|
2332
|
+
};
|
|
2333
|
+
```
|
|
2334
|
+
|
|
2335
|
+
## Readiness checks
|
|
2336
|
+
|
|
2337
|
+
Readiness is `isBooted && !isShuttingDown` plus every registered check. Register a check from a connector, a `main.ts`, or anywhere:
|
|
2338
|
+
|
|
2339
|
+
```ts
|
|
2340
|
+
import { health } from "@warlock.js/core";
|
|
2341
|
+
|
|
2342
|
+
health.addCheck("db", async () => {
|
|
2343
|
+
return database.isConnected();
|
|
2344
|
+
});
|
|
2345
|
+
|
|
2346
|
+
health.removeCheck("db"); // unregister later if needed
|
|
2347
|
+
```
|
|
2348
|
+
|
|
2349
|
+
A check returns `boolean | Promise<boolean>`. **A thrown error counts as a failed check** (it's surfaced in the `checks` map + the 503, not logged — probes poll often, so a failure is a normal signal, not an error event). Keep checks cheap and fast; they run on every `/ready` poll.
|
|
2350
|
+
|
|
2351
|
+
## Graceful shutdown (request draining)
|
|
2352
|
+
|
|
2353
|
+
On SIGINT/SIGTERM the framework tears down in order: **app `onShutdown` hooks → connectors in reverse priority**. The HTTP connector's teardown drains instead of dropping:
|
|
2354
|
+
|
|
2355
|
+
1. `Application.isShuttingDown` flips `true` at the very start → `/ready` immediately returns 503, so the load balancer stops sending new traffic.
|
|
2356
|
+
2. Fastify stops accepting new requests (answers 503 while closing) and lets in-flight ones finish.
|
|
2357
|
+
3. Draining is bounded by a timeout so one stuck request can't hang the deploy — after it, the server force-closes and a warning is logged.
|
|
2358
|
+
|
|
2359
|
+
```ts title="src/config/http.ts"
|
|
2360
|
+
const httpConfigurations: HttpConfigurations = {
|
|
2361
|
+
gracefulShutdown: {
|
|
2362
|
+
timeout: 10_000, // ms to wait for in-flight drain (default 10s)
|
|
2363
|
+
forceCloseConnections: "idle", // close idle keep-alives, let active finish (default)
|
|
2364
|
+
},
|
|
2365
|
+
};
|
|
2366
|
+
```
|
|
2367
|
+
|
|
2368
|
+
`forceCloseConnections`: `"idle"` (default) closes idle keep-alive connections and lets active requests finish; `true` force-closes everything immediately; `false` waits for every connection.
|
|
2369
|
+
|
|
2370
|
+
## The zero-downtime deploy flow
|
|
2371
|
+
|
|
2372
|
+
```
|
|
2373
|
+
SIGTERM → isShuttingDown = true → /ready returns 503
|
|
2374
|
+
→ LB stops routing new requests to this instance
|
|
2375
|
+
→ in-flight requests drain (up to gracefulShutdown.timeout)
|
|
2376
|
+
→ app onShutdown hooks already ran (db/cache still up)
|
|
2377
|
+
→ connectors close in reverse → process exits
|
|
2378
|
+
```
|
|
2379
|
+
|
|
2380
|
+
For an even smoother handoff, give the load balancer time to observe the 503 before the server closes — e.g. an `onShutdown` hook with a short `sleep` matched to your LB's health-check interval.
|
|
2381
|
+
|
|
2382
|
+
## Gotchas
|
|
2383
|
+
|
|
2384
|
+
- **`/health` is registered straight on Fastify, not the app router.** It's infra, so it's immune to HMR and route scanning — but if your app also defines a `/health` route you'll have a collision. Rename via `http.health.path`.
|
|
2385
|
+
- **Readiness needs a finished boot.** Before `Application.isBooted` (e.g. while late-phase connectors are still starting) `/ready` is 503 by design — that's the point.
|
|
2386
|
+
- **A hanging `onShutdown` hook delays the drain.** App hooks run before connector teardown and are only bounded by your process manager's kill timeout; keep them fast. The HTTP drain itself is bounded by `gracefulShutdown.timeout`.
|
|
2387
|
+
- **The `maintenance` middleware is a different 503.** It allowlists `/health` by default so probes pass during maintenance — but maintenance mode is operator-toggled downtime, not readiness. See `@warlock.js/core/use-middleware/SKILL.md`.
|
|
2388
|
+
|
|
2389
|
+
## See also
|
|
2390
|
+
|
|
2391
|
+
- [`use-app-context/SKILL.md`](../use-app-context/SKILL.md) — `Application.onShutdown` / `onceBooted` / `isShuttingDown`, the lifecycle hooks the endpoints build on.
|
|
2392
|
+
- [`configure-app/SKILL.md`](../configure-app/SKILL.md) — the `src/config/http.ts` shape.
|
|
2393
|
+
- [`add-connector/SKILL.md`](../add-connector/SKILL.md) — connector boot/shutdown order, where draining slots in.
|
|
2394
|
+
- [`use-middleware/SKILL.md`](../use-middleware/SKILL.md) — maintenance mode and other built-in middleware.
|
|
2395
|
+
|
|
2396
|
+
|
|
2295
2397
|
## lower-stage3-decorators `@warlock.js/core/lower-stage3-decorators/SKILL.md`
|
|
2296
2398
|
|
|
2297
2399
|
---
|
|
@@ -3043,6 +3145,7 @@ export default defineConfig({
|
|
|
3043
3145
|
exclude: ["**/node_modules/**", "**/dist/**", "**/.warlock/**", "**/.git/**"],
|
|
3044
3146
|
},
|
|
3045
3147
|
generateTypings: true, // background type generation
|
|
3148
|
+
checkForUpdates: true, // notify on a newer @warlock.js/core at dev start
|
|
3046
3149
|
healthCheckers: [...] /* or false */,
|
|
3047
3150
|
transpileCacheDebug: false, // name cache files <slug>.<hash>.js w/ // @source markers
|
|
3048
3151
|
},
|
|
@@ -3053,6 +3156,7 @@ export default defineConfig({
|
|
|
3053
3156
|
- **`generateTypings`** — turn off if you're committing generated typings and don't want them rewritten on every boot. The `--skip-typings` flag is the per-run version.
|
|
3054
3157
|
- **`healthCheckers`** — custom file health checker contracts (or `false` to disable). The `--skip-health` flag is the per-run version.
|
|
3055
3158
|
- **`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.
|
|
3159
|
+
- **`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. Run `warlock update` to upgrade. See [`update-packages/SKILL.md`](../update-packages/SKILL.md).
|
|
3056
3160
|
|
|
3057
3161
|
## `warlock build` — production bundle
|
|
3058
3162
|
|
|
@@ -3235,6 +3339,7 @@ NODE_OPTIONS=--max-old-space-size=4096 yarn warlock start
|
|
|
3235
3339
|
- [`configure-app/SKILL.md`](../configure-app/SKILL.md) — `warlock.config.ts` shape and `defineConfig`.
|
|
3236
3340
|
- [`use-app-context/SKILL.md`](../use-app-context/SKILL.md) — `Application.environment` vs `Application.runtimeStrategy`.
|
|
3237
3341
|
- [`add-connector/SKILL.md`](../add-connector/SKILL.md) — Early vs Late connector phases (why HTTP/socket boot late in dev).
|
|
3342
|
+
- [`update-packages/SKILL.md`](../update-packages/SKILL.md) — `warlock update` + the dev-server update notice (`devServer.checkForUpdates`).
|
|
3238
3343
|
|
|
3239
3344
|
|
|
3240
3345
|
## send-mail `@warlock.js/core/send-mail/SKILL.md`
|
|
@@ -4789,6 +4894,88 @@ describe("slugify", () => {
|
|
|
4789
4894
|
- [`write-cli-command/SKILL.md`](../write-cli-command/SKILL.md) — `warlock add test` for the initial scaffold.
|
|
4790
4895
|
|
|
4791
4896
|
|
|
4897
|
+
## update-packages `@warlock.js/core/update-packages/SKILL.md`
|
|
4898
|
+
|
|
4899
|
+
---
|
|
4900
|
+
name: update-packages
|
|
4901
|
+
description: 'Keep a project current with `warlock update` — bump every `@warlock.js/*` dependency in package.json to its latest published version (range operator preserved), then run the lockfile-detected package manager install. Also covers the `warlock dev` update notice and the `devServer.checkForUpdates` toggle. Triggers: `warlock update`, `--no-install`, `checkForUpdates`, `fetchLatestVersion`, `isNewerVersion`; "update warlock packages", "upgrade the framework", "is there a new warlock version", "update notice in the dev server", "bump @warlock.js/* to latest"; typical CLI `warlock update`. Skip: dev/build/start runtime — `@warlock.js/core/run-app/SKILL.md`; writing a custom command — `@warlock.js/core/write-cli-command/SKILL.md`; installing a NEW feature package (auth, mail, storage) — that is `warlock add`; releasing/publishing the framework — workspace release tooling, not this command.'
|
|
4902
|
+
---
|
|
4903
|
+
|
|
4904
|
+
# Warlock — update the framework
|
|
4905
|
+
|
|
4906
|
+
`warlock update` upgrades a project's Warlock packages in one step, and `warlock dev` tells you when an upgrade is available. Because the whole `@warlock.js/*` family ships in **lockstep** — every package shares one version — keeping them in sync is the normal case, and this command does exactly that.
|
|
4907
|
+
|
|
4908
|
+
## `warlock update`
|
|
4909
|
+
|
|
4910
|
+
```bash
|
|
4911
|
+
warlock update # bump every @warlock.js/* dep to latest, then install
|
|
4912
|
+
warlock update --no-install # rewrite package.json only; install yourself later
|
|
4913
|
+
```
|
|
4914
|
+
|
|
4915
|
+
| Flag | Type | Purpose |
|
|
4916
|
+
| -------------- | ------- | ----------------------------------------------------------------------------------- |
|
|
4917
|
+
| `--no-install` | boolean | Rewrite the versions in `package.json` without running the package manager install. |
|
|
4918
|
+
|
|
4919
|
+
What it does, in order:
|
|
4920
|
+
|
|
4921
|
+
1. Reads the project's root `package.json`.
|
|
4922
|
+
2. Collects every `@warlock.js/*` package across `dependencies` and `devDependencies`. Only the `@warlock.js/` scope is considered — `create-warlock` and unrelated dependencies are never touched.
|
|
4923
|
+
3. Looks up each package's latest version on the npm registry, in parallel.
|
|
4924
|
+
4. Rewrites each matching spec, **preserving the range operator**: `^4.2.0` → `^4.3.0`, `~4.2.0` → `~4.3.0`, an exact `4.2.0` → `4.3.0`. Specs that are not a plain semver — `workspace:*`, `*`, `latest`, git/file URLs — are left exactly as written, and any package already at or ahead of latest is skipped.
|
|
4925
|
+
5. Runs the project's install to reconcile `node_modules` — `npm install` / `yarn install` / `pnpm install`, chosen by the lockfile present (`package-lock.json` / `yarn.lock` / `pnpm-lock.yaml`, npm as the fallback). Skipped with `--no-install`.
|
|
4926
|
+
|
|
4927
|
+
Re-running on an already-current project is a no-op: nothing resolves as newer, so it prints "All @warlock.js packages are already up to date" and exits without writing or installing.
|
|
4928
|
+
|
|
4929
|
+
## The dev-server update notice
|
|
4930
|
+
|
|
4931
|
+
On start, `warlock dev` checks npm for a newer `@warlock.js/core` and prints a one-line notice when one exists:
|
|
4932
|
+
|
|
4933
|
+
```
|
|
4934
|
+
⚡ A new version of Warlock.js is available 4.2.11 → 4.3.0
|
|
4935
|
+
Run npx warlock update to update all @warlock.js packages
|
|
4936
|
+
Changelog https://warlock.js.org/changelog/
|
|
4937
|
+
```
|
|
4938
|
+
|
|
4939
|
+
Core's version stands in for the whole family (lockstep), so a single lookup is enough. The check is **best-effort and non-blocking** — it runs fire-and-forget after the server is ready, never delays or breaks startup, and stays silent on any failure (offline, registry down, timeout).
|
|
4940
|
+
|
|
4941
|
+
It is automatically skipped when:
|
|
4942
|
+
|
|
4943
|
+
- `process.env.CI` is set (CI runs),
|
|
4944
|
+
- stdout is not a TTY (piped / non-interactive shells),
|
|
4945
|
+
- `process.env.NO_UPDATE_NOTIFIER` is set, or
|
|
4946
|
+
- `devServer.checkForUpdates` is `false`.
|
|
4947
|
+
|
|
4948
|
+
```ts title="warlock.config.ts"
|
|
4949
|
+
import { defineConfig } from "@warlock.js/core";
|
|
4950
|
+
|
|
4951
|
+
export default defineConfig({
|
|
4952
|
+
devServer: {
|
|
4953
|
+
checkForUpdates: false, // silence the "update available" notice
|
|
4954
|
+
},
|
|
4955
|
+
});
|
|
4956
|
+
```
|
|
4957
|
+
|
|
4958
|
+
## Building blocks
|
|
4959
|
+
|
|
4960
|
+
Two small zero-dependency utilities back the tooling and are exported from `@warlock.js/core`:
|
|
4961
|
+
|
|
4962
|
+
- `fetchLatestVersion(name, timeoutMs?)` — the latest published version of an npm package, or `undefined` on any failure. Never throws.
|
|
4963
|
+
- `isNewerVersion(latest, current)` — `true` when `latest` is a strictly newer semver than `current`. Compares `major.minor.patch` and orders a stable release above its prereleases.
|
|
4964
|
+
|
|
4965
|
+
## Gotchas
|
|
4966
|
+
|
|
4967
|
+
- **Only the `@warlock.js/` scope is updated.** Mongez packages (`@mongez/*`), `create-warlock`, and everything else are left alone — update those with your package manager directly.
|
|
4968
|
+
- **Non-semver specs are intentionally skipped.** A `workspace:*` or `*` dependency stays as written; `update` will not pin it to a concrete version.
|
|
4969
|
+
- **The notice never blocks dev.** If npm is unreachable, `warlock dev` behaves exactly as before — no delay, no error.
|
|
4970
|
+
- **`warlock update` is not `warlock add`.** `add` installs a *new* feature package and runs its setup hooks; `update` only bumps the versions of packages you already depend on.
|
|
4971
|
+
|
|
4972
|
+
## See also
|
|
4973
|
+
|
|
4974
|
+
- [`run-app/SKILL.md`](../run-app/SKILL.md) — `warlock dev` / `build` / `start` and the `devServer.*` config knobs.
|
|
4975
|
+
- [`write-cli-command/SKILL.md`](../write-cli-command/SKILL.md) — author your own `warlock <cmd>`.
|
|
4976
|
+
- [`configure-app/SKILL.md`](../configure-app/SKILL.md) — `warlock.config.ts` shape and `defineConfig`.
|
|
4977
|
+
|
|
4978
|
+
|
|
4792
4979
|
## upload-file `@warlock.js/core/upload-file/SKILL.md`
|
|
4793
4980
|
|
|
4794
4981
|
---
|
|
@@ -5094,7 +5281,7 @@ The reverse direction — sending a stored file back via `response.sendFile(...)
|
|
|
5094
5281
|
|
|
5095
5282
|
---
|
|
5096
5283
|
name: use-app-context
|
|
5097
|
-
description: 'Read app-wide context — the `Application` static class (env, version, uptime, runtime strategy) plus the `app` runtime accessor (live Fastify, socket.io, router, database via the DI container). Triggers: `Application.isProduction`, `Application.environment`, `Application.runtimeStrategy`, `Application.uptime`, `Application.version`, `app.http`, `app.socket`, `app.database`, `app.router`; "branch on environment", "reach the live Fastify instance", "framework version in health endpoint", "dev vs production runtime check"; typical import `import { Application, app } from "@warlock.js/core"`. Skip: path helpers — `@warlock.js/core/resolve-path/SKILL.md`; connector start order — `@warlock.js/core/add-connector/SKILL.md`; competing patterns: bare `process.env.NODE_ENV`, ad-hoc Fastify imports.'
|
|
5284
|
+
description: 'Read app-wide context — the `Application` static class (env, version, uptime, runtime strategy, boot lifecycle) plus the `app` runtime accessor (live Fastify, socket.io, router, database via the DI container). Triggers: `Application.isProduction`, `Application.environment`, `Application.runtimeStrategy`, `Application.uptime`, `Application.version`, `Application.onceBooted`, `Application.whenBooted`, `Application.isBooted`, `Application.onShutdown`, `Application.isShuttingDown`, `app.http`, `app.socket`, `app.database`, `app.router`; "branch on environment", "reach the live Fastify instance", "framework version in health endpoint", "dev vs production runtime check", "run code once the app is fully booted", "after all connectors started", "app booted hook", "run cleanup before shutdown", "graceful shutdown hook"; typical import `import { Application, app } from "@warlock.js/core"`. Skip: path helpers — `@warlock.js/core/resolve-path/SKILL.md`; connector start order — `@warlock.js/core/add-connector/SKILL.md`; competing patterns: bare `process.env.NODE_ENV`, ad-hoc Fastify imports.'
|
|
5098
5285
|
---
|
|
5099
5286
|
|
|
5100
5287
|
# Warlock — use the application context
|
|
@@ -5169,6 +5356,64 @@ The dev-server CLI command sets it to `"development"`. The `build` command and `
|
|
|
5169
5356
|
|
|
5170
5357
|
Most app code shouldn't care about `runtimeStrategy` — branch on `environment` instead, which is the orthogonal "what world is this code talking to?" axis.
|
|
5171
5358
|
|
|
5359
|
+
## Lifecycle: boot & shutdown
|
|
5360
|
+
|
|
5361
|
+
`Application.onceBooted(callback)` runs a callback the moment the app is fully booted — every connector in **both** phases is active and all app files (locales, events, main, routes) are loaded. It's the only hook that fires *after* the late phase (http, socket) is up.
|
|
5362
|
+
|
|
5363
|
+
```ts
|
|
5364
|
+
import { Application } from "@warlock.js/core";
|
|
5365
|
+
|
|
5366
|
+
Application.onceBooted(({ environment, runtimeStrategy, bootDurationMs }) => {
|
|
5367
|
+
// http is listening, socket is bound, every model is registered
|
|
5368
|
+
});
|
|
5369
|
+
```
|
|
5370
|
+
|
|
5371
|
+
Why a dedicated hook instead of code at the bottom of `main.ts`? **App files load before the late phase.** `main.ts`, `events.ts`, `routes.ts`, and locales are all imported *between* the early and late connector phases — so when `main.ts` runs, http/socket are not listening yet. `onceBooted` defers your callback until the whole sequence finishes, which makes it the safe place to touch `app.http` / `app.socket` from `main.ts`-level code.
|
|
5372
|
+
|
|
5373
|
+
It's a **latch**, not a plain event listener: register before boot and it queues; register *after* boot and it fires on the next microtask. A late subscriber never silently misses the signal.
|
|
5374
|
+
|
|
5375
|
+
```ts
|
|
5376
|
+
Application.isBooted; // boolean — has boot finished?
|
|
5377
|
+
await Application.whenBooted(); // Promise<BootContext> — await instead of a callback
|
|
5378
|
+
```
|
|
5379
|
+
|
|
5380
|
+
`whenBooted()` is the promise form, resolving with the same `BootContext` (`{ environment, runtimeStrategy, bootDurationMs? }`). `bootDurationMs` is set by the dev server (which times boot) and omitted by the production entry.
|
|
5381
|
+
|
|
5382
|
+
The framework fires the latch for you — the dev server and the production bundle each call it once, right after the late phase. App code only ever *reads* it via `onceBooted` / `whenBooted` / `isBooted`; the internal `markBooted` is a framework entry point, not for application use.
|
|
5383
|
+
|
|
5384
|
+
### Run something once everything is listening
|
|
5385
|
+
|
|
5386
|
+
```ts title="src/app/main.ts"
|
|
5387
|
+
import { Application } from "@warlock.js/core";
|
|
5388
|
+
import { log } from "@warlock.js/logger";
|
|
5389
|
+
|
|
5390
|
+
Application.onceBooted(({ environment, bootDurationMs }) => {
|
|
5391
|
+
log.success("app", "booted", `ready in ${environment}`, { bootDurationMs });
|
|
5392
|
+
});
|
|
5393
|
+
```
|
|
5394
|
+
|
|
5395
|
+
This is the right home for "warm a cache", "register a recurring job", "ping a readiness endpoint", or "open an outbound connection that needs the http server already listening" — anything that must wait for a complete boot.
|
|
5396
|
+
|
|
5397
|
+
### Shutdown — clean up before the app goes down
|
|
5398
|
+
|
|
5399
|
+
`Application.onShutdown(callback)` is the mirror of `onceBooted`: it runs your teardown **once**, when the process is shutting down (SIGINT/SIGTERM, or the dev server stopping) — and crucially **before** the connectors (db, cache, http) are torn down, so your cleanup can still use them.
|
|
5400
|
+
|
|
5401
|
+
```ts title="src/app/main.ts"
|
|
5402
|
+
import { Application } from "@warlock.js/core";
|
|
5403
|
+
|
|
5404
|
+
Application.onceBooted(() => {
|
|
5405
|
+
const consumer = startQueueConsumer();
|
|
5406
|
+
|
|
5407
|
+
Application.onShutdown(async () => {
|
|
5408
|
+
await consumer.stop(); // db / cache / http are still up here
|
|
5409
|
+
});
|
|
5410
|
+
});
|
|
5411
|
+
```
|
|
5412
|
+
|
|
5413
|
+
Hooks run **LIFO** (reverse of registration — last opened, first closed), each is awaited, and a throwing hook is caught + logged so it can't block the rest. `Application.isShuttingDown` flips `true` the moment shutdown begins — the built-in `/ready` endpoint reads it to report not-ready so a load balancer drains the instance first. Like `onceBooted`, registering after shutdown has begun runs the callback immediately.
|
|
5414
|
+
|
|
5415
|
+
The framework triggers this for you (the connectors manager runs the hooks at the start of shutdown); app code only ever registers via `onShutdown`. For the HTTP-side story — `/health`, `/ready`, and graceful request draining — see `@warlock.js/core/health-checks/SKILL.md`.
|
|
5416
|
+
|
|
5172
5417
|
## Paths
|
|
5173
5418
|
|
|
5174
5419
|
For path helpers (`appPath`, `configPath`, `uploadsPath`, …) anchored at `process.cwd()`, the `paths.*` aggregate, and the `uploads.root` config override, see [`resolve-path/SKILL.md`](../resolve-path/SKILL.md).
|
|
@@ -5324,7 +5569,9 @@ cors: {
|
|
|
5324
5569
|
|
|
5325
5570
|
- **`version` is `null` until the first `await`.** The version loader is async (it reads `package.json`). On a cold start before any framework code has run `getWarlockVersion()`, `Application.version` returns `null`. The framework does load it during bootstrap, so anywhere downstream of bootstrap is fine — controllers, services, connectors after `start()`. CLI commands without `preload.bootstrap` may see `null`.
|
|
5326
5571
|
- **`Application` is static, not a DI registration.** Don't try to inject it. There's nothing to inject — it's a class with only static members.
|
|
5327
|
-
- **`app.*` accessors return `undefined` before their connector boots — they don't throw.** `app.socket` / `app.database` / `app.http` are populated by their respective connectors during boot; until then each getter returns `undefined` (a bare `container.get(...)`). Reading them earlier (eager module-load code, the top level of a `main.ts` for the late-phase `http`/`socket`, certain CLI commands without the right `preload.connectors`) hands you `undefined`, and chaining off it throws. Safe everywhere downstream of bootstrap.
|
|
5572
|
+
- **`app.*` accessors return `undefined` before their connector boots — they don't throw.** `app.socket` / `app.database` / `app.http` are populated by their respective connectors during boot; until then each getter returns `undefined` (a bare `container.get(...)`). Reading them earlier (eager module-load code, the top level of a `main.ts` for the late-phase `http`/`socket`, certain CLI commands without the right `preload.connectors`) hands you `undefined`, and chaining off it throws. Safe everywhere downstream of bootstrap — including inside `Application.onceBooted(...)`.
|
|
5573
|
+
- **`onceBooted` is a latch, not `events.on`.** A callback registered *after* boot completed still runs (next microtask) instead of silently missing the signal — so register it wherever it reads best, including module top-level in `main.ts`. A listener that throws is caught and logged; it can't break boot or the other listeners.
|
|
5574
|
+
- **`onShutdown` runs before connectors close, not after.** That ordering is deliberate so cleanup can still use db/cache/http — but it means a hook that hangs delays connector teardown (bounded only by your process manager's kill timeout). Keep teardown fast; for HTTP draining the framework already bounds it via `http.gracefulShutdown.timeout`.
|
|
5328
5575
|
|
|
5329
5576
|
## See also
|
|
5330
5577
|
|
package/llms.txt
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
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.
|
|
17
17
|
- [encrypt-data](@warlock.js/core/encrypt-data/SKILL.md): Reversible AES-256-GCM `encrypt` / `decrypt` for secrets you need to read back; one-way HMAC-SHA256 `hmacHash` for deterministic fingerprints (lookup/dedup of encrypted columns). Keys come from `src/config/encryption.ts`. Triggers: `encrypt`, `decrypt`, `hmacHash`, `EncryptionConfigurations`, `APP_ENCRYPTION_KEY`, `APP_HMAC_KEY`; "store an API key reversibly", "fingerprint an encrypted column for lookup", "AES-256-GCM secret", "HMAC-SHA256 dedup key"; typical import `import { encrypt, decrypt, hmacHash } from "@warlock.js/core"`. Skip: password hashing — `@warlock.js/core/hash-password/SKILL.md`; config wiring — `@warlock.js/core/configure-app/SKILL.md`; competing libs Node `crypto` direct, `crypto-js`, `libsodium-wrappers`.
|
|
18
18
|
- [hash-password](@warlock.js/core/hash-password/SKILL.md): One-way bcrypt password hashing — `hashPassword` / `verifyPassword`, plus the declarative `useHashedPassword()` schema transformer that auto-hashes a model's password field on save. Salt rounds come from `src/config/encryption.ts`. Triggers: `hashPassword`, `verifyPassword`, `useHashedPassword`, `password.salt`, `bcryptjs`; "hash a user password", "verify login credentials", "auto-hash on save", "rotate a password"; typical import `import { hashPassword, verifyPassword } from "@warlock.js/core"`. Skip: reversible secrets — `@warlock.js/core/encrypt-data/SKILL.md`; the other transformers — `@warlock.js/core/use-model-transformers/SKILL.md`; config wiring — `@warlock.js/core/configure-app/SKILL.md`; competing libs `bcrypt` native, `argon2`, `scrypt`.
|
|
19
|
+
- [health-checks](@warlock.js/core/health-checks/SKILL.md): Built-in liveness (`/health`) and readiness (`/ready`) endpoints plus graceful HTTP request draining for zero-downtime deploys — the `health` registry (`health.addCheck`/`removeCheck`), the `http.health.*` and `http.gracefulShutdown.*` config, and how readiness ties into `Application.isShuttingDown`. Triggers: `health`, `health.addCheck`, `health.removeCheck`, `HealthCheck`, `/health`, `/ready`, `http.health`, `http.gracefulShutdown`, `forceCloseConnections`, "liveness probe", "readiness probe", "graceful shutdown", "drain in-flight requests", "zero-downtime deploy", "kubernetes health check", "503 until ready"; typical import `import { health } from "@warlock.js/core"`. Skip: the `Application.onShutdown` / `onceBooted` lifecycle hooks — `@warlock.js/core/use-app-context/SKILL.md`; maintenance-mode 503s — `@warlock.js/core/use-middleware/SKILL.md`; connector lifecycle — `@warlock.js/core/add-connector/SKILL.md`; competing libs `@fastify/under-pressure`, `terminus`, hand-rolled `/health` controllers.
|
|
19
20
|
- [lower-stage3-decorators](@warlock.js/core/lower-stage3-decorators/SKILL.md): Vite/Vitest plugin `lowerStage3Decorators()` that lowers TC39 Stage-3 (native) decorators with esbuild before oxc / the SSR rewrite mangles them — so `@RegisterModel`-decorated Cascade models load under Vitest 4 / Vite 8. Triggers: `lowerStage3Decorators`, "SyntaxError: Invalid or unexpected token" on a decorated class, "@(0, __vite_ssr_import__.X)()", "decorator crashes vitest", "models won't load in tests", "Vite 8 oxc decorators"; typical import `import { lowerStage3Decorators } from "@warlock.js/core"` in `vite.config.ts` / `vitest.config.ts`. Skip: writing the tests themselves — `@warlock.js/core/test-service/SKILL.md` / `@warlock.js/core/test-http/SKILL.md`; runtime migrations — `@warlock.js/cascade/write-migration/SKILL.md`.
|
|
20
21
|
- [process-image](@warlock.js/core/process-image/SKILL.md): Transform images with the `Image` class — resize, crop, rotate, format, quality, watermark, blur, etc. — using a deferred pipeline that runs only at `save()` / `toBuffer()` / `toBase64()` / `toDataUrl()` time. Requires sharp via `warlock add image`. Triggers: `Image`, `Image.fromFile`, `Image.fromBuffer`, `Image.fromUrl`, `.resize`, `.crop`, `.watermark`, `.toBuffer`, `.toDataUrl`, `.apply`; "resize an image", "generate a thumbnail", "watermark a product photo", "build an image pipeline"; typical import `import { Image } from "@warlock.js/core"`. Skip: multipart upload entry — `@warlock.js/core/upload-file/SKILL.md`; storage persistence — `@warlock.js/core/store-file/SKILL.md`; competing libs `sharp` direct, `jimp`, `imagemagick`, `gm`.
|
|
21
22
|
- [register-route](@warlock.js/core/register-route/SKILL.md): Register HTTP routes via @warlock.js/core's router — single routes, prefix groups, middleware-guarded blocks, and RESTful resource chains. Routes always live in `src/app/<module>/routes.ts`. Triggers: `router.get`, `router.post`, `router.prefix`, `router.group`, `router.route`, `guarded`; "add a route", "wire a controller to a URL", "group routes by prefix", "register a RESTful resource"; typical import `import { router } from "@warlock.js/core"`. Skip: handler shape — `@warlock.js/core/create-controller/SKILL.md`; CRUD chain details — `@warlock.js/core/build-restful/SKILL.md`; middleware authoring — `@warlock.js/core/write-middleware/SKILL.md`; competing libs `express`, `fastify`, `koa`, `@nestjs/common`.
|
|
@@ -27,8 +28,9 @@
|
|
|
27
28
|
- [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`.
|
|
28
29
|
- [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`, `stopHttpTestServer`, `testGet`, `testPost`, `testPut`, `testPatch`, `testDelete`, `expectJson`, `getTestServerUrl`, `testRequest`; "integration-test a controller", "end-to-end HTTP test", "globalSetup HTTP server", "assert status and body shape"; typical import `import { testGet, testPost, expectJson } from "@warlock.js/core"`. 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`.
|
|
29
30
|
- [test-service](@warlock.js/core/test-service/SKILL.md): Pure unit tests against services, repositories, models, and use-cases — `setupTest({ connectors })` bootstraps each Vitest worker with its own DB/cache connections so you can call your code directly. Triggers: `setupTest`, `src/test-setup.ts`, `tests.connectors`, `Application.setEnvironment`; "unit-test a service", "test a repository query", "vitest setupFiles", "skip connectors for pure-logic tests"; typical import `import { setupTest } from "@warlock.js/core"`. 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`.
|
|
31
|
+
- [update-packages](@warlock.js/core/update-packages/SKILL.md): Keep a project current with `warlock update` — bump every `@warlock.js/*` dependency in package.json to its latest published version (range operator preserved), then run the lockfile-detected package manager install. Also covers the `warlock dev` update notice and the `devServer.checkForUpdates` toggle. Triggers: `warlock update`, `--no-install`, `checkForUpdates`, `fetchLatestVersion`, `isNewerVersion`; "update warlock packages", "upgrade the framework", "is there a new warlock version", "update notice in the dev server", "bump @warlock.js/* to latest"; typical CLI `warlock update`. Skip: dev/build/start runtime — `@warlock.js/core/run-app/SKILL.md`; writing a custom command — `@warlock.js/core/write-cli-command/SKILL.md`; installing a NEW feature package (auth, mail, storage) — that is `warlock add`; releasing/publishing the framework — workspace release tooling, not this command.
|
|
30
32
|
- [upload-file](@warlock.js/core/upload-file/SKILL.md): Handle multipart file uploads — read via `request.file()` or `request.validated()`, validate with `v.file()`, save via `UploadedFile.save()` or the storage layer, transform images inline. Triggers: `UploadedFile`, `request.file`, `v.file`, `.save`, `.saveAs`, `.resize`, `.format`, `.quality`, `.image`, `.mimeType`, `.maxSize`; "accept a file upload", "validate file size and mime", "save to S3 or local disk", "resize an uploaded image on save"; typical import `import type { UploadedFile, RequestHandler } from "@warlock.js/core"`. Skip: storage drivers + presigned URLs — `@warlock.js/core/store-file/SKILL.md`; image-only transforms — `@warlock.js/core/process-image/SKILL.md`; schema rules — `@warlock.js/core/validate-input/SKILL.md`; competing libs `multer`, `formidable`, `busboy`.
|
|
31
|
-
- [use-app-context](@warlock.js/core/use-app-context/SKILL.md): Read app-wide context — the `Application` static class (env, version, uptime, runtime strategy) plus the `app` runtime accessor (live Fastify, socket.io, router, database via the DI container). Triggers: `Application.isProduction`, `Application.environment`, `Application.runtimeStrategy`, `Application.uptime`, `Application.version`, `app.http`, `app.socket`, `app.database`, `app.router`; "branch on environment", "reach the live Fastify instance", "framework version in health endpoint", "dev vs production runtime check"; typical import `import { Application, app } from "@warlock.js/core"`. Skip: path helpers — `@warlock.js/core/resolve-path/SKILL.md`; connector start order — `@warlock.js/core/add-connector/SKILL.md`; competing patterns: bare `process.env.NODE_ENV`, ad-hoc Fastify imports.
|
|
33
|
+
- [use-app-context](@warlock.js/core/use-app-context/SKILL.md): Read app-wide context — the `Application` static class (env, version, uptime, runtime strategy, boot lifecycle) plus the `app` runtime accessor (live Fastify, socket.io, router, database via the DI container). Triggers: `Application.isProduction`, `Application.environment`, `Application.runtimeStrategy`, `Application.uptime`, `Application.version`, `Application.onceBooted`, `Application.whenBooted`, `Application.isBooted`, `Application.onShutdown`, `Application.isShuttingDown`, `app.http`, `app.socket`, `app.database`, `app.router`; "branch on environment", "reach the live Fastify instance", "framework version in health endpoint", "dev vs production runtime check", "run code once the app is fully booted", "after all connectors started", "app booted hook", "run cleanup before shutdown", "graceful shutdown hook"; typical import `import { Application, app } from "@warlock.js/core"`. Skip: path helpers — `@warlock.js/core/resolve-path/SKILL.md`; connector start order — `@warlock.js/core/add-connector/SKILL.md`; competing patterns: bare `process.env.NODE_ENV`, ad-hoc Fastify imports.
|
|
32
34
|
- [use-localization](@warlock.js/core/use-localization/SKILL.md): 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`.
|
|
33
35
|
- [use-middleware](@warlock.js/core/use-middleware/SKILL.md): Attach built-in HTTP middleware to routes via the `middleware` namespace from `@warlock.js/core` — rateLimit, concurrencyLimit, maxBodySize, idempotency, maintenance, ipFilter, cache. Plus `X-Request-Id` correlation, wired automatically. Triggers: `middleware.rateLimit`, `middleware.concurrencyLimit`, `middleware.maxBodySize`, `middleware.idempotency`, `middleware.maintenance`, `middleware.ipFilter`, `middleware.cache`, `X-Request-Id`, `Idempotency-Key`; "add rate limiting", "dedupe writes by idempotency key", "cap concurrent requests", "block IPs", "cache a GET response"; typical import `import { middleware } from "@warlock.js/core"`. Skip: author custom middleware — `@warlock.js/core/write-middleware/SKILL.md`; cache singleton — `@warlock.js/cache/cache-basics/SKILL.md`; competing libs `@fastify/rate-limit` direct, `express-rate-limit`, `helmet`.
|
|
34
36
|
- [use-model-transformers](@warlock.js/core/use-model-transformers/SKILL.md): Three schema-side helpers — `useHashedPassword()` (bcrypt on save) attaches via `.addTransformer(...)`; `useComputedSlug(field?, scope?)` (auto-slug from another field) and `useComputedModel(callback)` (arbitrary computed-on-save value) attach via `v.computed(...)`. Triggers: `useHashedPassword`, `useComputedSlug`, `useComputedModel`, `.addTransformer`, `v.computed`, `ComputedCallback`; "auto-hash a password field", "auto-slug from title on save", "derive a value at write time", "declarative model transformers"; typical import `import { useHashedPassword, useComputedSlug } from "@warlock.js/core"`. Skip: bcrypt setup details — `@warlock.js/core/hash-password/SKILL.md`; repository writes — `@warlock.js/core/use-repository/SKILL.md`; output filtering — `@warlock.js/core/define-resource/SKILL.md`; competing patterns: manual `await hashPassword(input)` in services, ORM lifecycle hooks.
|
package/package.json
CHANGED
|
@@ -36,13 +36,13 @@
|
|
|
36
36
|
"@mongez/slug": "^1.0.7",
|
|
37
37
|
"@mongez/supportive-is": "^2.1.3",
|
|
38
38
|
"@mongez/time-wizard": "^1.0.6",
|
|
39
|
-
"@warlock.js/auth": "4.
|
|
40
|
-
"@warlock.js/cache": "4.
|
|
41
|
-
"@warlock.js/cascade": "4.
|
|
42
|
-
"@warlock.js/context": "4.
|
|
43
|
-
"@warlock.js/logger": "4.
|
|
44
|
-
"@warlock.js/seal": "4.
|
|
45
|
-
"@warlock.js/fs": "4.
|
|
39
|
+
"@warlock.js/auth": "4.4.0",
|
|
40
|
+
"@warlock.js/cache": "4.4.0",
|
|
41
|
+
"@warlock.js/cascade": "4.4.0",
|
|
42
|
+
"@warlock.js/context": "4.4.0",
|
|
43
|
+
"@warlock.js/logger": "4.4.0",
|
|
44
|
+
"@warlock.js/seal": "4.4.0",
|
|
45
|
+
"@warlock.js/fs": "4.4.0",
|
|
46
46
|
"chokidar": "^5.0.0",
|
|
47
47
|
"dayjs": "^1.11.19",
|
|
48
48
|
"es-module-lexer": "^2.0.0",
|
|
@@ -68,12 +68,12 @@
|
|
|
68
68
|
"react": "^19.2.3",
|
|
69
69
|
"react-dom": "^19.2.3",
|
|
70
70
|
"@react-email/render": "^2.0.5",
|
|
71
|
-
"@warlock.js/herald": "4.
|
|
71
|
+
"@warlock.js/herald": "4.4.0"
|
|
72
72
|
},
|
|
73
73
|
"bin": {
|
|
74
74
|
"warlock": "bin/warlock.js"
|
|
75
75
|
},
|
|
76
|
-
"version": "4.
|
|
76
|
+
"version": "4.4.0",
|
|
77
77
|
"type": "module",
|
|
78
78
|
"main": "./esm/index.mjs",
|
|
79
79
|
"module": "./esm/index.mjs",
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: health-checks
|
|
3
|
+
description: 'Built-in liveness (`/health`) and readiness (`/ready`) endpoints plus graceful HTTP request draining for zero-downtime deploys — the `health` registry (`health.addCheck`/`removeCheck`), the `http.health.*` and `http.gracefulShutdown.*` config, and how readiness ties into `Application.isShuttingDown`. Triggers: `health`, `health.addCheck`, `health.removeCheck`, `HealthCheck`, `/health`, `/ready`, `http.health`, `http.gracefulShutdown`, `forceCloseConnections`, "liveness probe", "readiness probe", "graceful shutdown", "drain in-flight requests", "zero-downtime deploy", "kubernetes health check", "503 until ready"; typical import `import { health } from "@warlock.js/core"`. Skip: the `Application.onShutdown` / `onceBooted` lifecycle hooks — `@warlock.js/core/use-app-context/SKILL.md`; maintenance-mode 503s — `@warlock.js/core/use-middleware/SKILL.md`; connector lifecycle — `@warlock.js/core/add-connector/SKILL.md`; competing libs `@fastify/under-pressure`, `terminus`, hand-rolled `/health` controllers.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Warlock — health checks & graceful shutdown
|
|
7
|
+
|
|
8
|
+
Two endpoints and a drain, so a load balancer never routes to an instance that isn't ready and a deploy never kills an in-flight request. All built in; no controller to hand-roll.
|
|
9
|
+
|
|
10
|
+
## The two endpoints
|
|
11
|
+
|
|
12
|
+
The HTTP connector registers them on the Fastify server during boot (before route scanning), so they exist by the time the server listens:
|
|
13
|
+
|
|
14
|
+
| Path | Probe | 200 when | 503 when |
|
|
15
|
+
| --- | --- | --- | --- |
|
|
16
|
+
| `/health` | liveness | the process is up | shutdown has begun |
|
|
17
|
+
| `/ready` | readiness | booted **and** not shutting down **and** every check passes | before boot, during shutdown, or any failing check |
|
|
18
|
+
|
|
19
|
+
**Liveness** answers "should the orchestrator RESTART me?" — it ignores dependency checks (a failing DB doesn't mean restart the pod). **Readiness** answers "should the load balancer ROUTE to me?" — it gates on boot completion, shutdown state, and your registered checks.
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
GET /health → 200 {"status":"ok"}
|
|
23
|
+
GET /ready → 200 {"status":"ok","checks":{"db":true}}
|
|
24
|
+
→ 503 {"status":"error","checks":{"db":false}}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Config
|
|
28
|
+
|
|
29
|
+
```ts title="src/config/http.ts"
|
|
30
|
+
const httpConfigurations: HttpConfigurations = {
|
|
31
|
+
health: {
|
|
32
|
+
enabled: true, // default; set false to remove both endpoints
|
|
33
|
+
path: "/health", // liveness path
|
|
34
|
+
readinessPath: "/ready", // readiness path
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Readiness checks
|
|
40
|
+
|
|
41
|
+
Readiness is `isBooted && !isShuttingDown` plus every registered check. Register a check from a connector, a `main.ts`, or anywhere:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { health } from "@warlock.js/core";
|
|
45
|
+
|
|
46
|
+
health.addCheck("db", async () => {
|
|
47
|
+
return database.isConnected();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
health.removeCheck("db"); // unregister later if needed
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
A check returns `boolean | Promise<boolean>`. **A thrown error counts as a failed check** (it's surfaced in the `checks` map + the 503, not logged — probes poll often, so a failure is a normal signal, not an error event). Keep checks cheap and fast; they run on every `/ready` poll.
|
|
54
|
+
|
|
55
|
+
## Graceful shutdown (request draining)
|
|
56
|
+
|
|
57
|
+
On SIGINT/SIGTERM the framework tears down in order: **app `onShutdown` hooks → connectors in reverse priority**. The HTTP connector's teardown drains instead of dropping:
|
|
58
|
+
|
|
59
|
+
1. `Application.isShuttingDown` flips `true` at the very start → `/ready` immediately returns 503, so the load balancer stops sending new traffic.
|
|
60
|
+
2. Fastify stops accepting new requests (answers 503 while closing) and lets in-flight ones finish.
|
|
61
|
+
3. Draining is bounded by a timeout so one stuck request can't hang the deploy — after it, the server force-closes and a warning is logged.
|
|
62
|
+
|
|
63
|
+
```ts title="src/config/http.ts"
|
|
64
|
+
const httpConfigurations: HttpConfigurations = {
|
|
65
|
+
gracefulShutdown: {
|
|
66
|
+
timeout: 10_000, // ms to wait for in-flight drain (default 10s)
|
|
67
|
+
forceCloseConnections: "idle", // close idle keep-alives, let active finish (default)
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`forceCloseConnections`: `"idle"` (default) closes idle keep-alive connections and lets active requests finish; `true` force-closes everything immediately; `false` waits for every connection.
|
|
73
|
+
|
|
74
|
+
## The zero-downtime deploy flow
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
SIGTERM → isShuttingDown = true → /ready returns 503
|
|
78
|
+
→ LB stops routing new requests to this instance
|
|
79
|
+
→ in-flight requests drain (up to gracefulShutdown.timeout)
|
|
80
|
+
→ app onShutdown hooks already ran (db/cache still up)
|
|
81
|
+
→ connectors close in reverse → process exits
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
For an even smoother handoff, give the load balancer time to observe the 503 before the server closes — e.g. an `onShutdown` hook with a short `sleep` matched to your LB's health-check interval.
|
|
85
|
+
|
|
86
|
+
## Gotchas
|
|
87
|
+
|
|
88
|
+
- **`/health` is registered straight on Fastify, not the app router.** It's infra, so it's immune to HMR and route scanning — but if your app also defines a `/health` route you'll have a collision. Rename via `http.health.path`.
|
|
89
|
+
- **Readiness needs a finished boot.** Before `Application.isBooted` (e.g. while late-phase connectors are still starting) `/ready` is 503 by design — that's the point.
|
|
90
|
+
- **A hanging `onShutdown` hook delays the drain.** App hooks run before connector teardown and are only bounded by your process manager's kill timeout; keep them fast. The HTTP drain itself is bounded by `gracefulShutdown.timeout`.
|
|
91
|
+
- **The `maintenance` middleware is a different 503.** It allowlists `/health` by default so probes pass during maintenance — but maintenance mode is operator-toggled downtime, not readiness. See `@warlock.js/core/use-middleware/SKILL.md`.
|
|
92
|
+
|
|
93
|
+
## See also
|
|
94
|
+
|
|
95
|
+
- [`use-app-context/SKILL.md`](../use-app-context/SKILL.md) — `Application.onShutdown` / `onceBooted` / `isShuttingDown`, the lifecycle hooks the endpoints build on.
|
|
96
|
+
- [`configure-app/SKILL.md`](../configure-app/SKILL.md) — the `src/config/http.ts` shape.
|
|
97
|
+
- [`add-connector/SKILL.md`](../add-connector/SKILL.md) — connector boot/shutdown order, where draining slots in.
|
|
98
|
+
- [`use-middleware/SKILL.md`](../use-middleware/SKILL.md) — maintenance mode and other built-in middleware.
|
package/skills/run-app/SKILL.md
CHANGED
|
@@ -66,6 +66,7 @@ export default defineConfig({
|
|
|
66
66
|
exclude: ["**/node_modules/**", "**/dist/**", "**/.warlock/**", "**/.git/**"],
|
|
67
67
|
},
|
|
68
68
|
generateTypings: true, // background type generation
|
|
69
|
+
checkForUpdates: true, // notify on a newer @warlock.js/core at dev start
|
|
69
70
|
healthCheckers: [...] /* or false */,
|
|
70
71
|
transpileCacheDebug: false, // name cache files <slug>.<hash>.js w/ // @source markers
|
|
71
72
|
},
|
|
@@ -76,6 +77,7 @@ export default defineConfig({
|
|
|
76
77
|
- **`generateTypings`** — turn off if you're committing generated typings and don't want them rewritten on every boot. The `--skip-typings` flag is the per-run version.
|
|
77
78
|
- **`healthCheckers`** — custom file health checker contracts (or `false` to disable). The `--skip-health` flag is the per-run version.
|
|
78
79
|
- **`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.
|
|
80
|
+
- **`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. Run `warlock update` to upgrade. See [`update-packages/SKILL.md`](../update-packages/SKILL.md).
|
|
79
81
|
|
|
80
82
|
## `warlock build` — production bundle
|
|
81
83
|
|
|
@@ -258,3 +260,4 @@ NODE_OPTIONS=--max-old-space-size=4096 yarn warlock start
|
|
|
258
260
|
- [`configure-app/SKILL.md`](../configure-app/SKILL.md) — `warlock.config.ts` shape and `defineConfig`.
|
|
259
261
|
- [`use-app-context/SKILL.md`](../use-app-context/SKILL.md) — `Application.environment` vs `Application.runtimeStrategy`.
|
|
260
262
|
- [`add-connector/SKILL.md`](../add-connector/SKILL.md) — Early vs Late connector phases (why HTTP/socket boot late in dev).
|
|
263
|
+
- [`update-packages/SKILL.md`](../update-packages/SKILL.md) — `warlock update` + the dev-server update notice (`devServer.checkForUpdates`).
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: update-packages
|
|
3
|
+
description: 'Keep a project current with `warlock update` — bump every `@warlock.js/*` dependency in package.json to its latest published version (range operator preserved), then run the lockfile-detected package manager install. Also covers the `warlock dev` update notice and the `devServer.checkForUpdates` toggle. Triggers: `warlock update`, `--no-install`, `checkForUpdates`, `fetchLatestVersion`, `isNewerVersion`; "update warlock packages", "upgrade the framework", "is there a new warlock version", "update notice in the dev server", "bump @warlock.js/* to latest"; typical CLI `warlock update`. Skip: dev/build/start runtime — `@warlock.js/core/run-app/SKILL.md`; writing a custom command — `@warlock.js/core/write-cli-command/SKILL.md`; installing a NEW feature package (auth, mail, storage) — that is `warlock add`; releasing/publishing the framework — workspace release tooling, not this command.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Warlock — update the framework
|
|
7
|
+
|
|
8
|
+
`warlock update` upgrades a project's Warlock packages in one step, and `warlock dev` tells you when an upgrade is available. Because the whole `@warlock.js/*` family ships in **lockstep** — every package shares one version — keeping them in sync is the normal case, and this command does exactly that.
|
|
9
|
+
|
|
10
|
+
## `warlock update`
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
warlock update # bump every @warlock.js/* dep to latest, then install
|
|
14
|
+
warlock update --no-install # rewrite package.json only; install yourself later
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
| Flag | Type | Purpose |
|
|
18
|
+
| -------------- | ------- | ----------------------------------------------------------------------------------- |
|
|
19
|
+
| `--no-install` | boolean | Rewrite the versions in `package.json` without running the package manager install. |
|
|
20
|
+
|
|
21
|
+
What it does, in order:
|
|
22
|
+
|
|
23
|
+
1. Reads the project's root `package.json`.
|
|
24
|
+
2. Collects every `@warlock.js/*` package across `dependencies` and `devDependencies`. Only the `@warlock.js/` scope is considered — `create-warlock` and unrelated dependencies are never touched.
|
|
25
|
+
3. Looks up each package's latest version on the npm registry, in parallel.
|
|
26
|
+
4. Rewrites each matching spec, **preserving the range operator**: `^4.2.0` → `^4.3.0`, `~4.2.0` → `~4.3.0`, an exact `4.2.0` → `4.3.0`. Specs that are not a plain semver — `workspace:*`, `*`, `latest`, git/file URLs — are left exactly as written, and any package already at or ahead of latest is skipped.
|
|
27
|
+
5. Runs the project's install to reconcile `node_modules` — `npm install` / `yarn install` / `pnpm install`, chosen by the lockfile present (`package-lock.json` / `yarn.lock` / `pnpm-lock.yaml`, npm as the fallback). Skipped with `--no-install`.
|
|
28
|
+
|
|
29
|
+
Re-running on an already-current project is a no-op: nothing resolves as newer, so it prints "All @warlock.js packages are already up to date" and exits without writing or installing.
|
|
30
|
+
|
|
31
|
+
## The dev-server update notice
|
|
32
|
+
|
|
33
|
+
On start, `warlock dev` checks npm for a newer `@warlock.js/core` and prints a one-line notice when one exists:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
⚡ A new version of Warlock.js is available 4.2.11 → 4.3.0
|
|
37
|
+
Run npx warlock update to update all @warlock.js packages
|
|
38
|
+
Changelog https://warlock.js.org/changelog/
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Core's version stands in for the whole family (lockstep), so a single lookup is enough. The check is **best-effort and non-blocking** — it runs fire-and-forget after the server is ready, never delays or breaks startup, and stays silent on any failure (offline, registry down, timeout).
|
|
42
|
+
|
|
43
|
+
It is automatically skipped when:
|
|
44
|
+
|
|
45
|
+
- `process.env.CI` is set (CI runs),
|
|
46
|
+
- stdout is not a TTY (piped / non-interactive shells),
|
|
47
|
+
- `process.env.NO_UPDATE_NOTIFIER` is set, or
|
|
48
|
+
- `devServer.checkForUpdates` is `false`.
|
|
49
|
+
|
|
50
|
+
```ts title="warlock.config.ts"
|
|
51
|
+
import { defineConfig } from "@warlock.js/core";
|
|
52
|
+
|
|
53
|
+
export default defineConfig({
|
|
54
|
+
devServer: {
|
|
55
|
+
checkForUpdates: false, // silence the "update available" notice
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Building blocks
|
|
61
|
+
|
|
62
|
+
Two small zero-dependency utilities back the tooling and are exported from `@warlock.js/core`:
|
|
63
|
+
|
|
64
|
+
- `fetchLatestVersion(name, timeoutMs?)` — the latest published version of an npm package, or `undefined` on any failure. Never throws.
|
|
65
|
+
- `isNewerVersion(latest, current)` — `true` when `latest` is a strictly newer semver than `current`. Compares `major.minor.patch` and orders a stable release above its prereleases.
|
|
66
|
+
|
|
67
|
+
## Gotchas
|
|
68
|
+
|
|
69
|
+
- **Only the `@warlock.js/` scope is updated.** Mongez packages (`@mongez/*`), `create-warlock`, and everything else are left alone — update those with your package manager directly.
|
|
70
|
+
- **Non-semver specs are intentionally skipped.** A `workspace:*` or `*` dependency stays as written; `update` will not pin it to a concrete version.
|
|
71
|
+
- **The notice never blocks dev.** If npm is unreachable, `warlock dev` behaves exactly as before — no delay, no error.
|
|
72
|
+
- **`warlock update` is not `warlock add`.** `add` installs a *new* feature package and runs its setup hooks; `update` only bumps the versions of packages you already depend on.
|
|
73
|
+
|
|
74
|
+
## See also
|
|
75
|
+
|
|
76
|
+
- [`run-app/SKILL.md`](../run-app/SKILL.md) — `warlock dev` / `build` / `start` and the `devServer.*` config knobs.
|
|
77
|
+
- [`write-cli-command/SKILL.md`](../write-cli-command/SKILL.md) — author your own `warlock <cmd>`.
|
|
78
|
+
- [`configure-app/SKILL.md`](../configure-app/SKILL.md) — `warlock.config.ts` shape and `defineConfig`.
|