@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,435 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: The Application
|
|
3
|
+
description: Understand the kernel that owns the container, runs the provider lifecycle, and takes you from boot to a running server.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# The Application
|
|
7
|
+
|
|
8
|
+
The `Application` is the heart of every Zerotal project: the IoC container's owner,
|
|
9
|
+
the service-provider registry, the route loader, and the lifecycle engine that
|
|
10
|
+
takes you from `bun zt serve` to a running HTTP server. You construct it once
|
|
11
|
+
in `bootstrap/app.ts`, and the framework drives it the rest of the way.
|
|
12
|
+
|
|
13
|
+
Think of it as the **assembly line and the conductor** rolled into one. When the
|
|
14
|
+
app starts it walks a fixed sequence of phases — register everything, boot it,
|
|
15
|
+
start the server — and at each phase it gives your providers a chance to hook in.
|
|
16
|
+
The container holds the _services_; the Application decides _when_ they get wired
|
|
17
|
+
up, _when_ routes load, and _when_ the server opens for traffic.
|
|
18
|
+
|
|
19
|
+
In practice you touch a small slice of this. `bootstrap/app.ts` is a short,
|
|
20
|
+
mostly-declarative file: create the app, point it at your routes, and (optionally)
|
|
21
|
+
register a binding or two. Almost everything else — discovering providers, loading
|
|
22
|
+
config, scanning conventions — happens automatically. The rest of this page
|
|
23
|
+
explains what's happening behind that file, and the handful of methods you'll
|
|
24
|
+
actually call.
|
|
25
|
+
|
|
26
|
+
## Mental model
|
|
27
|
+
|
|
28
|
+
The app is created once and exposed as a singleton. Every configuration method
|
|
29
|
+
(`routing`, `bind`, `defer`, `use`, `withExceptionHandler`, …) returns the same
|
|
30
|
+
instance, so in real code they chain straight onto `Application.create(...)`:
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
┌──────────────────────────────────────────┐
|
|
34
|
+
bootstrap/app.ts│ Application.create() │
|
|
35
|
+
│ .bind(...).routing(...).use(...) │ ← config methods chain
|
|
36
|
+
└──────────────────┬───────────────────────┘
|
|
37
|
+
│ start() / boot() / bootAsWorker()
|
|
38
|
+
▼
|
|
39
|
+
┌─────────┐ ┌─────────┐ ┌────────┐ ┌──────────┐ ┌─────────┐
|
|
40
|
+
│ Register│ → │ Booting │ → │ Booted │ → │ Starting │ → │ Started │
|
|
41
|
+
└─────────┘ └─────────┘ └────────┘ └──────────┘ └─────────┘
|
|
42
|
+
phase 1 phase 2 phase 3 phase 4 phase 5
|
|
43
|
+
onRegister onBooting onBooted onStarting onStarted
|
|
44
|
+
+ routes load (Bun.serve live)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
> **Note** — wherever this page shows a bare `app`, it means _this_ instance: the
|
|
48
|
+
> one `Application.create()` returns and `bootstrap/app.ts` exports.
|
|
49
|
+
|
|
50
|
+
## Import
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
// bootstrap/app.ts
|
|
54
|
+
import { Application } from "zerotal";
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Creating the application
|
|
58
|
+
|
|
59
|
+
`Application.create()` builds the singleton instance and returns it. Call it once
|
|
60
|
+
in `bootstrap/app.ts` — no providers or config arguments needed; both are
|
|
61
|
+
discovered automatically at boot:
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
// bootstrap/app.ts
|
|
65
|
+
import { Application, basePath } from "zerotal";
|
|
66
|
+
|
|
67
|
+
export default Application.create().routing({ web: basePath("routes/web.ts") });
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
There is one application per process; once `create()` has run, the `currentApp()`
|
|
71
|
+
helper reaches it anywhere.
|
|
72
|
+
|
|
73
|
+
> **Note** — `Application.create({ providers })` and
|
|
74
|
+
> `Application.create({ providers, config, env })` accept explicit providers and
|
|
75
|
+
> config for lightweight in-process test setups that bypass auto-discovery. See
|
|
76
|
+
> [Testing](/docs/testing/index).
|
|
77
|
+
|
|
78
|
+
### The current application
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import { currentApp } from "zerotal";
|
|
82
|
+
|
|
83
|
+
// anywhere after create() has run
|
|
84
|
+
const app = currentApp(); // throws if create() hasn't run yet
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Calling `create()` a second time is an error — retrieve the running app with
|
|
88
|
+
`currentApp()`. Tests reset the process between cases with
|
|
89
|
+
`Application._resetInstance()`.
|
|
90
|
+
|
|
91
|
+
The configuration methods (`defer`, `use`, `routing`, `withExceptionHandler`, …)
|
|
92
|
+
each return `app`, so in real code they're usually chained directly onto
|
|
93
|
+
`Application.create(...)` rather than called on a stored variable.
|
|
94
|
+
|
|
95
|
+
## Configuration
|
|
96
|
+
|
|
97
|
+
An application needs its config loaded before providers boot. There are three ways
|
|
98
|
+
to supply it, and they compose cleanly.
|
|
99
|
+
|
|
100
|
+
The framework default is **zero-config auto-discovery**: if you never call
|
|
101
|
+
`useConfig()` and never pass `config` to `create()`, `boot()` scans
|
|
102
|
+
`<cwd>/config/*.ts` and loads each file's default export under its filename
|
|
103
|
+
(`config/app.ts` → the `app` namespace). Files that throw on import (e.g. a
|
|
104
|
+
missing env var) are skipped rather than crashing the boot.
|
|
105
|
+
|
|
106
|
+
To load config explicitly, use `configLoader()` and pass it to `useConfig()`:
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// bootstrap/app.ts
|
|
110
|
+
import { Application } from "zerotal";
|
|
111
|
+
import { configLoader } from "zerotal/config";
|
|
112
|
+
|
|
113
|
+
Application.create().useConfig(configLoader("./config"));
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`useConfig()` accepts a `ConfigLoader`, the generated configs barrel, or a raw
|
|
117
|
+
`{ namespace: { ... } }` map. If config was already provided via
|
|
118
|
+
`Application.create({ config })`, this call is **ignored** (create wins). The
|
|
119
|
+
managed `zt.ts` always calls `useConfig()` for you — in normal app usage you
|
|
120
|
+
never need to call it directly.
|
|
121
|
+
|
|
122
|
+
See [Configuration](/docs/config-system) for the config files themselves.
|
|
123
|
+
|
|
124
|
+
### Which config path should I use?
|
|
125
|
+
|
|
126
|
+
- **Do nothing** — let zero-config auto-discovery scan `config/*.ts`. This is the
|
|
127
|
+
right choice for almost every app.
|
|
128
|
+
- **`useConfig(configLoader(...))`** — when you need an explicit config directory
|
|
129
|
+
or want to pre-load before providers boot.
|
|
130
|
+
- **`create({ config })`** — for in-process tests that bypass file discovery.
|
|
131
|
+
|
|
132
|
+
## Registering providers
|
|
133
|
+
|
|
134
|
+
Providers are discovered automatically — you do not need to list them. At boot,
|
|
135
|
+
before the register phase, the application scans `app/providers/*` and
|
|
136
|
+
instantiates every `ServiceProvider` subclass it finds, running each through the
|
|
137
|
+
full lifecycle.
|
|
138
|
+
|
|
139
|
+
Create a provider and place it under `app/providers/`:
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
// app/providers/AppServiceProvider.ts
|
|
143
|
+
import { ServiceProvider } from "zerotal";
|
|
144
|
+
|
|
145
|
+
export class AppServiceProvider extends ServiceProvider {
|
|
146
|
+
onRegister(): void {
|
|
147
|
+
// bind services into the container
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
That file is picked up automatically — no manual registration required. See
|
|
153
|
+
[Service Providers](/docs/providers) for the full provider API and lifecycle
|
|
154
|
+
hooks.
|
|
155
|
+
|
|
156
|
+
### Package providers
|
|
157
|
+
|
|
158
|
+
Framework packages (`@zerotal/orm`, `@zerotal/auth`, `@zerotal/notifications`, …) ship
|
|
159
|
+
their own providers. List them in the **providers array** in
|
|
160
|
+
`bootstrap/providers.ts` and pass that array to `Application.create({ providers })`:
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
// bootstrap/providers.ts
|
|
164
|
+
import { DatabaseProvider } from "@zerotal/orm";
|
|
165
|
+
import { AuthProvider } from "@zerotal/auth";
|
|
166
|
+
import { AppServiceProvider } from "../app/providers/AppServiceProvider.ts";
|
|
167
|
+
|
|
168
|
+
const providers = [DatabaseProvider, AuthProvider, AppServiceProvider];
|
|
169
|
+
|
|
170
|
+
export default providers;
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
// bootstrap/app.ts
|
|
175
|
+
import { Application, basePath } from "zerotal";
|
|
176
|
+
import providers from "./providers.ts";
|
|
177
|
+
|
|
178
|
+
export default Application.create({ providers }).routing({ web: basePath("routes/web.ts") });
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
> **Note** — When one package depends on another, prefer declaring it on the class
|
|
182
|
+
> with `static dependsOn` rather than relying on list order — the dependency is then
|
|
183
|
+
> pulled in and booted first automatically, so the app only lists the features it
|
|
184
|
+
> uses. Registration is idempotent, so listing a provider that's also pulled in via
|
|
185
|
+
> `dependsOn` is harmless. See [Declaring dependencies](/docs/providers#declaring-dependencies).
|
|
186
|
+
|
|
187
|
+
### Deferred providers
|
|
188
|
+
|
|
189
|
+
A deferred provider boots lazily — only the first time one of its container
|
|
190
|
+
bindings is resolved. This keeps cold-start fast when a service isn't used on
|
|
191
|
+
every request.
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
// bootstrap/app.ts — defer() is called on the app instance (and returns it)
|
|
195
|
+
const app = Application.create({ providers });
|
|
196
|
+
|
|
197
|
+
// Single token
|
|
198
|
+
app.defer("cache", CacheProvider);
|
|
199
|
+
|
|
200
|
+
// Object map
|
|
201
|
+
app.defer({ cache: CacheProvider, mail: MailProvider });
|
|
202
|
+
|
|
203
|
+
// Array — each provider must declare `static provides = ['token'] as const`
|
|
204
|
+
app.defer([CacheProvider, MailProvider, QueueProvider]);
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Providers whose `static environments` list doesn't include the current runtime
|
|
208
|
+
mode are filtered out before they ever instantiate, so a `web`-only provider never
|
|
209
|
+
boots inside a `worker`. See [Conventions](/docs/conventions) for how the scanner
|
|
210
|
+
works.
|
|
211
|
+
|
|
212
|
+
## Registering services without a provider
|
|
213
|
+
|
|
214
|
+
A provider is the right tool when bootstrapping is involved — lifecycle hooks,
|
|
215
|
+
config-driven wiring, middleware. But when you just need to register a binding or
|
|
216
|
+
two, that's a lot of ceremony. For those cases, `bind()` lets you register
|
|
217
|
+
straight from `bootstrap/app.ts`:
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
// bootstrap/app.ts
|
|
221
|
+
import { Application, basePath } from "zerotal";
|
|
222
|
+
import { Clock, SystemClock } from "../app/services/clock.ts";
|
|
223
|
+
|
|
224
|
+
export default Application.create({ providers })
|
|
225
|
+
.bind((container) => {
|
|
226
|
+
container.singleton(Clock, () => new SystemClock());
|
|
227
|
+
})
|
|
228
|
+
.fileBasedRouting({ web: basePath("app/flow/pages") });
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
The callback receives the live container and runs once at boot — **after** the
|
|
232
|
+
core singletons are bound and **before** any provider's `onRegister()`, so a
|
|
233
|
+
provider can still override what you register here. It's the natural home for
|
|
234
|
+
**interface → implementation** bindings (binding a `Clock` contract to a
|
|
235
|
+
`SystemClock`), which a self-registering service class can't express on its own.
|
|
236
|
+
|
|
237
|
+
This is one of three lighter-than-a-provider paths, alongside the `app/services`
|
|
238
|
+
convention and the `App` facade. For the full picture — including when to pick
|
|
239
|
+
each — see [App-level dependency injection](/docs/container#app-level-dependency-injection).
|
|
240
|
+
|
|
241
|
+
> **Tip** — to _resolve_ a service anywhere in your app (a page, a controller, a
|
|
242
|
+
> job), use the [`App` facade](/docs/container#the-app-facade):
|
|
243
|
+
> `await App.make(MyService)`, or the `make()` / `app()` global helpers. You
|
|
244
|
+
> rarely need `currentApp()` directly.
|
|
245
|
+
|
|
246
|
+
## Routing
|
|
247
|
+
|
|
248
|
+
Declare where your routes live; the application loads them at boot, after every
|
|
249
|
+
provider has registered its middleware groups (so a route file can always
|
|
250
|
+
reference `web`, `api`, or any provider-supplied group).
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
// bootstrap/app.ts
|
|
254
|
+
Application.create().routing({
|
|
255
|
+
web: "./routes/web.ts",
|
|
256
|
+
api: "./routes/api.ts",
|
|
257
|
+
});
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
`web` and `api` have built-in defaults — `web` mounts at `/` with the `web`
|
|
261
|
+
middleware group, `api` mounts at `/api` with the `api` group. Any other group
|
|
262
|
+
name must declare both `prefix` and `middleware` explicitly (or an error is thrown
|
|
263
|
+
at boot):
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
// bootstrap/app.ts
|
|
267
|
+
.routing({
|
|
268
|
+
web: "./routes/web.ts",
|
|
269
|
+
admin: { file: "./routes/admin.ts", prefix: "/admin", middleware: ["web", "auth"] },
|
|
270
|
+
})
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
For directory-based routing, use `fileBasedRouting()` — same key semantics, but
|
|
274
|
+
each value is a directory that's scanned for exported HTTP-method handlers:
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
// bootstrap/app.ts
|
|
278
|
+
Application.create().fileBasedRouting({ web: "./app/routes" });
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Use [`basePath()`](/docs/helpers#basepath) to anchor these paths to the project
|
|
282
|
+
root regardless of the calling file.
|
|
283
|
+
|
|
284
|
+
## Middleware
|
|
285
|
+
|
|
286
|
+
Register global middleware that runs on every request, in array order:
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
// bootstrap/app.ts
|
|
290
|
+
import { CorsMiddleware, SecureHeadersMiddleware } from "zerotal";
|
|
291
|
+
|
|
292
|
+
Application.create().use([SecureHeadersMiddleware, CorsMiddleware]);
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
The resolved pipeline runs provider-registered middleware first, then everything
|
|
296
|
+
you added via `.use()`. You can read the final ordering back off the app instance
|
|
297
|
+
(handy in a test or a diagnostic):
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
// in a test or diagnostic
|
|
301
|
+
currentApp().globalMiddleware; // PipeClass[] in execution order
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Middleware discovered from `app/middleware/*` is registered as a named group keyed
|
|
305
|
+
by class name (so it isn't global by default); add `static global = true` to a
|
|
306
|
+
class to push it onto the global pipeline automatically. See
|
|
307
|
+
[Middleware](/docs/middleware).
|
|
308
|
+
|
|
309
|
+
> **Note** — `useOnce()` registers a middleware idempotently and is what providers
|
|
310
|
+
> use internally to guarantee their required middleware is present exactly once.
|
|
311
|
+
> You generally won't call it directly.
|
|
312
|
+
|
|
313
|
+
## Other wiring
|
|
314
|
+
|
|
315
|
+
These are the remaining configuration methods, all called on the app instance in
|
|
316
|
+
`bootstrap/app.ts` (and all returning it, so they chain):
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
// bootstrap/app.ts
|
|
320
|
+
import { Handler } from "../app/exceptions/Handler.ts";
|
|
321
|
+
|
|
322
|
+
const app = Application.create({ providers });
|
|
323
|
+
|
|
324
|
+
// Custom exception handler for all unhandled route errors
|
|
325
|
+
app.withExceptionHandler(Handler);
|
|
326
|
+
|
|
327
|
+
// Resolve the authenticated user from a session id (usually automatic — see Auth)
|
|
328
|
+
app.withUserResolver((id) => User.find(id));
|
|
329
|
+
|
|
330
|
+
// Register WebSocket handlers (used by the broadcasting provider)
|
|
331
|
+
app.withWebSocket(handlers, (req) => ({ userId: tokenFrom(req) }));
|
|
332
|
+
|
|
333
|
+
// Contribute a convention descriptor (used by packages, not apps)
|
|
334
|
+
app.registerConcern(myConcern);
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
Each of these returns `app`, so in practice they chain straight onto
|
|
338
|
+
`Application.create(...)` rather than being called on a stored variable.
|
|
339
|
+
|
|
340
|
+
## How the lifecycle works
|
|
341
|
+
|
|
342
|
+
You normally call exactly one of `start()`, `bootAsWorker()`, or `boot()`; the
|
|
343
|
+
managed `zt.ts` picks the right one based on the command. Each drives the
|
|
344
|
+
provider hooks through a fixed set of phases.
|
|
345
|
+
|
|
346
|
+
`boot()` runs phases 1–3 and is idempotent (a second call is a no-op):
|
|
347
|
+
|
|
348
|
+
1. **Register** — config and core singletons (`config`, `events`) are bound;
|
|
349
|
+
`app/providers/*` is discovered; each provider's synchronous `onRegister()`
|
|
350
|
+
runs.
|
|
351
|
+
2. **Booting** — each provider's `onBooting()` runs sequentially, in registration
|
|
352
|
+
order, so later providers can depend on earlier ones.
|
|
353
|
+
3. **Booted** — every provider's `onBooted()` runs concurrently. Then middleware
|
|
354
|
+
is discovered, route files load, and the convention phase registers models,
|
|
355
|
+
observers, policies, listeners, jobs, and validators.
|
|
356
|
+
|
|
357
|
+
`start(port = 3000)` boots (if needed) then runs phases 4–5 and binds the server:
|
|
358
|
+
|
|
359
|
+
4. **Starting** — `onStarting()` on every provider, before the socket opens.
|
|
360
|
+
5. **Started** — `Bun.serve()` is live; `onStarted()` fires. A health endpoint, a
|
|
361
|
+
PID file, and `SIGTERM`/`SIGINT`/`SIGUSR2` handlers are installed.
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
// zt.ts (managed) ultimately does:
|
|
365
|
+
const app = (await import("./bootstrap/app.ts")).default;
|
|
366
|
+
await app.start(Number(env("PORT", 3000)));
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
`bootAsWorker()` forces `worker` mode, boots, and runs the `starting`/`started`
|
|
370
|
+
phases without binding an HTTP server — used by the queue worker. On shutdown it
|
|
371
|
+
drains providers in reverse (LIFO) order.
|
|
372
|
+
|
|
373
|
+
`stop()` runs the teardown phases in LIFO order:
|
|
374
|
+
|
|
375
|
+
6. **Stopping** — `onStopping()` on each provider, newest first.
|
|
376
|
+
7. **Stopped** — `onStopped()` on each, the PID file is removed, and the process
|
|
377
|
+
exits.
|
|
378
|
+
|
|
379
|
+
### Which entry point should I use?
|
|
380
|
+
|
|
381
|
+
- **`start(port?)`** — the normal web server. Boots, binds `Bun.serve()`, installs
|
|
382
|
+
signal handlers.
|
|
383
|
+
- **`bootAsWorker()`** — the queue worker. Boots in `worker` mode with no HTTP
|
|
384
|
+
socket.
|
|
385
|
+
- **`boot()`** — phases 1–3 only, no server. Useful in tests and one-off scripts
|
|
386
|
+
that need the container wired but no listening port.
|
|
387
|
+
|
|
388
|
+
### Per-request provider hooks
|
|
389
|
+
|
|
390
|
+
Beyond the boot phases, providers can observe every request without registering
|
|
391
|
+
middleware. The application calls `onRequestReceived` before the pipeline,
|
|
392
|
+
`onRequestProcessed` after `ctx.response` is set, and `onResponseSent` after the
|
|
393
|
+
response has been flushed to the client. These power telemetry, request logging,
|
|
394
|
+
and similar cross-cutting concerns.
|
|
395
|
+
|
|
396
|
+
### Zero-downtime route reload
|
|
397
|
+
|
|
398
|
+
In development, sending `SIGUSR2` to the server re-runs your route files with
|
|
399
|
+
cache-busting imports and hot-swaps the compiled route table with no dropped
|
|
400
|
+
connections. The dev server wires this up for you on file change.
|
|
401
|
+
|
|
402
|
+
## References
|
|
403
|
+
|
|
404
|
+
| Member | Purpose |
|
|
405
|
+
| ---------------------------------------- | --------------------------------------------------------------------------- |
|
|
406
|
+
| `Application.create()` | Create the process's application. Providers and config are auto-discovered. |
|
|
407
|
+
| `currentApp()` | Return the current application (throws if not created). |
|
|
408
|
+
| `.register(providers)` | Add providers. |
|
|
409
|
+
| `.bind(callback)` | Register container bindings at boot, without a provider. |
|
|
410
|
+
| `.defer(token \| map \| array)` | Register providers that boot lazily on first resolution. |
|
|
411
|
+
| `.useConfig(input)` | Pre-load config (ignored if `create({ config })` was used). |
|
|
412
|
+
| `.routing(config)` | Declare explicit route files per named group. |
|
|
413
|
+
| `.fileBasedRouting(config)` | Declare file-route directories per named group. |
|
|
414
|
+
| `.use(middleware)` | Add global middleware (array order). |
|
|
415
|
+
| `.withExceptionHandler(Handler)` | Set the handler for unhandled errors. |
|
|
416
|
+
| `.withUserResolver(fn)` | Set the session → user loader. |
|
|
417
|
+
| `.withWebSocket(handlers, upgradeData?)` | Enable the WebSocket protocol. |
|
|
418
|
+
| `.registerConcern(descriptor)` | Contribute a convention descriptor (packages). |
|
|
419
|
+
| `.globalMiddleware` | Read the resolved global pipeline. |
|
|
420
|
+
| `.environment` | The runtime environment (`web`, `worker`, `console`, …). |
|
|
421
|
+
| `.booted` | Whether `boot()` has completed. |
|
|
422
|
+
| `.boot()` | Phases 1–3 (idempotent). |
|
|
423
|
+
| `.start(port?)` | Boot then bind the HTTP server (phases 4–5). |
|
|
424
|
+
| `.bootAsWorker()` | Boot in `worker` mode, no HTTP server. |
|
|
425
|
+
| `.stop()` | Teardown (phases 6–7) and exit. |
|
|
426
|
+
| `.container` | The IoC [container](/docs/container). |
|
|
427
|
+
| `.routerState` | The router state owned by this app. |
|
|
428
|
+
|
|
429
|
+
## Next steps
|
|
430
|
+
|
|
431
|
+
- [Service Container](/docs/container) — the IoC container the application owns.
|
|
432
|
+
- [Service Providers](/docs/providers) — the unit of bootstrapping.
|
|
433
|
+
- [Conventions](/docs/conventions) — auto-discovery of providers, models, and more.
|
|
434
|
+
- [Request Lifecycle](/docs/lifecycle) — how a request flows through the pipeline.
|
|
435
|
+
- [Configuration](/docs/config-system) — config files and the `config()` helper.
|