@zerotal/arch 1.7.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 (160) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/LICENSE +21 -0
  3. package/README.md +67 -0
  4. package/api-surface.md +283 -0
  5. package/docs/about.md +676 -0
  6. package/docs/admin/actions.md +183 -0
  7. package/docs/admin/auth.md +89 -0
  8. package/docs/admin/dashboard.md +155 -0
  9. package/docs/admin/extending-ui.md +229 -0
  10. package/docs/admin/extending.md +205 -0
  11. package/docs/admin/forms.md +155 -0
  12. package/docs/admin/index.md +144 -0
  13. package/docs/admin/operations.md +224 -0
  14. package/docs/admin/references.md +174 -0
  15. package/docs/admin/resources.md +132 -0
  16. package/docs/admin/structure.md +158 -0
  17. package/docs/admin/tables.md +185 -0
  18. package/docs/admin/testing.md +138 -0
  19. package/docs/ai.md +373 -0
  20. package/docs/application.md +435 -0
  21. package/docs/arch.md +263 -0
  22. package/docs/assets.md +755 -0
  23. package/docs/audit.md +367 -0
  24. package/docs/authentication.md +1000 -0
  25. package/docs/authorization.md +529 -0
  26. package/docs/broadcasting/channels.md +170 -0
  27. package/docs/broadcasting/client.md +108 -0
  28. package/docs/broadcasting/events.md +190 -0
  29. package/docs/broadcasting/index.md +163 -0
  30. package/docs/broadcasting/references.md +84 -0
  31. package/docs/broadcasting/testing.md +132 -0
  32. package/docs/cache.md +387 -0
  33. package/docs/carbon.md +830 -0
  34. package/docs/changelog.md +465 -0
  35. package/docs/client/auth.md +113 -0
  36. package/docs/client/errors.md +139 -0
  37. package/docs/client/files.md +118 -0
  38. package/docs/client/index.md +128 -0
  39. package/docs/client/references.md +58 -0
  40. package/docs/client/requests.md +131 -0
  41. package/docs/client/resilience.md +141 -0
  42. package/docs/client/testing.md +146 -0
  43. package/docs/commands.md +450 -0
  44. package/docs/components.md +2427 -0
  45. package/docs/config-system.md +258 -0
  46. package/docs/container.md +596 -0
  47. package/docs/context.md +579 -0
  48. package/docs/contributing.md +164 -0
  49. package/docs/controllers.md +354 -0
  50. package/docs/conventions.md +466 -0
  51. package/docs/cookies.md +101 -0
  52. package/docs/csrf.md +206 -0
  53. package/docs/database.md +476 -0
  54. package/docs/deployment.md +434 -0
  55. package/docs/devtools.md +1013 -0
  56. package/docs/email-verification.md +320 -0
  57. package/docs/encryption.md +380 -0
  58. package/docs/errors.md +317 -0
  59. package/docs/events.md +420 -0
  60. package/docs/flow/components.md +846 -0
  61. package/docs/flow/decorators.md +458 -0
  62. package/docs/flow/events.md +420 -0
  63. package/docs/flow/forms.md +551 -0
  64. package/docs/flow/index.md +568 -0
  65. package/docs/flow/layouts.md +793 -0
  66. package/docs/flow/lifecycle.md +336 -0
  67. package/docs/flow/pagination.md +402 -0
  68. package/docs/flow/performance.md +262 -0
  69. package/docs/flow/references.md +317 -0
  70. package/docs/flow/routing.md +550 -0
  71. package/docs/flow/testing.md +447 -0
  72. package/docs/getting-started.md +286 -0
  73. package/docs/health.md +269 -0
  74. package/docs/helpers.md +423 -0
  75. package/docs/i18n.md +414 -0
  76. package/docs/index.md +65 -0
  77. package/docs/inertia/build.md +95 -0
  78. package/docs/inertia/devtools.md +197 -0
  79. package/docs/inertia/index.md +267 -0
  80. package/docs/inertia/middleware.md +81 -0
  81. package/docs/inertia/props.md +507 -0
  82. package/docs/inertia/references.md +144 -0
  83. package/docs/inertia/rendering.md +189 -0
  84. package/docs/inertia/ssr.md +102 -0
  85. package/docs/inspirations.md +257 -0
  86. package/docs/lifecycle.md +331 -0
  87. package/docs/lock.md +454 -0
  88. package/docs/logger.md +517 -0
  89. package/docs/media.md +582 -0
  90. package/docs/middleware.md +509 -0
  91. package/docs/migrations.md +529 -0
  92. package/docs/monitor.md +377 -0
  93. package/docs/notifications.md +848 -0
  94. package/docs/orm/casts.md +365 -0
  95. package/docs/orm/factories.md +231 -0
  96. package/docs/orm/index.md +663 -0
  97. package/docs/orm/lifecycle.md +421 -0
  98. package/docs/orm/queries.md +692 -0
  99. package/docs/orm/relationships.md +440 -0
  100. package/docs/orm/serialization.md +251 -0
  101. package/docs/package-development.md +536 -0
  102. package/docs/pagination.md +340 -0
  103. package/docs/password-reset.md +335 -0
  104. package/docs/providers.md +447 -0
  105. package/docs/query-builder.md +465 -0
  106. package/docs/queue.md +549 -0
  107. package/docs/rate-limiting.md +253 -0
  108. package/docs/responses.md +336 -0
  109. package/docs/roles-and-2fa.md +418 -0
  110. package/docs/routing.md +1008 -0
  111. package/docs/scaffolding.md +335 -0
  112. package/docs/scheduler.md +533 -0
  113. package/docs/seeding.md +229 -0
  114. package/docs/session.md +451 -0
  115. package/docs/social.md +524 -0
  116. package/docs/storage.md +622 -0
  117. package/docs/structure.md +230 -0
  118. package/docs/support-policy.md +114 -0
  119. package/docs/telemetry.md +505 -0
  120. package/docs/tenancy.md +608 -0
  121. package/docs/testing/browser.md +153 -0
  122. package/docs/testing/console.md +130 -0
  123. package/docs/testing/database.md +238 -0
  124. package/docs/testing/flow-browser.md +216 -0
  125. package/docs/testing/http.md +441 -0
  126. package/docs/testing/index.md +240 -0
  127. package/docs/testing/mocking.md +410 -0
  128. package/docs/upgrade.md +192 -0
  129. package/docs/validator.md +549 -0
  130. package/docs/view.md +453 -0
  131. package/package.json +60 -0
  132. package/src/bin/mcp.ts +73 -0
  133. package/src/config.ts +63 -0
  134. package/src/errors.ts +27 -0
  135. package/src/index.ts +56 -0
  136. package/src/install/ArchInstallCommand.ts +189 -0
  137. package/src/install/detect.ts +98 -0
  138. package/src/install/guidelines.ts +290 -0
  139. package/src/install/markers.ts +77 -0
  140. package/src/install/mcpConfig.ts +81 -0
  141. package/src/mcp/index.ts +27 -0
  142. package/src/mcp/jsonrpc.ts +133 -0
  143. package/src/mcp/server.ts +307 -0
  144. package/src/mcp/stdio.ts +111 -0
  145. package/src/mcp/types.ts +146 -0
  146. package/src/probe/ArchProbeCommand.ts +48 -0
  147. package/src/probe/sentinel.ts +13 -0
  148. package/src/probe/topics.ts +291 -0
  149. package/src/provider/ArchProvider.ts +101 -0
  150. package/src/tools/_probe.ts +164 -0
  151. package/src/tools/apiSurface.ts +241 -0
  152. package/src/tools/appInfo.ts +88 -0
  153. package/src/tools/baselines.ts +210 -0
  154. package/src/tools/context.ts +37 -0
  155. package/src/tools/doctor.ts +93 -0
  156. package/src/tools/index.ts +50 -0
  157. package/src/tools/logs.ts +265 -0
  158. package/src/tools/routes.ts +133 -0
  159. package/src/tools/schema.ts +119 -0
  160. package/src/tools/searchDocs.ts +345 -0
