@dudousxd/nestjs-inertia 1.0.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 +139 -0
- package/LICENSE +21 -0
- package/README.md +317 -0
- package/dist/index.cjs +2314 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +461 -0
- package/dist/index.js +2265 -0
- package/dist/index.js.map +1 -0
- package/package.json +120 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Changelog — @dudousxd/nestjs-inertia
|
|
2
|
+
|
|
3
|
+
## 1.0.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`c5878e3`](https://github.com/DavideCarvalho/nestjs-inertia/commit/c5878e3f8827d9e89710df0154ea76996b6db62a) - First public release — Inertia.js v3 adapter for NestJS.
|
|
8
|
+
|
|
9
|
+
- Core: InertiaModule.forRoot/forRootAsync/forFeature, @Inertia decorator, Inertia.optional/defer/merge/always markers, CSRF with tokenContext, SSR support, Express + Fastify adapters
|
|
10
|
+
- Vite: setupInertiaVite + nestInertia plugin, @inertia/@vite/@inertiaHead shell directives
|
|
11
|
+
- Codegen: nestjs-inertia init (full scaffold + auto-patch), auto-watch in dev, static AST discovery, class-validator DTO support, Route/Path type helpers, @As hierarchical naming
|
|
12
|
+
- Client: defineContract + @ApplyContract, typed Link for React/Vue/Svelte with context providers, createFetcher, SSR hydration, rich error messages
|
|
13
|
+
- Testing: expectInertia matchers, assertInertia, InertiaTestingModule, fakes
|
|
14
|
+
|
|
15
|
+
For the full repository changelog see [`../../CHANGELOG.md`](../../CHANGELOG.md).
|
|
16
|
+
|
|
17
|
+
## 0.9.0-alpha.0 — 2026-05-22
|
|
18
|
+
|
|
19
|
+
### BREAKING CHANGE — Removed unimplemented CodegenOptions fields
|
|
20
|
+
|
|
21
|
+
`CodegenOptions.configFile` and `CodegenOptions.debounceMs` have been removed.
|
|
22
|
+
Both fields were declared in the public type but were never read by the implementation —
|
|
23
|
+
setting them had no effect. They were never functional, so this removal is strictly
|
|
24
|
+
a type cleanup. If you referenced these fields in your code, simply remove them.
|
|
25
|
+
|
|
26
|
+
### BREAKING CHANGE — Inertia v3 protocol
|
|
27
|
+
|
|
28
|
+
Four changes to align with the Inertia.js v3 wire protocol:
|
|
29
|
+
|
|
30
|
+
1. **Shell HTML format** — `@inertia` directive now emits `<div id="app"></div><script id="inertia-page" type="application/json">…</script>` instead of `<div id="app" data-page="…">`. Clients must be on Inertia v3+.
|
|
31
|
+
2. **`clearHistory` / `encryptHistory` omitted when falsy** — these page-level properties are no longer sent on the wire when their value is `false`/`undefined`, matching the v3 spec.
|
|
32
|
+
3. **`Inertia.lazy()` deprecated** — renamed to `Inertia.optional()`; the `lazy` alias is kept for backwards compatibility and logs a deprecation warning at runtime.
|
|
33
|
+
4. **Nested partial-reload dot-notation** — `only`/`except` arrays now support dot-notation paths (e.g. `"user.profile"`) to target nested props.
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- Version bump to `0.9.0-alpha.0`
|
|
38
|
+
|
|
39
|
+
## 0.8.0-alpha.0 — 2026-05-22
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
|
|
43
|
+
- **Auto-bootstrap codegen** — `InertiaModule` implements `OnApplicationBootstrap`; codegen is triggered automatically at app startup when `autoCodegen` is enabled in the module options
|
|
44
|
+
- **`RegistryRoutes` helper** — new export enabling module augmentation for typed route names and params; consumers import `RegistryRoutes` to get the full typed route map from the augmented `InertiaRegistry`
|
|
45
|
+
- **Probe loop fix** — `NESTJS_INERTIA_CODEGEN_PROBE` env guard prevents `OnApplicationBootstrap` from re-triggering codegen when the app is bootstrapped by the codegen probe itself
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
|
|
49
|
+
- Version bump to `0.8.0-alpha.0`
|
|
50
|
+
|
|
51
|
+
## 0.7.0-alpha.0 — 2026-05-22
|
|
52
|
+
|
|
53
|
+
### Changed
|
|
54
|
+
|
|
55
|
+
- Bundled with example app, CI workflows, Changesets, MIT LICENSE, and slim docs.
|
|
56
|
+
|
|
57
|
+
## [0.6.0-alpha.0] - 2026-05-22
|
|
58
|
+
|
|
59
|
+
### Changed
|
|
60
|
+
|
|
61
|
+
- Version bump to `0.6.0-alpha.0` (monorepo coordination with Plan D client release; no source changes)
|
|
62
|
+
|
|
63
|
+
## [0.5.0-alpha.0] - 2026-05-22
|
|
64
|
+
|
|
65
|
+
### Added
|
|
66
|
+
|
|
67
|
+
- `InertiaRegistry` interface — empty extensible interface for codegen-driven module augmentation. Augment it in your project to get typed page names and route params.
|
|
68
|
+
|
|
69
|
+
### Changed
|
|
70
|
+
|
|
71
|
+
- Version bump to `0.5.0-alpha.0` (monorepo coordination with codegen release)
|
|
72
|
+
|
|
73
|
+
## [0.4.0-alpha.0] - 2026-05-22
|
|
74
|
+
|
|
75
|
+
### Added (companion packages)
|
|
76
|
+
|
|
77
|
+
- `@dudousxd/nestjs-inertia-vite@0.1.0-alpha.0` — Vite dev/build helpers + plugin (`nestInertia({ ssr, react|vue|svelte, ... })`)
|
|
78
|
+
- `@dudousxd/nestjs-inertia-testing@0.1.0-alpha.0` — `expectInertia(res)` fluent matchers + `assertInertia(payload)` + `createFakeInertiaRequest/Response` + `InertiaTestingModule.forTest()` + Jest/Vitest `expect.extend` integration
|
|
79
|
+
|
|
80
|
+
### Changed
|
|
81
|
+
|
|
82
|
+
- `@dudousxd/nestjs-inertia` core bumped to `0.4.0-alpha.0` (monorepo coordination; no source changes)
|
|
83
|
+
|
|
84
|
+
## [0.3.0-alpha.0] - 2026-05-22
|
|
85
|
+
|
|
86
|
+
### Added
|
|
87
|
+
|
|
88
|
+
- `InertiaModule.forFeature({ scope })` and `forFeatureAsync` — multi-app support
|
|
89
|
+
- `@UseInertia('scope')` decorator (class + method level) for selecting scope
|
|
90
|
+
- `InertiaScopeSwitcherInterceptor` — auto-installed; replaces `req.inertia` with scoped service
|
|
91
|
+
- 4 template engine adapters: Handlebars, EJS, Pug, LiquidJS (peer deps, all optional)
|
|
92
|
+
- `MissingTemplateEngineDepException` with installation hint
|
|
93
|
+
- CSRF: `CsrfCookieInterceptor` (writes XSRF-TOKEN cookie) + `CsrfGuard` (validates X-XSRF-TOKEN header)
|
|
94
|
+
- `generateCsrfToken` / `verifyCsrfToken` HMAC-SHA256 helpers
|
|
95
|
+
- `MissingCookieDepException` + `InvalidCsrfTokenException` (extends `ForbiddenException` → 403)
|
|
96
|
+
- Fastify adapter — full parity with Express: `fastifyAdapter`, `registerFastifyInertia` (decorateRequest + onRequest), `registerFastifyMethodSpoof` (preHandler)
|
|
97
|
+
- Platform detection in `InertiaModule.onApplicationBootstrap` via `HttpAdapterHost.httpAdapter.getType()`
|
|
98
|
+
- `InertiaAuthGuard` is now platform-aware (Express `redirect(status, url)` vs Fastify `redirect(url, status)`)
|
|
99
|
+
- `RedirectInterceptor` patches `reply.code()` on Fastify for `@Res()` handlers that send manually
|
|
100
|
+
|
|
101
|
+
### Pending for future plans
|
|
102
|
+
|
|
103
|
+
- Companion packages: `@dudousxd/nestjs-inertia-vite` (Plan B), `-testing` (Plan B), `-codegen` (Plan C), `-client` (Plan D — Tuyau-style)
|
|
104
|
+
- Examples + docs site + CI workflows (Plan E)
|
|
105
|
+
|
|
106
|
+
## [0.2.0-alpha.0] - 2026-05-22
|
|
107
|
+
|
|
108
|
+
### Added
|
|
109
|
+
|
|
110
|
+
- `InertiaModule.forRootAsync()` with `useFactory + inject`, `useClass`, `useExisting` paths
|
|
111
|
+
- `@Inertia('Page')` decorator + `InertiaRenderInterceptor` (coexists with imperative `req.inertia.render()`)
|
|
112
|
+
- `Inertia.once()` prop marker (resolves once per session, refreshed via `X-Inertia-Reset-Once`)
|
|
113
|
+
- `RedirectInterceptor` — auto 302→303 for PUT/PATCH/DELETE Inertia requests
|
|
114
|
+
- `MethodSpoofMiddleware` — `_method=PUT/PATCH/DELETE` override on POST + multipart
|
|
115
|
+
- Shell HTML directives in file-based `rootView`: `@inertia`, `@inertiaHead`, `@vite('entry')`, `@viteRefresh`, `@asset('path')`
|
|
116
|
+
- Real SSR loader (dynamic `import(pathToFileURL)`, cache, `throwOnError`)
|
|
117
|
+
- `InertiaAuthGuard` (409 vs 302 based on X-Inertia header, return_to preserved)
|
|
118
|
+
- `InertiaNotFoundFilter` (JSON for `/api/*`, Inertia 'NotFound' page elsewhere)
|
|
119
|
+
- `ErrorBagInterceptor` (`X-Inertia-Error-Bag` namespaces props.errors)
|
|
120
|
+
- `FlashStore` interface (pluggable session-errors source, default no-op)
|
|
121
|
+
- `X-Inertia-Partial-Except` header
|
|
122
|
+
- `X-Inertia-Reset` header
|
|
123
|
+
- Dot-notation top-level prop unpacking
|
|
124
|
+
- Plain (non-marker) function props auto-invoked and awaited
|
|
125
|
+
|
|
126
|
+
### Changed
|
|
127
|
+
|
|
128
|
+
- `undefined` values in props converted to `null` on the JSON wire (Laravel parity)
|
|
129
|
+
- `Inertia` is now a callable function (decorator) AND retains namespace methods
|
|
130
|
+
|
|
131
|
+
### Pending for 0.3.0 (Plan A.3)
|
|
132
|
+
|
|
133
|
+
- `forFeature` / multi-app, template engines, CSRF, Fastify adapter
|
|
134
|
+
|
|
135
|
+
## [0.1.0-alpha.0] - 2026-05-22
|
|
136
|
+
|
|
137
|
+
### Added
|
|
138
|
+
|
|
139
|
+
- Initial release with core Express Inertia v2 protocol, partial reloads, prop markers, SSR stub, manifest/asset version providers, suppressPostSendWrites helper
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Davide Carvalho
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
# @dudousxd/nestjs-inertia
|
|
2
|
+
|
|
3
|
+
> Inertia.js v3 adapter for NestJS — Express + Fastify, multi-app via `forFeature`, 4 template engines, CSRF protection, full Inertia v3 protocol parity.
|
|
4
|
+
|
|
5
|
+
[](https://npmjs.com/package/@dudousxd/nestjs-inertia)
|
|
6
|
+
[](https://github.com/DavideCarvalho/nestjs-inertia/blob/main/LICENSE)
|
|
7
|
+
|
|
8
|
+
> **Status: alpha.** API is stabilising but may change before `1.0`. Not recommended for production use yet.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pnpm add @dudousxd/nestjs-inertia
|
|
14
|
+
|
|
15
|
+
# Pick your HTTP platform
|
|
16
|
+
pnpm add express @types/express # Express adapter (default)
|
|
17
|
+
# OR
|
|
18
|
+
pnpm add fastify @nestjs/platform-fastify @fastify/cookie # Fastify adapter
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick start
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
// app.module.ts
|
|
25
|
+
import { Module } from '@nestjs/common';
|
|
26
|
+
import { InertiaModule } from '@dudousxd/nestjs-inertia';
|
|
27
|
+
|
|
28
|
+
@Module({
|
|
29
|
+
imports: [
|
|
30
|
+
InertiaModule.forRoot({
|
|
31
|
+
version: () => process.env.ASSET_VERSION ?? 'dev',
|
|
32
|
+
rootView: 'inertia/root.html',
|
|
33
|
+
share: async (req) => ({ auth: req.user ?? null }),
|
|
34
|
+
}),
|
|
35
|
+
],
|
|
36
|
+
})
|
|
37
|
+
export class AppModule {}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`inertia/root.html`:
|
|
41
|
+
|
|
42
|
+
```html
|
|
43
|
+
<!doctype html>
|
|
44
|
+
<html lang="en">
|
|
45
|
+
<head>
|
|
46
|
+
<meta charset="utf-8" />
|
|
47
|
+
<title>My App</title>
|
|
48
|
+
@inertiaHead
|
|
49
|
+
@vite('app/client.tsx')
|
|
50
|
+
@viteRefresh
|
|
51
|
+
</head>
|
|
52
|
+
<body>
|
|
53
|
+
@inertia
|
|
54
|
+
</body>
|
|
55
|
+
</html>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Controllers
|
|
59
|
+
|
|
60
|
+
Two equivalent patterns coexist:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { Controller, Get, Req } from '@nestjs/common';
|
|
64
|
+
import { Inertia } from '@dudousxd/nestjs-inertia';
|
|
65
|
+
import type { Request } from 'express';
|
|
66
|
+
|
|
67
|
+
@Controller()
|
|
68
|
+
export class HomeController {
|
|
69
|
+
// Decorator pattern (idiomatic for new code)
|
|
70
|
+
@Get('/')
|
|
71
|
+
@Inertia('Home')
|
|
72
|
+
show() {
|
|
73
|
+
return { hello: 'world' };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Imperative pattern (use when you need fine control)
|
|
77
|
+
@Get('/crew')
|
|
78
|
+
async list(@Req() req: Request) {
|
|
79
|
+
await req.inertia
|
|
80
|
+
.share({ flash: req.session?.flash ?? {} })
|
|
81
|
+
.render('Crew', { crew: await this.svc.list() });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Async config
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
InertiaModule.forRootAsync({
|
|
90
|
+
imports: [ConfigModule],
|
|
91
|
+
inject: [ConfigService],
|
|
92
|
+
useFactory: (cfg: ConfigService) => ({
|
|
93
|
+
version: cfg.get('ASSET_VERSION'),
|
|
94
|
+
rootView: 'inertia/root.html',
|
|
95
|
+
}),
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Three paths: `useFactory + inject` (most common), `useClass`, `useExisting`.
|
|
100
|
+
|
|
101
|
+
## Multi-app with `forFeature`
|
|
102
|
+
|
|
103
|
+
Host two or more Inertia apps in the same NestJS process — each with its own Vite entry, shell, version, share, SSR bundle. Useful for admin panels, multi-tenant white-label, or migrations between frontend stacks.
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
@Module({
|
|
107
|
+
imports: [
|
|
108
|
+
InertiaModule.forRoot({ // main app
|
|
109
|
+
vite: { entry: 'app/client.tsx' },
|
|
110
|
+
rootView: 'inertia/root.html',
|
|
111
|
+
share: req => ({ auth: req.user }),
|
|
112
|
+
}),
|
|
113
|
+
InertiaModule.forFeature({ // admin app
|
|
114
|
+
scope: 'admin',
|
|
115
|
+
vite: { entry: 'admin/client.tsx' },
|
|
116
|
+
rootView: 'inertia/admin-root.html',
|
|
117
|
+
share: req => ({ admin: req.adminContext }),
|
|
118
|
+
}),
|
|
119
|
+
],
|
|
120
|
+
})
|
|
121
|
+
export class AppModule {}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Select scope per controller / method with `@UseInertia('scope')`:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
@Controller('admin')
|
|
128
|
+
@UseInertia('admin')
|
|
129
|
+
export class AdminDashboardController {
|
|
130
|
+
@Get('/')
|
|
131
|
+
@Inertia('AdminDashboard')
|
|
132
|
+
show() { return { stats: ... }; }
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`forFeatureAsync` works the same as `forRootAsync` (useFactory/useClass/useExisting). Reserved scope: `'default'` is owned by `forRoot()`.
|
|
137
|
+
|
|
138
|
+
## Template engines
|
|
139
|
+
|
|
140
|
+
`rootView` accepts `.html` (own parser) plus `.hbs` / `.ejs` / `.pug` / `.liquid` if the engine package is installed:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
pnpm add handlebars # or: ejs, pug, liquidjs
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
InertiaModule.forRoot({
|
|
148
|
+
rootView: 'inertia/root.hbs', // auto-detects Handlebars
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Each engine sees locals `{ page, inertia, inertiaHead, vite, viteRefresh, asset }`. Use the engine's own escape rules (e.g., `{{{inertia}}}` triple-stache in Handlebars, `<%- inertia %>` in EJS, `!= inertia` in Pug). The `@inertia`/`@vite`/`@asset` directives are also processed on the engine's output, so you can mix and match.
|
|
153
|
+
|
|
154
|
+
## CSRF
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
import { CsrfCookieInterceptor, CsrfGuard } from '@dudousxd/nestjs-inertia';
|
|
158
|
+
|
|
159
|
+
// Global cookie writer
|
|
160
|
+
app.useGlobalInterceptors(new CsrfCookieInterceptor({ secret: process.env.CSRF_SECRET }));
|
|
161
|
+
|
|
162
|
+
// Per-route validation
|
|
163
|
+
@UseGuards(new CsrfGuard({ secret: process.env.CSRF_SECRET }))
|
|
164
|
+
@Post('/profile')
|
|
165
|
+
async update() { ... }
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Cookie name `XSRF-TOKEN`, header name `X-XSRF-TOKEN` — both match the Inertia client convention. Signed via HMAC-SHA256. Requires `cookie-parser` (Express) or `@fastify/cookie` (Fastify) as peer dep.
|
|
169
|
+
|
|
170
|
+
## Fastify
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
import { FastifyAdapter } from '@nestjs/platform-fastify';
|
|
174
|
+
|
|
175
|
+
const app = await NestFactory.create<NestFastifyApplication>(AppModule, new FastifyAdapter());
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Full feature parity with Express: middleware, decorator, all interceptors, guards, filters, shell directives, SSR, FlashStore. `request.inertia` is wired via `decorateRequest` + `onRequest` hook automatically when the FastifyAdapter is detected.
|
|
179
|
+
|
|
180
|
+
## Prop markers
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
import { Inertia } from '@dudousxd/nestjs-inertia';
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
user: Inertia.always(() => currentUser()),
|
|
187
|
+
stats: Inertia.optional(() => heavyStatsCalculation()),
|
|
188
|
+
activity: Inertia.defer(() => activityFeed(), 'secondary'),
|
|
189
|
+
rows: Inertia.merge(() => paginated(p), { matchOn: 'id', deep: false }),
|
|
190
|
+
csrfToken: Inertia.once(() => generateToken()),
|
|
191
|
+
};
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
- `always` — resolves on every render (including partial reloads)
|
|
195
|
+
- `optional` — resolves only when listed in `X-Inertia-Partial-Data`
|
|
196
|
+
- `defer` — listed in `page.deferredProps`; client v2 dispatches a follow-up request
|
|
197
|
+
- `merge` — resolves + marks as merge target (client appends/replaces)
|
|
198
|
+
- `once` — resolves on first visit, cached until `X-Inertia-Reset-Once` lists the key
|
|
199
|
+
- `lazy` — alias for `optional` (v1 compat)
|
|
200
|
+
|
|
201
|
+
## Auto-included infrastructure
|
|
202
|
+
|
|
203
|
+
`forRoot()` installs:
|
|
204
|
+
- `InertiaMiddleware` (Express) or `FastifyInertiaPlugin` (Fastify) — `req.inertia` available everywhere
|
|
205
|
+
- `MethodSpoofMiddleware` (POST + multipart + `_method=PUT/PATCH/DELETE`)
|
|
206
|
+
- `RedirectInterceptor` (302 → 303 upgrade on PUT/PATCH/DELETE Inertia requests)
|
|
207
|
+
- `InertiaRenderInterceptor` (handles `@Inertia('Page')` decorator)
|
|
208
|
+
- `InertiaScopeSwitcherInterceptor` (handles `@UseInertia('scope')`)
|
|
209
|
+
|
|
210
|
+
Disable via knobs:
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
InertiaModule.forRoot({
|
|
214
|
+
methodSpoofing: false,
|
|
215
|
+
autoUpgrade303: false,
|
|
216
|
+
});
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Opt-in utilities
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
import {
|
|
223
|
+
InertiaAuthGuard,
|
|
224
|
+
InertiaNotFoundFilter,
|
|
225
|
+
ErrorBagInterceptor,
|
|
226
|
+
} from '@dudousxd/nestjs-inertia';
|
|
227
|
+
|
|
228
|
+
// Auth guard — applies per controller / handler
|
|
229
|
+
@UseGuards(new InertiaAuthGuard({ signInUrl: '/signin', allowList: ['/signin/*'] }))
|
|
230
|
+
|
|
231
|
+
// Not-found filter — register globally
|
|
232
|
+
app.useGlobalFilters(new InertiaNotFoundFilter({ apiPrefix: '/api', component: 'NotFound' }));
|
|
233
|
+
|
|
234
|
+
// Error bag interceptor — opt-in per route
|
|
235
|
+
@UseInterceptors(ErrorBagInterceptor)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## FlashStore (errors)
|
|
239
|
+
|
|
240
|
+
NestJS has no session of its own. Plug an adapter:
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
import type { FlashStore } from '@dudousxd/nestjs-inertia';
|
|
244
|
+
|
|
245
|
+
class ExpressSessionFlashStore implements FlashStore {
|
|
246
|
+
read(req) {
|
|
247
|
+
return (req as Request).session?.flash?.errors ?? {};
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
InertiaModule.forRoot({
|
|
252
|
+
flashStore: new ExpressSessionFlashStore(),
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## SSR
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
InertiaModule.forRoot({
|
|
260
|
+
ssr: {
|
|
261
|
+
enabled: process.env.NODE_ENV === 'production',
|
|
262
|
+
bundlePath: 'dist/inertia/ssr/ssr.mjs',
|
|
263
|
+
throwOnError: false,
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Bundle must export `default { render(page) }` or named `render(page)` returning `{ head: string[], body: string }`.
|
|
269
|
+
|
|
270
|
+
## Codegen auto-watch (dev mode)
|
|
271
|
+
|
|
272
|
+
When `@dudousxd/nestjs-inertia-codegen` is installed and a `nestjs-inertia.config.ts` config file is present, `InertiaModule` automatically starts the codegen file watcher when your app bootstraps — so `nest start --watch` is the only command you need in dev mode. Generated files appear under `.nestjs-inertia/` and update on every controller or page save. **No extra command, no extra terminal.**
|
|
273
|
+
|
|
274
|
+
**Auto-watch starts when all of these are true:**
|
|
275
|
+
1. `NODE_ENV !== 'production'`
|
|
276
|
+
2. `@dudousxd/nestjs-inertia-codegen` is installed (peer-optional — silently skipped if absent)
|
|
277
|
+
3. `nestjs-inertia.config.ts` is present at the project root
|
|
278
|
+
4. `NESTJS_INERTIA_DISABLE_AUTO_CODEGEN` is not set to `'1'`
|
|
279
|
+
|
|
280
|
+
**Running the CLI watcher manually:** `pnpm nestjs-inertia codegen --watch` in a separate terminal gives you explicit control. When both the auto-watcher and the CLI watcher run at the same time, only one holds the lock and generates files; the other logs a warning and becomes a no-op. Stale locks from crashed processes are detected via PID-liveness check and overwritten automatically.
|
|
281
|
+
|
|
282
|
+
**Disable auto-watch** (CI or explicit control):
|
|
283
|
+
|
|
284
|
+
```ts
|
|
285
|
+
InertiaModule.forRoot({
|
|
286
|
+
codegen: { enabled: false },
|
|
287
|
+
});
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**Optional `nest-cli.json` snippet** — only useful if your server bundle imports generated files:
|
|
291
|
+
|
|
292
|
+
```json
|
|
293
|
+
{
|
|
294
|
+
"compilerOptions": {
|
|
295
|
+
"assets": [".nestjs-inertia/**/*"],
|
|
296
|
+
"watchAssets": true
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Protocol parity
|
|
302
|
+
|
|
303
|
+
Full Inertia protocol: X-Inertia headers, version mismatch (409 + X-Inertia-Location, GET only), partial reloads, deferred props, merge/deepMerge with matchOn, once, history encryption / clear, error bags, X-Inertia-Reset, X-Inertia-Partial-Except, X-Inertia-Reset-Once, dot-notation unpacking, undefined→null wire conversion.
|
|
304
|
+
|
|
305
|
+
## Companion packages (planned)
|
|
306
|
+
|
|
307
|
+
- `@dudousxd/nestjs-inertia-vite` — Vite dev/build helpers (Plan B)
|
|
308
|
+
- `@dudousxd/nestjs-inertia-testing` — `expectInertia(res)` matchers (Plan B)
|
|
309
|
+
- `@dudousxd/nestjs-inertia-codegen` — typed pages (Plan C)
|
|
310
|
+
- `@dudousxd/nestjs-inertia-client` — Tuyau-style typed REST + TanStack Query (Plan D)
|
|
311
|
+
- Examples + docs site + CI workflows (Plan E)
|
|
312
|
+
|
|
313
|
+
See [`docs/design.md`](../../docs/design.md) for full design.
|
|
314
|
+
|
|
315
|
+
## License
|
|
316
|
+
|
|
317
|
+
MIT © Davi Carvalho
|