@blacklizard/inertia-express 2.0.0 → 3.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/README.md +153 -23
- package/dist/core/index.d.ts +2 -552
- package/dist/core/index.js +2 -662
- package/dist/core-ChQbBw-k.js +1499 -0
- package/dist/core-ChQbBw-k.js.map +1 -0
- package/dist/express/index.d.ts +17 -3
- package/dist/express/index.d.ts.map +1 -1
- package/dist/express/index.js +35 -6
- package/dist/express/index.js.map +1 -1
- package/dist/index-PWaujyMo.d.ts +1133 -0
- package/dist/index-PWaujyMo.d.ts.map +1 -0
- package/package.json +2 -2
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ The internal core is framework-agnostic, so future adapters for Fastify, Hono, N
|
|
|
17
17
|
- Full implementation of the Inertia.js v3 protocol
|
|
18
18
|
- `res.inertia(component, props, options)` Express helper
|
|
19
19
|
- Shared props (static or per-request)
|
|
20
|
-
-
|
|
20
|
+
- Optional, deferred (with rescue), always, merge, deep-merge, once, and infinite-scroll props
|
|
21
21
|
- Partial reloads (`X-Inertia-Partial-Data` / `X-Inertia-Partial-Except`)
|
|
22
22
|
- Asset version handling and automatic 409 + `X-Inertia-Location` on mismatch
|
|
23
23
|
- Inertia-aware redirects (auto 303 after PUT/PATCH/DELETE)
|
|
@@ -126,40 +126,169 @@ inertia({
|
|
|
126
126
|
});
|
|
127
127
|
```
|
|
128
128
|
|
|
129
|
-
##
|
|
129
|
+
## Prop helpers
|
|
130
|
+
|
|
131
|
+
Every prop value is either a plain value, a bare closure (evaluated on each
|
|
132
|
+
response that includes it, awaited if it returns a promise), or a descriptor
|
|
133
|
+
built by one of the constructors below. Descriptors are chainable and mirror
|
|
134
|
+
inertia-laravel's prop classes; the resolver is a port of its `PropsResolver`.
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
import { always, deepMerge, defer, merge, once, optional, scroll } from "@blacklizard/inertia-express";
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
A bare closure replaces 2.x's `lazy()`:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
app.get("/dashboard", async (req, res) => {
|
|
144
|
+
await res.inertia("Dashboard", {
|
|
145
|
+
user: req.user,
|
|
146
|
+
recent: () => userService.recent(),
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### `optional`
|
|
152
|
+
|
|
153
|
+
Omitted from the first load; resolved only when a partial reload asks for it.
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
app.get("/reports", async (req, res) => {
|
|
157
|
+
await res.inertia("Reports", {
|
|
158
|
+
heavy: optional(() => analytics.expensiveQuery()),
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Laravel: `Inertia::optional()`
|
|
164
|
+
|
|
165
|
+
### `defer`
|
|
166
|
+
|
|
167
|
+
Omitted from the first load; the client fetches it in a follow-up request with
|
|
168
|
+
the rest of its group (default `"default"`). `.rescue()` opts in to error
|
|
169
|
+
rescue: if the producer throws, the prop is dropped from the response, its path
|
|
170
|
+
is reported in `rescuedProps`, and the middleware's `onRescue` hook is called.
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
app.get("/dashboard", async (req, res) => {
|
|
174
|
+
await res.inertia("Dashboard", {
|
|
175
|
+
stats: defer(() => dashboard.getStats(), "dashboard"),
|
|
176
|
+
feed: defer(() => feedService.load()).rescue(),
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Laravel: `Inertia::defer()`
|
|
182
|
+
|
|
183
|
+
### `always`
|
|
184
|
+
|
|
185
|
+
Included on every response, even partial reloads that do not request it.
|
|
186
|
+
Takes a plain value or a closure.
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
app.get("/inbox", async (req, res) => {
|
|
190
|
+
await res.inertia("Inbox", {
|
|
191
|
+
flash: always(() => req.session.flash),
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Laravel: `Inertia::always()`
|
|
197
|
+
|
|
198
|
+
### `merge` / `deepMerge`
|
|
199
|
+
|
|
200
|
+
`merge(value)` tells the client to append the prop to its existing value at the
|
|
201
|
+
root; `deepMerge(value)` merges recursively. Chain `.append(path?, matchOn?)`
|
|
202
|
+
or `.prepend(path?, matchOn?)` to target nested paths, and `.matchOn(path)` to
|
|
203
|
+
dedupe items by a key instead of blindly appending. A path argument may be
|
|
204
|
+
`true` (the root, the default), a dotted path string, a list of paths, or a
|
|
205
|
+
`{ path: matchOnKey }` record. A client can send `X-Inertia-Reset: <key>` to
|
|
206
|
+
have a merge prop replaced instead of merged on that visit.
|
|
130
207
|
|
|
131
208
|
```ts
|
|
132
|
-
|
|
209
|
+
app.get("/notifications", async (req, res) => {
|
|
210
|
+
await res.inertia("Notifications", {
|
|
211
|
+
notifications: merge(() => db.notifications.page(req.query.page)).matchOn("id"),
|
|
212
|
+
feed: merge(() => feedService.page(req.query.page)).append("data", "id").prepend("pinned"),
|
|
213
|
+
filters: deepMerge({ sort: { field: "name" } }),
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
```
|
|
133
217
|
|
|
134
|
-
|
|
135
|
-
// Always evaluated.
|
|
136
|
-
user: req.user,
|
|
218
|
+
Laravel: `Inertia::merge()` / `Inertia::deepMerge()`
|
|
137
219
|
|
|
138
|
-
|
|
139
|
-
recent: lazy(() => userService.recent()),
|
|
220
|
+
### `once`
|
|
140
221
|
|
|
141
|
-
|
|
142
|
-
|
|
222
|
+
Resolved once and cached by the client across visits; later visits skip the
|
|
223
|
+
producer while the client still holds the value. `.as(key)` sets a custom cache
|
|
224
|
+
key (shareable across pages), `.until(delay)` sets an expiry (a number of
|
|
225
|
+
seconds, or a `Date`), and `.fresh()` forces re-resolution on this response.
|
|
226
|
+
`optional`, `defer` and `merge` descriptors also accept `.once()`.
|
|
143
227
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
228
|
+
```ts
|
|
229
|
+
app.get("/settings", async (req, res) => {
|
|
230
|
+
await res.inertia("Settings", {
|
|
231
|
+
countries: once(() => geo.countries()).as("countries").until(3600),
|
|
232
|
+
plans: once(() => billing.plans()).until(new Date("2027-01-01")),
|
|
233
|
+
banner: once(() => cms.banner()).fresh(req.query.refresh === "1"),
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Laravel: `Inertia::once()`
|
|
147
239
|
|
|
148
|
-
|
|
149
|
-
flash: always(() => req.session.flash),
|
|
240
|
+
### `scroll`
|
|
150
241
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
242
|
+
An infinite-scroll prop: merged at its `wrapper` key (default `"data"`) and
|
|
243
|
+
emitted with pagination metadata. `metadata` is required — either an object
|
|
244
|
+
`{ pageName, previousPage, nextPage, currentPage }` or a function deriving it
|
|
245
|
+
from the resolved value. The merge direction follows the client's
|
|
246
|
+
`X-Inertia-Infinite-Scroll-Merge-Intent` header (`prepend`, otherwise
|
|
247
|
+
`append`). Chain `.defer(group?)` to load the first page in a follow-up
|
|
248
|
+
request, and `.matchOn(path)` to dedupe.
|
|
155
249
|
|
|
156
|
-
|
|
157
|
-
|
|
250
|
+
```ts
|
|
251
|
+
app.get("/posts", async (req, res) => {
|
|
252
|
+
await res.inertia("Posts", {
|
|
253
|
+
posts: scroll(() => postService.paginate(req.query.page), {
|
|
254
|
+
metadata: (page) => ({ pageName: "page", previousPage: page.prev, nextPage: page.next, currentPage: page.current }),
|
|
255
|
+
}).matchOn("id"),
|
|
256
|
+
comments: scroll(() => commentService.page(1), { wrapper: "items", metadata: { pageName: "page", previousPage: null, nextPage: 2, currentPage: 1 } }).defer(),
|
|
257
|
+
});
|
|
158
258
|
});
|
|
159
259
|
```
|
|
160
260
|
|
|
161
|
-
|
|
162
|
-
|
|
261
|
+
Laravel: `Inertia::scroll()`
|
|
262
|
+
|
|
263
|
+
### Rescue hook and fragment preservation
|
|
264
|
+
|
|
265
|
+
Pass `onRescue: (error, path, req) => …` to `inertia({ … })` to report errors
|
|
266
|
+
from rescued deferred props (the default logs them). Pass
|
|
267
|
+
`{ preserveFragment: true }` as the third argument to `res.inertia()` to have
|
|
268
|
+
the client keep the current URL `#fragment` across that visit.
|
|
269
|
+
|
|
270
|
+
### Migrating from 2.x
|
|
271
|
+
|
|
272
|
+
| 2.x | 3.0 |
|
|
273
|
+
|---|---|
|
|
274
|
+
| `lazy(fn)` | `fn` (a bare closure) |
|
|
275
|
+
| `merge(value, matchOn)` | `merge(value).matchOn(matchOn)` |
|
|
276
|
+
| `deepMerge(value, matchOn)` | `deepMerge(value).matchOn(matchOn)` |
|
|
277
|
+
| `defer(fn, group)` | unchanged |
|
|
278
|
+
| `optional(fn)`, `always(fn)` | unchanged |
|
|
279
|
+
| `resolveProps()` → `{ props, deferred, merge, deepMerge, matchOn }` | `{ props, metadata }` |
|
|
280
|
+
| Types `MergeProp<T> = T & {...}` | descriptor types; the page `props` type is unchanged |
|
|
281
|
+
|
|
282
|
+
Other changes to check when upgrading:
|
|
283
|
+
|
|
284
|
+
- `merge(value, "id")` and `deepMerge(value, "id")` now throw a `TypeError` instead of silently ignoring the second argument. Use `.matchOn("id")`.
|
|
285
|
+
- Partial reloads follow dotted paths. `only: users.data` on a plain-object `users` now returns just `{ users: { data } }`; children of a closure or descriptor still bypass filtering and come back whole. `except: users.data` drops the whole top-level `users`, as in 2.x and in the upstream reference server; inertia-laravel keeps `users` and drops only the child, and the adapter deliberately differs from it here.
|
|
286
|
+
- Types: `DeferredProp` is renamed to `DeferProp`. `LazyProp`, `DeepMergeProp` and `PropValue` are removed. `MergeProp<T>` is now the descriptor interface returned by `merge()` / `deepMerge()`, not `T & {...}`.
|
|
287
|
+
- Core-entry adapter authors who build these objects by hand: `InertiaPage.preserveFragment` is now required, and `InertiaRequestInfo` gains the required fields `exceptOnceProps` and `mergeIntent`.
|
|
288
|
+
- `sharedProps` (the list of shared top-level keys) is now emitted on every page that has shared props.
|
|
289
|
+
- Descriptors are builders. Create them inside the handler for each request rather than mutating one hoisted to module level; the resolver itself only reads them, so a hoisted descriptor that is never changed after creation is safe to reuse.
|
|
290
|
+
- Sibling props still resolve concurrently, as in 2.x, and the output key order and metadata order are deterministic (insertion order).
|
|
291
|
+
- Errors thrown by the `onRescue` hook (or a rejected promise it returns) are logged with `console.error` and never propagated.
|
|
163
292
|
|
|
164
293
|
## Partial reloads
|
|
165
294
|
|
|
@@ -537,6 +666,7 @@ For typed page props on the client side, generic `res.inertia<TProps>("Component
|
|
|
537
666
|
- The default HTML shell is intentionally minimal. Real apps should provide a `rootView`.
|
|
538
667
|
- This adapter does not bundle a session implementation — bring your own (`express-session`, `cookie-session`, etc.).
|
|
539
668
|
- Optimistic-update / `useHttp` server-side helpers are intentionally minimal here; the protocol primitives (`mergeProps`, `deepMergeProps`, `matchPropsOn`, `deferredProps`, `clearHistory`, `encryptHistory`) are exposed.
|
|
669
|
+
- Behaviour that differs from inertia-laravel or from 2.x is listed on the docs site's [Caveats](https://blacklizard.github.io/inertia-express/guide/caveats) page.
|
|
540
670
|
|
|
541
671
|
## License
|
|
542
672
|
|