@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,596 @@
1
+ ---
2
+ title: Service Container
3
+ description: Register how each object is built once, then resolve it by name with dependencies wired automatically.
4
+ ---
5
+
6
+ # Service Container
7
+
8
+ The service container is the part of Zerotal that **builds your objects for you**:
9
+ you describe how to construct something once, then ask for it by name and it wires
10
+ up the dependencies.
11
+
12
+ Most classes need other things to do their job: a repository needs a database
13
+ connection, a mailer needs an API key, a service needs a logger. Wiring all of
14
+ that by hand — `new PostRepository(new CacheManager(config), new Logger())` —
15
+ gets tedious and couples every caller to the exact construction details. The
16
+ container takes that job over: you tell it _how_ to build something once, and from
17
+ then on you just ask for it by name. It figures out the dependencies, builds them
18
+ in the right order, and hands you a finished object.
19
+
20
+ This is **dependency injection**: your classes declare _what_ they need, and the
21
+ container supplies it — instead of each class reaching out and constructing its
22
+ own dependencies. The payoff is testability (swap a real mailer for a fake one in
23
+ one line) and decoupling (a class depends on a `Mailer` contract, not on
24
+ `SendGridMailer`).
25
+
26
+ > **Note** — Do I need to learn all of this? Not up front. In everyday app code
27
+ > you'll mostly _resolve_ services with [`App.make()`](#app-level-dependency-injection)
28
+ > or let [`@inject()`](#auto-wiring-with-inject) wire them for you, and _register_
29
+ > the occasional one with the [`app/services` convention](#app-level-dependency-injection).
30
+ > The lifetimes, contextual bindings, aliases, and hooks below are there when you
31
+ > need finer control — reach for them as the need arises, not before.
32
+
33
+ ## Accessing the container
34
+
35
+ Inside a `ServiceProvider`, the container lives at `this.app.container`:
36
+
37
+ ```typescript
38
+ // in a ServiceProvider method
39
+ this.app.container.singleton(CacheManager, () => new CacheManager());
40
+ const cache = await this.app.container.make(CacheManager);
41
+ ```
42
+
43
+ Anywhere else, reach it through the application singleton:
44
+
45
+ ```typescript
46
+ // in application code outside a provider
47
+ import { Application } from "zerotal";
48
+
49
+ const container = currentApp().container;
50
+ const cache = await container.make(CacheManager);
51
+ ```
52
+
53
+ > **Tip** — In application code, prefer [`@inject()`](#auto-wiring-with-inject) so the
54
+ > container wires dependencies for you, or the [`App` facade](#app-level-dependency-injection)
55
+ > (`App.make(...)`) for terse access — reaching for `currentApp()` is a
56
+ > last resort.
57
+
58
+ > **Note** — Reading the examples: a code block that opens with
59
+ > `this.app.container.…` is inside a `ServiceProvider`. A block that uses a bare
60
+ > `container.…` assumes you already obtained it one of the two ways above. Blocks
61
+ > that belong in a specific file (`bootstrap/app.ts`, `app/services/…`) say so in a
62
+ > comment on the first line.
63
+
64
+ ## Binding lifetimes
65
+
66
+ A _lifetime_ answers one question: **when you ask for this thing twice, do you get
67
+ the same instance or a new one?** That's the only real decision when registering a
68
+ binding, and it comes down to whether the object holds state and who that state
69
+ belongs to.
70
+
71
+ **Which should I use?**
72
+
73
+ - Reach for **singleton** by default for services — a database manager, a cache,
74
+ an HTTP client. They're expensive to build, safe to share, and you want one of
75
+ them. This is the most common choice.
76
+ - Use **scoped** when the object carries data that belongs to _one request_ and
77
+ must never bleed into another — the current user, a per-request "unit of work",
78
+ a request-id. Each request gets its own; concurrent requests stay isolated.
79
+ - Use **transient** for cheap, stateless, or deliberately short-lived objects
80
+ where sharing would be surprising — a fresh report builder per call.
81
+ - Use **value** when you already _have_ the finished object and just want the
82
+ container to hand it back — a config blob, a pre-configured SDK client.
83
+
84
+ When in doubt, start with singleton; move a binding to scoped only once you find
85
+ it holding per-request state.
86
+
87
+ | Lifetime | Method | Instances created | Use for |
88
+ | --------- | ------------- | ------------------------------- | -------------------------------------- |
89
+ | Singleton | `singleton()` | Once per application | Shared, stateful services (cache, db) |
90
+ | Scoped | `scoped()` | Once per HTTP request | Per-request state (current user, cart) |
91
+ | Transient | `bind()` | Every `make()` call | Stateless or short-lived objects |
92
+ | Value | `value()` | Never — you supply the instance | Config objects, pre-built clients |
93
+
94
+ ### Singleton
95
+
96
+ Created once, then cached for the lifetime of the application. Every caller gets
97
+ the same instance. The factory receives the container so it can resolve its own
98
+ dependencies:
99
+
100
+ ```typescript
101
+ // in a ServiceProvider's onRegister()
102
+ this.app.container.singleton(CacheManager, async (c) => {
103
+ const cfg = await c.make("config");
104
+ return new CacheManager(cfg.get("cache"));
105
+ });
106
+ ```
107
+
108
+ Singleton resolution is **concurrency-safe**: if two requests resolve the same
109
+ unresolved singleton at the same time, the factory runs exactly once and the
110
+ second caller awaits the same in-flight promise.
111
+
112
+ ### Scoped
113
+
114
+ Created once per HTTP request and isolated between concurrent requests via
115
+ `AsyncLocalStorage`. Resolving a scoped binding outside of a request context
116
+ throws `ScopedOutsideRequestError`:
117
+
118
+ ```typescript
119
+ // in a ServiceProvider's onRegister()
120
+ this.app.container.scoped(UserSession, (c) => new UserSession());
121
+ ```
122
+
123
+ ### Transient
124
+
125
+ A fresh instance on every `make()`:
126
+
127
+ ```typescript
128
+ // in a ServiceProvider's onRegister()
129
+ this.app.container.bind(ReportGenerator, () => new ReportGenerator());
130
+ ```
131
+
132
+ ### Value
133
+
134
+ Bind a pre-built instance directly — no factory, no lazy construction:
135
+
136
+ ```typescript
137
+ // in a ServiceProvider's onRegister()
138
+ this.app.container.value("config", configObject);
139
+ ```
140
+
141
+ ## Registering bindings
142
+
143
+ Bindings are registered in a `ServiceProvider`'s `onRegister()`:
144
+
145
+ ```typescript
146
+ // app/providers/AppServiceProvider.ts
147
+ import { ServiceProvider } from "zerotal";
148
+ import { CacheManager } from "../services/CacheManager.ts";
149
+ import { PaymentGateway } from "../services/PaymentGateway.ts";
150
+
151
+ export class AppServiceProvider extends ServiceProvider {
152
+ onRegister(): void {
153
+ // Singleton — shared across all requests
154
+ this.app.container.singleton(CacheManager, async (c) => {
155
+ const cfg = await c.make("config");
156
+ return new CacheManager(cfg.get("cache"));
157
+ });
158
+
159
+ // Scoped — isolated per request
160
+ this.app.container.scoped(PaymentGateway, async (c) => {
161
+ const cfg = await c.make("config");
162
+ return new PaymentGateway(cfg.get("stripe.secret"));
163
+ });
164
+ }
165
+ }
166
+ ```
167
+
168
+ > **Warning** — `onRegister()` is synchronous and runs before any binding is resolved.
169
+ > Register here; **resolve** in `onBooted()`, once every provider has registered. See
170
+ > [Service Providers](/docs/providers).
171
+
172
+ ## App-level dependency injection
173
+
174
+ Everything above is the container's full API, mostly used by framework packages
175
+ inside providers. For your own application services you rarely need that
176
+ machinery — a full `ServiceProvider` is overkill for one class. These are the
177
+ lighter, everyday paths, all backed by the same container.
178
+
179
+ **Which one should I use?** Match the path to what you're doing:
180
+
181
+ - **Just need to _use_ a service?** Resolve it — [`App.make(MyService)`](#the-app-facade)
182
+ or `@inject(MyService)` on the class that depends on it. No registration needed
183
+ for a plain class; the container auto-wires it.
184
+ - **Want a service to be a shared singleton?** Drop it in `app/services/` and add
185
+ [`static lifetime = "singleton"`](#the-appservices-convention). Zero wiring.
186
+ - **Binding a contract to an implementation** (e.g. `Mailer` → `SendGridMailer`),
187
+ or doing setup that doesn't belong next to a single class? Use the
188
+ [bootstrap `bind()` callback](#bootstrap-bind-callback).
189
+ - **Genuinely complex bootstrapping** — config-driven wiring, lifecycle hooks,
190
+ middleware registration? _Then_ write a [`ServiceProvider`](/docs/providers).
191
+
192
+ As a rule of thumb: start with the convention, graduate to `bind()` when a class
193
+ can't register itself, and only reach for a provider when you need lifecycle hooks.
194
+
195
+ ### The App facade
196
+
197
+ `App` is the canonical surface for application code — resolve from anywhere
198
+ (pages, controllers, jobs) without importing `Application`:
199
+
200
+ ```typescript
201
+ // in a controller, page, or job
202
+ import { App } from "zerotal";
203
+ import { UsersService } from "@app/services/users-service.ts";
204
+
205
+ const users = await App.make(UsersService); // auto-wired
206
+ const events = await App.make("events"); // named binding
207
+ ```
208
+
209
+ It also exposes registration (`bind`, `singleton`, `scoped`, `value`, `alias`,
210
+ `forget`), introspection (`bound`, `container`, `instance()`), and environment
211
+ helpers (`environment()`, `isProduction()`, `isLocal()`). The `make()` and
212
+ `app()` global helpers are shorthands:
213
+
214
+ ```typescript
215
+ // in application code
216
+ import { make, app } from "zerotal";
217
+
218
+ const users = await make(UsersService); // = App.make(UsersService)
219
+ const kernel = app(); // the Application instance
220
+ const same = await app(UsersService); // = make(UsersService)
221
+ ```
222
+
223
+ ### Bootstrap `bind()` callback
224
+
225
+ Register bindings in `bootstrap/app.ts` without a provider. The callback runs at
226
+ boot, before any provider's `onRegister()`, so providers can still override:
227
+
228
+ ```typescript
229
+ // bootstrap/app.ts — the callback receives the live container
230
+ Application.create({ providers })
231
+ .bind((container) => {
232
+ container.singleton(Clock, () => new SystemClock());
233
+ container.for(ReportService).give(Clock, () => new FixedClock()); // contextual
234
+ })
235
+ .fileBasedRouting({ web: basePath("app/flow/pages") });
236
+ ```
237
+
238
+ This is the place for **interface → implementation** bindings, since a class
239
+ can't register itself against a different token:
240
+
241
+ ```typescript
242
+ // bootstrap/app.ts
243
+ Application.create({ providers }).bind((container) =>
244
+ container.singleton(Mailer, () => new SendGridMailer(env("SENDGRID_KEY"))),
245
+ );
246
+ ```
247
+
248
+ ### The `app/services` convention
249
+
250
+ Any class under `app/services/` is auto-discovered at boot. Declare its lifetime
251
+ next to the class with a `static lifetime` flag and it's registered for you:
252
+
253
+ ```typescript
254
+ // app/services/users-service.ts
255
+ @inject(Auth)
256
+ export class UsersService {
257
+ static lifetime = "singleton" as const; // "singleton" | "scoped" | "transient"
258
+
259
+ constructor(private auth: AuthManager) {}
260
+ }
261
+ ```
262
+
263
+ Now `App.make(UsersService)` returns the **same** singleton on every call.
264
+ `transient` (or no flag) registers nothing — the container still auto-wires the
265
+ class on demand, just with a fresh instance each time.
266
+
267
+ ### Registration is boot-time only
268
+
269
+ The container is process-global and shared across every concurrent request, so
270
+ the `App` registration methods are **locked once `boot()` completes** — calling
271
+ `App.singleton()` (etc.) from inside a request throws `ContainerLockedError`.
272
+ Register during boot; for genuinely per-request state, register a `scoped`
273
+ binding at boot and the container hands each request its own instance.
274
+
275
+ > **Danger** — The container is shared across every concurrent request. Mutating
276
+ > it at request time would leak state between requests — which is exactly why the
277
+ > `App` facade locks registration after boot. Use a `scoped` binding for anything
278
+ > that must be per-request.
279
+
280
+ > **Note** — the lock guards the `App` facade, not the raw container. Framework
281
+ > internals (deferred providers that register lazily) and tests use
282
+ > `container.*` directly and are intentionally unaffected.
283
+
284
+ ## Resolving bindings
285
+
286
+ ### Async resolution — make
287
+
288
+ `make()` is the primary way to resolve a binding. It's async because factories
289
+ may be async and deferred providers may need to boot first:
290
+
291
+ ```typescript
292
+ // in application code
293
+ const cache = await container.make(CacheManager);
294
+ const cfg = await container.make("config");
295
+ ```
296
+
297
+ ### Sync resolution — makeSync
298
+
299
+ `makeSync()` resolves **without awaiting** but only works for two cases:
300
+
301
+ - **value** bindings, and
302
+ - **singleton** bindings that have already been resolved.
303
+
304
+ Anything else throws `SyncResolutionError`. This is what [facades](#facades) use
305
+ internally — which is why providers pre-resolve (warm) their singleton in
306
+ `onBooted()` before any facade call happens:
307
+
308
+ ```typescript
309
+ // in application code
310
+ const cache = container.makeSync(CacheManager); // throws if not yet resolved
311
+ ```
312
+
313
+ ### Safe lookup — tryMake
314
+
315
+ `tryMake()` resolves a string-keyed binding synchronously, returning `undefined`
316
+ instead of throwing when the token isn't registered. Useful for optional
317
+ services that exist only in certain runtime modes:
318
+
319
+ ```typescript
320
+ // in a ServiceProvider — CommandRunner is only bound in console mode
321
+ const runner = container.tryMake("commandRunner");
322
+ if (runner) runner.register(MyCommand);
323
+ ```
324
+
325
+ ## Auto-wiring with @inject
326
+
327
+ _Auto-wiring_ means: list what a class needs, and the container builds those
328
+ dependencies and passes them to the constructor for you — no factory function, no
329
+ manual `new`. It's the most common way app code consumes the container, because
330
+ the class stays honest about its dependencies (they're right there in the
331
+ constructor) while you never have to assemble them by hand.
332
+
333
+ Pass a class's dependency tokens straight to `@inject(...)`. The container
334
+ resolves each token — recursively, and **in parallel** — before constructing the
335
+ class:
336
+
337
+ ```typescript
338
+ // app/repositories/PostRepository.ts
339
+ import { inject } from "zerotal";
340
+ import { CacheManager } from "../services/CacheManager.ts";
341
+ import { Logger } from "../services/Logger.ts";
342
+
343
+ @inject(CacheManager, Logger)
344
+ export class PostRepository {
345
+ constructor(
346
+ private cache: CacheManager,
347
+ private logger: Logger,
348
+ ) {}
349
+ }
350
+ ```
351
+
352
+ No registration needed — the container auto-wires on first `make()`:
353
+
354
+ ```typescript
355
+ // in application code
356
+ const repo = await container.make(PostRepository);
357
+ ```
358
+
359
+ The token order must match the constructor parameter order. Any listed token may
360
+ itself be auto-wired or bound by a provider. Tokens may be classes, abstract
361
+ classes, or string keys from `ContainerBindings`.
362
+
363
+ ## Binding tokens
364
+
365
+ A token is the key the container resolves against — a class constructor, an
366
+ abstract class, or a string key declared in `ContainerBindings`:
367
+
368
+ ```typescript
369
+ // in application code
370
+ // Class token (most common)
371
+ container.singleton(CacheManager, factory);
372
+ const cache = await container.make(CacheManager);
373
+
374
+ // String token (typed via ContainerBindings)
375
+ container.value("config", cfg);
376
+ const cfg = await container.make("config");
377
+ ```
378
+
379
+ Packages extend the `ContainerBindings` interface via declaration merging so
380
+ string tokens stay fully type-safe:
381
+
382
+ ```typescript
383
+ // in a package's types.ts
384
+ declare module "zerotal" {
385
+ interface ContainerBindings {
386
+ db: SQL;
387
+ search: SearchClient;
388
+ }
389
+ }
390
+ ```
391
+
392
+ ## Resolution order
393
+
394
+ When you call `make(token, consumer?)`, the container walks these steps:
395
+
396
+ 1. **Alias** — follow the alias map to the canonical token.
397
+ 2. **Deferred** — if a deferred provider is registered for this token, boot it
398
+ now (once), then continue.
399
+ 3. **Contextual** — if a `consumer` was supplied and has a contextual override
400
+ for this token, use it.
401
+ 4. **Registry** — otherwise use the registered binding.
402
+ 5. **Auto-wire** — if nothing is registered but the token is a class with
403
+ `@inject()`, construct it by resolving its dependencies.
404
+ 6. Otherwise throw `BindingNotFoundError`.
405
+
406
+ After construction, any [`resolving()`](#resolving-hooks) hooks for the token fire.
407
+
408
+ ## Contextual bindings
409
+
410
+ Hand a different implementation of the same dependency to different consumers:
411
+
412
+ ```typescript
413
+ // in a ServiceProvider or bootstrap bind() callback
414
+ // PostController gets the Redis cache; ReportController gets the file cache
415
+ container.for(PostController).give(CacheDriver, () => new RedisCache());
416
+ container.for(ReportController).give(CacheDriver, () => new FileCache());
417
+ ```
418
+
419
+ `for(consumer).give(dependency, factory)` registers a transient contextual
420
+ binding. The variants control lifetime:
421
+
422
+ | Method | Lifetime of the contextual instance |
423
+ | ----------------------------- | ------------------------------------- |
424
+ | `give(dep, factory)` | Transient — new instance per resolve |
425
+ | `giveSingleton(dep, factory)` | Singleton within the contextual scope |
426
+ | `giveValue(dep, instance)` | A pre-built value |
427
+
428
+ The `consumer` argument flows automatically when a class is auto-wired — the
429
+ container passes the class being constructed as the consumer when resolving each
430
+ of its `@inject` tokens.
431
+
432
+ ## Aliases
433
+
434
+ Bind one token as an alias for another. Resolving the alias returns the target's
435
+ instance — handy for binding an interface/contract token to a concrete class:
436
+
437
+ ```typescript
438
+ // in a ServiceProvider or bootstrap bind() callback
439
+ container.alias(CacheContract, CacheManager);
440
+ const cache = await container.make(CacheContract); // → the CacheManager singleton
441
+ ```
442
+
443
+ Alias chains are followed to the canonical token, and the resolver guards against
444
+ alias cycles.
445
+
446
+ ## resolving hooks
447
+
448
+ Run a callback every time a token resolves — for post-construction setup without
449
+ subclassing or wrapping the factory:
450
+
451
+ ```typescript
452
+ // in a ServiceProvider or bootstrap bind() callback
453
+ container.resolving(Logger, (logger) => {
454
+ logger.setChannel("app");
455
+ });
456
+ ```
457
+
458
+ Hooks fire for every binding kind (and for auto-wired classes) right after the
459
+ instance is constructed.
460
+
461
+ ## Deferred providers
462
+
463
+ Register a provider so it boots only when one of its tokens is first resolved —
464
+ keeping cold-start fast for services not used on every request:
465
+
466
+ ```typescript
467
+ // in a ServiceProvider or bootstrap bind() callback
468
+ container.defer(SearchClient, SearchServiceProvider);
469
+ ```
470
+
471
+ The first `make(SearchClient)` runs the provider's full
472
+ `onRegister → onBooting → onBooted` sequence, then resolves the binding. The
473
+ provider is also tracked so its `onStopping`/`onStopped` hooks run at shutdown.
474
+ See [The Application](/docs/application#deferred-providers) for the app-level
475
+ `defer()` sugar.
476
+
477
+ ## Scoped resolution lifecycle
478
+
479
+ Each HTTP request runs inside `container.runScoped()`, which creates a fresh
480
+ `ScopedResolver`, stores it in `AsyncLocalStorage`, and flushes it when the
481
+ request finishes:
482
+
483
+ - Every `make()` of a scoped binding during the request resolves against **that**
484
+ request's resolver — even across `await` boundaries and concurrent requests.
485
+ - There is no shared mutable state on the container, so scoped instances can
486
+ never leak between requests.
487
+ - After the response is sent the resolver is flushed; resolving a scoped binding
488
+ afterward throws `ScopedAfterFlushError`.
489
+
490
+ You rarely call `runScoped()` yourself — the HTTP pipeline does it per request.
491
+
492
+ ## Errors
493
+
494
+ | Error | Thrown when |
495
+ | --------------------------- | -------------------------------------------------------------------- |
496
+ | `BindingNotFoundError` | A token has no binding and can't be auto-wired. |
497
+ | `ScopedOutsideRequestError` | A scoped binding is resolved outside any request scope. |
498
+ | `SyncResolutionError` | `makeSync()` is used on a non-value / unresolved-singleton binding. |
499
+ | `CircularDependencyError` | Auto-wiring detects a dependency cycle (clear chain in the message). |
500
+ | `ScopedAfterFlushError` | A scoped binding is resolved after its request scope was flushed. |
501
+ | `ContainerLockedError` | An `App` registration method is called after `boot()` completes. |
502
+
503
+ All extend `ZerotalError`.
504
+
505
+ ## References
506
+
507
+ Members of the `Container` class:
508
+
509
+ | Method | Signature | Description |
510
+ | --------------------------- | ---------------------------------------------------------------------- | -------------------------------------------------------------------- |
511
+ | `singleton(token, factory)` | `singleton<T>(token: BindingToken<T>, factory: Factory<T>): this` | Bind a once-per-app instance. |
512
+ | `scoped(token, factory)` | `scoped<T>(token: BindingToken<T>, factory: Factory<T>): this` | Bind a once-per-request instance. |
513
+ | `bind(token, factory)` | `bind<T>(token: BindingToken<T>, factory: Factory<T>): this` | Bind a transient (new each time). |
514
+ | `value(token, instance)` | `value<T>(token: BindingToken<T>, instance: T): this` | Bind a pre-built value. |
515
+ | `make(token, consumer?)` | `make<T>(token: BindingToken<T>, consumer?: unknown): Promise<T>` | Resolve asynchronously (preferred). |
516
+ | `makeSync(token)` | `makeSync<T>(token: BindingToken<T>): T` | Resolve synchronously (value / resolved-singleton only). |
517
+ | `build(ctor)` | `build<T>(ctor: new (...args: unknown[]) => T): Promise<T>` | Auto-wire a class, ignoring any registered binding. |
518
+ | `tryMake(token)` | `tryMake<K>(token: K): ContainerBindings[K] \| undefined` | Sync resolve or `undefined` if unregistered. |
519
+ | `forget(token)` | `forget(token: BindingToken): boolean` | Remove a binding; `true` if one existed. |
520
+ | `alias(from, to)` | `alias(from: unknown, to: unknown): this` | Resolve `from` as `to`. |
521
+ | `for(consumer)` | `for<C>(consumer: BindingToken<C>): ContextualBindingBuilder<C>` | Begin a contextual binding (`give` / `giveSingleton` / `giveValue`). |
522
+ | `resolving(token, hook)` | `resolving<T>(token: BindingToken<T>, hook: (i: T) => void): this` | Run a hook after each resolution. |
523
+ | `defer(token, Provider)` | `defer(token: unknown, provider: new (app) => unknown): this` | Boot a provider lazily on first resolve. |
524
+ | `runScoped(cb)` | `runScoped<T>(cb: (scoped: ScopedResolver) => Promise<T>): Promise<T>` | Run `cb` inside a fresh request scope. |
525
+
526
+ ## Facades
527
+
528
+ A facade is a thin static class that resolves a container binding for you, so
529
+ consumers write `Cache.get(key)` instead of `await container.make("cache")`.
530
+ Facades are sugar over the container — same instance, less ceremony.
531
+
532
+ They are only usable **after the application has booted** (all providers have run
533
+ `onRegister`/`onBooting`/`onBooted`), because they resolve their binding
534
+ synchronously via `makeSync`. That's why providers pre-resolve their singleton in
535
+ `onBooted()` — it warms the binding so the facade works everywhere afterwards.
536
+
537
+ > **Warning** — Using a facade at module scope (top-level code that runs on
538
+ > import) throws `FacadeAccessedBeforeBootError`, because the container isn't ready
539
+ > yet. Move facade calls inside a function, controller method, or provider hook.
540
+
541
+ ### Built-in facades
542
+
543
+ Core ships a few, importable from `zerotal`:
544
+
545
+ ```typescript
546
+ // in application code (after boot)
547
+ import { Config, Events, Artisan } from "zerotal";
548
+
549
+ // Config — read loaded configuration
550
+ Config.get("app.name");
551
+
552
+ // Events — the class-based event bus (events and listeners are plain classes)
553
+ Events.on(UserRegistered, SendWelcomeEmail);
554
+ await Events.emit(new UserRegistered(user.id, user.email));
555
+
556
+ // Artisan — invoke a CLI command programmatically
557
+ await Artisan.call("migrate");
558
+ ```
559
+
560
+ Packages ship their own — for example `Cache` from `@zerotal/cache`, `Auth` from
561
+ `@zerotal/auth`:
562
+
563
+ ```typescript
564
+ // in application code (after boot)
565
+ import { Cache } from "@zerotal/cache";
566
+ import { Auth } from "@zerotal/auth";
567
+
568
+ await Cache.put("key", value, 300);
569
+ Auth.check(); // → boolean
570
+ Auth.user(); // → AuthenticatedUser (throws if guest)
571
+ Auth.userOrNull(); // → AuthenticatedUser | undefined
572
+ ```
573
+
574
+ ### Defining a facade
575
+
576
+ Build one with `createFacade<T>(token)`, passing the same token the provider binds:
577
+
578
+ ```typescript
579
+ // src/facades/Cache.ts
580
+ import { createFacade } from "zerotal";
581
+ import type { CacheManager } from "../CacheManager.ts";
582
+
583
+ export const Cache = createFacade<CacheManager>("cache");
584
+ ```
585
+
586
+ Every static call proxies to the resolved instance, so `Cache.get(...)` is exactly
587
+ `(await container.make("cache")).get(...)` — just synchronous and terminless. See
588
+ [Package Development](/docs/package-development#facades) for where facades
589
+ fit in a package, and prefer [`@inject()`](#auto-wiring-with-inject) over facades
590
+ inside your own application services where testability matters.
591
+
592
+ ## Next steps
593
+
594
+ - [Service Providers](/docs/providers) — where bindings are registered and booted.
595
+ - [The Application](/docs/application) — the container's owner and lifecycle engine.
596
+ - [Request Lifecycle](/docs/lifecycle) — where the per-request scope is opened and flushed.