@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.
- package/CHANGELOG.md +31 -0
- package/LICENSE +21 -0
- package/README.md +67 -0
- package/api-surface.md +283 -0
- package/docs/about.md +676 -0
- package/docs/admin/actions.md +183 -0
- package/docs/admin/auth.md +89 -0
- package/docs/admin/dashboard.md +155 -0
- package/docs/admin/extending-ui.md +229 -0
- package/docs/admin/extending.md +205 -0
- package/docs/admin/forms.md +155 -0
- package/docs/admin/index.md +144 -0
- package/docs/admin/operations.md +224 -0
- package/docs/admin/references.md +174 -0
- package/docs/admin/resources.md +132 -0
- package/docs/admin/structure.md +158 -0
- package/docs/admin/tables.md +185 -0
- package/docs/admin/testing.md +138 -0
- package/docs/ai.md +373 -0
- package/docs/application.md +435 -0
- package/docs/arch.md +263 -0
- package/docs/assets.md +755 -0
- package/docs/audit.md +367 -0
- package/docs/authentication.md +1000 -0
- package/docs/authorization.md +529 -0
- package/docs/broadcasting/channels.md +170 -0
- package/docs/broadcasting/client.md +108 -0
- package/docs/broadcasting/events.md +190 -0
- package/docs/broadcasting/index.md +163 -0
- package/docs/broadcasting/references.md +84 -0
- package/docs/broadcasting/testing.md +132 -0
- package/docs/cache.md +387 -0
- package/docs/carbon.md +830 -0
- package/docs/changelog.md +465 -0
- package/docs/client/auth.md +113 -0
- package/docs/client/errors.md +139 -0
- package/docs/client/files.md +118 -0
- package/docs/client/index.md +128 -0
- package/docs/client/references.md +58 -0
- package/docs/client/requests.md +131 -0
- package/docs/client/resilience.md +141 -0
- package/docs/client/testing.md +146 -0
- package/docs/commands.md +450 -0
- package/docs/components.md +2427 -0
- package/docs/config-system.md +258 -0
- package/docs/container.md +596 -0
- package/docs/context.md +579 -0
- package/docs/contributing.md +164 -0
- package/docs/controllers.md +354 -0
- package/docs/conventions.md +466 -0
- package/docs/cookies.md +101 -0
- package/docs/csrf.md +206 -0
- package/docs/database.md +476 -0
- package/docs/deployment.md +434 -0
- package/docs/devtools.md +1013 -0
- package/docs/email-verification.md +320 -0
- package/docs/encryption.md +380 -0
- package/docs/errors.md +317 -0
- package/docs/events.md +420 -0
- package/docs/flow/components.md +846 -0
- package/docs/flow/decorators.md +458 -0
- package/docs/flow/events.md +420 -0
- package/docs/flow/forms.md +551 -0
- package/docs/flow/index.md +568 -0
- package/docs/flow/layouts.md +793 -0
- package/docs/flow/lifecycle.md +336 -0
- package/docs/flow/pagination.md +402 -0
- package/docs/flow/performance.md +262 -0
- package/docs/flow/references.md +317 -0
- package/docs/flow/routing.md +550 -0
- package/docs/flow/testing.md +447 -0
- package/docs/getting-started.md +286 -0
- package/docs/health.md +269 -0
- package/docs/helpers.md +423 -0
- package/docs/i18n.md +414 -0
- package/docs/index.md +65 -0
- package/docs/inertia/build.md +95 -0
- package/docs/inertia/devtools.md +197 -0
- package/docs/inertia/index.md +267 -0
- package/docs/inertia/middleware.md +81 -0
- package/docs/inertia/props.md +507 -0
- package/docs/inertia/references.md +144 -0
- package/docs/inertia/rendering.md +189 -0
- package/docs/inertia/ssr.md +102 -0
- package/docs/inspirations.md +257 -0
- package/docs/lifecycle.md +331 -0
- package/docs/lock.md +454 -0
- package/docs/logger.md +517 -0
- package/docs/media.md +582 -0
- package/docs/middleware.md +509 -0
- package/docs/migrations.md +529 -0
- package/docs/monitor.md +377 -0
- package/docs/notifications.md +848 -0
- package/docs/orm/casts.md +365 -0
- package/docs/orm/factories.md +231 -0
- package/docs/orm/index.md +663 -0
- package/docs/orm/lifecycle.md +421 -0
- package/docs/orm/queries.md +692 -0
- package/docs/orm/relationships.md +440 -0
- package/docs/orm/serialization.md +251 -0
- package/docs/package-development.md +536 -0
- package/docs/pagination.md +340 -0
- package/docs/password-reset.md +335 -0
- package/docs/providers.md +447 -0
- package/docs/query-builder.md +465 -0
- package/docs/queue.md +549 -0
- package/docs/rate-limiting.md +253 -0
- package/docs/responses.md +336 -0
- package/docs/roles-and-2fa.md +418 -0
- package/docs/routing.md +1008 -0
- package/docs/scaffolding.md +335 -0
- package/docs/scheduler.md +533 -0
- package/docs/seeding.md +229 -0
- package/docs/session.md +451 -0
- package/docs/social.md +524 -0
- package/docs/storage.md +622 -0
- package/docs/structure.md +230 -0
- package/docs/support-policy.md +114 -0
- package/docs/telemetry.md +505 -0
- package/docs/tenancy.md +608 -0
- package/docs/testing/browser.md +153 -0
- package/docs/testing/console.md +130 -0
- package/docs/testing/database.md +238 -0
- package/docs/testing/flow-browser.md +216 -0
- package/docs/testing/http.md +441 -0
- package/docs/testing/index.md +240 -0
- package/docs/testing/mocking.md +410 -0
- package/docs/upgrade.md +192 -0
- package/docs/validator.md +549 -0
- package/docs/view.md +453 -0
- package/package.json +60 -0
- package/src/bin/mcp.ts +73 -0
- package/src/config.ts +63 -0
- package/src/errors.ts +27 -0
- package/src/index.ts +56 -0
- package/src/install/ArchInstallCommand.ts +189 -0
- package/src/install/detect.ts +98 -0
- package/src/install/guidelines.ts +290 -0
- package/src/install/markers.ts +77 -0
- package/src/install/mcpConfig.ts +81 -0
- package/src/mcp/index.ts +27 -0
- package/src/mcp/jsonrpc.ts +133 -0
- package/src/mcp/server.ts +307 -0
- package/src/mcp/stdio.ts +111 -0
- package/src/mcp/types.ts +146 -0
- package/src/probe/ArchProbeCommand.ts +48 -0
- package/src/probe/sentinel.ts +13 -0
- package/src/probe/topics.ts +291 -0
- package/src/provider/ArchProvider.ts +101 -0
- package/src/tools/_probe.ts +164 -0
- package/src/tools/apiSurface.ts +241 -0
- package/src/tools/appInfo.ts +88 -0
- package/src/tools/baselines.ts +210 -0
- package/src/tools/context.ts +37 -0
- package/src/tools/doctor.ts +93 -0
- package/src/tools/index.ts +50 -0
- package/src/tools/logs.ts +265 -0
- package/src/tools/routes.ts +133 -0
- package/src/tools/schema.ts +119 -0
- package/src/tools/searchDocs.ts +345 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Request Lifecycle
|
|
3
|
+
description: Understand the boot and per-request lifecycles so your logic runs in the right place.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Request Lifecycle
|
|
7
|
+
|
|
8
|
+
A Zerotal app has **two lifecycles** — one that runs once at startup and one that
|
|
9
|
+
runs for every request. Keeping them straight is the key to putting logic in the
|
|
10
|
+
right place.
|
|
11
|
+
|
|
12
|
+
- **The boot lifecycle** runs **once**, when the server starts. It wires
|
|
13
|
+
everything together — binds services, loads routes, scans your `app/*`
|
|
14
|
+
directories — and then opens the socket. One-time setup belongs here, in a
|
|
15
|
+
provider hook.
|
|
16
|
+
- **The request lifecycle** runs **for every incoming request**. It opens a
|
|
17
|
+
per-request scope, walks the middleware pipeline to your controller, builds a
|
|
18
|
+
response, and tears the scope back down. Per-request work belongs here, in
|
|
19
|
+
middleware, a controller, or an after-response hook.
|
|
20
|
+
|
|
21
|
+
If you only remember one thing: _boot is for the app, request is for the
|
|
22
|
+
visitor._
|
|
23
|
+
|
|
24
|
+
## Mental model
|
|
25
|
+
|
|
26
|
+
Two clocks tick at different rates. The boot clock ticks once — everything it
|
|
27
|
+
sets up (container bindings, routes, the compiled route table) is shared by every
|
|
28
|
+
request that follows. The request clock ticks on each visit and gets its own
|
|
29
|
+
isolated scope, so concurrent requests never share state.
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
boot clock ──tick── (server starts: bind, load routes, open socket)
|
|
33
|
+
│
|
|
34
|
+
request clock ───────────┼── tick (GET /a) → scope A → response → flush A
|
|
35
|
+
├── tick (GET /b) → scope B → response → flush B
|
|
36
|
+
└── tick (POST /c) → scope C → response → flush C
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Boot lifecycle
|
|
40
|
+
|
|
41
|
+
Booting starts from the two files you own — `bootstrap/app.ts` (where you
|
|
42
|
+
configure the app, declaratively) and the managed `zt.ts` entry point (which
|
|
43
|
+
imports it and calls `start()`). From there `Application.boot()` runs a fixed
|
|
44
|
+
sequence, **once**, and is idempotent (a second call does nothing):
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
# boot sequence (overview)
|
|
48
|
+
bootstrap/app.ts ← you configure the app (declarative)
|
|
49
|
+
Application.create({ providers })
|
|
50
|
+
.bind((container) => …) ← optional: register container bindings
|
|
51
|
+
.routing({ … }) ← declare explicit route files
|
|
52
|
+
.fileBasedRouting({ … }) ← declare route directories
|
|
53
|
+
.use([ …middleware ]) ← register global middleware
|
|
54
|
+
└─ exports `app`
|
|
55
|
+
│
|
|
56
|
+
▼
|
|
57
|
+
zt.ts (managed) ← imports bootstrap/app.ts, then: app.start(port)
|
|
58
|
+
│
|
|
59
|
+
▼
|
|
60
|
+
app.boot() ── runs once, idempotent ───────────────────────────────────
|
|
61
|
+
1. bind core singletons `config`, `events`
|
|
62
|
+
2. load config scan `config/*.ts` (unless preloaded)
|
|
63
|
+
3. discover providers scan `app/providers/*`
|
|
64
|
+
4. run app.bind() callbacks your bootstrap bindings
|
|
65
|
+
5. onRegister() each, sync providers bind their services
|
|
66
|
+
6. onBooting() each, in order providers prepare; may use earlier ones
|
|
67
|
+
7. onBooted() all, in parallel every binding is now resolvable
|
|
68
|
+
8. discover middleware scan `app/middleware/*`
|
|
69
|
+
9. load routes routing() + fileBasedRouting() files run
|
|
70
|
+
10. convention phase scan app/{models, observers, policies, listeners,
|
|
71
|
+
events, jobs, services, validators}; serve public/
|
|
72
|
+
│
|
|
73
|
+
▼
|
|
74
|
+
app.start() ── continues ────────────────────────────────────────────────
|
|
75
|
+
11. onStarting() each last chance before the socket opens
|
|
76
|
+
12. Bun.serve() server binds and starts listening
|
|
77
|
+
13. onStarted() each health endpoint, PID file, signal handlers
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The three provider phases (steps 5–7) are the part you'll touch most. They're
|
|
81
|
+
**ordered for a reason**: `onRegister()` only _binds_ (nothing is resolved yet),
|
|
82
|
+
`onBooting()` runs **sequentially** so a later provider can depend on an earlier
|
|
83
|
+
one, and `onBooted()` runs in **parallel** once every binding exists — so it's the
|
|
84
|
+
safe place to resolve services that depend on other providers. See
|
|
85
|
+
[Provider lifecycle hooks](#provider-lifecycle-hooks) below.
|
|
86
|
+
|
|
87
|
+
> **Tip** — _Bind in `onRegister()`, resolve in `onBooted()`_ is the rule that
|
|
88
|
+
> avoids 90% of ordering bugs.
|
|
89
|
+
|
|
90
|
+
The convention phase (step 10) scans the `app/*` directories and registers what it
|
|
91
|
+
finds — models, observers, policies, listeners, jobs, services, validators.
|
|
92
|
+
Providers declare which directories get scanned via `this.app.registerConcern(...)`.
|
|
93
|
+
See [Conventions](/docs/conventions).
|
|
94
|
+
|
|
95
|
+
## Request lifecycle
|
|
96
|
+
|
|
97
|
+
Now the per-request path. Every request gets its **own isolated scope** — created
|
|
98
|
+
when it arrives, flushed when it leaves — so concurrent requests never share
|
|
99
|
+
state. The route table was already compiled at boot, so matching is a fast lookup,
|
|
100
|
+
not a re-scan:
|
|
101
|
+
|
|
102
|
+
```text
|
|
103
|
+
# per-request flow
|
|
104
|
+
Bun.serve() receives Request
|
|
105
|
+
│
|
|
106
|
+
▼
|
|
107
|
+
compiled route table lookup
|
|
108
|
+
→ match path + method → RouteDefinition
|
|
109
|
+
│
|
|
110
|
+
├── no match → 404 NotFoundError → ExceptionHandler.render()
|
|
111
|
+
│
|
|
112
|
+
▼
|
|
113
|
+
createRouteHandler(definition, container)
|
|
114
|
+
│
|
|
115
|
+
▼
|
|
116
|
+
ScopedResolver created ← request-scoped DI scope
|
|
117
|
+
HttpContext created ← ctx.requestId, ctx.startedAt, ctx.url, …
|
|
118
|
+
RequestContext.run(ctx, fn) ← AsyncLocalStorage stores ctx for this async tree
|
|
119
|
+
│
|
|
120
|
+
▼
|
|
121
|
+
Model bindings resolved ← the model's resolver / .bind() runs
|
|
122
|
+
ctx._models populated
|
|
123
|
+
│
|
|
124
|
+
▼
|
|
125
|
+
Pipeline runs (middleware chain):
|
|
126
|
+
[global middleware] → [group middleware] → [route middleware] → controller action
|
|
127
|
+
│
|
|
128
|
+
├── any middleware can short-circuit by returning a Response (or setting ctx.response)
|
|
129
|
+
│
|
|
130
|
+
▼
|
|
131
|
+
Controller action executes
|
|
132
|
+
→ sets ctx.response (via ctx.json(), ctx.view(), ctx.redirect(), etc.)
|
|
133
|
+
│
|
|
134
|
+
▼
|
|
135
|
+
Pipeline unwinds (finally blocks in middleware run here — e.g. SessionMiddleware saves)
|
|
136
|
+
│
|
|
137
|
+
▼
|
|
138
|
+
ctx.response returned to Bun.serve()
|
|
139
|
+
│
|
|
140
|
+
▼
|
|
141
|
+
afterResponse callbacks fire ← ctx.afterResponse(() => sendEmail())
|
|
142
|
+
│
|
|
143
|
+
▼
|
|
144
|
+
ScopedResolver.flush() ← request-scoped bindings disposed
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Middleware execution order
|
|
148
|
+
|
|
149
|
+
Middleware nests: the outermost layer runs first on the way in and last on the way
|
|
150
|
+
out. The list below reads top-to-bottom as the order requests enter, then unwinds
|
|
151
|
+
in reverse as responses leave:
|
|
152
|
+
|
|
153
|
+
```text
|
|
154
|
+
# nesting order (outer → inner)
|
|
155
|
+
Global (app.use)
|
|
156
|
+
└── Provider auto-registered (SessionMiddleware, AuthMiddleware, …)
|
|
157
|
+
└── Switch middleware (withCors, withThrottle, withSecureHeaders)
|
|
158
|
+
└── Group middleware (Router.group({ middleware: [...] }))
|
|
159
|
+
└── Route middleware (Router.get('/...', C, 'a', [M]))
|
|
160
|
+
└── Controller action
|
|
161
|
+
┌── (unwind)
|
|
162
|
+
┌── (unwind)
|
|
163
|
+
┌── (unwind — session saved here)
|
|
164
|
+
┌── (unwind)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Middleware wraps the next step — `await next()` is where the inner layers
|
|
168
|
+
run. Code before `next` runs on the way in; code after runs on the way out.
|
|
169
|
+
`next()` resolves to the downstream `Response`.
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
// app/middleware/TimingMiddleware.ts
|
|
173
|
+
import type { HttpContext } from "zerotal";
|
|
174
|
+
import type { Pipe, NextFn } from "zerotal";
|
|
175
|
+
|
|
176
|
+
export class TimingMiddleware implements Pipe<HttpContext> {
|
|
177
|
+
async handle(ctx: HttpContext, next: NextFn): Promise<Response | void> {
|
|
178
|
+
const start = performance.now();
|
|
179
|
+
const response = await next(); // ← inner layers run here
|
|
180
|
+
console.log(`${ctx.path()} took ${Math.round(performance.now() - start)}ms`);
|
|
181
|
+
return response;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Exception handling
|
|
187
|
+
|
|
188
|
+
If any middleware or controller throws, the pipeline catches the error and calls
|
|
189
|
+
the exception handler — first to report it, then to turn it into a `Response`:
|
|
190
|
+
|
|
191
|
+
```text
|
|
192
|
+
# on an unhandled throw
|
|
193
|
+
ExceptionHandler.report(err, ctx) ← log / Sentry / whatever
|
|
194
|
+
ExceptionHandler.render(err, ctx) → Response
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
The response is sent to the client, and `afterResponse` callbacks still fire.
|
|
198
|
+
|
|
199
|
+
In production, raw 500 errors show a minimal "Internal Server Error" page.
|
|
200
|
+
In dev, unhandled exceptions show a full stack-trace page. See
|
|
201
|
+
[Error Handling](/docs/errors) for custom handlers.
|
|
202
|
+
|
|
203
|
+
## AsyncLocalStorage context
|
|
204
|
+
|
|
205
|
+
`RequestContext` stores the request's `HttpContext` in an `AsyncLocalStorage` so
|
|
206
|
+
that any code running inside the async tree of a request — facades, services, ORM
|
|
207
|
+
models — can reach the current context without prop-drilling:
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
// in a service, anywhere in the async tree
|
|
211
|
+
import { RequestContext } from "zerotal";
|
|
212
|
+
|
|
213
|
+
const ctx = RequestContext.tryGet(); // HttpContext | undefined
|
|
214
|
+
const user = ctx?.user;
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
> **Note** — Use `RequestContext.tryGet()` (returns `undefined` outside a request)
|
|
218
|
+
> for code that runs in both request and non-request contexts — CLI commands,
|
|
219
|
+
> queue workers, scheduled jobs. Use `RequestContext.get()` when you want it to
|
|
220
|
+
> throw if there is no active request.
|
|
221
|
+
|
|
222
|
+
Facades like `Auth` and `Config` use this internally. You rarely need to access
|
|
223
|
+
`RequestContext` directly.
|
|
224
|
+
|
|
225
|
+
## Provider lifecycle hooks
|
|
226
|
+
|
|
227
|
+
A provider can hook into any step of the boot and shutdown sequence by overriding
|
|
228
|
+
these methods. The three most-used ones map to boot steps 5–7 above; the rest
|
|
229
|
+
bracket the server starting and stopping.
|
|
230
|
+
|
|
231
|
+
| Hook | When it runs | Typical use |
|
|
232
|
+
| -------------- | --------------------------------------- | --------------------------------------------- |
|
|
233
|
+
| `onRegister()` | Boot step 5 — sync, nothing resolved | `container.singleton()`, `Router.macro()` |
|
|
234
|
+
| `onBooting()` | Boot step 6 — sequential, in order | Prepare a service that a later provider needs |
|
|
235
|
+
| `onBooted()` | Boot step 7 — parallel, all bound | Resolve cross-provider deps, warm singletons |
|
|
236
|
+
| `onStarting()` | Just before the socket opens | Final pre-flight checks |
|
|
237
|
+
| `onStarted()` | After the server is listening | Start background timers / workers |
|
|
238
|
+
| `onStopping()` | Graceful shutdown, reverse order | Release resources, flush buffers |
|
|
239
|
+
| `onStopped()` | After shutdown completes, reverse order | Final cleanup |
|
|
240
|
+
|
|
241
|
+
Providers can also observe **every request** without registering middleware —
|
|
242
|
+
`onRequestReceived()` (before the pipeline runs), `onRequestProcessed()` (after
|
|
243
|
+
the pipeline sets `ctx.response`), and `onResponseSent()` (after the response is
|
|
244
|
+
sent). The full phase reference lives in
|
|
245
|
+
[The Application](/docs/application) and [Service Providers](/docs/providers).
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
// app/providers/PaymentProvider.ts
|
|
249
|
+
import { ServiceProvider } from "zerotal";
|
|
250
|
+
|
|
251
|
+
export class PaymentProvider extends ServiceProvider {
|
|
252
|
+
onRegister(): void {
|
|
253
|
+
this.app.container.singleton(
|
|
254
|
+
PaymentGateway,
|
|
255
|
+
() => new StripeGateway({ key: Bun.env.STRIPE_KEY! }),
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async onBooted(): Promise<void> {
|
|
260
|
+
const gw = await this.app.container.make(PaymentGateway);
|
|
261
|
+
gw.setWebhookSecret(Bun.env.STRIPE_WEBHOOK_SECRET!);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
> **Warning** — Do not resolve services in `onRegister()`. Nothing is resolvable
|
|
267
|
+
> yet at that phase — only _bind_ there, and resolve in `onBooted()` once every
|
|
268
|
+
> provider has registered its bindings.
|
|
269
|
+
|
|
270
|
+
## Which hook should I use?
|
|
271
|
+
|
|
272
|
+
- **Registering a binding (singleton, macro, alias)** → `onRegister()`. It runs
|
|
273
|
+
first and only binds; nothing is resolved.
|
|
274
|
+
- **Preparing a service a _later_ provider depends on** → `onBooting()`. It runs
|
|
275
|
+
sequentially in registration order, so earlier providers are already booting.
|
|
276
|
+
- **Resolving a service that spans providers, or warming a singleton** →
|
|
277
|
+
`onBooted()`. Every binding exists by now and these run in parallel.
|
|
278
|
+
- **Opening a socket / starting timers after the server is up** → `onStarted()`.
|
|
279
|
+
- **Releasing resources on shutdown** → `onStopping()` / `onStopped()` (reverse
|
|
280
|
+
order).
|
|
281
|
+
- **Observing each request without owning a middleware slot** →
|
|
282
|
+
`onRequestReceived()` / `onRequestProcessed()` / `onResponseSent()`.
|
|
283
|
+
|
|
284
|
+
## Reference
|
|
285
|
+
|
|
286
|
+
Boot and shutdown phases in order, and the per-request hook surface.
|
|
287
|
+
|
|
288
|
+
| Phase / hook | Runs | Concurrency |
|
|
289
|
+
| ---------------------- | ---------------------------- | ------------------- |
|
|
290
|
+
| `onRegister()` | Boot — bind services | Sync, sequential |
|
|
291
|
+
| `onBooting()` | Boot — prepare services | Async, sequential |
|
|
292
|
+
| `onBooted()` | Boot — every binding ready | Async, parallel |
|
|
293
|
+
| `onStarting()` | Before `Bun.serve()` binds | Async, parallel |
|
|
294
|
+
| `onStarted()` | After server is listening | Async, parallel |
|
|
295
|
+
| `onStopping()` | Graceful shutdown | Async, reverse |
|
|
296
|
+
| `onStopped()` | After shutdown completes | Async/sync, reverse |
|
|
297
|
+
| `onRequestReceived()` | Per request, before pipeline | Async, parallel |
|
|
298
|
+
| `onRequestProcessed()` | Per request, after pipeline | Async, parallel |
|
|
299
|
+
| `onResponseSent()` | Per request, after response | Async, parallel |
|
|
300
|
+
|
|
301
|
+
### What booting cost
|
|
302
|
+
|
|
303
|
+
`app.bootDurationMs` is the total. `app.providerReport` is the breakdown — one
|
|
304
|
+
`ProviderReport` per provider, in boot order:
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
for (const { name, durationMs, bindings } of app.providerReport) {
|
|
308
|
+
console.log(`${name} ${durationMs}ms, bound ${bindings.length}`);
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
| Field | Is |
|
|
313
|
+
| ------------ | ------------------------------------------------------- |
|
|
314
|
+
| `name` | Provider class name. The array order is the boot order. |
|
|
315
|
+
| `durationMs` | Wall-clock across all three boot hooks. |
|
|
316
|
+
| `bindings` | Container tokens this provider bound, as names. |
|
|
317
|
+
|
|
318
|
+
Boot order is worth reading on its own: it decides who wins a contested binding.
|
|
319
|
+
And because `onBooted()` runs in parallel, these durations **overlap** — they do
|
|
320
|
+
not sum to `bootDurationMs`, and the report says so rather than serialising the
|
|
321
|
+
boot to produce a tidier number.
|
|
322
|
+
|
|
323
|
+
[DevTools](/docs/devtools) draws this as its Providers and Container tabs.
|
|
324
|
+
|
|
325
|
+
## Next steps
|
|
326
|
+
|
|
327
|
+
- [Providers](/docs/providers) — register and boot services.
|
|
328
|
+
- [Middleware](/docs/middleware) — the request pipeline in depth.
|
|
329
|
+
- [Container](/docs/container) — dependency injection and scopes.
|
|
330
|
+
- [Conventions](/docs/conventions) — auto-registration of app directories.
|
|
331
|
+
- [HttpContext](/docs/context) — the per-request object and its helpers.
|