@@ -0,0 +1,286 @@
1
+ ---
2
+ title: Getting Started
3
+ description: Scaffold, configure, and run your first Zerotal application on Bun in a few minutes.
4
+ ---
5
+
6
+ # Getting Started
7
+
8
+ Zerotal is a full-stack framework for [Bun](https://bun.sh). This guide walks you
9
+ from a blank machine to a running app with a route, a model, and a service
10
+ provider.
11
+
12
+ ## Requirements
13
+
14
+ - **Bun** ≥ 1.1 — [install](https://bun.sh/docs/installation)
15
+ - A PostgreSQL, MySQL, or SQLite database (SQLite requires nothing extra)
16
+
17
+ ## Create a new project
18
+
19
+ ```bash
20
+ # in your project root's parent directory
21
+ bun create zerotal my-app # or: bunx create-zerotal my-app
22
+ cd my-app
23
+ ```
24
+
25
+ The scaffolder prompts for a project name and a template, then generates a
26
+ ready-to-run project and installs dependencies. The **database** prompt only
27
+ appears for the API template:
28
+
29
+ | Prompt | Options |
30
+ | ------------ | --------------------------------------------------------------------------- |
31
+ | Project name | Defaults to `my-zerotal-app` |
32
+ | Template | **API**, **Flow**, **React**, **Vue**, or **Minimal** (see the guide below) |
33
+ | Database | **SQLite** (zero setup), **PostgreSQL**, or **MySQL** — API template only |
34
+
35
+ It writes a fresh `APP_KEY` into the generated `.env.example` for you (no manual
36
+ generation needed). For Postgres/MySQL it reminds you to set `DATABASE_URL`
37
+ before migrating.
38
+
39
+ ### Which template should I use?
40
+
41
+ - **API** — JSON REST API with core, [ORM](/docs/orm), [auth](/docs/authentication),
42
+ [validation](/docs/validator), and [testing](/docs/testing/index). The default for a backend service.
43
+ - **Flow** — server-driven reactive UI ([Flow](/docs/flow) pages, top nav, Tailwind).
44
+ - **React** — [Inertia](/docs/inertia) + React SPA with file-based routes and Tailwind.
45
+ - **Vue** — Inertia + Vue SPA with file-based routes and Tailwind.
46
+ - **Minimal** — a single page with JSX views and Tailwind on the bare framework.
47
+
48
+ > **Note** — Only the **API** template ships a database config and migrations.
49
+ > The Flow, React, Vue, and Minimal templates start without a database.
50
+
51
+ ## Project structure
52
+
53
+ The **API** template generates the following. Other templates vary (SPA
54
+ templates add a `resources/` frontend, for example):
55
+
56
+ ```
57
+ # my-app/ (API template)
58
+ my-app/
59
+ ├── app/
60
+ │ ├── controllers/ # HTTP controllers
61
+ │ ├── middleware/ # Custom middleware
62
+ │ └── models/ # ORM models (auto-discovered)
63
+ ├── bootstrap/
64
+ │ └── app.ts # Builds the Application (providers + routing)
65
+ ├── config/ # Config files (database, session, queue…) — auto-loaded
66
+ ├── database/
67
+ │ └── migrations/ # Database migrations
68
+ ├── routes/
69
+ │ └── index.ts # Route definitions
70
+ ├── tests/ # Test suites
71
+ ├── zt.ts # Managed CLI entry point — do not edit
72
+ └── .env # Environment variables (git-ignored)
73
+ ```
74
+
75
+ `zt.ts` is the entry point for **everything** — `bun zt serve`, `migrate`,
76
+ `test`, and every `make:*` generator run through it. It boots `bootstrap/app.ts`,
77
+ which defines your [Application](/docs/application). Anything under `app/models/`,
78
+ `app/providers/`, and `config/` is [auto-discovered](/docs/conventions) at boot —
79
+ no manual registration.
80
+
81
+ > **Tip** — `app/providers/` is auto-discovered when present. The API template
82
+ > doesn't generate one; run `bun zt make:provider …` and the framework picks it
83
+ > up at boot.
84
+
85
+ ## Environment setup
86
+
87
+ The scaffolder generates `.env.example` with a ready-made `APP_KEY`. Copy it to
88
+ `.env`:
89
+
90
+ ```bash
91
+ # in your project root
92
+ cp .env.example .env
93
+ ```
94
+
95
+ ```ini
96
+ # .env
97
+ APP_ENV=development
98
+ APP_KEY=base64:… # pre-generated; rotate with `bun zt key:generate`
99
+ DATABASE_URL=./database/db.sqlite
100
+ SESSION_SECRET=change-me-in-production
101
+ ```
102
+
103
+ The database is configured with a single **`DATABASE_URL`** — SQLite uses a file
104
+ path, Postgres/MySQL use a connection string:
105
+
106
+ ```ini
107
+ # .env — alternative DATABASE_URL forms
108
+ # DATABASE_URL=postgres://user:pass@localhost:5432/my_app
109
+ # DATABASE_URL=mysql://root@localhost:3306/my_app
110
+ ```
111
+
112
+ > **Danger** — Change `SESSION_SECRET` before deploying. The placeholder value is
113
+ > not safe for production.
114
+
115
+ See [Configuration](/docs/config-system) for the full config system.
116
+
117
+ ## Run migrations
118
+
119
+ ```bash
120
+ # in your project root
121
+ bun zt migrate
122
+ ```
123
+
124
+ ## Start the dev server
125
+
126
+ ```bash
127
+ # in your project root
128
+ bun dev # → bun zt serve --dev
129
+ ```
130
+
131
+ Your app is now running at **http://localhost:3000** with hot reload enabled —
132
+ `--dev` watches your files and hot-swaps routes without dropping connections. The
133
+ generated `package.json` also gives you `bun start` (production serve) and
134
+ `bun test`.
135
+
136
+ ## Your first route
137
+
138
+ Open `routes/index.ts` and add:
139
+
140
+ ```typescript
141
+ // routes/index.ts
142
+ import { Router } from "zerotal";
143
+ import { PostController } from "../app/controllers/PostController.ts";
144
+
145
+ Router.get("/posts", PostController, "index");
146
+ Router.post("/posts", PostController, "store");
147
+ ```
148
+
149
+ Create the controller:
150
+
151
+ ```typescript
152
+ // app/controllers/PostController.ts
153
+ import type { HttpContext } from "zerotal";
154
+
155
+ export class PostController {
156
+ async index(ctx: HttpContext) {
157
+ return ctx.json({ posts: [] });
158
+ }
159
+
160
+ async store(ctx: HttpContext) {
161
+ const body = await ctx.request.json();
162
+ return ctx.json({ created: body }, 201);
163
+ }
164
+ }
165
+ ```
166
+
167
+ > **Note** — Every controller action receives the request
168
+ > [HttpContext](/docs/context) directly. Read route params and resolved model
169
+ > bindings from `ctx.params`; type them with the generic, e.g.
170
+ > `ctx: HttpContext<{ post: Post }>`.
171
+
172
+ ## Your first model
173
+
174
+ ```bash
175
+ # in your project root
176
+ bun zt make:model Post --migration
177
+ ```
178
+
179
+ This generates `app/models/Post.ts` and a matching migration. Models in
180
+ `app/models/` are auto-discovered at boot — no manual registration, and the table
181
+ name is derived by convention (`Post` → `posts`), so `@table` is optional. The
182
+ same applies to observers, policies, and event listeners; see
183
+ [Conventions](/docs/conventions).
184
+
185
+ Open the migration and define your columns:
186
+
187
+ ```typescript
188
+ // database/migrations/xxxx_create_posts_table.ts
189
+ import { Migration, Schema } from "@zerotal/orm";
190
+
191
+ export default class CreatePostsTable extends Migration {
192
+ async up(): Promise<void> {
193
+ await Schema.create("posts", (table) => {
194
+ table.increments("id");
195
+ table.string("title");
196
+ table.text("body");
197
+ table.string("slug").unique();
198
+ table.timestamps();
199
+ });
200
+ }
201
+
202
+ async down(): Promise<void> {
203
+ await Schema.drop("posts");
204
+ }
205
+ }
206
+ ```
207
+
208
+ Run it:
209
+
210
+ ```bash
211
+ # in your project root
212
+ bun zt migrate
213
+ ```
214
+
215
+ Your model is ready to use:
216
+
217
+ ```typescript
218
+ // in a controller or service
219
+ import { Post } from "../models/Post.ts";
220
+
221
+ const posts = await Post.query().latest().limit(10).get();
222
+ const post = await Post.find(1);
223
+ const fresh = await Post.create({ title: "Hello", body: "...", slug: "hello" });
224
+ ```
225
+
226
+ ## Adding a service provider
227
+
228
+ Providers are where you wire up your own bindings. Scaffold one:
229
+
230
+ ```bash
231
+ # in your project root
232
+ bun zt make:provider AppServiceProvider
233
+ ```
234
+
235
+ ```typescript
236
+ // app/providers/AppServiceProvider.ts
237
+ import { ServiceProvider } from "zerotal";
238
+ import { PaymentGateway } from "../services/PaymentGateway.ts";
239
+ import { StripeGateway } from "../services/StripeGateway.ts";
240
+
241
+ export class AppServiceProvider extends ServiceProvider {
242
+ onRegister(): void {
243
+ this.app.container.singleton(
244
+ PaymentGateway,
245
+ () => new StripeGateway({ key: Bun.env.STRIPE_KEY! }),
246
+ );
247
+ }
248
+ }
249
+ ```
250
+
251
+ Anything under `app/providers/` is **auto-discovered and registered at boot** — no
252
+ manual wiring needed. You only touch `bootstrap/app.ts` when you need to control
253
+ _ordering_ (e.g. one provider must register before another). See
254
+ [Service Providers](/docs/providers) and [Conventions](/docs/conventions).
255
+
256
+ ## Available commands
257
+
258
+ Everything runs through `zt.ts`. The most common:
259
+
260
+ ```bash
261
+ # in your project root
262
+ bun zt make:model Name --migration # model + migration
263
+ bun zt make:controller Name # controller class
264
+ bun zt make:middleware Name # middleware class
265
+ bun zt make:provider Name # service provider
266
+ bun zt make:job Name # queue job
267
+ bun zt migrate # run pending migrations
268
+ bun zt migrate:rollback # roll back last batch
269
+ bun zt migrate:status # show migration status
270
+ bun zt migrate:fresh # roll back all + re-migrate
271
+ bun zt key:generate # generate a fresh APP_KEY
272
+ bun zt queue:work # start the queue worker
273
+ ```
274
+
275
+ There are many more (`make:policy`, `make:factory`, `make:seeder`, `make:page`,
276
+ `db:seed`, `schedule:list`, …). Run `bun zt list` to see them all, or see
277
+ [Commands](/docs/commands) for the full list and
278
+ [Scaffolding](/docs/scaffolding) for what each generator produces.
279
+
280
+ ## Next steps
281
+
282
+ - [Routing](/docs/routing) — route definitions, groups, parameters, and file-based routes.
283
+ - [Controllers](/docs/controllers) — move route logic into classes.
284
+ - [ORM](/docs/orm) — models, queries, and relationships.
285
+ - [Configuration](/docs/config-system) — how config and auto-discovery work.
286
+ - [Flow](/docs/flow) — server-driven reactive UI (or [Inertia](/docs/inertia) for a React/Vue SPA).
package/docs/health.md ADDED
@@ -0,0 +1,269 @@
1
+ ---
2
+ title: Health Checks
3
+ description: Serve a probe-friendly /health endpoint that reports liveness and the readiness of your app's dependencies.
4
+ ---
5
+
6
+ # Health Checks
7
+
8
+ Zerotal serves a `/health` endpoint for liveness/readiness probes (Kubernetes,
9
+ load balancers, uptime monitors). It is **on by default outside production** and
10
+ **off in production until you enable it and protect it**.
11
+
12
+ Health checks are a core built-in — there is no package to install and no
13
+ provider to register. You configure the endpoint under the `health` key of
14
+ `config/app.ts`, then register dependency checks via the `Health` registry.
15
+
16
+ ## Getting Started
17
+
18
+ Health checks are built into `@zerotal/core` — nothing to install and no
19
+ provider to register. The endpoint is registered when the application starts:
20
+
21
+ ```typescript
22
+ import { Health } from "zerotal";
23
+ ```
24
+
25
+ ## Defaults
26
+
27
+ | Environment | Default | Access |
28
+ | ---------------------------- | -------- | ------------------------------------------------------------ |
29
+ | development / test / staging | enabled | open |
30
+ | production | disabled | requires a `secret` (`?key=` or `X-Health-Key`) once enabled |
31
+
32
+ ## Configuration
33
+
34
+ The health config lives under the `health` key of `config/app.ts`. Use the
35
+ `AppConfig()` helper so every field stays type-checked:
36
+
37
+ ```typescript
38
+ // config/app.ts
39
+ import { env } from "zerotal";
40
+ import { AppConfig } from "zerotal/config";
41
+
42
+ export default AppConfig({
43
+ name: "My App",
44
+ health: {
45
+ enabled: true, // default: on outside production, off in production
46
+ path: "/health", // default: '/health'
47
+ secret: env("HEALTH_KEY"), // required in production
48
+ showDetails: true, // false → bare { "status": "ok" }
49
+ },
50
+ });
51
+ ```
52
+
53
+ | Field | Required | Default | Description |
54
+ | ------------- | -------- | --------------- | ---------------------------------------------------------------------- |
55
+ | `enabled` | no | on outside prod | Serve the endpoint. Off in production unless explicitly set. |
56
+ | `path` | no | `"/health"` | Route path the endpoint is registered at. |
57
+ | `secret` | in prod | unset | Shared secret, supplied via `?key=` or the `X-Health-Key` header. |
58
+ | `showDetails` | no | `true` | Include per-check details; `false` collapses the body to `{ status }`. |
59
+
60
+ > **Note** — The endpoint is only registered when the app runs in the `web` or
61
+ > `worker` environment. Console commands and tests never expose it.
62
+
63
+ > **Note** — A legacy `config/health.ts` namespace and a bare `health: true`
64
+ > boolean on `config/app.ts` are still honoured for back-compat. New projects
65
+ > should author the `health` object directly under `config/app.ts`.
66
+
67
+ ### Protecting the endpoint
68
+
69
+ In production the endpoint refuses to serve unless a `secret` is configured —
70
+ so it can never be exposed unprotected by accident. Call it with the key:
71
+
72
+ ```bash
73
+ # from a probe or terminal
74
+ curl "https://your-app.com/health?key=YOUR_HEALTH_KEY"
75
+ # or via header
76
+ curl -H "X-Health-Key: YOUR_HEALTH_KEY" https://your-app.com/health
77
+ ```
78
+
79
+ Access control resolves as follows:
80
+
81
+ - **A `secret` is set** — the request must supply a matching `?key=` or
82
+ `X-Health-Key`. A wrong or missing key returns **HTTP 401**.
83
+ - **Production with no `secret`** — the endpoint is refused with **HTTP 503**;
84
+ it must be protected before it can be exposed.
85
+ - **Otherwise** (development, no secret) — access is open.
86
+
87
+ You can also leave `secret` unset and instead wrap the path in your own
88
+ auth/IP-allowlist middleware.
89
+
90
+ > **Danger** — In production, set a `secret` (or wrap the path in your own auth)
91
+ > before enabling the endpoint. The detailed report exposes dependency status,
92
+ > versions, and memory; an unprotected `/health` leaks that to anyone.
93
+
94
+ ## Registering checks
95
+
96
+ Out of the box the endpoint is a **liveness** probe (the process answered) plus
97
+ a built-in `runtime` probe (memory, Bun version, in-flight requests). It becomes
98
+ a **readiness** probe as you register dependency checks against the `Health`
99
+ registry exported from `zerotal`:
100
+
101
+ ```typescript
102
+ // in a bootstrap file (e.g. bootstrap/health.ts)
103
+ import { Health } from "zerotal/health";
104
+
105
+ // Critical checks drive the overall status and the HTTP 503 response.
106
+ Health.register(
107
+ "database",
108
+ async () => {
109
+ await DB.raw("select 1");
110
+ },
111
+ { critical: true },
112
+ );
113
+
114
+ // Non-critical failures degrade the report without failing readiness.
115
+ Health.register("cache", async () => {
116
+ await Cache.put("__health", "1", 5);
117
+ return { meta: { driver: "redis" } };
118
+ });
119
+
120
+ Health.register("disk", () => {
121
+ const free = getFreeBytes();
122
+ return free < 100_000_000
123
+ ? { status: "degraded", message: "low disk" }
124
+ : { status: "ok", meta: { freeBytes: free } };
125
+ });
126
+ ```
127
+
128
+ A check is healthy when it returns (or returns `{ status: 'ok' }`), `degraded`
129
+ when it returns `{ status: 'degraded' }`, and `down` when it returns
130
+ `{ status: 'down' }` **or throws** (the thrown message becomes the check's
131
+ `message`).
132
+
133
+ ### Overall status
134
+
135
+ The aggregate status is derived from every check:
136
+
137
+ - **`down`** (HTTP 503) — any `critical` check is down.
138
+ - **`degraded`** (HTTP 200) — a non-critical check is down or degraded.
139
+ - **`ok`** (HTTP 200) — everything passed.
140
+
141
+ > **Tip** — Mark only the dependencies your app genuinely cannot serve requests
142
+ > without (your primary database, say) as `critical`. A flaky cache should
143
+ > `degrade` the report, not knock the whole app out of the load balancer.
144
+
145
+ ## What the endpoint returns
146
+
147
+ ```json
148
+ {
149
+ "status": "ok",
150
+ "app": { "name": "my-app", "version": "1.4.0", "environment": "production" },
151
+ "uptime": 3672,
152
+ "timestamp": "2026-06-15T10:32:00.000Z",
153
+ "checks": {
154
+ "runtime": {
155
+ "status": "ok",
156
+ "durationMs": 0,
157
+ "critical": false,
158
+ "meta": { "memory": { "rss": 84934656 }, "bun": "1.3.14", "pendingRequests": 3 }
159
+ },
160
+ "database": { "status": "ok", "durationMs": 4, "critical": true },
161
+ "cache": { "status": "ok", "durationMs": 2, "critical": false, "meta": { "driver": "redis" } }
162
+ }
163
+ }
164
+ ```
165
+
166
+ `uptime` is whole seconds since the process started. With `showDetails: false`
167
+ the body collapses to `{ "status": "ok" }` — handy for public uptime monitors
168
+ that should not see internal details.
169
+
170
+ ## Testing
171
+
172
+ Set your suite up once as described in [Testing](/docs/testing). A health check
173
+ is a function you registered, so test it directly — and test what happens when it
174
+ fails, since that is the case the endpoint exists for.
175
+
176
+ ```typescript
177
+ // tests/health/checks.test.ts
178
+ import { test, expect, afterEach } from "bun:test";
179
+ import { Health } from "zerotal";
180
+
181
+ afterEach(() => Health.clear());
182
+
183
+ test("a failing critical check brings the report down", async () => {
184
+ Health.register(
185
+ "database",
186
+ async () => {
187
+ throw new Error("connection refused");
188
+ },
189
+ { critical: true },
190
+ );
191
+
192
+ const report = await Health.run({ name: "app", version: "1.0.0", environment: "test" });
193
+
194
+ expect(report.status).toBe("down");
195
+ expect(report.checks.database?.status).toBe("down");
196
+ });
197
+ ```
198
+
199
+ **`Health.clear()` in `afterEach` matters.** The registry is process-wide, so a
200
+ check registered by one test runs in every later one — and a deliberately failing
201
+ check left behind turns the rest of your suite red for reasons that have nothing
202
+ to do with it.
203
+
204
+ **Critical and non-critical fail differently**, and that distinction is the whole
205
+ design. A non-critical check that fails must leave the overall status up,
206
+ otherwise a flaky cache probe takes your deployment out of the load balancer:
207
+
208
+ ```typescript
209
+ // tests/health/checks.test.ts
210
+ test("a non-critical failure degrades without going down", async () => {
211
+ Health.register("cache", async () => {
212
+ throw new Error("timeout");
213
+ }); // critical defaults to false
214
+
215
+ const report = await Health.run(meta);
216
+
217
+ expect(report.status).not.toBe("down");
218
+ expect(report.checks.cache?.status).toBe("down");
219
+ });
220
+ ```
221
+
222
+ **The endpoint's access rules deserve their own test**, because a health endpoint
223
+ that leaks internals is a reconnaissance gift:
224
+
225
+ ```typescript
226
+ // tests/http/health.test.ts
227
+ const res = await app.get("/health");
228
+
229
+ res.assertOk();
230
+ res.assertDontSee("connection refused"); // no error detail to an anonymous caller
231
+ ```
232
+
233
+ > **Note** — Check functions run on every request to the endpoint. A check that
234
+ > queries the database on each call turns your load balancer's probe into
235
+ > steady traffic — worth a test asserting yours is cheap, or a cache in front.
236
+
237
+ ## References
238
+
239
+ ### `Health` registry
240
+
241
+ ```typescript
242
+ import { Health } from "zerotal/health";
243
+ ```
244
+
245
+ | Method | Signature | Description |
246
+ | ---------- | ----------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
247
+ | `register` | `register(name: string, fn: HealthCheckFn, options?: { critical?: boolean }): this` | Register (or replace) a named check; `critical` checks fail readiness. |
248
+ | `remove` | `remove(name: string): this` | Remove a previously registered check by name. |
249
+ | `clear` | `clear(): this` | Remove every registered check. |
250
+ | `has` | `has(name: string): boolean` | Whether a check is registered under the given name. |
251
+ | `names` | `names: string[]` | The names of all registered checks. |
252
+ | `run` | `run(meta: HealthRunMeta): Promise<HealthReport>` | Run every check and assemble the aggregate report. |
253
+
254
+ ### `HealthResult`
255
+
256
+ What a check returns to describe its own state. A check may also return nothing
257
+ (or `undefined`) to mean `ok`.
258
+
259
+ | Field | Type | Description |
260
+ | --------- | ------------------------------ | ------------------------------------------------------- |
261
+ | `status` | `"ok" \| "degraded" \| "down"` | Defaults to `ok` when a check returns without throwing. |
262
+ | `message` | `string` | Optional human-readable detail. |
263
+ | `meta` | `Record<string, unknown>` | Optional structured metadata surfaced in the report. |
264
+
265
+ ## Next steps
266
+
267
+ - [Telemetry](/docs/telemetry) — collect the metrics behind your readiness checks.
268
+ - [Deployment](/docs/deployment) — wire `/health` into liveness and readiness probes.
269
+ - [Logger](/docs/logger) — record failing checks for later inspection.