@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,447 @@
1
+ ---
2
+ title: Service Providers
3
+ description: Register a feature's bindings and hook into the application boot and shutdown sequence.
4
+ ---
5
+
6
+ # Service Providers
7
+
8
+ A service provider is a **feature's setup script**. When the app boots, each
9
+ provider gets a turn to register its bindings into the container, wire up the
10
+ things that feature needs, and (optionally) hook into later stages of startup and
11
+ shutdown. Every framework feature — ORM, sessions, mail, queues — ships as a
12
+ provider, and your own app can add as many as it needs.
13
+
14
+ The mental model: the [container](/docs/container) holds the _services_, the
15
+ [Application](/docs/application) runs the _boot sequence_, and a provider is how a
16
+ feature plugs into that sequence. The two methods you'll use most are
17
+ `onRegister()` (bind things) and `onBooted()` (use things) — the rest of this page
18
+ is mostly about those two and when each runs.
19
+
20
+ > **Note** — Do you even need a provider? Often not. Registering a single binding
21
+ > or two doesn't justify a whole class — use the bootstrap [`app.bind()`](/docs/application#registering-services-without-a-provider)
22
+ > callback or the [`app/services` convention](/docs/container#the-appservices-convention)
23
+ > instead. Reach for a provider when there's real _bootstrapping_ involved:
24
+ > lifecycle hooks, config-driven wiring, middleware registration, or a feature
25
+ > that spans several bindings.
26
+
27
+ ## Mental model
28
+
29
+ A provider extends `ServiceProvider` from `zerotal` and overrides only the
30
+ hooks it cares about. The framework drives every provider through the same boot
31
+ sequence in list order:
32
+
33
+ ```
34
+ register all providers onRegister() ← bind, no resolving yet
35
+
36
+
37
+ boot (sequential) onBooting() ← prepare a service a later provider needs
38
+
39
+
40
+ boot (parallel) onBooted() ← resolve and use, including from other providers
41
+
42
+
43
+ server binding onStarting / onStarted
44
+
45
+
46
+ graceful shutdown onStopping / onStopped
47
+ ```
48
+
49
+ ```typescript
50
+ // in a provider
51
+ import { ServiceProvider } from "zerotal";
52
+ ```
53
+
54
+ ## Anatomy of a provider
55
+
56
+ ```typescript
57
+ // app/providers/AppServiceProvider.ts
58
+ import { ServiceProvider } from "zerotal";
59
+ import { PaymentGateway } from "../services/PaymentGateway.ts";
60
+ import { StripeGateway } from "../services/StripeGateway.ts";
61
+
62
+ export class AppServiceProvider extends ServiceProvider {
63
+ /**
64
+ * Register bindings into the container.
65
+ * Called synchronously before boot. No resolved bindings are available yet.
66
+ */
67
+ onRegister(): void {
68
+ this.app.container.singleton(
69
+ PaymentGateway,
70
+ () => new StripeGateway({ key: Bun.env.STRIPE_KEY! }),
71
+ );
72
+ }
73
+
74
+ /**
75
+ * Called after all providers have registered.
76
+ * Safe to resolve bindings from other providers here.
77
+ */
78
+ async onBooted(): Promise<void> {
79
+ const gw = await this.app.container.make(PaymentGateway);
80
+ await gw.ping(); // verify connectivity at startup
81
+ }
82
+ }
83
+ ```
84
+
85
+ ## How it works
86
+
87
+ A provider boots in three ordered steps. The golden rule lives here: **bind in
88
+ `onRegister()`, resolve in `onBooted()`.** Trying to resolve a binding too early
89
+ is the most common provider mistake.
90
+
91
+ | Hook | Runs | You should… |
92
+ | -------------- | --------------------------------- | -------------------------------------------------- |
93
+ | `onRegister()` | sync, before anything is resolved | _bind_ services — nothing is resolvable yet |
94
+ | `onBooting()` | async, sequentially in list order | prepare a service a _later_ provider needs |
95
+ | `onBooted()` | async, in parallel once all bound | _resolve_ services, including from other providers |
96
+
97
+ Why the order matters: during `onRegister()` no binding has been resolved yet, so
98
+ you can only register. By `onBooted()` every provider has registered, so resolving
99
+ across providers is safe. `onBooting()` sits between them for the rarer case where
100
+ one provider must be fully prepared before another even registers. There are also
101
+ `onStarting`/`onStarted` (around the server binding) and `onStopping`/`onStopped`
102
+ (graceful shutdown), plus per-request hooks (`onRequestReceived`,
103
+ `onRequestProcessed`, `onResponseSent`) — see [the full phase list](/docs/lifecycle#provider-lifecycle-hooks).
104
+
105
+ ## Registering providers
106
+
107
+ List providers in `bootstrap/providers.ts`. Order matters — a provider can
108
+ only resolve bindings registered by providers that appear earlier in the list:
109
+
110
+ ```typescript
111
+ // bootstrap/providers.ts
112
+ import { DatabaseProvider } from "@zerotal/orm";
113
+ import { CacheProvider } from "@zerotal/cache";
114
+ import { AppServiceProvider } from "../app/providers/AppServiceProvider.ts";
115
+
116
+ const providers = [
117
+ DatabaseProvider, // registers DB bindings
118
+ CacheProvider, // may depend on DB
119
+ AppServiceProvider, // can use both DB and Cache
120
+ ];
121
+
122
+ export default providers;
123
+ ```
124
+
125
+ > **Note** — `app/providers/*` are auto-discovered. Your own providers (e.g. `AppServiceProvider`) don't
126
+ > need to be listed — any `ServiceProvider` under `app/providers/` is registered automatically and
127
+ > runs its full lifecycle, appended after the explicitly-listed providers (so framework providers
128
+ > boot first). List a provider explicitly only to control its order relative to others. See
129
+ > [Conventions](/docs/conventions#providers-appproviders).
130
+
131
+ ## Declaring dependencies
132
+
133
+ Listing providers in the right order works, but it's fragile — it asks every app to
134
+ remember that, say, `AdminProvider` needs `FlowProvider`'s `Router.flow()` macro to
135
+ exist first. Instead, a provider can declare what it needs on the class itself with
136
+ `static dependsOn`, and the framework pulls that dependency in and boots it first:
137
+
138
+ ```typescript
139
+ // packages/admin/src/provider/AdminProvider.ts
140
+ import { ServiceProvider } from "zerotal";
141
+ import { FlowProvider } from "@zerotal/flow";
142
+
143
+ export class AdminProvider extends ServiceProvider {
144
+ static dependsOn = [FlowProvider];
145
+ }
146
+ ```
147
+
148
+ `dependsOn` holds **provider classes** — real imports, not magic strings — so it's
149
+ type-checked and survives renames. It does two jobs at once:
150
+
151
+ - **Pulls the dependency in.** You register only the feature you want —
152
+ `AdminProvider` — and `FlowProvider` comes along automatically, even when it
153
+ isn't listed in `bootstrap/providers.ts`. You list the _features_ your app uses,
154
+ not the plumbing they each need.
155
+ - **Orders it first.** A dependency always boots before the provider that declared
156
+ it, regardless of where either sits in the list.
157
+
158
+ So an app that uses the admin panel only needs the panel itself:
159
+
160
+ ```typescript
161
+ // bootstrap/providers.ts — FlowProvider arrives via AdminProvider.dependsOn
162
+ const providers = [AdminProvider];
163
+
164
+ export default providers;
165
+ ```
166
+
167
+ ### Registration is idempotent
168
+
169
+ You can list a provider explicitly _and_ have it pulled in through someone's
170
+ `dependsOn` — it still boots exactly once. Registration is idempotent by class
171
+ identity, and the first registration keeps its position, so explicit and automatic
172
+ registration safely overlap. Listing `FlowProvider` yourself while `AdminProvider`
173
+ also depends on it is harmless, not a double-boot.
174
+
175
+ ### The priority tiebreak
176
+
177
+ `dependsOn` orders a provider relative to the ones it names. Providers with _no_
178
+ dependency relationship fall back to `static priority` (lower boots earlier;
179
+ defaults to `0`), then to registration order. It's a coarse knob — useful for a
180
+ framework-core provider that should generally boot ahead of everything else,
181
+ without every other provider having to name it explicitly:
182
+
183
+ ```typescript
184
+ export class CoreProvider extends ServiceProvider {
185
+ static priority = -100; // boots before ordinary (priority 0) providers
186
+ }
187
+ ```
188
+
189
+ Reach for `dependsOn` to express a real, specific dependency; reach for `priority`
190
+ only for broad "this should come early/late" ordering.
191
+
192
+ ### Cycles and environments
193
+
194
+ The dependency graph is resolved once, at boot, with a few guarantees worth knowing:
195
+
196
+ - **Transitive.** A dependency's own `dependsOn` is pulled in too, recursively, and
197
+ de-duplicated.
198
+ - **Environment-aware.** A dependency excluded by its own `static environments`
199
+ isn't dragged in where it doesn't belong — a `web`-only provider won't be pulled
200
+ into a `console` boot just because a console-active provider lists it.
201
+ - **Cycle-checked.** A circular `dependsOn` throws at boot with the offending path
202
+ (`Circular provider dependency: A → B → A`), failing fast rather than booting in a
203
+ surprising order.
204
+
205
+ > **Note** — your explicit list is never reshuffled. `dependsOn`/`priority` order the
206
+ > providers that are _pulled in_; the providers you list by hand in
207
+ > `bootstrap/providers.ts` keep their authored order, with dependencies slotted in
208
+ > ahead of the providers that need them.
209
+
210
+ ## onRegister — bind things
211
+
212
+ Register container bindings, named middleware groups, and router macros. This hook
213
+ is **synchronous** — no `await` — and runs before any binding is resolved, so
214
+ treat it as pure wiring. The factory closures you pass don't run yet; they run
215
+ _later_, when the binding is first resolved.
216
+
217
+ ```typescript
218
+ // inside a ServiceProvider
219
+ onRegister(): void {
220
+ // Singleton — one shared instance per app lifetime
221
+ this.app.container.singleton(AnalyticsService, () =>
222
+ new AnalyticsService({ apiKey: Bun.env.ANALYTICS_KEY! })
223
+ );
224
+
225
+ // Per-request scoped binding. The factory may be async — `make()` returns a
226
+ // promise, so await it inside the factory.
227
+ this.app.container.scoped(CartService, async (c) =>
228
+ new CartService(await c.make(DB))
229
+ );
230
+
231
+ // A named middleware group usable in Router.group({ middleware: ['api'] })
232
+ Router.middlewareGroup('api', [ThrottleMiddleware, JsonMiddleware]);
233
+
234
+ // Extend the Router with a custom route method (macro)
235
+ Router.macro('webhook', webhookRoute);
236
+ }
237
+ ```
238
+
239
+ ## onBooting — prepare in order
240
+
241
+ `onBooting()` runs **sequentially**, in provider-list order, between register and
242
+ booted. Reach for it only when one provider must finish preparing before the next
243
+ one even registers — most providers skip it entirely. It's async, so you can
244
+ `await`:
245
+
246
+ ```typescript
247
+ // inside a ServiceProvider
248
+ async onBooting(): Promise<void> {
249
+ // e.g. open a connection pool that a provider listed after this one
250
+ // expects to already exist when its own onRegister() runs.
251
+ const db = await this.app.container.make(DB);
252
+ await db.connect();
253
+ }
254
+ ```
255
+
256
+ ## onBooted — use things
257
+
258
+ By `onBooted()` every provider has registered, so this is the safe place to
259
+ _resolve_ bindings (including from other providers), start background work, and
260
+ register event listeners. It runs in parallel across all providers:
261
+
262
+ ```typescript
263
+ // inside a ServiceProvider
264
+ async onBooted(): Promise<void> {
265
+ // Resolve a binding from another provider
266
+ const db = await this.app.container.make(DB);
267
+
268
+ // Register global event listeners
269
+ Events.on(UserRegistered, async (event) => {
270
+ await sendWelcomeEmail(event.user);
271
+ });
272
+
273
+ // Start a background polling loop (only in the 'web' runtime)
274
+ if (this.app.environment === 'web') {
275
+ startHealthMonitor();
276
+ }
277
+ }
278
+ ```
279
+
280
+ ## this.app API
281
+
282
+ `this.app` is the [Application](/docs/application) instance. The members you'll
283
+ use from inside a provider:
284
+
285
+ | Property / Method | Description |
286
+ | ----------------------------- | ------------------------------------------------------------ |
287
+ | `this.app.container` | The IoC [container](/docs/container) — bind and resolve here |
288
+ | `this.app.environment` | `'web' \| 'console' \| 'worker' \| 'test' \| 'repl'` |
289
+ | `this.app.useOnce(mw)` | Add a middleware to the global pipeline exactly once |
290
+ | `this.app.registerConcern(d)` | Contribute an auto-discovery concern (see below) |
291
+
292
+ To read configuration, resolve it from the container or use the `Config` facade
293
+ — `this.app.container.makeSync('config').get('app.name')`, or simply
294
+ `Config.get('app.name')` once booted.
295
+
296
+ ### Contributing a convention
297
+
298
+ A provider can add its own auto-discovered directory by registering a concern descriptor in
299
+ `onRegister()`/`onBooting()`. The framework scans the directory at boot and calls `register()`
300
+ for each file's exports:
301
+
302
+ ```typescript
303
+ // inside a ServiceProvider
304
+ onRegister(): void {
305
+ this.app.registerConcern({
306
+ name: "validators",
307
+ order: 60, // models=10, observers=20, policies=30, listeners=40
308
+ dir: "app/validators",
309
+ register(mod, ctx) {
310
+ for (const exported of Object.values(mod)) {
311
+ /* register exported as needed */
312
+ }
313
+ },
314
+ });
315
+ }
316
+ ```
317
+
318
+ A `run(ctx)` hook (without `dir`) defines a one-shot step instead of a directory scan. See
319
+ [Conventions](/docs/conventions#custom-concerns).
320
+
321
+ ## Deferred providers
322
+
323
+ When a provider registers something that's rarely needed at boot — a search
324
+ engine, a payment SDK — you can **defer** it so it doesn't run until one of its
325
+ bindings is first resolved. This keeps cold-start fast.
326
+
327
+ Deferral takes two parts. First, the provider declares which tokens it provides:
328
+
329
+ ```typescript
330
+ // app/providers/SearchProvider.ts
331
+ import { ServiceProvider } from "zerotal";
332
+
333
+ export class SearchProvider extends ServiceProvider {
334
+ static provides = ["search.engine"] as const;
335
+
336
+ onRegister(): void {
337
+ this.app.container.singleton(
338
+ "search.engine",
339
+ () => new MeilisearchEngine({ host: Bun.env.MEILISEARCH_URL! }),
340
+ );
341
+ }
342
+ }
343
+ ```
344
+
345
+ Second — and this is the part that actually defers it — register it with
346
+ `app.defer()` instead of the normal providers array. `static provides` on its own
347
+ is just metadata; it's `defer()` that wires the lazy boot:
348
+
349
+ ```typescript
350
+ // bootstrap/app.ts
351
+ const app = Application.create({ providers });
352
+ app.defer([SearchProvider]); // array form reads each provider's `static provides`
353
+ ```
354
+
355
+ The first `make("search.engine")` runs the provider's full
356
+ `onRegister → onBooting → onBooted` sequence, then resolves the binding.
357
+
358
+ > **Warning** — A deferred provider must _not_ also sit in the eager providers list
359
+ > or in `app/providers/*` auto-discovery, or it would boot at startup anyway,
360
+ > defeating the deferral. See [The Application](/docs/application#deferred-providers)
361
+ > for the `defer()` overloads.
362
+
363
+ ## Environment-specific registration
364
+
365
+ A provider runs in every runtime by default. Branch on `this.app.environment` to
366
+ bind a different implementation per environment — a fake mailer under `test`, the
367
+ real one everywhere else:
368
+
369
+ ```typescript
370
+ // inside a ServiceProvider
371
+ onRegister(): void {
372
+ if (this.app.environment === 'test') {
373
+ this.app.container.singleton(Mailer, () => new FakeMailer());
374
+ } else {
375
+ this.app.container.singleton(Mailer, () => new SmtpMailer());
376
+ }
377
+ }
378
+ ```
379
+
380
+ To skip a provider entirely outside certain runtimes, set `static environments`
381
+ on the class (e.g. `static environments = ['web']`) — the Application filters it
382
+ out before it's ever instantiated.
383
+
384
+ ## Auto-registering middleware
385
+
386
+ Providers can push middleware into the global pipeline via `this.app.useOnce()` —
387
+ the framework guarantees it's added exactly once, even if the same middleware is
388
+ registered by several providers:
389
+
390
+ ```typescript
391
+ // inside a ServiceProvider
392
+ onBooting(): Promise<void> {
393
+ this.app.useOnce(SessionMiddleware);
394
+ this.app.useOnce(AuthMiddleware);
395
+ }
396
+ ```
397
+
398
+ This is how framework packages (e.g. `@zerotal/session`) inject their middleware
399
+ transparently, without the app developer needing to add it manually.
400
+
401
+ ## Which hook should I use?
402
+
403
+ - **`onRegister`** — for _binding_ services into the container, registering
404
+ middleware groups, and router macros. Nothing is resolvable yet, so never call
405
+ `make()` here.
406
+ - **`onBooting`** — only when one provider must be fully prepared before a later
407
+ provider registers, or to auto-register middleware via `useOnce()`. Most
408
+ providers skip it.
409
+ - **`onBooted`** — for _resolving_ services (including across providers), starting
410
+ background work, and registering event listeners.
411
+ - **No provider at all** — for one or two bindings with no lifecycle needs, prefer
412
+ the bootstrap [`app.bind()`](/docs/application#registering-services-without-a-provider)
413
+ callback or the [`app/services` convention](/docs/container#the-appservices-convention).
414
+
415
+ ## References
416
+
417
+ The lifecycle hooks a provider may override, in boot order:
418
+
419
+ | Hook | Signature | When it runs |
420
+ | -------------------- | ----------------------------------------------------- | ----------------------------------------------------------------- |
421
+ | `onRegister` | `onRegister(): void` | Sync, before any binding is resolved. Bind here. |
422
+ | `onBooting` | `onBooting(): Promise<void>` | Sequentially in list order, after all providers registered. |
423
+ | `onBooted` | `onBooted(): Promise<void>` | In parallel once every provider has booted. Resolve here. |
424
+ | `onStarting` | `onStarting(): Promise<void>` | Just before the app starts accepting work (e.g. the HTTP server). |
425
+ | `onStarted` | `onStarted(): Promise<void>` | Once the app has started. |
426
+ | `onStopping` | `onStopping(): Promise<void>` | When graceful shutdown begins. Release resources here. |
427
+ | `onStopped` | `onStopped(): Promise<void>` | Once shutdown is complete. |
428
+ | `onRequestReceived` | `onRequestReceived(ctx: HttpContext): Promise<void>` | Before the middleware pipeline runs, per request. |
429
+ | `onRequestProcessed` | `onRequestProcessed(ctx: HttpContext): Promise<void>` | After the pipeline completes and `ctx.response` is set. |
430
+ | `onResponseSent` | `onResponseSent(ctx: HttpContext): Promise<void>` | After the response has been sent to the client. |
431
+ | `replContext` | `replContext(): Record<string, unknown>` | Returns variables to expose in `bun zt repl`. |
432
+
433
+ Static members on the provider class:
434
+
435
+ | Member | Type | Description |
436
+ | --------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
437
+ | `static environments` | `AppEnvironment[]` | Runtimes the provider participates in. Defaults to all five. |
438
+ | `static provides` | `readonly (keyof ContainerBindings)[]` | Tokens this provider registers. Required for the array form of `app.defer([Provider])`. |
439
+ | `static dependsOn` | `ProviderClass[]` | Providers this one needs — pulled in automatically and booted first. See [Declaring dependencies](/docs/providers#declaring-dependencies). |
440
+ | `static priority` | `number` | Boot-order tiebreak among providers with no `dependsOn` relationship. Lower boots earlier; default `0`. |
441
+
442
+ ## Next steps
443
+
444
+ - [Container](/docs/container) — the IoC container providers bind into.
445
+ - [Lifecycle](/docs/lifecycle) — when `onRegister` and `onBooted` run during boot.
446
+ - [Conventions](/docs/conventions) — auto-discovery of providers and concerns.
447
+ - [Application](/docs/application) — the `this.app` instance and its environment.