@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,197 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Inertia DevTools
|
|
3
|
+
description: Record every Inertia request — component, props, timing, and headers — and read it in the Inertia DevTools browser extension.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Inertia DevTools
|
|
7
|
+
|
|
8
|
+
The [Inertia DevTools](https://inertiajs.com/docs/v3/advanced/devtools) browser
|
|
9
|
+
extension shows a timeline of every Inertia request: which component rendered,
|
|
10
|
+
which props it received, which of those were deferred or shared, and how long the
|
|
11
|
+
server took. Zerotal implements the server half of that — the recorder and the
|
|
12
|
+
read API the extension talks to.
|
|
13
|
+
|
|
14
|
+
It is on by default in development and off everywhere else, so there is normally
|
|
15
|
+
nothing to configure.
|
|
16
|
+
|
|
17
|
+
## What you get
|
|
18
|
+
|
|
19
|
+
Install the extension, run `bun zt dev`, and open the Inertia panel. Each request
|
|
20
|
+
becomes one entry carrying:
|
|
21
|
+
|
|
22
|
+
- The **component** that rendered and the **route** that matched, by name.
|
|
23
|
+
- Every **prop**, tagged with the wrapper that produced it — `defer` (and its
|
|
24
|
+
group), `optional`, `always`, `merge` (and its direction), `once`, `scroll` —
|
|
25
|
+
plus which props came from `share()`.
|
|
26
|
+
- The **resolved values**, with sensitive keys removed.
|
|
27
|
+
- **Request and response headers**, with sensitive ones removed.
|
|
28
|
+
- **Status, method, URL, and server time**.
|
|
29
|
+
|
|
30
|
+
Follow-up requests are grouped with the navigation that caused them, so a page
|
|
31
|
+
whose deferred props arrive in three later requests reads as one batch rather
|
|
32
|
+
than four unrelated rows.
|
|
33
|
+
|
|
34
|
+
### The same data, without the extension
|
|
35
|
+
|
|
36
|
+
The recorder also feeds Zerotal's own [DevTools](/docs/devtools) panel, on an
|
|
37
|
+
**Inertia** tab, whenever that package is installed. Nothing to configure and no
|
|
38
|
+
extension to add — the entry is recorded against the same request as everything
|
|
39
|
+
else, so one row shows the component and its prop tree _and_ the SQL that
|
|
40
|
+
produced them. A visit and the deferred loads it triggers fold into one entry on
|
|
41
|
+
that panel's All tab, keyed on the same batch the extension groups by.
|
|
42
|
+
|
|
43
|
+
This is a fan-out, not a replacement: the read API below is a published contract
|
|
44
|
+
and keeps serving the extension either way. Neither package depends on the other
|
|
45
|
+
— the panel is found through the container, and its absence is the ordinary case.
|
|
46
|
+
|
|
47
|
+
### The client half needs Inertia 3
|
|
48
|
+
|
|
49
|
+
Two independent halves feed the panel. The **server recorder** is this package,
|
|
50
|
+
and it works on any supported Inertia version. The **client hooks** — visit
|
|
51
|
+
options, prefetch-cache entries, and the grouping that tells a poll apart from a
|
|
52
|
+
navigation — live in the Inertia adapter, and only in **version 3 or later**.
|
|
53
|
+
|
|
54
|
+
On an older adapter the panel still fills with requests, but reports that the app
|
|
55
|
+
is not running in dev mode and suggests starting a Vite dev server. That advice
|
|
56
|
+
does not apply here — there is no Vite in a Zerotal app — and the fix is the
|
|
57
|
+
adapter version:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# in your project root
|
|
61
|
+
bun add @inertiajs/react@^3 # or @inertiajs/vue3@^3
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Apps scaffolded from Zerotal 1.6.1 onwards already pin it. The React adapter
|
|
65
|
+
requires React 19, which the template has always pinned.
|
|
66
|
+
|
|
67
|
+
Nothing else is needed. The adapter's `dev` option defaults to `import.meta.env.DEV`,
|
|
68
|
+
a Vite convention that Bun's bundler does not define on its own — so Zerotal
|
|
69
|
+
defines it for every Inertia build, development and production alike. You do not
|
|
70
|
+
set `dev` yourself, and there is no Vite server to run.
|
|
71
|
+
|
|
72
|
+
## Turning it on and off
|
|
73
|
+
|
|
74
|
+
The recorder follows the same gate as the stack-trace error page: on when this
|
|
75
|
+
process is a development process, off otherwise. A production deploy records
|
|
76
|
+
nothing and registers no endpoints — there is nothing there to probe.
|
|
77
|
+
|
|
78
|
+
To override, set the environment variable:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
INERTIA_DEVTOOLS_ENABLED=false # off, even in dev
|
|
82
|
+
INERTIA_DEVTOOLS_ENABLED=true # on — see the warning below
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Or configure it:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// config/inertia.ts
|
|
89
|
+
import { InertiaConfig } from "zerotal/inertia";
|
|
90
|
+
|
|
91
|
+
export default InertiaConfig({
|
|
92
|
+
devtools: {
|
|
93
|
+
enabled: null, // null = follow the dev-surface gate (default)
|
|
94
|
+
maxEntries: 200,
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
> **Running it outside development.** Entries contain request headers and
|
|
100
|
+
> resolved props for real users' requests. If you enable the recorder on a shared
|
|
101
|
+
> environment, set a `gate` — without one the read API refuses every request
|
|
102
|
+
> rather than defaulting to open.
|
|
103
|
+
|
|
104
|
+
## Configuration
|
|
105
|
+
|
|
106
|
+
All keys live under `devtools` in `config/inertia.ts`, typed as
|
|
107
|
+
`InertiaDevtoolsConfig`.
|
|
108
|
+
|
|
109
|
+
| Key | Default | What it does |
|
|
110
|
+
| --------------- | ------- | ------------------------------------------------------------------------------ |
|
|
111
|
+
| `enabled` | `null` | `null` follows the dev-surface gate; `true`/`false` decides explicitly. |
|
|
112
|
+
| `maxEntries` | `200` | How many entries to keep before the oldest is dropped. |
|
|
113
|
+
| `redact` | `[]` | Extra prop/body key patterns to withhold, on top of the built-in list. |
|
|
114
|
+
| `redactHeaders` | `[]` | Extra header names to withhold, on top of the built-in list. |
|
|
115
|
+
| `except` | `[]` | Path prefixes never recorded. |
|
|
116
|
+
| `gate` | `null` | `(request) => boolean` authorising the read API outside a development process. |
|
|
117
|
+
|
|
118
|
+
### Redaction
|
|
119
|
+
|
|
120
|
+
Values are redacted **before** an entry is stored, so a withheld value is never
|
|
121
|
+
written down in the first place.
|
|
122
|
+
|
|
123
|
+
Redacted out of the box: any key containing `password`, `secret`, `token`,
|
|
124
|
+
`authorization`, `api_key`, `apikey`, `credit_card`, `card_number`, `cvv`, `ssn`,
|
|
125
|
+
or `private_key`, and the `authorization`, `cookie`, `set-cookie`,
|
|
126
|
+
`proxy-authorization`, `x-api-key`, `x-csrf-token`, and `x-xsrf-token` headers.
|
|
127
|
+
Matching is a case-insensitive substring, so `password` also covers
|
|
128
|
+
`password_confirmation` and `currentPassword`.
|
|
129
|
+
|
|
130
|
+
Add your own:
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
// config/inertia.ts
|
|
134
|
+
export default InertiaConfig({
|
|
135
|
+
devtools: {
|
|
136
|
+
redact: ["national_id", "account_number"],
|
|
137
|
+
redactHeaders: ["x-internal-signature"],
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Uploaded files are summarised rather than inlined, and a prop graph containing a
|
|
143
|
+
cycle is recorded with `[Circular]` in place of the loop instead of failing the
|
|
144
|
+
request.
|
|
145
|
+
|
|
146
|
+
### Keeping noise out
|
|
147
|
+
|
|
148
|
+
The read API never records itself. Add anything else that would bury the
|
|
149
|
+
timeline:
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
// config/inertia.ts
|
|
153
|
+
export default InertiaConfig({
|
|
154
|
+
devtools: { except: ["/health", "/metrics"] },
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Gating a shared environment
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
// config/inertia.ts
|
|
162
|
+
export default InertiaConfig({
|
|
163
|
+
devtools: {
|
|
164
|
+
enabled: true,
|
|
165
|
+
gate: (request) => request.headers.get("X-Debug-Key") === Bun.env["DEBUG_KEY"],
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The gate is never consulted on a development machine — a gate that can lock you
|
|
171
|
+
out of your own laptop is a gate that gets switched off.
|
|
172
|
+
|
|
173
|
+
## How it works
|
|
174
|
+
|
|
175
|
+
`InertiaProvider` registers `InertiaDevtoolsMiddleware` and the read API when the
|
|
176
|
+
recorder is enabled, so an app adds nothing to its middleware stack.
|
|
177
|
+
|
|
178
|
+
Each response carries the id of the entry it produced, and the first full-page
|
|
179
|
+
load also embeds it in a script tag so the extension can find the entry before
|
|
180
|
+
any XHR happens. The extension then reads the entry from
|
|
181
|
+
`/_inertia/devtools/entries` (`DEVTOOLS_API_PREFIX`), which serves
|
|
182
|
+
`DevtoolsEntry` objects newest-first and accepts `component`, `type`, `exclude`,
|
|
183
|
+
`offset`, and `limit` filters.
|
|
184
|
+
|
|
185
|
+
Entries are kept in memory in the process that recorded them, capped at
|
|
186
|
+
`maxEntries`. They do not survive a restart — which under `bun zt dev` happens on
|
|
187
|
+
every save anyway, and which keeps recording off the disk and out of the request
|
|
188
|
+
path.
|
|
189
|
+
|
|
190
|
+
`devtoolsEnabled()` reports whether the recorder is currently active, if you need
|
|
191
|
+
to branch on it yourself.
|
|
192
|
+
|
|
193
|
+
## Next steps
|
|
194
|
+
|
|
195
|
+
- [Props](/docs/inertia/props) — the wrappers the panel reports on.
|
|
196
|
+
- [Middleware & Versioning](/docs/inertia/middleware) — the rest of the Inertia middleware stack.
|
|
197
|
+
- [DevTools](/docs/devtools) — Zerotal's own in-browser panel for SQL, logs, mail, and jobs.
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Inertia
|
|
3
|
+
description: Build a single-page app with server-side routing and controllers — no separate API layer or client-side router.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Inertia
|
|
7
|
+
|
|
8
|
+
[Inertia.js](https://inertiajs.com) lets you build a single-page app using
|
|
9
|
+
server-side routing and controllers — no separate API layer, no client-side router.
|
|
10
|
+
Your controllers return Inertia **page responses**; Inertia renders the matching
|
|
11
|
+
React (or Vue) component client-side on navigation, and returns a full HTML document
|
|
12
|
+
on the first load.
|
|
13
|
+
|
|
14
|
+
Zerotal's `@zerotal/inertia` package is a native, Bun-powered Inertia server adapter with full
|
|
15
|
+
**Inertia v3** protocol support: controller-less page routes, automatic shared props, asset
|
|
16
|
+
versioning, the complete data-props layer (partial reloads, `optional`/`defer`/`merge`/`once` props,
|
|
17
|
+
history encryption, precognition), a `make:page` generator, a `Bun.build`-based bundler, and optional
|
|
18
|
+
streaming SSR — all wired by a single provider. The stock `@inertiajs/react` / `@inertiajs/vue3`
|
|
19
|
+
clients work against it unchanged.
|
|
20
|
+
|
|
21
|
+
## How it works
|
|
22
|
+
|
|
23
|
+
1. **First load** — a `GET` request hits a controller, which calls `inertia()`. The
|
|
24
|
+
server renders the full HTML document with the page object (component name +
|
|
25
|
+
props) embedded as JSON.
|
|
26
|
+
2. **Client boot** — the Inertia client reads that page object and mounts the named
|
|
27
|
+
component.
|
|
28
|
+
3. **Navigation** — subsequent links/visits send an `X-Inertia: true` request. The
|
|
29
|
+
same controller runs, but `inertia()` returns **only the JSON page object** — the
|
|
30
|
+
client swaps the page component without a full reload.
|
|
31
|
+
|
|
32
|
+
The server stays the source of truth for routing and data; the client is just a thin
|
|
33
|
+
renderer that morphs between pages.
|
|
34
|
+
|
|
35
|
+
## Getting Started
|
|
36
|
+
|
|
37
|
+
The framework adapter (React or Vue) is a peer dependency you install in your app —
|
|
38
|
+
the server package works against whichever you choose:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# in your project root
|
|
42
|
+
bun add @inertiajs/react react react-dom # React
|
|
43
|
+
# or
|
|
44
|
+
bun add @inertiajs/vue3 vue # Vue
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`@zerotal/inertia` itself ships with the framework. If you're adding it to an
|
|
48
|
+
existing app, `bun add @zerotal/inertia`.
|
|
49
|
+
|
|
50
|
+
## Register the provider
|
|
51
|
+
|
|
52
|
+
Add `InertiaProvider` to the providers array in `bootstrap/providers.ts`:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
// bootstrap/providers.ts
|
|
56
|
+
import { InertiaProvider } from "@zerotal/inertia";
|
|
57
|
+
|
|
58
|
+
const providers = [
|
|
59
|
+
// …your other providers
|
|
60
|
+
InertiaProvider,
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
export default providers;
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
That's the only wiring you need. Registering the provider switches on the following (in lifecycle order):
|
|
67
|
+
|
|
68
|
+
- `onRegister` — registers the `Router.inertia()` route macro so it's available before routes load.
|
|
69
|
+
- `onBooting` — auto-registers `InertiaMiddleware` via `app.useOnce()`, loads the HTML template and asset version into memory, resolves the pages directory, applies the history-encryption default, and (when `ssr: true`) registers `POST /__ssr`.
|
|
70
|
+
- `onBooted` — registers Inertia's dev build routine (alongside any other view layer's, so both keep rebuilding) and lazily registers the `make:page` and `inertia:build` CLI commands when running in console mode.
|
|
71
|
+
|
|
72
|
+
You do **not** add `InertiaMiddleware` to `.use()` manually (see [Middleware & Versioning](/docs/inertia/middleware)).
|
|
73
|
+
|
|
74
|
+
## Configuration
|
|
75
|
+
|
|
76
|
+
Create `config/inertia.ts` with the `InertiaConfig()` helper (or `satisfies
|
|
77
|
+
InertiaConfigShape`). Every field has a default, so an empty `InertiaConfig({})` is valid:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
// config/inertia.ts
|
|
81
|
+
import { InertiaConfig } from "@zerotal/inertia";
|
|
82
|
+
import { env } from "zerotal";
|
|
83
|
+
|
|
84
|
+
export default InertiaConfig({
|
|
85
|
+
htmlTemplate: "./resources/app.html", // root template — must contain <!-- @inertia -->
|
|
86
|
+
version: env("ASSET_VERSION", "1"), // cache-bust string; bump on each deploy
|
|
87
|
+
assetsUrl: "/", // public URL prefix for built assets
|
|
88
|
+
pagesDir: "resources/js/pages", // where page components live
|
|
89
|
+
ssr: false, // set true to enable POST /__ssr (see SSR)
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
| Field | Required | Default | Description |
|
|
94
|
+
| ---------------- | -------- | ------------------------ | ---------------------------------------------------------------------------------------------------- |
|
|
95
|
+
| `htmlTemplate` | no | `"./resources/app.html"` | Path to the root HTML template (must contain `<!-- @inertia -->`); falls back to a built-in default. |
|
|
96
|
+
| `version` | no | `"1"` | Asset version string embedded in every page object; bump on each deploy. |
|
|
97
|
+
| `assetsUrl` | no | `"/"` | Public URL prefix for built assets. |
|
|
98
|
+
| `pagesDir` | no | `"resources/js/pages"` | Directory (relative to the project root) where Inertia page components live. |
|
|
99
|
+
| `ssr` | no | `false` | Register `POST /__ssr` for endpoint SSR (requires a server renderer). |
|
|
100
|
+
| `encryptHistory` | no | `false` | Encrypt browser history state globally; per-page overrides via `Inertia.encryptHistory()`. |
|
|
101
|
+
|
|
102
|
+
### HTML template
|
|
103
|
+
|
|
104
|
+
The template is loaded **once at boot**; `<!-- @inertia -->` is where the page
|
|
105
|
+
object and root `<div>` are injected on every response:
|
|
106
|
+
|
|
107
|
+
```html
|
|
108
|
+
<!-- resources/app.html -->
|
|
109
|
+
<!DOCTYPE html>
|
|
110
|
+
<html>
|
|
111
|
+
<head>
|
|
112
|
+
<meta charset="utf-8" />
|
|
113
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
114
|
+
<title>My App</title>
|
|
115
|
+
<script type="module" src="/assets/app.js" defer></script>
|
|
116
|
+
</head>
|
|
117
|
+
<body>
|
|
118
|
+
<!-- @inertia -->
|
|
119
|
+
</body>
|
|
120
|
+
</html>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
> **Note** — If `htmlTemplate` is missing, `InertiaProvider` falls back to a built-in default
|
|
124
|
+
> template in development so the app still boots — but in production a missing template
|
|
125
|
+
> throws. Provide your own for real projects.
|
|
126
|
+
|
|
127
|
+
## Your first page
|
|
128
|
+
|
|
129
|
+
A controller action calls `inertia(component, props)`:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
// app/controllers/DashboardController.ts
|
|
133
|
+
import type { HttpContext } from "zerotal";
|
|
134
|
+
import { inertia } from "@zerotal/inertia";
|
|
135
|
+
import { Post } from "../models/Post.ts";
|
|
136
|
+
|
|
137
|
+
export class DashboardController {
|
|
138
|
+
async index(ctx: HttpContext): Promise<void> {
|
|
139
|
+
const posts = await Post.query().latest().limit(5).get();
|
|
140
|
+
return inertia("Dashboard", { posts });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`inertia()` reads the current request from context — it takes **no context
|
|
146
|
+
argument** — and sets the response as a side effect, so the action returns
|
|
147
|
+
`Promise<void>`. The component name (`"Dashboard"`) maps to
|
|
148
|
+
`resources/js/pages/Dashboard.tsx`.
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
// resources/js/pages/Dashboard.tsx
|
|
152
|
+
import { Link } from "@inertiajs/react";
|
|
153
|
+
|
|
154
|
+
interface Props {
|
|
155
|
+
posts: { id: number; slug: string; title: string }[];
|
|
156
|
+
auth: { user: { name: string } | null }; // from shared props
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export default function Dashboard({ posts, auth }: Props) {
|
|
160
|
+
return (
|
|
161
|
+
<main>
|
|
162
|
+
<h1>Dashboard</h1>
|
|
163
|
+
{auth.user && <p>Welcome back, {auth.user.name}</p>}
|
|
164
|
+
{posts.map((post) => (
|
|
165
|
+
<Link key={post.id} href={`/posts/${post.slug}`}>
|
|
166
|
+
{post.title}
|
|
167
|
+
</Link>
|
|
168
|
+
))}
|
|
169
|
+
</main>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Note `auth` is available without the controller passing it — see
|
|
175
|
+
[Shared Props](/docs/inertia/props). Generate new pages with
|
|
176
|
+
[`make:page`](/docs/inertia/build#generating-a-page) and bundle them with
|
|
177
|
+
[`inertia:build`](/docs/inertia/build#building-assets).
|
|
178
|
+
|
|
179
|
+
## Testing
|
|
180
|
+
|
|
181
|
+
Set your suite up once as described in [Testing](/docs/testing). An Inertia route
|
|
182
|
+
serves two different things depending on one header, so a test has to say which
|
|
183
|
+
one it wants.
|
|
184
|
+
|
|
185
|
+
**Send `X-Inertia: true` to get the page object** instead of the HTML shell. This
|
|
186
|
+
is the assertion you want in almost every route test — it checks the component
|
|
187
|
+
and its props without parsing markup:
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
// tests/http/dashboard.test.ts
|
|
191
|
+
import { test } from "bun:test";
|
|
192
|
+
import { createApp } from "../helpers.ts";
|
|
193
|
+
|
|
194
|
+
test("the dashboard renders with its stats", async () => {
|
|
195
|
+
const app = await createApp();
|
|
196
|
+
|
|
197
|
+
const res = await app.actingAs(user).get("/dashboard", { "X-Inertia": "true" });
|
|
198
|
+
|
|
199
|
+
res.assertJsonPath("component", "Dashboard");
|
|
200
|
+
res.assertJsonPath("props.stats.orders", 12);
|
|
201
|
+
await app.close();
|
|
202
|
+
});
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**Without that header you get the full HTML document** with the page object
|
|
206
|
+
embedded in a `data-page` attribute — right for asserting the first paint, wrong
|
|
207
|
+
for asserting props:
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
// tests/http/dashboard.test.ts
|
|
211
|
+
const res = await app.actingAs(user).get("/dashboard");
|
|
212
|
+
|
|
213
|
+
res.assertSee('<div id="app"');
|
|
214
|
+
res.assertHeader("Vary", "X-Inertia"); // the response varies by that header
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
**Test partial reloads by the props they omit.** A partial reload that quietly
|
|
218
|
+
returns everything is a performance bug no page-level assertion catches:
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
// tests/http/dashboard.test.ts
|
|
222
|
+
const res = await app.actingAs(user).get("/dashboard", {
|
|
223
|
+
"X-Inertia": "true",
|
|
224
|
+
"X-Inertia-Partial-Data": "stats",
|
|
225
|
+
"X-Inertia-Partial-Component": "Dashboard",
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
res.assertJsonPath("props.stats.orders", 12);
|
|
229
|
+
const page = res.json<{ props: Record<string, unknown> }>();
|
|
230
|
+
expect(page.props.notifications).toBeUndefined(); // excluded, as asked
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**A version mismatch is a `409`, not an error.** It tells the client to reload so
|
|
234
|
+
it picks up new assets — worth a test if you set `ASSET_VERSION` on deploy:
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
// tests/http/dashboard.test.ts
|
|
238
|
+
const res = await app.get("/dashboard", { "X-Inertia": "true", "X-Inertia-Version": "stale" });
|
|
239
|
+
|
|
240
|
+
res.assertStatus(409);
|
|
241
|
+
res.assertHeader("X-Inertia-Location");
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
> **Note** — For the client half — a component rendering, a form submitting, a
|
|
245
|
+
> deferred prop arriving — use [Browser Tests](/docs/testing/browser). These
|
|
246
|
+
> assertions stop at the boundary your server owns.
|
|
247
|
+
|
|
248
|
+
## The rest of the guide
|
|
249
|
+
|
|
250
|
+
| Page | What it covers |
|
|
251
|
+
| --------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
252
|
+
| [Rendering Pages](/docs/inertia/rendering) | Return a page from a controller, choose a component, and control the response. |
|
|
253
|
+
| [Props](/docs/inertia/props) | Pass data to a page — eager, lazy, deferred, and merged props, plus the shared props every page receives. |
|
|
254
|
+
| [Middleware & Versioning](/docs/inertia/middleware) | The Inertia middleware, asset versioning, and forcing a full reload after a deploy. |
|
|
255
|
+
| [Server-Side Rendering](/docs/inertia/ssr) | Render the first paint on the server, and what changes when you do. |
|
|
256
|
+
| [DevTools](/docs/inertia/devtools) | Record each request's component, props, and timing for the Inertia DevTools extension. |
|
|
257
|
+
| [CLI & Build](/docs/inertia/build) | The page registry, the bundler pipeline, and building for production. |
|
|
258
|
+
| [Reference](/docs/inertia/references) | Every helper, prop type, and config key in one table. |
|
|
259
|
+
|
|
260
|
+
## Next steps
|
|
261
|
+
|
|
262
|
+
- [Controllers](/docs/controllers) — where most `inertia()` calls live.
|
|
263
|
+
- [Routing](/docs/routing) — declare routes and the `Router.inertia()` macro targets.
|
|
264
|
+
- [Middleware](/docs/middleware) — how `InertiaMiddleware` slots into the pipeline.
|
|
265
|
+
- [Validator](/docs/validator) — the validation that feeds the `errors` shared prop and precognition.
|
|
266
|
+
- [Providers](/docs/providers) — the lifecycle hooks `InertiaProvider` builds on.
|
|
267
|
+
- [Pagination](/docs/pagination) — the paginators `merge()` and `scroll()` consume.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Inertia Middleware & Versioning
|
|
3
|
+
description: The Inertia middleware, asset versioning, and forcing a full reload after a deploy.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Middleware & versioning
|
|
7
|
+
|
|
8
|
+
`InertiaMiddleware` implements the parts of the Inertia protocol that have to happen
|
|
9
|
+
at the HTTP layer. You don't register it manually — `InertiaProvider` adds it for you
|
|
10
|
+
via `useOnce()` during boot.
|
|
11
|
+
|
|
12
|
+
## What the middleware does
|
|
13
|
+
|
|
14
|
+
`InertiaMiddleware` handles three protocol requirements on every response:
|
|
15
|
+
|
|
16
|
+
1. **302 → 303 for non-GET redirects.** Inertia requires a 303 after a
|
|
17
|
+
POST/PUT/DELETE so the browser issues a `GET` on the redirect target instead of
|
|
18
|
+
replaying the form submission. Without this, redirect-after-submit flows break.
|
|
19
|
+
|
|
20
|
+
2. **Asset-version mismatch → 409.** When the client's `X-Inertia-Version` header
|
|
21
|
+
differs from the server's current [asset version](#asset-versioning), the
|
|
22
|
+
middleware responds **409** with an `X-Inertia-Location` header. The Inertia
|
|
23
|
+
client reacts by doing a full page reload to pull the new assets — this is how
|
|
24
|
+
zero-downtime deploys avoid stale-bundle errors.
|
|
25
|
+
|
|
26
|
+
3. **`Vary: X-Inertia` on every response.** Prevents a browser/CDN cache from serving
|
|
27
|
+
a JSON navigation response where an HTML document is expected (or vice versa) on
|
|
28
|
+
Back/Refresh.
|
|
29
|
+
|
|
30
|
+
## Automatic registration
|
|
31
|
+
|
|
32
|
+
`InertiaProvider.onBooting()` calls `this.app.useOnce(InertiaMiddleware)`, so simply
|
|
33
|
+
registering the provider is enough:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
// bootstrap/providers.ts — this is all you need
|
|
37
|
+
import { InertiaProvider } from "@zerotal/inertia";
|
|
38
|
+
|
|
39
|
+
export default [
|
|
40
|
+
// …auth, session providers…
|
|
41
|
+
InertiaProvider,
|
|
42
|
+
];
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
You do **not** add `InertiaMiddleware` to `Application.create().use([...])` — doing so
|
|
46
|
+
would just register it twice (and `useOnce` guards against that anyway).
|
|
47
|
+
|
|
48
|
+
> **Note** — Ordering & `ctx.user`. Shared props read `ctx.user` at the moment `inertia()`
|
|
49
|
+
> runs _inside your controller_ — which is after the entire middleware pipeline. So
|
|
50
|
+
> the authenticated user is always populated by the time props are built; you don't
|
|
51
|
+
> need to hand-order `InertiaMiddleware` relative to auth.
|
|
52
|
+
|
|
53
|
+
## Asset versioning
|
|
54
|
+
|
|
55
|
+
The asset version is a string sent as part of every page object. When it changes, the
|
|
56
|
+
client knows its cached bundle is stale and triggers a reload (the 409 flow above).
|
|
57
|
+
|
|
58
|
+
Set a baseline in [config](/docs/inertia#configuration) (`version`), or compute one at boot
|
|
59
|
+
from the actual bundle so it changes automatically on every deploy:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
// in a ServiceProvider.onBooting()
|
|
63
|
+
import { setAssetVersion } from "@zerotal/inertia";
|
|
64
|
+
|
|
65
|
+
const hash = Bun.hash(await Bun.file("public/assets/app.js").text()).toString(16);
|
|
66
|
+
setAssetVersion(hash);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
| Function | Purpose |
|
|
70
|
+
| -------------------- | -------------------------------------------------- |
|
|
71
|
+
| `setAssetVersion(v)` | Set the current version (called by the provider). |
|
|
72
|
+
| `assetVersion()` | Read the current version (embedded in every page). |
|
|
73
|
+
|
|
74
|
+
Hashing the built bundle is the most robust option: the version is guaranteed to
|
|
75
|
+
change exactly when the client code changes, so users always reload onto matching
|
|
76
|
+
assets after a deploy.
|
|
77
|
+
|
|
78
|
+
## Next steps
|
|
79
|
+
|
|
80
|
+
- [Inertia overview](/docs/inertia) — the guide's front page and the rest of the sections.
|
|
81
|
+
- [Reference](/docs/inertia/references) — the full API surface in one table.
|