@forinda/kickjs-inertia 0.0.0 → 0.2.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 +8 -4
- package/dist/index.d.mts +117 -38
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +177 -34
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[Inertia.js](https://inertiajs.com/) v3 adapter for [KickJS](https://kickjs.app/) —
|
|
4
4
|
server-driven pages rendered by React, Vue or Svelte, with server-side
|
|
5
|
-
rendering, on Express, Fastify or h3.
|
|
5
|
+
rendering, on Express, Fastify or h3 (v2).
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
pnpm add @forinda/kickjs-inertia @inertiajs/react react react-dom
|
|
@@ -75,21 +75,25 @@ export class PagesController {
|
|
|
75
75
|
| --------------------------------------------------------------------- | --------------------------------------------------------------- |
|
|
76
76
|
| `inertia(component, props, { status, encryptHistory, clearHistory })` | Render a page — HTML on a first visit, JSON on an Inertia visit |
|
|
77
77
|
| `optional(fn)` / `always(v)` / `defer(fn, group)` / `merge(v, mode)` | When a prop is sent |
|
|
78
|
+
| `once(fn, { key, ttl })` | Sent once; the client keeps it |
|
|
79
|
+
| `scroll(fn, { pageName, matchOn })` | One page of an `<InfiniteScroll>` list |
|
|
78
80
|
| `redirect(url)` / `back(ctx)` | 303 after a submit; `back` stays on this site |
|
|
79
81
|
| `location(url)` | A full browser visit (another site) |
|
|
80
82
|
| `flash(ctx, data)` | Data for the next page's `flash` |
|
|
83
|
+
| `@Middleware(precognitive)` | Live validation for `useForm().validate()` |
|
|
81
84
|
|
|
82
85
|
## What the adapter handles
|
|
83
86
|
|
|
84
|
-
- The protocol: `X-Inertia` JSON responses, `Vary`, partial reloads,
|
|
85
|
-
version check (409),
|
|
87
|
+
- The protocol: `X-Inertia` JSON responses, `Vary`, partial reloads, once
|
|
88
|
+
props, infinite scroll, the asset version check (409), redirects that keep a
|
|
89
|
+
`#fragment`, Precognition, 302 → 303 after PUT / PATCH / DELETE.
|
|
86
90
|
- Assets: Vite's dev server under `kick dev`; the build manifest, `assets/` and
|
|
87
91
|
`public/` files in production.
|
|
88
92
|
- Server-side rendering through Vite in dev and a built bundle in production,
|
|
89
93
|
falling back to client rendering if a render fails.
|
|
90
94
|
- Validation errors and flash data through the session.
|
|
91
95
|
|
|
92
|
-
Requires `@forinda/kickjs`
|
|
96
|
+
Requires `@forinda/kickjs` 10 or later.
|
|
93
97
|
|
|
94
98
|
## License
|
|
95
99
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,80 @@
|
|
|
1
1
|
|
|
2
|
-
import { RequestContext, Respondable, RuntimeResponse } from "@forinda/kickjs";
|
|
2
|
+
import { MiddlewareHandler, RequestContext, Respondable, RuntimeResponse } from "@forinda/kickjs";
|
|
3
|
+
//#region src/props.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Prop wrappers that change when a prop is sent. A plain value or a function
|
|
6
|
+
* is sent on every full visit and on a partial reload that asks for it; a
|
|
7
|
+
* function is only called when the prop is actually sent.
|
|
8
|
+
*
|
|
9
|
+
* @module @forinda/kickjs-inertia/props
|
|
10
|
+
*/
|
|
11
|
+
type Resolver<T> = T | (() => T | Promise<T>);
|
|
12
|
+
declare const PROP: unique symbol;
|
|
13
|
+
type PropKind = 'optional' | 'always' | 'defer' | 'merge' | 'once' | 'scroll';
|
|
14
|
+
/** Where an infinite-scroll prop is in its pages; `null` past either end. */
|
|
15
|
+
interface ScrollMeta {
|
|
16
|
+
currentPage: number | string | null;
|
|
17
|
+
nextPage: number | string | null;
|
|
18
|
+
previousPage: number | string | null;
|
|
19
|
+
}
|
|
20
|
+
interface InertiaProp<T = unknown> {
|
|
21
|
+
readonly [PROP]: PropKind;
|
|
22
|
+
readonly value: Resolver<T>;
|
|
23
|
+
/** `defer` only — props in one group load in one request. */
|
|
24
|
+
readonly group?: string;
|
|
25
|
+
/** `merge` only — append, prepend or deep-merge into the client's copy. */
|
|
26
|
+
readonly mode?: 'append' | 'prepend' | 'deep';
|
|
27
|
+
/** `merge` and `scroll` — item fields that identify a row, e.g. `['id']`. */
|
|
28
|
+
readonly matchOn?: string[];
|
|
29
|
+
/** `once` only — the name the client caches it under. */
|
|
30
|
+
readonly key?: string;
|
|
31
|
+
/** `once` only — milliseconds the client may keep it. */
|
|
32
|
+
readonly ttl?: number;
|
|
33
|
+
/** `scroll` only. */
|
|
34
|
+
readonly pageName?: string;
|
|
35
|
+
readonly wrapper?: string;
|
|
36
|
+
readonly meta?: (value: any) => ScrollMeta;
|
|
37
|
+
}
|
|
38
|
+
/** Sent only when a partial reload names it (`router.reload({ only: [...] })`). */
|
|
39
|
+
export declare function optional<T>(value: () => T | Promise<T>): InertiaProp<T>;
|
|
40
|
+
/** Sent on every response, including partial reloads that did not name it. */
|
|
41
|
+
export declare function always<T>(value: Resolver<T>): InertiaProp<T>;
|
|
42
|
+
/**
|
|
43
|
+
* Left out of the first response; the client fetches it right after the page
|
|
44
|
+
* renders, one request per `group`.
|
|
45
|
+
*/
|
|
46
|
+
export declare function defer<T>(value: () => T | Promise<T>, group?: string): InertiaProp<T>;
|
|
47
|
+
/**
|
|
48
|
+
* Merged into the client's current value on a partial reload instead of
|
|
49
|
+
* replacing it — paginated lists, infinite scroll.
|
|
50
|
+
*/
|
|
51
|
+
export declare function merge<T>(value: Resolver<T>, mode?: 'append' | 'prepend' | 'deep', options?: {
|
|
52
|
+
matchOn?: string | string[];
|
|
53
|
+
}): InertiaProp<T>;
|
|
54
|
+
/**
|
|
55
|
+
* Resolved once, then kept by the client: later visits skip it until `ttl`
|
|
56
|
+
* runs out. For data that rarely changes — plans, countries, a settings
|
|
57
|
+
* list. Pages that share a `key` share the cached value.
|
|
58
|
+
*/
|
|
59
|
+
export declare function once<T>(value: Resolver<T>, options?: {
|
|
60
|
+
key?: string;
|
|
61
|
+
ttl?: number;
|
|
62
|
+
}): InertiaProp<T>;
|
|
63
|
+
/**
|
|
64
|
+
* One page of an infinite list, for Inertia's `<InfiniteScroll>`. The client
|
|
65
|
+
* asks for the next or previous page with `?page=N` and appends or prepends
|
|
66
|
+
* the rows under `wrapper`.
|
|
67
|
+
*
|
|
68
|
+
* The value is read as `{ data, meta: { page, hasNext, hasPrev } }` — the
|
|
69
|
+
* `PaginatedResponse` shape; pass `meta` to read another one.
|
|
70
|
+
*/
|
|
71
|
+
export declare function scroll<T>(value: Resolver<T>, options?: {
|
|
72
|
+
pageName?: string;
|
|
73
|
+
wrapper?: string;
|
|
74
|
+
matchOn?: string | string[];
|
|
75
|
+
meta?: (value: T) => ScrollMeta;
|
|
76
|
+
}): InertiaProp<T>;
|
|
77
|
+
//#endregion
|
|
3
78
|
//#region src/inertia.d.ts
|
|
4
79
|
/** The page object the Inertia client renders — see the protocol docs. */
|
|
5
80
|
interface Page {
|
|
@@ -15,8 +90,18 @@ interface Page {
|
|
|
15
90
|
mergeProps?: string[];
|
|
16
91
|
prependProps?: string[];
|
|
17
92
|
deepMergeProps?: string[];
|
|
93
|
+
matchPropsOn?: string[];
|
|
94
|
+
scrollProps?: Record<string, ScrollMeta & {
|
|
95
|
+
pageName: string;
|
|
96
|
+
reset: boolean;
|
|
97
|
+
}>;
|
|
98
|
+
onceProps?: Record<string, {
|
|
99
|
+
prop: string;
|
|
100
|
+
expiresAt?: number;
|
|
101
|
+
}>;
|
|
18
102
|
encryptHistory?: boolean;
|
|
19
103
|
clearHistory?: boolean;
|
|
104
|
+
preserveFragment?: boolean;
|
|
20
105
|
}
|
|
21
106
|
interface PageOptions {
|
|
22
107
|
/** Encrypt this page's history entry in the browser. */
|
|
@@ -41,14 +126,26 @@ export declare function buildPage(ctx: RequestContext, component: string, props:
|
|
|
41
126
|
* ```
|
|
42
127
|
*/
|
|
43
128
|
export declare function inertia(component: string, props?: Record<string, unknown>, options?: PageOptions): Respondable;
|
|
129
|
+
interface RedirectOptions {
|
|
130
|
+
/**
|
|
131
|
+
* Keep the `#fragment` of the URL the request was sent to — a form that
|
|
132
|
+
* posts to `/posts/1/comments#comments` lands on `#comments` of the page
|
|
133
|
+
* this redirects to. Needs `session()`.
|
|
134
|
+
*/
|
|
135
|
+
preserveFragment?: boolean;
|
|
136
|
+
}
|
|
44
137
|
/**
|
|
45
138
|
* Redirect after a form submit. Uses 303 for anything but GET, so the browser
|
|
46
139
|
* follows with a GET, and saves the session first so flashed errors and
|
|
47
140
|
* messages are there when that GET arrives.
|
|
141
|
+
*
|
|
142
|
+
* A URL with a `#fragment` is sent to an Inertia visit as 409 +
|
|
143
|
+
* `X-Inertia-Redirect`: a redirect followed inside a fetch drops the
|
|
144
|
+
* fragment, so the client makes the visit itself.
|
|
48
145
|
*/
|
|
49
|
-
export declare function redirect(url: string): Respondable;
|
|
146
|
+
export declare function redirect(url: string, options?: RedirectOptions): Respondable;
|
|
50
147
|
/** Redirect back to the page the request came from. */
|
|
51
|
-
export declare function back(ctx: RequestContext, fallback?: string): Respondable;
|
|
148
|
+
export declare function back(ctx: RequestContext, fallback?: string, options?: RedirectOptions): Respondable;
|
|
52
149
|
/**
|
|
53
150
|
* A full browser visit, outside Inertia — another site, or a page that is not
|
|
54
151
|
* an Inertia page. An Inertia request gets 409 + `X-Inertia-Location`, which
|
|
@@ -135,45 +232,27 @@ interface SsrResult {
|
|
|
135
232
|
}
|
|
136
233
|
export declare const InertiaAdapter: import("@forinda/kickjs").AdapterFactory<InertiaAdapterOptions, unknown>;
|
|
137
234
|
//#endregion
|
|
138
|
-
//#region src/
|
|
235
|
+
//#region src/errors.d.ts
|
|
236
|
+
type ErrorHandler = (err: any, req: any, res: RuntimeResponse, next: (err?: unknown) => void) => unknown;
|
|
139
237
|
/**
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
238
|
+
* Live validation for Inertia's `useForm().validate()` — Laravel Precognition.
|
|
239
|
+
* Put it on a route that validates its body; a precognitive request runs the
|
|
240
|
+
* validation and stops before the handler:
|
|
143
241
|
*
|
|
144
|
-
*
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
readonly value: Resolver<T>;
|
|
152
|
-
/** `defer` only — props in one group load in one request. */
|
|
153
|
-
readonly group?: string;
|
|
154
|
-
/** `merge` only — append, prepend or deep-merge into the client's copy. */
|
|
155
|
-
readonly mode?: 'append' | 'prepend' | 'deep';
|
|
156
|
-
}
|
|
157
|
-
/** Sent only when a partial reload names it (`router.reload({ only: [...] })`). */
|
|
158
|
-
export declare function optional<T>(value: () => T | Promise<T>): InertiaProp<T>;
|
|
159
|
-
/** Sent on every response, including partial reloads that did not name it. */
|
|
160
|
-
export declare function always<T>(value: Resolver<T>): InertiaProp<T>;
|
|
161
|
-
/**
|
|
162
|
-
* Left out of the first response; the client fetches it right after the page
|
|
163
|
-
* renders, one request per `group`.
|
|
164
|
-
*/
|
|
165
|
-
export declare function defer<T>(value: () => T | Promise<T>, group?: string): InertiaProp<T>;
|
|
166
|
-
/**
|
|
167
|
-
* Merged into the client's current value on a partial reload instead of
|
|
168
|
-
* replacing it — paginated lists, infinite scroll.
|
|
242
|
+
* ```ts
|
|
243
|
+
* @Post('/users', { body: createUserSchema })
|
|
244
|
+
* @Middleware(precognitive)
|
|
245
|
+
* create(ctx: RequestContext) { ... }
|
|
246
|
+
* ```
|
|
247
|
+
*
|
|
248
|
+
* A failed check is answered by `inertiaErrorHandler()`.
|
|
169
249
|
*/
|
|
170
|
-
export declare
|
|
171
|
-
//#endregion
|
|
172
|
-
//#region src/errors.d.ts
|
|
173
|
-
type ErrorHandler = (err: any, req: any, res: RuntimeResponse, next: (err?: unknown) => void) => void;
|
|
250
|
+
export declare const precognitive: MiddlewareHandler<RequestContext>;
|
|
174
251
|
/**
|
|
175
252
|
* Pass to `bootstrap({ onError: inertiaErrorHandler() })`.
|
|
176
253
|
*
|
|
254
|
+
* - A validation 422 on a Precognition request is answered with the errors
|
|
255
|
+
* of the fields it asked about (see `precognitive`).
|
|
177
256
|
* - A validation 422 on an Inertia visit goes back to the form with the
|
|
178
257
|
* errors (needs the `session()` middleware to carry them).
|
|
179
258
|
* - Any other error on a page request renders the adapter's `errorPage`,
|
|
@@ -181,7 +260,7 @@ type ErrorHandler = (err: any, req: any, res: RuntimeResponse, next: (err?: unkn
|
|
|
181
260
|
* - Everything else — API calls, assets — goes to `fallback`.
|
|
182
261
|
*/
|
|
183
262
|
export declare function inertiaErrorHandler(fallback?: ErrorHandler): ErrorHandler;
|
|
184
|
-
type NotFoundHandler = (req: any, res: RuntimeResponse, next: (err?: unknown) => void) => void
|
|
263
|
+
type NotFoundHandler = (req: any, res: RuntimeResponse, next: (err?: unknown) => void) => void | Promise<void>;
|
|
185
264
|
/**
|
|
186
265
|
* Pass to `bootstrap({ onNotFound: inertiaNotFound() })`. A browser or Inertia
|
|
187
266
|
* request for an unknown URL gets the adapter's `errorPage` with 404; anything
|
|
@@ -189,5 +268,5 @@ type NotFoundHandler = (req: any, res: RuntimeResponse, next: (err?: unknown) =>
|
|
|
189
268
|
*/
|
|
190
269
|
export declare function inertiaNotFound(): NotFoundHandler;
|
|
191
270
|
//#endregion
|
|
192
|
-
export type { HtmlParts, HtmlTemplate, InertiaAdapterOptions, InertiaProp, Page, PageOptions, SsrResult };
|
|
271
|
+
export type { HtmlParts, HtmlTemplate, InertiaAdapterOptions, InertiaProp, Page, PageOptions, RedirectOptions, ScrollMeta, SsrResult };
|
|
193
272
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/props.ts","../src/inertia.ts","../src/html.ts","../src/adapter.ts","../src/errors.ts"],"mappings":";;;;;;;;;;KAQK,SAAS,KAAK,WAAW,IAAI,QAAQ;cAEpC;KAEM;;UAGK;EACf;EACA;EACA;;UAGe,YAAY;YACjB,OAAO;WACR,OAAO,SAAS;;WAEhB;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;WACA;WACA,QAAQ,eAAe;;;wBAQlB,SAAS,GAAG,aAAa,IAAI,QAAQ,KAAK,YAAY;;wBAKtD,OAAO,GAAG,OAAO,SAAS,KAAK,YAAY;;;;;wBAQ3C,MAAM,GAAG,aAAa,IAAI,QAAQ,IAAI,iBAAoB,YAAY;;;;;wBAQtE,MAAM,GACpB,OAAO,SAAS,IAChB,sCACA;EAAW;IACV,YAAY;;;;;;wBASC,KAAK,GACnB,OAAO,SAAS,IAChB;EAAW;EAAc;IACxB,YAAY;;;;;;;;;wBAYC,OAAO,GACrB,OAAO,SAAS,IAChB;EACE;EACA;EACA;EACA,QAAQ,OAAO,MAAM;IAEtB,YAAY;;;;UCpFE;EACf;EACA,OAAO;IAA4B,QAAQ;;EAC3C;EACA;EACA,OAAO;EACP;EACA,gBAAgB;EAChB;EACA;EACA;EACA;EACA,cAAc,eAAe;IAAe;IAAkB;;EAC9D,YAAY;IAAiB;IAAc;;EAC3C;EACA;EACA;;UAGe;;EAEf;;EAEA;;EAEA;;wBAyDc,iBAAiB,KAAK;;wBActB,MAAM,KAAK,gBAAgB,MAAM;wBAM3B,UACpB,KAAK,gBACL,mBACA,OAAO,yBACP,UAAS,cACR,QAAQ;;;;;;;;;;;wBAqGK,QACd,mBACA,QAAO,yBACP,UAAU,cACT;UAmBc;;;;;;EAMf;;;;;;;;;;;wBAYc,SAAS,aAAa,UAAS,kBAAuB;;wBAmDtD,KAAK,KAAK,gBAAgB,mBAAgB,UAAU,kBAAkB;;;;;;wBAgBtE,SAAS,cAAc;;wBAcvB,cAAc,SAAS;;;;;;wBAsBvB,UAAU,mBAAmB,gBAAgB,kBAAkB;;;UCxW9D;;EAEf;;EAEA;EACA,MAAM;;KAGI,gBAAgB,OAAO;qBAKtB,kBAAe,kBACT;;;;;;wBAoBH,SAAS,MAAM,MAAM;;;UCxBpB;;;;;;EAMf;;EAEA;;;;;;EAMA;;EAEA,SAAS,KAAK,mBAAmB,0BAA0B,QAAQ;;EAEnE;;;;;EAKA;;EAEA,WAAW;;;;;;;EAOX;;;;;;;;;EASA;IAAQ;IAAe;;;;UAIR;EACf;EACA;;qBAoFW,0CAAc,eAAA;;;KClItB,gBACH,UACA,UACA,KAAK,iBACL,OAAO;;;;;;;;;;;;;;qBA8DI,cAAc,kBAAkB;;;;;;;;;;;;wBAkB7B,oBAAoB,WAAU,eAAgC;KA2CzE,mBACH,UACA,KAAK,iBACL,OAAO,kCACG;;;;;;wBAOI,mBAAmB"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs-inertia v0.
|
|
2
|
+
* @forinda/kickjs-inertia v0.2.0
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -42,8 +42,47 @@ function defer(value, group = "default") {
|
|
|
42
42
|
* Merged into the client's current value on a partial reload instead of
|
|
43
43
|
* replacing it — paginated lists, infinite scroll.
|
|
44
44
|
*/
|
|
45
|
-
function merge(value, mode = "append") {
|
|
46
|
-
return prop("merge", value, {
|
|
45
|
+
function merge(value, mode = "append", options = {}) {
|
|
46
|
+
return prop("merge", value, {
|
|
47
|
+
mode,
|
|
48
|
+
matchOn: toList(options.matchOn)
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Resolved once, then kept by the client: later visits skip it until `ttl`
|
|
53
|
+
* runs out. For data that rarely changes — plans, countries, a settings
|
|
54
|
+
* list. Pages that share a `key` share the cached value.
|
|
55
|
+
*/
|
|
56
|
+
function once(value, options = {}) {
|
|
57
|
+
return prop("once", value, options);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* One page of an infinite list, for Inertia's `<InfiniteScroll>`. The client
|
|
61
|
+
* asks for the next or previous page with `?page=N` and appends or prepends
|
|
62
|
+
* the rows under `wrapper`.
|
|
63
|
+
*
|
|
64
|
+
* The value is read as `{ data, meta: { page, hasNext, hasPrev } }` — the
|
|
65
|
+
* `PaginatedResponse` shape; pass `meta` to read another one.
|
|
66
|
+
*/
|
|
67
|
+
function scroll(value, options = {}) {
|
|
68
|
+
return prop("scroll", value, {
|
|
69
|
+
pageName: options.pageName ?? "page",
|
|
70
|
+
wrapper: options.wrapper ?? "data",
|
|
71
|
+
matchOn: toList(options.matchOn),
|
|
72
|
+
meta: options.meta ?? paginatedMeta
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function toList(value) {
|
|
76
|
+
return value === void 0 ? void 0 : Array.isArray(value) ? value : [value];
|
|
77
|
+
}
|
|
78
|
+
function paginatedMeta(value) {
|
|
79
|
+
const meta = value?.meta;
|
|
80
|
+
if (typeof meta?.page !== "number") throw new Error("scroll(): the value has no `meta.page` — return `{ data, meta: { page, hasNext, hasPrev } }` or pass `meta`.");
|
|
81
|
+
return {
|
|
82
|
+
currentPage: meta.page,
|
|
83
|
+
nextPage: meta.hasNext ? meta.page + 1 : null,
|
|
84
|
+
previousPage: meta.hasPrev ? meta.page - 1 : null
|
|
85
|
+
};
|
|
47
86
|
}
|
|
48
87
|
function isInertiaProp(value) {
|
|
49
88
|
return typeof value === "object" && value !== null && PROP in value;
|
|
@@ -70,17 +109,25 @@ const ERRORS_KEY = "__inertia_errors";
|
|
|
70
109
|
const FLASH_KEY = "__inertia_flash";
|
|
71
110
|
/** URL of the last page rendered — where a failed submit goes back to. */
|
|
72
111
|
const PREVIOUS_KEY = "__inertia_previous";
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
112
|
+
/** Set by `redirect(url, { preserveFragment: true })` for the next page. */
|
|
113
|
+
const FRAGMENT_KEY = "__inertia_fragment";
|
|
114
|
+
/**
|
|
115
|
+
* Where the adapter's middleware leaves its runtime on each request, so every
|
|
116
|
+
* helper uses the adapter of the app that is handling that request — two apps
|
|
117
|
+
* in one process (tests, a reload that overlaps the old app) never share one.
|
|
118
|
+
*/
|
|
119
|
+
const RUNTIME_SLOT = Symbol.for("kickjs.inertia.runtime");
|
|
120
|
+
function bindInertiaRuntime(req, runtime) {
|
|
121
|
+
req[RUNTIME_SLOT] = runtime;
|
|
76
122
|
}
|
|
77
|
-
/** The
|
|
78
|
-
function
|
|
79
|
-
return
|
|
123
|
+
/** The runtime of the adapter that handled this request, or null without one. */
|
|
124
|
+
function inertiaRuntimeFor(req) {
|
|
125
|
+
return req?.[RUNTIME_SLOT] ?? null;
|
|
80
126
|
}
|
|
81
|
-
function runtime() {
|
|
82
|
-
|
|
83
|
-
|
|
127
|
+
function runtime(ctx) {
|
|
128
|
+
const rt = inertiaRuntimeFor(ctx.req);
|
|
129
|
+
if (!rt) throw new Error("inertia() needs InertiaAdapter — add it to bootstrap({ adapters: [InertiaAdapter({ entry })] }).");
|
|
130
|
+
return rt;
|
|
84
131
|
}
|
|
85
132
|
function header(ctx, name) {
|
|
86
133
|
const value = ctx.headers[name];
|
|
@@ -114,7 +161,7 @@ function flash(ctx, data) {
|
|
|
114
161
|
};
|
|
115
162
|
}
|
|
116
163
|
async function buildPage(ctx, component, props, options = {}) {
|
|
117
|
-
const rt = runtime();
|
|
164
|
+
const rt = runtime(ctx);
|
|
118
165
|
const shared = await rt.share?.(ctx) ?? {};
|
|
119
166
|
const all = {
|
|
120
167
|
...shared,
|
|
@@ -124,6 +171,8 @@ async function buildPage(ctx, component, props, options = {}) {
|
|
|
124
171
|
const only = partial ? list(header(ctx, "x-inertia-partial-data")) : [];
|
|
125
172
|
const except = partial ? list(header(ctx, "x-inertia-partial-except")) : [];
|
|
126
173
|
const reset = list(header(ctx, "x-inertia-reset"));
|
|
174
|
+
const cachedOnce = list(header(ctx, "x-inertia-except-once-props"));
|
|
175
|
+
const scrollIntent = header(ctx, "x-inertia-infinite-scroll-merge-intent") === "prepend" ? "prepend" : "append";
|
|
127
176
|
const named = (key) => only.some((o) => o === key || o.startsWith(`${key}.`));
|
|
128
177
|
const resolved = {};
|
|
129
178
|
const deferredProps = {};
|
|
@@ -132,12 +181,23 @@ async function buildPage(ctx, component, props, options = {}) {
|
|
|
132
181
|
prepend: [],
|
|
133
182
|
deep: []
|
|
134
183
|
};
|
|
184
|
+
const matchPropsOn = [];
|
|
185
|
+
const scrollProps = {};
|
|
186
|
+
const onceProps = {};
|
|
135
187
|
for (const [key, value] of Object.entries(all)) {
|
|
136
188
|
const kind = isInertiaProp(value) ? propKind(value) : void 0;
|
|
137
189
|
let send;
|
|
138
190
|
if (kind === "always") send = true;
|
|
139
191
|
else if (partial) send = (only.length === 0 || named(key)) && !except.includes(key);
|
|
140
192
|
else send = kind !== "optional" && kind !== "defer";
|
|
193
|
+
if (kind === "once" && isInertiaProp(value)) {
|
|
194
|
+
const onceKey = value.key ?? key;
|
|
195
|
+
onceProps[onceKey] = {
|
|
196
|
+
prop: key,
|
|
197
|
+
...value.ttl === void 0 ? {} : { expiresAt: Date.now() + value.ttl }
|
|
198
|
+
};
|
|
199
|
+
if (cachedOnce.includes(onceKey) && !(partial && named(key))) send = false;
|
|
200
|
+
}
|
|
141
201
|
if (!send) {
|
|
142
202
|
if (!partial && kind === "defer" && isInertiaProp(value)) {
|
|
143
203
|
const group = value.group ?? "default";
|
|
@@ -146,7 +206,21 @@ async function buildPage(ctx, component, props, options = {}) {
|
|
|
146
206
|
continue;
|
|
147
207
|
}
|
|
148
208
|
resolved[key] = await resolveProp(value);
|
|
149
|
-
if (kind === "merge" && isInertiaProp(value) && !reset.includes(key))
|
|
209
|
+
if (kind === "merge" && isInertiaProp(value) && !reset.includes(key)) {
|
|
210
|
+
merges[value.mode ?? "append"].push(key);
|
|
211
|
+
for (const field of value.matchOn ?? []) matchPropsOn.push(`${key}.${field}`);
|
|
212
|
+
}
|
|
213
|
+
if (kind === "scroll" && isInertiaProp(value)) {
|
|
214
|
+
const path = `${key}.${value.wrapper}`;
|
|
215
|
+
const isReset = reset.includes(key);
|
|
216
|
+
scrollProps[key] = {
|
|
217
|
+
pageName: value.pageName,
|
|
218
|
+
...value.meta(resolved[key]),
|
|
219
|
+
reset: isReset
|
|
220
|
+
};
|
|
221
|
+
if (!isReset) merges[scrollIntent].push(path);
|
|
222
|
+
for (const field of value.matchOn ?? []) matchPropsOn.push(`${path}.${field}`);
|
|
223
|
+
}
|
|
150
224
|
}
|
|
151
225
|
const page = {
|
|
152
226
|
component,
|
|
@@ -164,6 +238,13 @@ async function buildPage(ctx, component, props, options = {}) {
|
|
|
164
238
|
if (merges.append.length) page.mergeProps = merges.append;
|
|
165
239
|
if (merges.prepend.length) page.prependProps = merges.prepend;
|
|
166
240
|
if (merges.deep.length) page.deepMergeProps = merges.deep;
|
|
241
|
+
if (matchPropsOn.length) page.matchPropsOn = matchPropsOn;
|
|
242
|
+
if (Object.keys(scrollProps).length) page.scrollProps = scrollProps;
|
|
243
|
+
if (Object.keys(onceProps).length) page.onceProps = onceProps;
|
|
244
|
+
if (ctx.session?.data["__inertia_fragment"]) {
|
|
245
|
+
delete ctx.session.data[FRAGMENT_KEY];
|
|
246
|
+
page.preserveFragment = true;
|
|
247
|
+
}
|
|
167
248
|
if (options.encryptHistory) page.encryptHistory = true;
|
|
168
249
|
if (options.clearHistory) page.clearHistory = true;
|
|
169
250
|
return page;
|
|
@@ -187,17 +268,27 @@ function inertia(component, props = {}, options) {
|
|
|
187
268
|
ctx.json(page, options?.status ?? 200);
|
|
188
269
|
return;
|
|
189
270
|
}
|
|
190
|
-
ctx.html(await runtime().renderHtml(page, ctx), options?.status ?? 200);
|
|
271
|
+
ctx.html(await runtime(ctx).renderHtml(page, ctx), options?.status ?? 200);
|
|
191
272
|
} };
|
|
192
273
|
}
|
|
193
274
|
/**
|
|
194
275
|
* Redirect after a form submit. Uses 303 for anything but GET, so the browser
|
|
195
276
|
* follows with a GET, and saves the session first so flashed errors and
|
|
196
277
|
* messages are there when that GET arrives.
|
|
278
|
+
*
|
|
279
|
+
* A URL with a `#fragment` is sent to an Inertia visit as 409 +
|
|
280
|
+
* `X-Inertia-Redirect`: a redirect followed inside a fetch drops the
|
|
281
|
+
* fragment, so the client makes the visit itself.
|
|
197
282
|
*/
|
|
198
|
-
function redirect(url) {
|
|
283
|
+
function redirect(url, options = {}) {
|
|
199
284
|
return { async [RESPOND](ctx) {
|
|
285
|
+
if (options.preserveFragment && ctx.session) ctx.session.data[FRAGMENT_KEY] = true;
|
|
200
286
|
await ctx.session?.save();
|
|
287
|
+
if (url.includes("#") && isInertiaRequest(ctx)) {
|
|
288
|
+
ctx.setHeader("X-Inertia-Redirect", url);
|
|
289
|
+
ctx.html("", 409);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
201
292
|
const method = ctx.req.method ?? "GET";
|
|
202
293
|
ctx.redirect(url, method === "GET" ? 302 : 303);
|
|
203
294
|
} };
|
|
@@ -226,11 +317,11 @@ function previousUrl(session, headers, fallback = "/") {
|
|
|
226
317
|
return localPath(session?.data["__inertia_previous"]) ?? sameOriginPath(one(headers.referer), one(headers.host)) ?? fallback;
|
|
227
318
|
}
|
|
228
319
|
/** Redirect back to the page the request came from. */
|
|
229
|
-
function back(ctx, fallback = "/") {
|
|
320
|
+
function back(ctx, fallback = "/", options) {
|
|
230
321
|
return redirect(previousUrl(ctx.session, {
|
|
231
322
|
referer: header(ctx, "referer"),
|
|
232
323
|
host: header(ctx, "host")
|
|
233
|
-
}, fallback));
|
|
324
|
+
}, fallback), options);
|
|
234
325
|
}
|
|
235
326
|
/**
|
|
236
327
|
* A full browser visit, outside Inertia — another site, or a page that is not
|
|
@@ -285,12 +376,13 @@ function errorPage(component, status, error) {
|
|
|
285
376
|
*
|
|
286
377
|
* @module @forinda/kickjs-inertia/html
|
|
287
378
|
*/
|
|
379
|
+
const escapeHtml = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
288
380
|
const defaultTemplate = (title) => ({ head, body }) => `<!doctype html>
|
|
289
381
|
<html lang="en">
|
|
290
382
|
<head>
|
|
291
383
|
<meta charset="utf-8" />
|
|
292
384
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
293
|
-
<title>${title}</title>
|
|
385
|
+
<title>${escapeHtml(title)}</title>
|
|
294
386
|
${head}
|
|
295
387
|
</head>
|
|
296
388
|
<body>
|
|
@@ -373,6 +465,15 @@ function devStylesheets(vite, entry) {
|
|
|
373
465
|
function viteServer() {
|
|
374
466
|
return globalThis.__kickjs_viteServer ?? null;
|
|
375
467
|
}
|
|
468
|
+
/**
|
|
469
|
+
* A build path, relative to the app: the working directory, or
|
|
470
|
+
* `KICK_APP_ROOT` when set — the Netlify and Vercel functions `kick build:*`
|
|
471
|
+
* writes set it to where they packaged the build, since the platform starts
|
|
472
|
+
* them elsewhere.
|
|
473
|
+
*/
|
|
474
|
+
function fromAppRoot(path) {
|
|
475
|
+
return resolve(process.env.KICK_APP_ROOT ?? process.cwd(), path);
|
|
476
|
+
}
|
|
376
477
|
function appendVary(res, field) {
|
|
377
478
|
const current = res.getHeader("Vary");
|
|
378
479
|
const list = (Array.isArray(current) ? current.join(",") : String(current ?? "")).split(",").map((v) => v.trim()).filter(Boolean);
|
|
@@ -394,11 +495,14 @@ const InertiaAdapter = defineAdapter({
|
|
|
394
495
|
title: "KickJS"
|
|
395
496
|
},
|
|
396
497
|
build: (config) => {
|
|
397
|
-
const clientDir =
|
|
498
|
+
const clientDir = fromAppRoot(config.clientDir);
|
|
398
499
|
const template = config.template ?? defaultTemplate(config.title);
|
|
399
500
|
let version = () => null;
|
|
501
|
+
let runtime = null;
|
|
400
502
|
const protocol = (req, res, next) => {
|
|
503
|
+
if (runtime) bindInertiaRuntime(req, runtime);
|
|
401
504
|
appendVary(res, "X-Inertia");
|
|
505
|
+
if (req.headers.precognition === "true") appendVary(res, "Precognition");
|
|
402
506
|
if (req.headers["x-inertia"] !== "true") return next();
|
|
403
507
|
const current = version();
|
|
404
508
|
if (req.method === "GET" && current && req.headers["x-inertia-version"] !== current) {
|
|
@@ -447,7 +551,7 @@ const InertiaAdapter = defineAdapter({
|
|
|
447
551
|
let prodRender;
|
|
448
552
|
const loadRender = async () => {
|
|
449
553
|
if (vite) return (await vite.ssrLoadModule(`/${ssr.entry.replace(/^\/+/, "")}`)).default;
|
|
450
|
-
const url = pathToFileURL(
|
|
554
|
+
const url = pathToFileURL(fromAppRoot(ssr.bundle ?? "dist/ssr/ssr.js")).href;
|
|
451
555
|
prodRender ??= import(
|
|
452
556
|
/* @vite-ignore */
|
|
453
557
|
url
|
|
@@ -464,7 +568,7 @@ const InertiaAdapter = defineAdapter({
|
|
|
464
568
|
return null;
|
|
465
569
|
}
|
|
466
570
|
};
|
|
467
|
-
|
|
571
|
+
runtime = {
|
|
468
572
|
version: () => version(),
|
|
469
573
|
share: config.share,
|
|
470
574
|
errorPage: config.errorPage,
|
|
@@ -483,16 +587,13 @@ const InertiaAdapter = defineAdapter({
|
|
|
483
587
|
});
|
|
484
588
|
return vite ? vite.transformIndexHtml(requestUrl(ctx.req), html) : html;
|
|
485
589
|
}
|
|
486
|
-
}
|
|
590
|
+
};
|
|
487
591
|
},
|
|
488
592
|
middleware() {
|
|
489
593
|
return [{
|
|
490
594
|
handler: protocol,
|
|
491
595
|
phase: "afterGlobal"
|
|
492
596
|
}];
|
|
493
|
-
},
|
|
494
|
-
shutdown() {
|
|
495
|
-
setInertiaRuntime(null);
|
|
496
597
|
}
|
|
497
598
|
};
|
|
498
599
|
}
|
|
@@ -519,9 +620,52 @@ function toErrors(details) {
|
|
|
519
620
|
}
|
|
520
621
|
return errors;
|
|
521
622
|
}
|
|
623
|
+
function isPrecognitive(req) {
|
|
624
|
+
return req?.headers?.precognition === "true";
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* A Precognition answer: 204 when nothing the client asked about failed,
|
|
628
|
+
* else 422 with those fields' errors. `Precognition-Validate-Only` names the
|
|
629
|
+
* fields a form has touched, so errors on the rest are not shown yet.
|
|
630
|
+
*/
|
|
631
|
+
function precognitionResult(req, res, errors) {
|
|
632
|
+
const only = String(req.headers["precognition-validate-only"] ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
633
|
+
const shown = only.length ? Object.fromEntries(Object.entries(errors).filter(([field]) => only.some((o) => field === o || field.startsWith(`${o}.`)))) : errors;
|
|
634
|
+
res.setHeader("Precognition", "true");
|
|
635
|
+
if (Object.keys(shown).length === 0) {
|
|
636
|
+
res.setHeader("Precognition-Success", "true");
|
|
637
|
+
res.status(204).end();
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
res.status(422).json({
|
|
641
|
+
message: "The given data was invalid.",
|
|
642
|
+
errors: shown
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Live validation for Inertia's `useForm().validate()` — Laravel Precognition.
|
|
647
|
+
* Put it on a route that validates its body; a precognitive request runs the
|
|
648
|
+
* validation and stops before the handler:
|
|
649
|
+
*
|
|
650
|
+
* ```ts
|
|
651
|
+
* @Post('/users', { body: createUserSchema })
|
|
652
|
+
* @Middleware(precognitive)
|
|
653
|
+
* create(ctx: RequestContext) { ... }
|
|
654
|
+
* ```
|
|
655
|
+
*
|
|
656
|
+
* A failed check is answered by `inertiaErrorHandler()`.
|
|
657
|
+
*/
|
|
658
|
+
const precognitive = (ctx, next) => {
|
|
659
|
+
if (!isPrecognitive(ctx.req)) return next();
|
|
660
|
+
ctx.setHeader("Precognition", "true");
|
|
661
|
+
ctx.setHeader("Precognition-Success", "true");
|
|
662
|
+
ctx.noContent();
|
|
663
|
+
};
|
|
522
664
|
/**
|
|
523
665
|
* Pass to `bootstrap({ onError: inertiaErrorHandler() })`.
|
|
524
666
|
*
|
|
667
|
+
* - A validation 422 on a Precognition request is answered with the errors
|
|
668
|
+
* of the fields it asked about (see `precognitive`).
|
|
525
669
|
* - A validation 422 on an Inertia visit goes back to the form with the
|
|
526
670
|
* errors (needs the `session()` middleware to carry them).
|
|
527
671
|
* - Any other error on a page request renders the adapter's `errorPage`,
|
|
@@ -533,16 +677,16 @@ function inertiaErrorHandler(fallback = errorHandler()) {
|
|
|
533
677
|
return (err, req, res, next) => {
|
|
534
678
|
if (res.headersSent) return fallback(err, req, res, next);
|
|
535
679
|
const errors = err instanceof HttpException && err.status === 422 ? toErrors(err.details) : null;
|
|
680
|
+
if (errors && isPrecognitive(req)) return precognitionResult(req, res, errors);
|
|
536
681
|
if (errors && req?.headers?.["x-inertia"] === "true") {
|
|
537
682
|
const session = req.session;
|
|
538
683
|
if (session) {
|
|
539
684
|
const bag = req.headers["x-inertia-error-bag"];
|
|
540
685
|
session.data[ERRORS_KEY] = bag ? { [bag]: errors } : errors;
|
|
541
|
-
session.save().then(() => {
|
|
686
|
+
return session.save().then(() => {
|
|
542
687
|
res.status(303).setHeader("location", previousUrl(session, req.headers));
|
|
543
688
|
res.end();
|
|
544
689
|
}).catch((saveErr) => fallback(saveErr, req, res, next));
|
|
545
|
-
return;
|
|
546
690
|
}
|
|
547
691
|
if (!warned) {
|
|
548
692
|
log.warn("Validation failed on an Inertia request, but there is no session() middleware to carry the errors — answering 422 instead.");
|
|
@@ -550,13 +694,12 @@ function inertiaErrorHandler(fallback = errorHandler()) {
|
|
|
550
694
|
}
|
|
551
695
|
return fallback(err, req, res, next);
|
|
552
696
|
}
|
|
553
|
-
const component =
|
|
697
|
+
const component = inertiaRuntimeFor(req)?.errorPage;
|
|
554
698
|
if (component && req?.headers && isPageRequest(req.headers)) {
|
|
555
699
|
const status = err instanceof HttpException ? err.status : 500;
|
|
556
700
|
if (status >= 500) log.error(err, "Unhandled error on a page request");
|
|
557
701
|
const ctx = new RequestContext(req, res, next, res);
|
|
558
|
-
Promise.resolve(errorPage(component, status, err)[RESPOND](ctx)).catch((renderErr) => fallback(renderErr, req, res, next));
|
|
559
|
-
return;
|
|
702
|
+
return Promise.resolve(errorPage(component, status, err)[RESPOND](ctx)).catch((renderErr) => fallback(renderErr, req, res, next));
|
|
560
703
|
}
|
|
561
704
|
return fallback(err, req, res, next);
|
|
562
705
|
};
|
|
@@ -568,14 +711,14 @@ function inertiaErrorHandler(fallback = errorHandler()) {
|
|
|
568
711
|
*/
|
|
569
712
|
function inertiaNotFound() {
|
|
570
713
|
return (req, res, next) => {
|
|
571
|
-
const component =
|
|
714
|
+
const component = inertiaRuntimeFor(req)?.errorPage;
|
|
572
715
|
const method = req?.method ?? "GET";
|
|
573
716
|
if (!component || method !== "GET" && method !== "HEAD" || !isPageRequest(req.headers ?? {})) return next();
|
|
574
717
|
const ctx = new RequestContext(req, res, next, res);
|
|
575
|
-
Promise.resolve(errorPage(component, 404)[RESPOND](ctx)).catch(next);
|
|
718
|
+
return Promise.resolve(errorPage(component, 404)[RESPOND](ctx)).catch(next);
|
|
576
719
|
};
|
|
577
720
|
}
|
|
578
721
|
|
|
579
722
|
//#endregion
|
|
580
|
-
export { InertiaAdapter, always, back, buildPage, defaultTemplate, defer, errorPage, flash, inertia, inertiaErrorHandler, inertiaNotFound, isInertiaRequest, isPageRequest, location, merge, optional, pageBody, redirect };
|
|
723
|
+
export { InertiaAdapter, always, back, buildPage, defaultTemplate, defer, errorPage, flash, inertia, inertiaErrorHandler, inertiaNotFound, isInertiaRequest, isPageRequest, location, merge, once, optional, pageBody, precognitive, redirect, scroll };
|
|
581
724
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["log"],"sources":["../src/props.ts","../src/inertia.ts","../src/html.ts","../src/adapter.ts","../src/errors.ts"],"sourcesContent":["/**\n * Prop wrappers that change when a prop is sent. A plain value or a function\n * is sent on every full visit and on a partial reload that asks for it; a\n * function is only called when the prop is actually sent.\n *\n * @module @forinda/kickjs-inertia/props\n */\n\ntype Resolver<T> = T | (() => T | Promise<T>)\n\nconst PROP = Symbol.for('kickjs.inertia.prop')\n\nexport type PropKind = 'optional' | 'always' | 'defer' | 'merge'\n\nexport interface InertiaProp<T = unknown> {\n readonly [PROP]: PropKind\n readonly value: Resolver<T>\n /** `defer` only — props in one group load in one request. */\n readonly group?: string\n /** `merge` only — append, prepend or deep-merge into the client's copy. */\n readonly mode?: 'append' | 'prepend' | 'deep'\n}\n\nfunction prop<T>(kind: PropKind, value: Resolver<T>, extra?: object): InertiaProp<T> {\n return { [PROP]: kind, value, ...extra }\n}\n\n/** Sent only when a partial reload names it (`router.reload({ only: [...] })`). */\nexport function optional<T>(value: () => T | Promise<T>): InertiaProp<T> {\n return prop('optional', value)\n}\n\n/** Sent on every response, including partial reloads that did not name it. */\nexport function always<T>(value: Resolver<T>): InertiaProp<T> {\n return prop('always', value)\n}\n\n/**\n * Left out of the first response; the client fetches it right after the page\n * renders, one request per `group`.\n */\nexport function defer<T>(value: () => T | Promise<T>, group = 'default'): InertiaProp<T> {\n return prop('defer', value, { group })\n}\n\n/**\n * Merged into the client's current value on a partial reload instead of\n * replacing it — paginated lists, infinite scroll.\n */\nexport function merge<T>(\n value: Resolver<T>,\n mode: 'append' | 'prepend' | 'deep' = 'append',\n): InertiaProp<T> {\n return prop('merge', value, { mode })\n}\n\nexport function isInertiaProp(value: unknown): value is InertiaProp {\n return typeof value === 'object' && value !== null && PROP in value\n}\n\nexport function propKind(value: InertiaProp): PropKind {\n return value[PROP]\n}\n\n/** Unwrap a wrapper, call a function, await a promise. */\nexport async function resolveProp(value: unknown): Promise<unknown> {\n const raw = isInertiaProp(value) ? value.value : value\n return typeof raw === 'function' ? await (raw as () => unknown)() : await raw\n}\n","/**\n * Page responses. `inertia(component, props)` is a handler return value: on a\n * first visit it renders the HTML shell with the page embedded, on an Inertia\n * visit (`X-Inertia: true`) it sends the page object as JSON.\n *\n * @module @forinda/kickjs-inertia/inertia\n */\nimport { RESPOND, type RequestContext, type Respondable } from '@forinda/kickjs'\nimport { isInertiaProp, propKind, resolveProp } from './props'\n\nexport const ERRORS_KEY = '__inertia_errors'\nexport const FLASH_KEY = '__inertia_flash'\n/** URL of the last page rendered — where a failed submit goes back to. */\nexport const PREVIOUS_KEY = '__inertia_previous'\n\n/** The page object the Inertia client renders — see the protocol docs. */\nexport interface Page {\n component: string\n props: Record<string, unknown> & { errors: Record<string, unknown> }\n url: string\n version: string | null\n flash: Record<string, unknown>\n sharedProps?: string[]\n deferredProps?: Record<string, string[]>\n mergeProps?: string[]\n prependProps?: string[]\n deepMergeProps?: string[]\n encryptHistory?: boolean\n clearHistory?: boolean\n}\n\nexport interface PageOptions {\n /** Encrypt this page's history entry in the browser. */\n encryptHistory?: boolean\n /** Wipe the browser's Inertia history, e.g. after logout. */\n clearHistory?: boolean\n /** Response status. Default 200; error pages send theirs (404, 500). */\n status?: number\n}\n\n/** What the adapter hands the page renderer. Set in `beforeMount`. */\nexport interface InertiaRuntime {\n version(): string | null\n share?(ctx: RequestContext): Record<string, unknown> | Promise<Record<string, unknown>>\n renderHtml(page: Page, ctx: RequestContext): Promise<string>\n /** Component that renders error and not-found pages, when configured. */\n errorPage?: string\n}\n\n// ponytail: one active adapter per process, which is one app. An app booting\n// two Inertia apps in one process would need this keyed per Application.\nlet active: InertiaRuntime | null = null\n\nexport function setInertiaRuntime(runtime: InertiaRuntime | null): void {\n active = runtime\n}\n\n/** The active adapter's runtime, or null when no InertiaAdapter is mounted. */\nexport function inertiaRuntime(): InertiaRuntime | null {\n return active\n}\n\nfunction runtime(): InertiaRuntime {\n if (!active) {\n throw new Error(\n 'inertia() needs InertiaAdapter — add it to bootstrap({ adapters: [InertiaAdapter({ entry })] }).',\n )\n }\n return active\n}\n\nfunction header(ctx: RequestContext, name: string): string | undefined {\n const value = (ctx.headers as Record<string, string | string[] | undefined>)[name]\n return Array.isArray(value) ? value[0] : value\n}\n\nfunction list(value: string | undefined): string[] {\n return value\n ? value\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n : []\n}\n\nexport function requestUrl(req: unknown): string {\n const r = req as { originalUrl?: string; url?: string }\n return r.originalUrl ?? r.url ?? '/'\n}\n\nexport function isInertiaRequest(ctx: RequestContext): boolean {\n return header(ctx, 'x-inertia') === 'true'\n}\n\n/** Read and remove a one-request value from the session. */\nfunction take(ctx: RequestContext, key: string): Record<string, unknown> {\n const data = ctx.session?.data\n if (!data || data[key] === undefined) return {}\n const value = data[key] as Record<string, unknown>\n delete data[key]\n return value\n}\n\n/** Flash data for the next page, sent as the page's top-level `flash`. */\nexport function flash(ctx: RequestContext, data: Record<string, unknown>): void {\n const session = ctx.session\n if (!session) throw new Error('flash() needs the session() middleware.')\n session.data[FLASH_KEY] = { ...(session.data[FLASH_KEY] as object), ...data }\n}\n\nexport async function buildPage(\n ctx: RequestContext,\n component: string,\n props: Record<string, unknown>,\n options: PageOptions = {},\n): Promise<Page> {\n const rt = runtime()\n const shared = (await rt.share?.(ctx)) ?? {}\n const all: Record<string, unknown> = { ...shared, ...props }\n\n const partial = header(ctx, 'x-inertia-partial-component') === component\n const only = partial ? list(header(ctx, 'x-inertia-partial-data')) : []\n const except = partial ? list(header(ctx, 'x-inertia-partial-except')) : []\n const reset = list(header(ctx, 'x-inertia-reset'))\n\n // A dot path in `only` ('users.data') still selects its top-level prop.\n const named = (key: string) => only.some((o) => o === key || o.startsWith(`${key}.`))\n\n const resolved: Record<string, unknown> = {}\n const deferredProps: Record<string, string[]> = {}\n const merges = { append: [] as string[], prepend: [] as string[], deep: [] as string[] }\n\n for (const [key, value] of Object.entries(all)) {\n const kind = isInertiaProp(value) ? propKind(value) : undefined\n let send: boolean\n if (kind === 'always') send = true\n else if (partial) send = (only.length === 0 || named(key)) && !except.includes(key)\n else send = kind !== 'optional' && kind !== 'defer'\n\n if (!send) {\n if (!partial && kind === 'defer' && isInertiaProp(value)) {\n const group = value.group ?? 'default'\n ;(deferredProps[group] ??= []).push(key)\n }\n continue\n }\n resolved[key] = await resolveProp(value)\n if (kind === 'merge' && isInertiaProp(value) && !reset.includes(key)) {\n merges[value.mode ?? 'append'].push(key)\n }\n }\n\n const page: Page = {\n component,\n props: { ...resolved, errors: take(ctx, ERRORS_KEY) },\n url: requestUrl(ctx.req),\n version: rt.version(),\n flash: take(ctx, FLASH_KEY),\n }\n const sharedKeys = Object.keys(shared)\n if (sharedKeys.length) page.sharedProps = sharedKeys\n if (Object.keys(deferredProps).length) page.deferredProps = deferredProps\n if (merges.append.length) page.mergeProps = merges.append\n if (merges.prepend.length) page.prependProps = merges.prepend\n if (merges.deep.length) page.deepMergeProps = merges.deep\n if (options.encryptHistory) page.encryptHistory = true\n if (options.clearHistory) page.clearHistory = true\n return page\n}\n\n/**\n * Render an Inertia page. Return it from a handler:\n *\n * ```ts\n * @Get('/users')\n * async index() {\n * return inertia('Users/Index', { users: await this.users.list() })\n * }\n * ```\n */\nexport function inertia(\n component: string,\n props: Record<string, unknown> = {},\n options?: PageOptions,\n): Respondable {\n return {\n async [RESPOND](ctx: RequestContext) {\n const page = await buildPage(ctx, component, props, options)\n // Remembered for back(): helmet's default Referrer-Policy (no-referrer)\n // means the browser sends no Referer to go back to.\n if (ctx.session) ctx.session.data[PREVIOUS_KEY] = page.url\n // `Vary: X-Inertia` is added by the adapter's middleware, which can\n // append to a Vary another layer set instead of replacing it.\n if (isInertiaRequest(ctx)) {\n ctx.setHeader('X-Inertia', 'true')\n ctx.json(page, options?.status ?? 200)\n return\n }\n ctx.html(await runtime().renderHtml(page, ctx), options?.status ?? 200)\n },\n }\n}\n\n/**\n * Redirect after a form submit. Uses 303 for anything but GET, so the browser\n * follows with a GET, and saves the session first so flashed errors and\n * messages are there when that GET arrives.\n */\nexport function redirect(url: string): Respondable {\n return {\n async [RESPOND](ctx: RequestContext) {\n await ctx.session?.save()\n const method = (ctx.req as { method?: string }).method ?? 'GET'\n ctx.redirect(url, method === 'GET' ? 302 : 303)\n },\n }\n}\n\n/** A path on this site — not `//host` or `/\\\\host`, which browsers treat as another origin. */\nfunction localPath(value: unknown): string | undefined {\n return typeof value === 'string' && /^\\/(?![/\\\\])/.test(value) ? value : undefined\n}\n\n/** The path of a Referer, only when it points at this host. */\nfunction sameOriginPath(referer: string | undefined, host: string | undefined): string | undefined {\n if (!referer || !host) return undefined\n try {\n const url = new URL(referer)\n return url.host === host ? localPath(url.pathname + url.search) : undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * Where \"back\" is: the last page this session rendered, else a same-origin\n * Referer, else `fallback`. Never another site — a form on another site that\n * posts here must not be able to choose where the user lands.\n */\nexport function previousUrl(\n session: { data: Record<string, unknown> } | undefined,\n headers: { referer?: string | string[]; host?: string | string[] },\n fallback = '/',\n): string {\n const one = (v: string | string[] | undefined) => (Array.isArray(v) ? v[0] : v)\n return (\n localPath(session?.data[PREVIOUS_KEY]) ??\n sameOriginPath(one(headers.referer), one(headers.host)) ??\n fallback\n )\n}\n\n/** Redirect back to the page the request came from. */\nexport function back(ctx: RequestContext, fallback = '/'): Respondable {\n return redirect(\n previousUrl(\n ctx.session,\n { referer: header(ctx, 'referer'), host: header(ctx, 'host') },\n fallback,\n ),\n )\n}\n\n/**\n * A full browser visit, outside Inertia — another site, or a page that is not\n * an Inertia page. An Inertia request gets 409 + `X-Inertia-Location`, which\n * the client turns into `window.location`.\n */\nexport function location(url: string): Respondable {\n return {\n async [RESPOND](ctx: RequestContext) {\n if (isInertiaRequest(ctx)) {\n ctx.setHeader('X-Inertia-Location', url)\n ctx.html('', 409)\n return\n }\n ctx.redirect(url)\n },\n }\n}\n\n/** A browser navigation or an Inertia visit — not an API or asset request. */\nexport function isPageRequest(headers: Record<string, string | string[] | undefined>): boolean {\n const accept = String(headers.accept ?? '')\n return headers['x-inertia'] === 'true' || accept.includes('text/html')\n}\n\nconst STATUS_TEXT: Record<number, string> = {\n 400: 'Bad Request',\n 401: 'Unauthorized',\n 403: 'Forbidden',\n 404: 'Page Not Found',\n 405: 'Method Not Allowed',\n 419: 'Page Expired',\n 429: 'Too Many Requests',\n 500: 'Server Error',\n 503: 'Service Unavailable',\n}\n\n/**\n * The configured error page as a response, with `{ status, message }` as its\n * props. A 5xx message is replaced in production — an exception's text is\n * for the log, not the visitor.\n */\nexport function errorPage(component: string, status: number, error?: unknown): Respondable {\n const fallback = STATUS_TEXT[status] ?? (status >= 500 ? 'Server Error' : 'Error')\n const raw = error instanceof Error && error.message ? error.message : fallback\n const message = status >= 500 && process.env.NODE_ENV === 'production' ? fallback : raw\n return inertia(component, { status, message }, { status })\n}\n","/**\n * The HTML shell for a first visit, and the script tags that load the client:\n * Vite's dev server while `kick dev` runs, the build manifest in production.\n *\n * @module @forinda/kickjs-inertia/html\n */\nimport { readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport type { Page } from './inertia'\n\nexport interface HtmlParts {\n /** Script and style tags for the client entry. Put inside `<head>`. */\n head: string\n /** The page data and the mount element. Put inside `<body>`. */\n body: string\n page: Page\n}\n\nexport type HtmlTemplate = (parts: HtmlParts) => string\n\nexport const defaultTemplate =\n (title: string): HtmlTemplate =>\n ({ head, body }) =>\n `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>${title}</title>\n ${head}\n </head>\n <body>\n ${body}\n </body>\n</html>`\n\n/**\n * The page as `<script data-page=\"app\" type=\"application/json\">` — where the\n * v3 client reads it — followed by the mount element. `<` and `/` are escaped\n * so a prop containing `</script>` cannot end the tag.\n */\nexport function pageBody(page: Page, id = 'app'): string {\n const json = JSON.stringify(page, (_k, v) => (typeof v === 'bigint' ? v.toString() : v))\n .replace(/</g, '\\\\u003c')\n .replace(/\\//g, '\\\\/')\n return `<script data-page=\"${id}\" type=\"application/json\">${json}</script>\\n <div id=\"${id}\"></div>`\n}\n\n/** Dev: one module script; Vite's `transformIndexHtml` adds its client and plugin preambles. */\nexport function devHead(entry: string): string {\n return `<script type=\"module\" src=\"/${entry.replace(/^\\/+/, '')}\"></script>`\n}\n\ninterface ManifestChunk {\n file: string\n css?: string[]\n imports?: string[]\n}\n\n/** Production: the entry's script, its CSS, and the CSS and preloads of what it imports. */\nexport function manifestHead(clientDir: string, entry: string): string {\n const manifest = JSON.parse(\n readFileSync(join(clientDir, '.vite', 'manifest.json'), 'utf8'),\n ) as Record<string, ManifestChunk>\n const chunk = manifest[entry]\n if (!chunk) {\n throw new Error(\n `InertiaAdapter: '${entry}' is not in ${clientDir}/.vite/manifest.json — build the client with build.manifest and this entry as an input.`,\n )\n }\n const css = new Set<string>()\n const preload = new Set<string>()\n const walk = (key: string, root: boolean) => {\n const c = manifest[key]\n if (!c) return\n for (const f of c.css ?? []) css.add(f)\n if (!root) preload.add(c.file)\n for (const i of c.imports ?? []) walk(i, false)\n }\n walk(entry, true)\n return [\n ...[...css].map((f) => `<link rel=\"stylesheet\" href=\"/${f}\" />`),\n ...[...preload].map((f) => `<link rel=\"modulepreload\" href=\"/${f}\" />`),\n `<script type=\"module\" src=\"/${chunk.file}\"></script>`,\n ].join('\\n ')\n}\n","/**\n * `InertiaAdapter` — wires the protocol into the app:\n *\n * - makes `inertia()` able to render (HTML shell, asset tags, version);\n * - answers a stale Inertia GET with 409 so the client reloads the page;\n * - turns a 302 after PUT / PATCH / DELETE into 303, so the browser follows\n * with a GET instead of repeating the method;\n * - in production, serves the built client's `assets/`.\n *\n * @module @forinda/kickjs-inertia/adapter\n */\nimport { createHash } from 'node:crypto'\nimport { existsSync, readFileSync, readdirSync } from 'node:fs'\nimport { join, resolve } from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { defineAdapter, Logger, type RequestContext } from '@forinda/kickjs'\nimport { setInertiaRuntime, requestUrl, type Page } from './inertia'\nimport { defaultTemplate, devHead, manifestHead, pageBody, type HtmlTemplate } from './html'\n\nexport interface InertiaAdapterOptions {\n /**\n * The client entry, relative to the project root — the file that calls\n * `createInertiaApp`. Served by Vite in dev, looked up in the build\n * manifest in production. Default: `'src/client/main.tsx'`.\n */\n entry: string\n /** Where `vite build` wrote the client, with `build.manifest: true`. Default: `'dist/client'`. */\n clientDir: string\n /**\n * Asset version. A client on another version reloads the page on its next\n * visit. Default: a hash of the build manifest in production, none in dev\n * (Vite reloads changed modules itself).\n */\n version?: string | (() => string)\n /** Props every page gets — the current user, app name. */\n share?: (ctx: RequestContext) => Record<string, unknown> | Promise<Record<string, unknown>>\n /** `<title>` of the default HTML shell. */\n title: string\n /**\n * Tags added to every page's `<head>` — a favicon, meta tags, fonts. Per-page\n * titles and meta belong in the page, with Inertia's `<Head>` component.\n */\n head?: string\n /** Replace the HTML shell entirely. */\n template?: HtmlTemplate\n /**\n * Page component for errors and unknown URLs, e.g. `'Error'`. It receives\n * `{ status, message }`. Rendered for page requests by `inertiaErrorHandler()`\n * (thrown errors) and `inertiaNotFound()` (unknown URLs); API and asset\n * requests keep the usual JSON answers.\n */\n errorPage?: string\n /**\n * Render pages on the server. `entry` is a module whose default export is\n * `(page) => Promise<{ head: string[]; body: string }>` — what\n * `createInertiaApp({ page, render: renderToString, ... })` returns. Loaded\n * through Vite in dev; in production from `bundle`, the output of\n * `vite build --ssr <entry>` (default `'dist/ssr/ssr.js'`). A render that\n * throws falls back to client rendering for that request.\n */\n ssr?: { entry: string; bundle?: string }\n}\n\n/** What an SSR entry's default export returns. */\nexport interface SsrResult {\n head: string[]\n body: string\n}\n\ntype SsrRender = (page: Page) => Promise<SsrResult>\n\nconst log = Logger.for('InertiaAdapter')\n\ninterface ViteModuleLike {\n url: string\n id: string | null\n importedModules: Set<ViteModuleLike>\n}\n\ninterface ViteDevServerLike {\n config: { root: string }\n environments: { ssr: { moduleGraph: { getModuleById(id: string): ViteModuleLike | undefined } } }\n transformIndexHtml(url: string, html: string): Promise<string>\n ssrLoadModule(url: string): Promise<Record<string, unknown>>\n ssrFixStacktrace(err: Error): void\n}\n\nconst CSS_REQUEST = /\\.(css|less|sass|scss|styl|stylus|pcss|postcss|sss)(?:$|\\?)/\n\n/**\n * Dev only: the stylesheets the SSR entry imports, as `<link>` tags. In dev\n * Vite injects CSS from JavaScript, so server-rendered HTML would show\n * unstyled until the scripts run. `data-vite-dev-id` lets Vite's client\n * recognize these and not inject the same styles again.\n */\nfunction devStylesheets(vite: ViteDevServerLike, entry: string): string[] {\n const root = vite.environments.ssr.moduleGraph.getModuleById(resolve(vite.config.root, entry))\n if (!root) return []\n const links: string[] = []\n const seen = new Set<ViteModuleLike>()\n const walk = (mod: ViteModuleLike) => {\n if (seen.has(mod)) return\n seen.add(mod)\n if (CSS_REQUEST.test(mod.url)) {\n const devId = mod.id ? ` data-vite-dev-id=\"${mod.id.replace(/\"/g, '"')}\"` : ''\n links.push(`<link rel=\"stylesheet\" href=\"${mod.url}\"${devId} />`)\n return\n }\n for (const imported of mod.importedModules) walk(imported)\n }\n walk(root)\n return links\n}\n\nfunction viteServer(): ViteDevServerLike | null {\n return (\n (globalThis as { __kickjs_viteServer?: ViteDevServerLike | null }).__kickjs_viteServer ?? null\n )\n}\n\nfunction appendVary(res: ServerResponse, field: string): void {\n const current = res.getHeader('Vary')\n const list = (Array.isArray(current) ? current.join(',') : String(current ?? ''))\n .split(',')\n .map((v) => v.trim())\n .filter(Boolean)\n if (list.includes('*') || list.some((v) => v.toLowerCase() === field.toLowerCase())) return\n res.setHeader('Vary', [...list, field].join(', '))\n}\n\nfunction safeDecode(path: string): string {\n try {\n return decodeURIComponent(path)\n } catch {\n return path\n }\n}\n\ntype NodeMiddleware = (req: IncomingMessage, res: ServerResponse, next: () => void) => void\n\nexport const InertiaAdapter = defineAdapter<InertiaAdapterOptions>({\n name: 'InertiaAdapter',\n defaults: {\n entry: 'src/client/main.tsx',\n clientDir: 'dist/client',\n title: 'KickJS',\n },\n build: (config) => {\n const clientDir = resolve(config.clientDir)\n const template = config.template ?? defaultTemplate(config.title)\n let version: () => string | null = () => null\n\n const protocol: NodeMiddleware = (req, res, next) => {\n // The same URL answers HTML or JSON depending on X-Inertia, so a shared\n // cache must key on it. Appended, not set: replacing a Vary another\n // layer added (Origin, Accept-Encoding) lets a cache serve one\n // client's variant to another.\n appendVary(res, 'X-Inertia')\n if (req.headers['x-inertia'] !== 'true') return next()\n\n const current = version()\n if (req.method === 'GET' && current && req.headers['x-inertia-version'] !== current) {\n res.statusCode = 409\n res.setHeader('X-Inertia-Location', requestUrl(req))\n res.end()\n return\n }\n\n if (req.method === 'PUT' || req.method === 'PATCH' || req.method === 'DELETE') {\n // Node sends every status through writeHead — also the implicit one\n // behind res.end() — so this catches a 302 from any code path.\n const writeHead = res.writeHead\n res.writeHead = function (this: ServerResponse, status: number, ...rest: unknown[]) {\n return (writeHead as (...a: unknown[]) => ServerResponse).call(\n this,\n status === 302 ? 303 : status,\n ...rest,\n )\n } as typeof res.writeHead\n }\n next()\n }\n\n return {\n beforeMount({ http }) {\n const vite = viteServer()\n let head: (page: Page) => string\n\n if (vite) {\n head = () => devHead(config.entry)\n } else {\n const tags = manifestHead(clientDir, config.entry)\n head = () => tags\n const hash = createHash('sha1')\n .update(readFileSync(join(clientDir, '.vite', 'manifest.json')))\n .digest('hex')\n .slice(0, 12)\n version = () => hash\n // The whole build: hashed `assets/` plus what Vite copied from\n // `public/` — favicon, robots.txt, a static sitemap.\n if (existsSync(join(clientDir, 'package.json'))) {\n throw new Error(\n `InertiaAdapter: clientDir ${clientDir} looks like a project root — it must be the client build output (default dist/client), which is served as-is.`,\n )\n }\n // Not every engine's static handler skips dotfiles, so the build's\n // own (`.vite/manifest.json`) are refused here, before it runs.\n const hidden = new Set(readdirSync(clientDir).filter((name) => name.startsWith('.')))\n const hideBuildFiles: NodeMiddleware = (req, res, next) => {\n const first = safeDecode((req.url ?? '/').split('?')[0]!)\n .split('/')\n .find(Boolean)\n if (first && hidden.has(first)) {\n res.statusCode = 404\n res.end()\n return\n }\n next()\n }\n http.use(hideBuildFiles as never)\n http.serveStatic('/', clientDir)\n }\n if (config.version !== undefined) {\n const v = config.version\n version = typeof v === 'function' ? v : () => v\n }\n\n const ssr = config.ssr\n let prodRender: Promise<SsrRender> | undefined\n const loadRender = async (): Promise<SsrRender> => {\n if (vite) {\n // Every request in dev, so an edited page renders fresh.\n const mod = await vite.ssrLoadModule(`/${ssr!.entry.replace(/^\\/+/, '')}`)\n return mod.default as SsrRender\n }\n const url = pathToFileURL(resolve(ssr!.bundle ?? 'dist/ssr/ssr.js')).href\n // The app's own build output, chosen at runtime — nothing for Vite to analyze.\n prodRender ??= import(/* @vite-ignore */ url).then((mod) => mod.default as SsrRender)\n return prodRender\n }\n const render = async (page: Page): Promise<SsrResult | null> => {\n if (!ssr) return null\n try {\n return await (\n await loadRender()\n )(page)\n } catch (err) {\n if (vite && err instanceof Error) vite.ssrFixStacktrace(err)\n log.error(err, `SSR failed for ${page.component} — sending it client-rendered`)\n return null\n }\n }\n\n setInertiaRuntime({\n version: () => version(),\n share: config.share,\n errorPage: config.errorPage,\n async renderHtml(page, ctx) {\n const rendered = await render(page)\n const styles = vite && rendered && ssr ? devStylesheets(vite, ssr.entry) : []\n const html = template({\n head: [config.head ?? '', head(page), ...styles, ...(rendered?.head ?? [])]\n .filter(Boolean)\n .join('\\n '),\n body: rendered?.body ?? pageBody(page),\n page,\n })\n return vite ? vite.transformIndexHtml(requestUrl(ctx.req), html) : html\n },\n })\n },\n\n middleware() {\n return [{ handler: protocol as never, phase: 'afterGlobal' as const }]\n },\n\n shutdown() {\n setInertiaRuntime(null)\n },\n }\n },\n})\n","/**\n * Validation errors the Inertia way: on an Inertia request, a 422 from route\n * validation becomes a redirect back with the errors in the session, and the\n * next page gets them as `props.errors`. Everything else goes to the usual\n * error handler.\n *\n * @module @forinda/kickjs-inertia/errors\n */\nimport {\n errorHandler,\n HttpException,\n Logger,\n RequestContext,\n RESPOND,\n type RuntimeResponse,\n} from '@forinda/kickjs'\nimport { ERRORS_KEY, errorPage, inertiaRuntime, isPageRequest, previousUrl } from './inertia'\n\nconst log = Logger.for('Inertia')\n\ntype ErrorHandler = (\n err: any,\n req: any,\n res: RuntimeResponse,\n next: (err?: unknown) => void,\n) => void\n\ninterface SessionLike {\n data: Record<string, unknown>\n save(): Promise<void>\n}\n\n/** `[{ field, message }]` from route validation → `{ field: firstMessage }`. */\nfunction toErrors(details: unknown): Record<string, string> | null {\n if (!Array.isArray(details)) return null\n const errors: Record<string, string> = {}\n for (const d of details as Array<{ field?: string; message?: string }>) {\n const field = d.field || 'form'\n errors[field] ??= d.message ?? 'Invalid value'\n }\n return errors\n}\n\n/**\n * Pass to `bootstrap({ onError: inertiaErrorHandler() })`.\n *\n * - A validation 422 on an Inertia visit goes back to the form with the\n * errors (needs the `session()` middleware to carry them).\n * - Any other error on a page request renders the adapter's `errorPage`,\n * when one is configured.\n * - Everything else — API calls, assets — goes to `fallback`.\n */\nexport function inertiaErrorHandler(fallback: ErrorHandler = errorHandler()): ErrorHandler {\n let warned = false\n return (err, req, res, next) => {\n if (res.headersSent) return fallback(err, req, res, next)\n\n const errors = err instanceof HttpException && err.status === 422 ? toErrors(err.details) : null\n if (errors && req?.headers?.['x-inertia'] === 'true') {\n const session = req.session as SessionLike | undefined\n if (session) {\n const bag = req.headers['x-inertia-error-bag'] as string | undefined\n session.data[ERRORS_KEY] = bag ? { [bag]: errors } : errors\n session\n .save()\n .then(() => {\n res.status(303).setHeader('location', previousUrl(session, req.headers))\n res.end()\n })\n .catch((saveErr) => fallback(saveErr, req, res, next))\n return\n }\n if (!warned) {\n log.warn(\n 'Validation failed on an Inertia request, but there is no session() middleware to carry the errors — answering 422 instead.',\n )\n warned = true\n }\n return fallback(err, req, res, next)\n }\n\n const component = inertiaRuntime()?.errorPage\n if (component && req?.headers && isPageRequest(req.headers)) {\n const status = err instanceof HttpException ? err.status : 500\n if (status >= 500) log.error(err, 'Unhandled error on a page request')\n const ctx = new RequestContext(req, res as never, next, res)\n Promise.resolve(errorPage(component, status, err)[RESPOND](ctx)).catch((renderErr) =>\n fallback(renderErr, req, res, next),\n )\n return\n }\n return fallback(err, req, res, next)\n }\n}\n\ntype NotFoundHandler = (req: any, res: RuntimeResponse, next: (err?: unknown) => void) => void\n\n/**\n * Pass to `bootstrap({ onNotFound: inertiaNotFound() })`. A browser or Inertia\n * request for an unknown URL gets the adapter's `errorPage` with 404; anything\n * else — API calls, other methods — goes on to the built-in 404 / 405.\n */\nexport function inertiaNotFound(): NotFoundHandler {\n return (req, res, next) => {\n const component = inertiaRuntime()?.errorPage\n const method = req?.method ?? 'GET'\n if (\n !component ||\n (method !== 'GET' && method !== 'HEAD') ||\n !isPageRequest(req.headers ?? {})\n ) {\n return next()\n }\n const ctx = new RequestContext(req, res as never, next, res)\n Promise.resolve(errorPage(component, 404)[RESPOND](ctx)).catch(next)\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["log"],"sources":["../src/props.ts","../src/inertia.ts","../src/html.ts","../src/adapter.ts","../src/errors.ts"],"sourcesContent":["/**\n * Prop wrappers that change when a prop is sent. A plain value or a function\n * is sent on every full visit and on a partial reload that asks for it; a\n * function is only called when the prop is actually sent.\n *\n * @module @forinda/kickjs-inertia/props\n */\n\ntype Resolver<T> = T | (() => T | Promise<T>)\n\nconst PROP = Symbol.for('kickjs.inertia.prop')\n\nexport type PropKind = 'optional' | 'always' | 'defer' | 'merge' | 'once' | 'scroll'\n\n/** Where an infinite-scroll prop is in its pages; `null` past either end. */\nexport interface ScrollMeta {\n currentPage: number | string | null\n nextPage: number | string | null\n previousPage: number | string | null\n}\n\nexport interface InertiaProp<T = unknown> {\n readonly [PROP]: PropKind\n readonly value: Resolver<T>\n /** `defer` only — props in one group load in one request. */\n readonly group?: string\n /** `merge` only — append, prepend or deep-merge into the client's copy. */\n readonly mode?: 'append' | 'prepend' | 'deep'\n /** `merge` and `scroll` — item fields that identify a row, e.g. `['id']`. */\n readonly matchOn?: string[]\n /** `once` only — the name the client caches it under. */\n readonly key?: string\n /** `once` only — milliseconds the client may keep it. */\n readonly ttl?: number\n /** `scroll` only. */\n readonly pageName?: string\n readonly wrapper?: string\n readonly meta?: (value: any) => ScrollMeta\n}\n\nfunction prop<T>(kind: PropKind, value: Resolver<T>, extra?: object): InertiaProp<T> {\n return { [PROP]: kind, value, ...extra }\n}\n\n/** Sent only when a partial reload names it (`router.reload({ only: [...] })`). */\nexport function optional<T>(value: () => T | Promise<T>): InertiaProp<T> {\n return prop('optional', value)\n}\n\n/** Sent on every response, including partial reloads that did not name it. */\nexport function always<T>(value: Resolver<T>): InertiaProp<T> {\n return prop('always', value)\n}\n\n/**\n * Left out of the first response; the client fetches it right after the page\n * renders, one request per `group`.\n */\nexport function defer<T>(value: () => T | Promise<T>, group = 'default'): InertiaProp<T> {\n return prop('defer', value, { group })\n}\n\n/**\n * Merged into the client's current value on a partial reload instead of\n * replacing it — paginated lists, infinite scroll.\n */\nexport function merge<T>(\n value: Resolver<T>,\n mode: 'append' | 'prepend' | 'deep' = 'append',\n options: { matchOn?: string | string[] } = {},\n): InertiaProp<T> {\n return prop('merge', value, { mode, matchOn: toList(options.matchOn) })\n}\n\n/**\n * Resolved once, then kept by the client: later visits skip it until `ttl`\n * runs out. For data that rarely changes — plans, countries, a settings\n * list. Pages that share a `key` share the cached value.\n */\nexport function once<T>(\n value: Resolver<T>,\n options: { key?: string; ttl?: number } = {},\n): InertiaProp<T> {\n return prop('once', value, options)\n}\n\n/**\n * One page of an infinite list, for Inertia's `<InfiniteScroll>`. The client\n * asks for the next or previous page with `?page=N` and appends or prepends\n * the rows under `wrapper`.\n *\n * The value is read as `{ data, meta: { page, hasNext, hasPrev } }` — the\n * `PaginatedResponse` shape; pass `meta` to read another one.\n */\nexport function scroll<T>(\n value: Resolver<T>,\n options: {\n pageName?: string\n wrapper?: string\n matchOn?: string | string[]\n meta?: (value: T) => ScrollMeta\n } = {},\n): InertiaProp<T> {\n return prop('scroll', value, {\n pageName: options.pageName ?? 'page',\n wrapper: options.wrapper ?? 'data',\n matchOn: toList(options.matchOn),\n meta: options.meta ?? paginatedMeta,\n })\n}\n\nfunction toList(value: string | string[] | undefined): string[] | undefined {\n return value === undefined ? undefined : Array.isArray(value) ? value : [value]\n}\n\nfunction paginatedMeta(value: unknown): ScrollMeta {\n const meta = (value as { meta?: { page?: number; hasNext?: boolean; hasPrev?: boolean } })?.meta\n if (typeof meta?.page !== 'number') {\n throw new Error(\n 'scroll(): the value has no `meta.page` — return `{ data, meta: { page, hasNext, hasPrev } }` or pass `meta`.',\n )\n }\n return {\n currentPage: meta.page,\n nextPage: meta.hasNext ? meta.page + 1 : null,\n previousPage: meta.hasPrev ? meta.page - 1 : null,\n }\n}\n\nexport function isInertiaProp(value: unknown): value is InertiaProp {\n return typeof value === 'object' && value !== null && PROP in value\n}\n\nexport function propKind(value: InertiaProp): PropKind {\n return value[PROP]\n}\n\n/** Unwrap a wrapper, call a function, await a promise. */\nexport async function resolveProp(value: unknown): Promise<unknown> {\n const raw = isInertiaProp(value) ? value.value : value\n return typeof raw === 'function' ? await (raw as () => unknown)() : await raw\n}\n","/**\n * Page responses. `inertia(component, props)` is a handler return value: on a\n * first visit it renders the HTML shell with the page embedded, on an Inertia\n * visit (`X-Inertia: true`) it sends the page object as JSON.\n *\n * @module @forinda/kickjs-inertia/inertia\n */\nimport { RESPOND, type RequestContext, type Respondable } from '@forinda/kickjs'\nimport { isInertiaProp, propKind, resolveProp, type ScrollMeta } from './props'\n\nexport const ERRORS_KEY = '__inertia_errors'\nexport const FLASH_KEY = '__inertia_flash'\n/** URL of the last page rendered — where a failed submit goes back to. */\nexport const PREVIOUS_KEY = '__inertia_previous'\n/** Set by `redirect(url, { preserveFragment: true })` for the next page. */\nexport const FRAGMENT_KEY = '__inertia_fragment'\n\n/** The page object the Inertia client renders — see the protocol docs. */\nexport interface Page {\n component: string\n props: Record<string, unknown> & { errors: Record<string, unknown> }\n url: string\n version: string | null\n flash: Record<string, unknown>\n sharedProps?: string[]\n deferredProps?: Record<string, string[]>\n mergeProps?: string[]\n prependProps?: string[]\n deepMergeProps?: string[]\n matchPropsOn?: string[]\n scrollProps?: Record<string, ScrollMeta & { pageName: string; reset: boolean }>\n onceProps?: Record<string, { prop: string; expiresAt?: number }>\n encryptHistory?: boolean\n clearHistory?: boolean\n preserveFragment?: boolean\n}\n\nexport interface PageOptions {\n /** Encrypt this page's history entry in the browser. */\n encryptHistory?: boolean\n /** Wipe the browser's Inertia history, e.g. after logout. */\n clearHistory?: boolean\n /** Response status. Default 200; error pages send theirs (404, 500). */\n status?: number\n}\n\n/** What the adapter hands the page renderer. Set in `beforeMount`. */\nexport interface InertiaRuntime {\n version(): string | null\n share?(ctx: RequestContext): Record<string, unknown> | Promise<Record<string, unknown>>\n renderHtml(page: Page, ctx: RequestContext): Promise<string>\n /** Component that renders error and not-found pages, when configured. */\n errorPage?: string\n}\n\n/**\n * Where the adapter's middleware leaves its runtime on each request, so every\n * helper uses the adapter of the app that is handling that request — two apps\n * in one process (tests, a reload that overlaps the old app) never share one.\n */\nconst RUNTIME_SLOT = Symbol.for('kickjs.inertia.runtime')\n\nexport function bindInertiaRuntime(req: object, runtime: InertiaRuntime): void {\n ;(req as Record<symbol, unknown>)[RUNTIME_SLOT] = runtime\n}\n\n/** The runtime of the adapter that handled this request, or null without one. */\nexport function inertiaRuntimeFor(req: unknown): InertiaRuntime | null {\n return ((req as Record<symbol, unknown> | null)?.[RUNTIME_SLOT] as InertiaRuntime) ?? null\n}\n\nfunction runtime(ctx: RequestContext): InertiaRuntime {\n const rt = inertiaRuntimeFor(ctx.req)\n if (!rt) {\n throw new Error(\n 'inertia() needs InertiaAdapter — add it to bootstrap({ adapters: [InertiaAdapter({ entry })] }).',\n )\n }\n return rt\n}\n\nfunction header(ctx: RequestContext, name: string): string | undefined {\n const value = (ctx.headers as Record<string, string | string[] | undefined>)[name]\n return Array.isArray(value) ? value[0] : value\n}\n\nfunction list(value: string | undefined): string[] {\n return value\n ? value\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n : []\n}\n\nexport function requestUrl(req: unknown): string {\n const r = req as { originalUrl?: string; url?: string }\n return r.originalUrl ?? r.url ?? '/'\n}\n\nexport function isInertiaRequest(ctx: RequestContext): boolean {\n return header(ctx, 'x-inertia') === 'true'\n}\n\n/** Read and remove a one-request value from the session. */\nfunction take(ctx: RequestContext, key: string): Record<string, unknown> {\n const data = ctx.session?.data\n if (!data || data[key] === undefined) return {}\n const value = data[key] as Record<string, unknown>\n delete data[key]\n return value\n}\n\n/** Flash data for the next page, sent as the page's top-level `flash`. */\nexport function flash(ctx: RequestContext, data: Record<string, unknown>): void {\n const session = ctx.session\n if (!session) throw new Error('flash() needs the session() middleware.')\n session.data[FLASH_KEY] = { ...(session.data[FLASH_KEY] as object), ...data }\n}\n\nexport async function buildPage(\n ctx: RequestContext,\n component: string,\n props: Record<string, unknown>,\n options: PageOptions = {},\n): Promise<Page> {\n const rt = runtime(ctx)\n const shared = (await rt.share?.(ctx)) ?? {}\n const all: Record<string, unknown> = { ...shared, ...props }\n\n const partial = header(ctx, 'x-inertia-partial-component') === component\n const only = partial ? list(header(ctx, 'x-inertia-partial-data')) : []\n const except = partial ? list(header(ctx, 'x-inertia-partial-except')) : []\n const reset = list(header(ctx, 'x-inertia-reset'))\n // Once props the client already holds; it fills them in from its cache.\n const cachedOnce = list(header(ctx, 'x-inertia-except-once-props'))\n const scrollIntent =\n header(ctx, 'x-inertia-infinite-scroll-merge-intent') === 'prepend' ? 'prepend' : 'append'\n\n // A dot path in `only` ('users.data') still selects its top-level prop.\n const named = (key: string) => only.some((o) => o === key || o.startsWith(`${key}.`))\n\n const resolved: Record<string, unknown> = {}\n const deferredProps: Record<string, string[]> = {}\n const merges = { append: [] as string[], prepend: [] as string[], deep: [] as string[] }\n const matchPropsOn: string[] = []\n const scrollProps: NonNullable<Page['scrollProps']> = {}\n const onceProps: NonNullable<Page['onceProps']> = {}\n\n for (const [key, value] of Object.entries(all)) {\n const kind = isInertiaProp(value) ? propKind(value) : undefined\n let send: boolean\n if (kind === 'always') send = true\n else if (partial) send = (only.length === 0 || named(key)) && !except.includes(key)\n else send = kind !== 'optional' && kind !== 'defer'\n\n if (kind === 'once' && isInertiaProp(value)) {\n const onceKey = value.key ?? key\n onceProps[onceKey] = {\n prop: key,\n ...(value.ttl === undefined ? {} : { expiresAt: Date.now() + value.ttl }),\n }\n // Asked for by name, it is sent even when cached.\n if (cachedOnce.includes(onceKey) && !(partial && named(key))) send = false\n }\n\n if (!send) {\n if (!partial && kind === 'defer' && isInertiaProp(value)) {\n const group = value.group ?? 'default'\n ;(deferredProps[group] ??= []).push(key)\n }\n continue\n }\n resolved[key] = await resolveProp(value)\n if (kind === 'merge' && isInertiaProp(value) && !reset.includes(key)) {\n merges[value.mode ?? 'append'].push(key)\n for (const field of value.matchOn ?? []) matchPropsOn.push(`${key}.${field}`)\n }\n if (kind === 'scroll' && isInertiaProp(value)) {\n const path = `${key}.${value.wrapper}`\n const isReset = reset.includes(key)\n scrollProps[key] = {\n pageName: value.pageName!,\n ...value.meta!(resolved[key]),\n reset: isReset,\n }\n if (!isReset) merges[scrollIntent].push(path)\n for (const field of value.matchOn ?? []) matchPropsOn.push(`${path}.${field}`)\n }\n }\n\n const page: Page = {\n component,\n props: { ...resolved, errors: take(ctx, ERRORS_KEY) },\n url: requestUrl(ctx.req),\n version: rt.version(),\n flash: take(ctx, FLASH_KEY),\n }\n const sharedKeys = Object.keys(shared)\n if (sharedKeys.length) page.sharedProps = sharedKeys\n if (Object.keys(deferredProps).length) page.deferredProps = deferredProps\n if (merges.append.length) page.mergeProps = merges.append\n if (merges.prepend.length) page.prependProps = merges.prepend\n if (merges.deep.length) page.deepMergeProps = merges.deep\n if (matchPropsOn.length) page.matchPropsOn = matchPropsOn\n if (Object.keys(scrollProps).length) page.scrollProps = scrollProps\n if (Object.keys(onceProps).length) page.onceProps = onceProps\n if (ctx.session?.data[FRAGMENT_KEY]) {\n delete ctx.session.data[FRAGMENT_KEY]\n page.preserveFragment = true\n }\n if (options.encryptHistory) page.encryptHistory = true\n if (options.clearHistory) page.clearHistory = true\n return page\n}\n\n/**\n * Render an Inertia page. Return it from a handler:\n *\n * ```ts\n * @Get('/users')\n * async index() {\n * return inertia('Users/Index', { users: await this.users.list() })\n * }\n * ```\n */\nexport function inertia(\n component: string,\n props: Record<string, unknown> = {},\n options?: PageOptions,\n): Respondable {\n return {\n async [RESPOND](ctx: RequestContext) {\n const page = await buildPage(ctx, component, props, options)\n // Remembered for back(): helmet's default Referrer-Policy (no-referrer)\n // means the browser sends no Referer to go back to.\n if (ctx.session) ctx.session.data[PREVIOUS_KEY] = page.url\n // `Vary: X-Inertia` is added by the adapter's middleware, which can\n // append to a Vary another layer set instead of replacing it.\n if (isInertiaRequest(ctx)) {\n ctx.setHeader('X-Inertia', 'true')\n ctx.json(page, options?.status ?? 200)\n return\n }\n ctx.html(await runtime(ctx).renderHtml(page, ctx), options?.status ?? 200)\n },\n }\n}\n\nexport interface RedirectOptions {\n /**\n * Keep the `#fragment` of the URL the request was sent to — a form that\n * posts to `/posts/1/comments#comments` lands on `#comments` of the page\n * this redirects to. Needs `session()`.\n */\n preserveFragment?: boolean\n}\n\n/**\n * Redirect after a form submit. Uses 303 for anything but GET, so the browser\n * follows with a GET, and saves the session first so flashed errors and\n * messages are there when that GET arrives.\n *\n * A URL with a `#fragment` is sent to an Inertia visit as 409 +\n * `X-Inertia-Redirect`: a redirect followed inside a fetch drops the\n * fragment, so the client makes the visit itself.\n */\nexport function redirect(url: string, options: RedirectOptions = {}): Respondable {\n return {\n async [RESPOND](ctx: RequestContext) {\n if (options.preserveFragment && ctx.session) ctx.session.data[FRAGMENT_KEY] = true\n await ctx.session?.save()\n if (url.includes('#') && isInertiaRequest(ctx)) {\n ctx.setHeader('X-Inertia-Redirect', url)\n ctx.html('', 409)\n return\n }\n const method = (ctx.req as { method?: string }).method ?? 'GET'\n ctx.redirect(url, method === 'GET' ? 302 : 303)\n },\n }\n}\n\n/** A path on this site — not `//host` or `/\\\\host`, which browsers treat as another origin. */\nfunction localPath(value: unknown): string | undefined {\n return typeof value === 'string' && /^\\/(?![/\\\\])/.test(value) ? value : undefined\n}\n\n/** The path of a Referer, only when it points at this host. */\nfunction sameOriginPath(referer: string | undefined, host: string | undefined): string | undefined {\n if (!referer || !host) return undefined\n try {\n const url = new URL(referer)\n return url.host === host ? localPath(url.pathname + url.search) : undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * Where \"back\" is: the last page this session rendered, else a same-origin\n * Referer, else `fallback`. Never another site — a form on another site that\n * posts here must not be able to choose where the user lands.\n */\nexport function previousUrl(\n session: { data: Record<string, unknown> } | undefined,\n headers: { referer?: string | string[]; host?: string | string[] },\n fallback = '/',\n): string {\n const one = (v: string | string[] | undefined) => (Array.isArray(v) ? v[0] : v)\n return (\n localPath(session?.data[PREVIOUS_KEY]) ??\n sameOriginPath(one(headers.referer), one(headers.host)) ??\n fallback\n )\n}\n\n/** Redirect back to the page the request came from. */\nexport function back(ctx: RequestContext, fallback = '/', options?: RedirectOptions): Respondable {\n return redirect(\n previousUrl(\n ctx.session,\n { referer: header(ctx, 'referer'), host: header(ctx, 'host') },\n fallback,\n ),\n options,\n )\n}\n\n/**\n * A full browser visit, outside Inertia — another site, or a page that is not\n * an Inertia page. An Inertia request gets 409 + `X-Inertia-Location`, which\n * the client turns into `window.location`.\n */\nexport function location(url: string): Respondable {\n return {\n async [RESPOND](ctx: RequestContext) {\n if (isInertiaRequest(ctx)) {\n ctx.setHeader('X-Inertia-Location', url)\n ctx.html('', 409)\n return\n }\n ctx.redirect(url)\n },\n }\n}\n\n/** A browser navigation or an Inertia visit — not an API or asset request. */\nexport function isPageRequest(headers: Record<string, string | string[] | undefined>): boolean {\n const accept = String(headers.accept ?? '')\n return headers['x-inertia'] === 'true' || accept.includes('text/html')\n}\n\nconst STATUS_TEXT: Record<number, string> = {\n 400: 'Bad Request',\n 401: 'Unauthorized',\n 403: 'Forbidden',\n 404: 'Page Not Found',\n 405: 'Method Not Allowed',\n 419: 'Page Expired',\n 429: 'Too Many Requests',\n 500: 'Server Error',\n 503: 'Service Unavailable',\n}\n\n/**\n * The configured error page as a response, with `{ status, message }` as its\n * props. A 5xx message is replaced in production — an exception's text is\n * for the log, not the visitor.\n */\nexport function errorPage(component: string, status: number, error?: unknown): Respondable {\n const fallback = STATUS_TEXT[status] ?? (status >= 500 ? 'Server Error' : 'Error')\n const raw = error instanceof Error && error.message ? error.message : fallback\n const message = status >= 500 && process.env.NODE_ENV === 'production' ? fallback : raw\n return inertia(component, { status, message }, { status })\n}\n","/**\n * The HTML shell for a first visit, and the script tags that load the client:\n * Vite's dev server while `kick dev` runs, the build manifest in production.\n *\n * @module @forinda/kickjs-inertia/html\n */\nimport { readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport type { Page } from './inertia'\n\nexport interface HtmlParts {\n /** Script and style tags for the client entry. Put inside `<head>`. */\n head: string\n /** The page data and the mount element. Put inside `<body>`. */\n body: string\n page: Page\n}\n\nexport type HtmlTemplate = (parts: HtmlParts) => string\n\nconst escapeHtml = (value: string): string =>\n value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"')\n\nexport const defaultTemplate =\n (title: string): HtmlTemplate =>\n ({ head, body }) =>\n `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>${escapeHtml(title)}</title>\n ${head}\n </head>\n <body>\n ${body}\n </body>\n</html>`\n\n/**\n * The page as `<script data-page=\"app\" type=\"application/json\">` — where the\n * v3 client reads it — followed by the mount element. `<` and `/` are escaped\n * so a prop containing `</script>` cannot end the tag.\n */\nexport function pageBody(page: Page, id = 'app'): string {\n const json = JSON.stringify(page, (_k, v) => (typeof v === 'bigint' ? v.toString() : v))\n .replace(/</g, '\\\\u003c')\n .replace(/\\//g, '\\\\/')\n return `<script data-page=\"${id}\" type=\"application/json\">${json}</script>\\n <div id=\"${id}\"></div>`\n}\n\n/** Dev: one module script; Vite's `transformIndexHtml` adds its client and plugin preambles. */\nexport function devHead(entry: string): string {\n return `<script type=\"module\" src=\"/${entry.replace(/^\\/+/, '')}\"></script>`\n}\n\ninterface ManifestChunk {\n file: string\n css?: string[]\n imports?: string[]\n}\n\n/** Production: the entry's script, its CSS, and the CSS and preloads of what it imports. */\nexport function manifestHead(clientDir: string, entry: string): string {\n const manifest = JSON.parse(\n readFileSync(join(clientDir, '.vite', 'manifest.json'), 'utf8'),\n ) as Record<string, ManifestChunk>\n const chunk = manifest[entry]\n if (!chunk) {\n throw new Error(\n `InertiaAdapter: '${entry}' is not in ${clientDir}/.vite/manifest.json — build the client with build.manifest and this entry as an input.`,\n )\n }\n const css = new Set<string>()\n const preload = new Set<string>()\n const walk = (key: string, root: boolean) => {\n const c = manifest[key]\n if (!c) return\n for (const f of c.css ?? []) css.add(f)\n if (!root) preload.add(c.file)\n for (const i of c.imports ?? []) walk(i, false)\n }\n walk(entry, true)\n return [\n ...[...css].map((f) => `<link rel=\"stylesheet\" href=\"/${f}\" />`),\n ...[...preload].map((f) => `<link rel=\"modulepreload\" href=\"/${f}\" />`),\n `<script type=\"module\" src=\"/${chunk.file}\"></script>`,\n ].join('\\n ')\n}\n","/**\n * `InertiaAdapter` — wires the protocol into the app:\n *\n * - makes `inertia()` able to render (HTML shell, asset tags, version);\n * - answers a stale Inertia GET with 409 so the client reloads the page;\n * - turns a 302 after PUT / PATCH / DELETE into 303, so the browser follows\n * with a GET instead of repeating the method;\n * - in production, serves the built client's `assets/`.\n *\n * @module @forinda/kickjs-inertia/adapter\n */\nimport { createHash } from 'node:crypto'\nimport { existsSync, readFileSync, readdirSync } from 'node:fs'\nimport { join, resolve } from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { defineAdapter, Logger, type RequestContext } from '@forinda/kickjs'\nimport { bindInertiaRuntime, requestUrl, type InertiaRuntime, type Page } from './inertia'\nimport { defaultTemplate, devHead, manifestHead, pageBody, type HtmlTemplate } from './html'\n\nexport interface InertiaAdapterOptions {\n /**\n * The client entry, relative to the project root — the file that calls\n * `createInertiaApp`. Served by Vite in dev, looked up in the build\n * manifest in production. Default: `'src/client/main.tsx'`.\n */\n entry: string\n /** Where `vite build` wrote the client, with `build.manifest: true`. Default: `'dist/client'`. */\n clientDir: string\n /**\n * Asset version. A client on another version reloads the page on its next\n * visit. Default: a hash of the build manifest in production, none in dev\n * (Vite reloads changed modules itself).\n */\n version?: string | (() => string)\n /** Props every page gets — the current user, app name. */\n share?: (ctx: RequestContext) => Record<string, unknown> | Promise<Record<string, unknown>>\n /** `<title>` of the default HTML shell. */\n title: string\n /**\n * Tags added to every page's `<head>` — a favicon, meta tags, fonts. Per-page\n * titles and meta belong in the page, with Inertia's `<Head>` component.\n */\n head?: string\n /** Replace the HTML shell entirely. */\n template?: HtmlTemplate\n /**\n * Page component for errors and unknown URLs, e.g. `'Error'`. It receives\n * `{ status, message }`. Rendered for page requests by `inertiaErrorHandler()`\n * (thrown errors) and `inertiaNotFound()` (unknown URLs); API and asset\n * requests keep the usual JSON answers.\n */\n errorPage?: string\n /**\n * Render pages on the server. `entry` is a module whose default export is\n * `(page) => Promise<{ head: string[]; body: string }>` — what\n * `createInertiaApp({ page, render: renderToString, ... })` returns. Loaded\n * through Vite in dev; in production from `bundle`, the output of\n * `vite build --ssr <entry>` (default `'dist/ssr/ssr.js'`). A render that\n * throws falls back to client rendering for that request.\n */\n ssr?: { entry: string; bundle?: string }\n}\n\n/** What an SSR entry's default export returns. */\nexport interface SsrResult {\n head: string[]\n body: string\n}\n\ntype SsrRender = (page: Page) => Promise<SsrResult>\n\nconst log = Logger.for('InertiaAdapter')\n\ninterface ViteModuleLike {\n url: string\n id: string | null\n importedModules: Set<ViteModuleLike>\n}\n\ninterface ViteDevServerLike {\n config: { root: string }\n environments: { ssr: { moduleGraph: { getModuleById(id: string): ViteModuleLike | undefined } } }\n transformIndexHtml(url: string, html: string): Promise<string>\n ssrLoadModule(url: string): Promise<Record<string, unknown>>\n ssrFixStacktrace(err: Error): void\n}\n\nconst CSS_REQUEST = /\\.(css|less|sass|scss|styl|stylus|pcss|postcss|sss)(?:$|\\?)/\n\n/**\n * Dev only: the stylesheets the SSR entry imports, as `<link>` tags. In dev\n * Vite injects CSS from JavaScript, so server-rendered HTML would show\n * unstyled until the scripts run. `data-vite-dev-id` lets Vite's client\n * recognize these and not inject the same styles again.\n */\nfunction devStylesheets(vite: ViteDevServerLike, entry: string): string[] {\n const root = vite.environments.ssr.moduleGraph.getModuleById(resolve(vite.config.root, entry))\n if (!root) return []\n const links: string[] = []\n const seen = new Set<ViteModuleLike>()\n const walk = (mod: ViteModuleLike) => {\n if (seen.has(mod)) return\n seen.add(mod)\n if (CSS_REQUEST.test(mod.url)) {\n const devId = mod.id ? ` data-vite-dev-id=\"${mod.id.replace(/\"/g, '"')}\"` : ''\n links.push(`<link rel=\"stylesheet\" href=\"${mod.url}\"${devId} />`)\n return\n }\n for (const imported of mod.importedModules) walk(imported)\n }\n walk(root)\n return links\n}\n\nfunction viteServer(): ViteDevServerLike | null {\n return (\n (globalThis as { __kickjs_viteServer?: ViteDevServerLike | null }).__kickjs_viteServer ?? null\n )\n}\n\n/**\n * A build path, relative to the app: the working directory, or\n * `KICK_APP_ROOT` when set — the Netlify and Vercel functions `kick build:*`\n * writes set it to where they packaged the build, since the platform starts\n * them elsewhere.\n */\nfunction fromAppRoot(path: string): string {\n return resolve(process.env.KICK_APP_ROOT ?? process.cwd(), path)\n}\n\nfunction appendVary(res: ServerResponse, field: string): void {\n const current = res.getHeader('Vary')\n const list = (Array.isArray(current) ? current.join(',') : String(current ?? ''))\n .split(',')\n .map((v) => v.trim())\n .filter(Boolean)\n if (list.includes('*') || list.some((v) => v.toLowerCase() === field.toLowerCase())) return\n res.setHeader('Vary', [...list, field].join(', '))\n}\n\nfunction safeDecode(path: string): string {\n try {\n return decodeURIComponent(path)\n } catch {\n return path\n }\n}\n\ntype NodeMiddleware = (req: IncomingMessage, res: ServerResponse, next: () => void) => void\n\nexport const InertiaAdapter = defineAdapter<InertiaAdapterOptions>({\n name: 'InertiaAdapter',\n defaults: {\n entry: 'src/client/main.tsx',\n clientDir: 'dist/client',\n title: 'KickJS',\n },\n build: (config) => {\n const clientDir = fromAppRoot(config.clientDir)\n const template = config.template ?? defaultTemplate(config.title)\n let version: () => string | null = () => null\n\n // Built in beforeMount, once the dev server / build is known.\n let runtime: InertiaRuntime | null = null\n\n const protocol: NodeMiddleware = (req, res, next) => {\n if (runtime) bindInertiaRuntime(req, runtime)\n // The same URL answers HTML or JSON depending on X-Inertia, so a shared\n // cache must key on it. Appended, not set: replacing a Vary another\n // layer added (Origin, Accept-Encoding) lets a cache serve one\n // client's variant to another.\n appendVary(res, 'X-Inertia')\n // A Precognition request gets a validation answer instead of the\n // route's response.\n if (req.headers.precognition === 'true') appendVary(res, 'Precognition')\n if (req.headers['x-inertia'] !== 'true') return next()\n\n const current = version()\n if (req.method === 'GET' && current && req.headers['x-inertia-version'] !== current) {\n res.statusCode = 409\n res.setHeader('X-Inertia-Location', requestUrl(req))\n res.end()\n return\n }\n\n if (req.method === 'PUT' || req.method === 'PATCH' || req.method === 'DELETE') {\n // Node sends every status through writeHead — also the implicit one\n // behind res.end() — so this catches a 302 from any code path.\n const writeHead = res.writeHead\n res.writeHead = function (this: ServerResponse, status: number, ...rest: unknown[]) {\n return (writeHead as (...a: unknown[]) => ServerResponse).call(\n this,\n status === 302 ? 303 : status,\n ...rest,\n )\n } as typeof res.writeHead\n }\n next()\n }\n\n return {\n beforeMount({ http }) {\n const vite = viteServer()\n let head: (page: Page) => string\n\n if (vite) {\n head = () => devHead(config.entry)\n } else {\n const tags = manifestHead(clientDir, config.entry)\n head = () => tags\n const hash = createHash('sha1')\n .update(readFileSync(join(clientDir, '.vite', 'manifest.json')))\n .digest('hex')\n .slice(0, 12)\n version = () => hash\n // The whole build: hashed `assets/` plus what Vite copied from\n // `public/` — favicon, robots.txt, a static sitemap.\n if (existsSync(join(clientDir, 'package.json'))) {\n throw new Error(\n `InertiaAdapter: clientDir ${clientDir} looks like a project root — it must be the client build output (default dist/client), which is served as-is.`,\n )\n }\n // Not every engine's static handler skips dotfiles, so the build's\n // own (`.vite/manifest.json`) are refused here, before it runs.\n const hidden = new Set(readdirSync(clientDir).filter((name) => name.startsWith('.')))\n const hideBuildFiles: NodeMiddleware = (req, res, next) => {\n const first = safeDecode((req.url ?? '/').split('?')[0]!)\n .split('/')\n .find(Boolean)\n if (first && hidden.has(first)) {\n res.statusCode = 404\n res.end()\n return\n }\n next()\n }\n http.use(hideBuildFiles as never)\n http.serveStatic('/', clientDir)\n }\n if (config.version !== undefined) {\n const v = config.version\n version = typeof v === 'function' ? v : () => v\n }\n\n const ssr = config.ssr\n let prodRender: Promise<SsrRender> | undefined\n const loadRender = async (): Promise<SsrRender> => {\n if (vite) {\n // Every request in dev, so an edited page renders fresh.\n const mod = await vite.ssrLoadModule(`/${ssr!.entry.replace(/^\\/+/, '')}`)\n return mod.default as SsrRender\n }\n const url = pathToFileURL(fromAppRoot(ssr!.bundle ?? 'dist/ssr/ssr.js')).href\n // The app's own build output, chosen at runtime — nothing for Vite to analyze.\n prodRender ??= import(/* @vite-ignore */ url).then((mod) => mod.default as SsrRender)\n return prodRender\n }\n const render = async (page: Page): Promise<SsrResult | null> => {\n if (!ssr) return null\n try {\n return await (\n await loadRender()\n )(page)\n } catch (err) {\n if (vite && err instanceof Error) vite.ssrFixStacktrace(err)\n log.error(err, `SSR failed for ${page.component} — sending it client-rendered`)\n return null\n }\n }\n\n runtime = {\n version: () => version(),\n share: config.share,\n errorPage: config.errorPage,\n async renderHtml(page, ctx) {\n const rendered = await render(page)\n const styles = vite && rendered && ssr ? devStylesheets(vite, ssr.entry) : []\n const html = template({\n head: [config.head ?? '', head(page), ...styles, ...(rendered?.head ?? [])]\n .filter(Boolean)\n .join('\\n '),\n body: rendered?.body ?? pageBody(page),\n page,\n })\n return vite ? vite.transformIndexHtml(requestUrl(ctx.req), html) : html\n },\n }\n },\n\n middleware() {\n return [{ handler: protocol as never, phase: 'afterGlobal' as const }]\n },\n }\n },\n})\n","/**\n * Validation errors the Inertia way: on an Inertia request, a 422 from route\n * validation becomes a redirect back with the errors in the session, and the\n * next page gets them as `props.errors`. Everything else goes to the usual\n * error handler.\n *\n * @module @forinda/kickjs-inertia/errors\n */\nimport {\n errorHandler,\n HttpException,\n Logger,\n RequestContext,\n RESPOND,\n type MiddlewareHandler,\n type RuntimeResponse,\n} from '@forinda/kickjs'\nimport { ERRORS_KEY, errorPage, inertiaRuntimeFor, isPageRequest, previousUrl } from './inertia'\n\nconst log = Logger.for('Inertia')\n\ntype ErrorHandler = (\n err: any,\n req: any,\n res: RuntimeResponse,\n next: (err?: unknown) => void,\n) => unknown\n\ninterface SessionLike {\n data: Record<string, unknown>\n save(): Promise<void>\n}\n\n/** `[{ field, message }]` from route validation → `{ field: firstMessage }`. */\nfunction toErrors(details: unknown): Record<string, string> | null {\n if (!Array.isArray(details)) return null\n const errors: Record<string, string> = {}\n for (const d of details as Array<{ field?: string; message?: string }>) {\n const field = d.field || 'form'\n errors[field] ??= d.message ?? 'Invalid value'\n }\n return errors\n}\n\nfunction isPrecognitive(req: any): boolean {\n return req?.headers?.precognition === 'true'\n}\n\n/**\n * A Precognition answer: 204 when nothing the client asked about failed,\n * else 422 with those fields' errors. `Precognition-Validate-Only` names the\n * fields a form has touched, so errors on the rest are not shown yet.\n */\nfunction precognitionResult(req: any, res: RuntimeResponse, errors: Record<string, string>) {\n const only = String(req.headers['precognition-validate-only'] ?? '')\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n const shown = only.length\n ? Object.fromEntries(\n Object.entries(errors).filter(([field]) =>\n only.some((o) => field === o || field.startsWith(`${o}.`)),\n ),\n )\n : errors\n res.setHeader('Precognition', 'true')\n if (Object.keys(shown).length === 0) {\n res.setHeader('Precognition-Success', 'true')\n res.status(204).end()\n return\n }\n res.status(422).json({ message: 'The given data was invalid.', errors: shown })\n}\n\n/**\n * Live validation for Inertia's `useForm().validate()` — Laravel Precognition.\n * Put it on a route that validates its body; a precognitive request runs the\n * validation and stops before the handler:\n *\n * ```ts\n * @Post('/users', { body: createUserSchema })\n * @Middleware(precognitive)\n * create(ctx: RequestContext) { ... }\n * ```\n *\n * A failed check is answered by `inertiaErrorHandler()`.\n */\nexport const precognitive: MiddlewareHandler<RequestContext> = (ctx, next) => {\n if (!isPrecognitive(ctx.req)) return next()\n ctx.setHeader('Precognition', 'true')\n ctx.setHeader('Precognition-Success', 'true')\n ctx.noContent()\n}\n\n/**\n * Pass to `bootstrap({ onError: inertiaErrorHandler() })`.\n *\n * - A validation 422 on a Precognition request is answered with the errors\n * of the fields it asked about (see `precognitive`).\n * - A validation 422 on an Inertia visit goes back to the form with the\n * errors (needs the `session()` middleware to carry them).\n * - Any other error on a page request renders the adapter's `errorPage`,\n * when one is configured.\n * - Everything else — API calls, assets — goes to `fallback`.\n */\nexport function inertiaErrorHandler(fallback: ErrorHandler = errorHandler()): ErrorHandler {\n let warned = false\n return (err, req, res, next) => {\n if (res.headersSent) return fallback(err, req, res, next)\n\n const errors = err instanceof HttpException && err.status === 422 ? toErrors(err.details) : null\n if (errors && isPrecognitive(req)) return precognitionResult(req, res, errors)\n if (errors && req?.headers?.['x-inertia'] === 'true') {\n const session = req.session as SessionLike | undefined\n if (session) {\n const bag = req.headers['x-inertia-error-bag'] as string | undefined\n session.data[ERRORS_KEY] = bag ? { [bag]: errors } : errors\n // Returned, so an engine that waits on async handlers waits on this.\n return session\n .save()\n .then(() => {\n res.status(303).setHeader('location', previousUrl(session, req.headers))\n res.end()\n })\n .catch((saveErr) => fallback(saveErr, req, res, next))\n }\n if (!warned) {\n log.warn(\n 'Validation failed on an Inertia request, but there is no session() middleware to carry the errors — answering 422 instead.',\n )\n warned = true\n }\n return fallback(err, req, res, next)\n }\n\n const component = inertiaRuntimeFor(req)?.errorPage\n if (component && req?.headers && isPageRequest(req.headers)) {\n const status = err instanceof HttpException ? err.status : 500\n if (status >= 500) log.error(err, 'Unhandled error on a page request')\n const ctx = new RequestContext(req, res as never, next, res)\n return Promise.resolve(errorPage(component, status, err)[RESPOND](ctx)).catch((renderErr) =>\n fallback(renderErr, req, res, next),\n )\n }\n return fallback(err, req, res, next)\n }\n}\n\ntype NotFoundHandler = (\n req: any,\n res: RuntimeResponse,\n next: (err?: unknown) => void,\n) => void | Promise<void>\n\n/**\n * Pass to `bootstrap({ onNotFound: inertiaNotFound() })`. A browser or Inertia\n * request for an unknown URL gets the adapter's `errorPage` with 404; anything\n * else — API calls, other methods — goes on to the built-in 404 / 405.\n */\nexport function inertiaNotFound(): NotFoundHandler {\n return (req, res, next) => {\n const component = inertiaRuntimeFor(req)?.errorPage\n const method = req?.method ?? 'GET'\n if (\n !component ||\n (method !== 'GET' && method !== 'HEAD') ||\n !isPageRequest(req.headers ?? {})\n ) {\n return next()\n }\n const ctx = new RequestContext(req, res as never, next, res)\n return Promise.resolve(errorPage(component, 404)[RESPOND](ctx)).catch(next)\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAUA,MAAM,OAAO,OAAO,IAAI,qBAAqB;AA8B7C,SAAS,KAAQ,MAAgB,OAAoB,OAAgC;CACnF,OAAO;GAAG,OAAO;EAAM;EAAO,GAAG;CAAM;AACzC;;AAGA,SAAgB,SAAY,OAA6C;CACvE,OAAO,KAAK,YAAY,KAAK;AAC/B;;AAGA,SAAgB,OAAU,OAAoC;CAC5D,OAAO,KAAK,UAAU,KAAK;AAC7B;;;;;AAMA,SAAgB,MAAS,OAA6B,QAAQ,WAA2B;CACvF,OAAO,KAAK,SAAS,OAAO,EAAE,MAAM,CAAC;AACvC;;;;;AAMA,SAAgB,MACd,OACA,OAAsC,UACtC,UAA2C,CAAC,GAC5B;CAChB,OAAO,KAAK,SAAS,OAAO;EAAE;EAAM,SAAS,OAAO,QAAQ,OAAO;CAAE,CAAC;AACxE;;;;;;AAOA,SAAgB,KACd,OACA,UAA0C,CAAC,GAC3B;CAChB,OAAO,KAAK,QAAQ,OAAO,OAAO;AACpC;;;;;;;;;AAUA,SAAgB,OACd,OACA,UAKI,CAAC,GACW;CAChB,OAAO,KAAK,UAAU,OAAO;EAC3B,UAAU,QAAQ,YAAY;EAC9B,SAAS,QAAQ,WAAW;EAC5B,SAAS,OAAO,QAAQ,OAAO;EAC/B,MAAM,QAAQ,QAAQ;CACxB,CAAC;AACH;AAEA,SAAS,OAAO,OAA4D;CAC1E,OAAO,UAAU,SAAY,SAAY,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;AAChF;AAEA,SAAS,cAAc,OAA4B;CACjD,MAAM,OAAQ,OAA8E;CAC5F,IAAI,OAAO,MAAM,SAAS,UACxB,MAAM,IAAI,MACR,8GACF;CAEF,OAAO;EACL,aAAa,KAAK;EAClB,UAAU,KAAK,UAAU,KAAK,OAAO,IAAI;EACzC,cAAc,KAAK,UAAU,KAAK,OAAO,IAAI;CAC/C;AACF;AAEA,SAAgB,cAAc,OAAsC;CAClE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,QAAQ;AAChE;AAEA,SAAgB,SAAS,OAA8B;CACrD,OAAO,MAAM;AACf;;AAGA,eAAsB,YAAY,OAAkC;CAClE,MAAM,MAAM,cAAc,KAAK,IAAI,MAAM,QAAQ;CACjD,OAAO,OAAO,QAAQ,aAAa,MAAO,IAAsB,IAAI,MAAM;AAC5E;;;;;;;;;;;ACnIA,MAAa,aAAa;AAC1B,MAAa,YAAY;;AAEzB,MAAa,eAAe;;AAE5B,MAAa,eAAe;;;;;;AA6C5B,MAAM,eAAe,OAAO,IAAI,wBAAwB;AAExD,SAAgB,mBAAmB,KAAa,SAA+B;CAC5E,AAAC,IAAgC,gBAAgB;AACpD;;AAGA,SAAgB,kBAAkB,KAAqC;CACrE,OAAS,MAAyC,iBAAoC;AACxF;AAEA,SAAS,QAAQ,KAAqC;CACpD,MAAM,KAAK,kBAAkB,IAAI,GAAG;CACpC,IAAI,CAAC,IACH,MAAM,IAAI,MACR,kGACF;CAEF,OAAO;AACT;AAEA,SAAS,OAAO,KAAqB,MAAkC;CACrE,MAAM,QAAS,IAAI,QAA0D;CAC7E,OAAO,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK;AAC3C;AAEA,SAAS,KAAK,OAAqC;CACjD,OAAO,QACH,MACG,MAAM,GAAG,CAAC,CACV,KAAK,MAAM,EAAE,KAAK,CAAC,CAAC,CACpB,OAAO,OAAO,IACjB,CAAC;AACP;AAEA,SAAgB,WAAW,KAAsB;CAC/C,MAAM,IAAI;CACV,OAAO,EAAE,eAAe,EAAE,OAAO;AACnC;AAEA,SAAgB,iBAAiB,KAA8B;CAC7D,OAAO,OAAO,KAAK,WAAW,MAAM;AACtC;;AAGA,SAAS,KAAK,KAAqB,KAAsC;CACvE,MAAM,OAAO,IAAI,SAAS;CAC1B,IAAI,CAAC,QAAQ,KAAK,SAAS,QAAW,OAAO,CAAC;CAC9C,MAAM,QAAQ,KAAK;CACnB,OAAO,KAAK;CACZ,OAAO;AACT;;AAGA,SAAgB,MAAM,KAAqB,MAAqC;CAC9E,MAAM,UAAU,IAAI;CACpB,IAAI,CAAC,SAAS,MAAM,IAAI,MAAM,yCAAyC;CACvE,QAAQ,KAAK,aAAa;EAAE,GAAI,QAAQ,KAAK;EAAuB,GAAG;CAAK;AAC9E;AAEA,eAAsB,UACpB,KACA,WACA,OACA,UAAuB,CAAC,GACT;CACf,MAAM,KAAK,QAAQ,GAAG;CACtB,MAAM,SAAU,MAAM,GAAG,QAAQ,GAAG,KAAM,CAAC;CAC3C,MAAM,MAA+B;EAAE,GAAG;EAAQ,GAAG;CAAM;CAE3D,MAAM,UAAU,OAAO,KAAK,6BAA6B,MAAM;CAC/D,MAAM,OAAO,UAAU,KAAK,OAAO,KAAK,wBAAwB,CAAC,IAAI,CAAC;CACtE,MAAM,SAAS,UAAU,KAAK,OAAO,KAAK,0BAA0B,CAAC,IAAI,CAAC;CAC1E,MAAM,QAAQ,KAAK,OAAO,KAAK,iBAAiB,CAAC;CAEjD,MAAM,aAAa,KAAK,OAAO,KAAK,6BAA6B,CAAC;CAClE,MAAM,eACJ,OAAO,KAAK,wCAAwC,MAAM,YAAY,YAAY;CAGpF,MAAM,SAAS,QAAgB,KAAK,MAAM,MAAM,MAAM,OAAO,EAAE,WAAW,GAAG,IAAI,EAAE,CAAC;CAEpF,MAAM,WAAoC,CAAC;CAC3C,MAAM,gBAA0C,CAAC;CACjD,MAAM,SAAS;EAAE,QAAQ,CAAC;EAAe,SAAS,CAAC;EAAe,MAAM,CAAC;CAAc;CACvF,MAAM,eAAyB,CAAC;CAChC,MAAM,cAAgD,CAAC;CACvD,MAAM,YAA4C,CAAC;CAEnD,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,GAAG,GAAG;EAC9C,MAAM,OAAO,cAAc,KAAK,IAAI,SAAS,KAAK,IAAI;EACtD,IAAI;EACJ,IAAI,SAAS,UAAU,OAAO;OACzB,IAAI,SAAS,QAAQ,KAAK,WAAW,KAAK,MAAM,GAAG,MAAM,CAAC,OAAO,SAAS,GAAG;OAC7E,OAAO,SAAS,cAAc,SAAS;EAE5C,IAAI,SAAS,UAAU,cAAc,KAAK,GAAG;GAC3C,MAAM,UAAU,MAAM,OAAO;GAC7B,UAAU,WAAW;IACnB,MAAM;IACN,GAAI,MAAM,QAAQ,SAAY,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,IAAI,MAAM,IAAI;GACzE;GAEA,IAAI,WAAW,SAAS,OAAO,KAAK,EAAE,WAAW,MAAM,GAAG,IAAI,OAAO;EACvE;EAEA,IAAI,CAAC,MAAM;GACT,IAAI,CAAC,WAAW,SAAS,WAAW,cAAc,KAAK,GAAG;IACxD,MAAM,QAAQ,MAAM,SAAS;IAC5B,CAAC,cAAc,WAAW,CAAC,EAAC,CAAE,KAAK,GAAG;GACzC;GACA;EACF;EACA,SAAS,OAAO,MAAM,YAAY,KAAK;EACvC,IAAI,SAAS,WAAW,cAAc,KAAK,KAAK,CAAC,MAAM,SAAS,GAAG,GAAG;GACpE,OAAO,MAAM,QAAQ,SAAS,CAAC,KAAK,GAAG;GACvC,KAAK,MAAM,SAAS,MAAM,WAAW,CAAC,GAAG,aAAa,KAAK,GAAG,IAAI,GAAG,OAAO;EAC9E;EACA,IAAI,SAAS,YAAY,cAAc,KAAK,GAAG;GAC7C,MAAM,OAAO,GAAG,IAAI,GAAG,MAAM;GAC7B,MAAM,UAAU,MAAM,SAAS,GAAG;GAClC,YAAY,OAAO;IACjB,UAAU,MAAM;IAChB,GAAG,MAAM,KAAM,SAAS,IAAI;IAC5B,OAAO;GACT;GACA,IAAI,CAAC,SAAS,OAAO,aAAa,CAAC,KAAK,IAAI;GAC5C,KAAK,MAAM,SAAS,MAAM,WAAW,CAAC,GAAG,aAAa,KAAK,GAAG,KAAK,GAAG,OAAO;EAC/E;CACF;CAEA,MAAM,OAAa;EACjB;EACA,OAAO;GAAE,GAAG;GAAU,QAAQ,KAAK,KAAK,UAAU;EAAE;EACpD,KAAK,WAAW,IAAI,GAAG;EACvB,SAAS,GAAG,QAAQ;EACpB,OAAO,KAAK,KAAK,SAAS;CAC5B;CACA,MAAM,aAAa,OAAO,KAAK,MAAM;CACrC,IAAI,WAAW,QAAQ,KAAK,cAAc;CAC1C,IAAI,OAAO,KAAK,aAAa,CAAC,CAAC,QAAQ,KAAK,gBAAgB;CAC5D,IAAI,OAAO,OAAO,QAAQ,KAAK,aAAa,OAAO;CACnD,IAAI,OAAO,QAAQ,QAAQ,KAAK,eAAe,OAAO;CACtD,IAAI,OAAO,KAAK,QAAQ,KAAK,iBAAiB,OAAO;CACrD,IAAI,aAAa,QAAQ,KAAK,eAAe;CAC7C,IAAI,OAAO,KAAK,WAAW,CAAC,CAAC,QAAQ,KAAK,cAAc;CACxD,IAAI,OAAO,KAAK,SAAS,CAAC,CAAC,QAAQ,KAAK,YAAY;CACpD,IAAI,IAAI,SAAS,4BAAoB;EACnC,OAAO,IAAI,QAAQ,KAAK;EACxB,KAAK,mBAAmB;CAC1B;CACA,IAAI,QAAQ,gBAAgB,KAAK,iBAAiB;CAClD,IAAI,QAAQ,cAAc,KAAK,eAAe;CAC9C,OAAO;AACT;;;;;;;;;;;AAYA,SAAgB,QACd,WACA,QAAiC,CAAC,GAClC,SACa;CACb,OAAO,EACL,OAAO,SAAS,KAAqB;EACnC,MAAM,OAAO,MAAM,UAAU,KAAK,WAAW,OAAO,OAAO;EAG3D,IAAI,IAAI,SAAS,IAAI,QAAQ,KAAK,gBAAgB,KAAK;EAGvD,IAAI,iBAAiB,GAAG,GAAG;GACzB,IAAI,UAAU,aAAa,MAAM;GACjC,IAAI,KAAK,MAAM,SAAS,UAAU,GAAG;GACrC;EACF;EACA,IAAI,KAAK,MAAM,QAAQ,GAAG,CAAC,CAAC,WAAW,MAAM,GAAG,GAAG,SAAS,UAAU,GAAG;CAC3E,EACF;AACF;;;;;;;;;;AAoBA,SAAgB,SAAS,KAAa,UAA2B,CAAC,GAAgB;CAChF,OAAO,EACL,OAAO,SAAS,KAAqB;EACnC,IAAI,QAAQ,oBAAoB,IAAI,SAAS,IAAI,QAAQ,KAAK,gBAAgB;EAC9E,MAAM,IAAI,SAAS,KAAK;EACxB,IAAI,IAAI,SAAS,GAAG,KAAK,iBAAiB,GAAG,GAAG;GAC9C,IAAI,UAAU,sBAAsB,GAAG;GACvC,IAAI,KAAK,IAAI,GAAG;GAChB;EACF;EACA,MAAM,SAAU,IAAI,IAA4B,UAAU;EAC1D,IAAI,SAAS,KAAK,WAAW,QAAQ,MAAM,GAAG;CAChD,EACF;AACF;;AAGA,SAAS,UAAU,OAAoC;CACrD,OAAO,OAAO,UAAU,YAAY,eAAe,KAAK,KAAK,IAAI,QAAQ;AAC3E;;AAGA,SAAS,eAAe,SAA6B,MAA8C;CACjG,IAAI,CAAC,WAAW,CAAC,MAAM,OAAO;CAC9B,IAAI;EACF,MAAM,MAAM,IAAI,IAAI,OAAO;EAC3B,OAAO,IAAI,SAAS,OAAO,UAAU,IAAI,WAAW,IAAI,MAAM,IAAI;CACpE,QAAQ;EACN;CACF;AACF;;;;;;AAOA,SAAgB,YACd,SACA,SACA,WAAW,KACH;CACR,MAAM,OAAO,MAAsC,MAAM,QAAQ,CAAC,IAAI,EAAE,KAAK;CAC7E,OACE,UAAU,SAAS,0BAAkB,KACrC,eAAe,IAAI,QAAQ,OAAO,GAAG,IAAI,QAAQ,IAAI,CAAC,KACtD;AAEJ;;AAGA,SAAgB,KAAK,KAAqB,WAAW,KAAK,SAAwC;CAChG,OAAO,SACL,YACE,IAAI,SACJ;EAAE,SAAS,OAAO,KAAK,SAAS;EAAG,MAAM,OAAO,KAAK,MAAM;CAAE,GAC7D,QACF,GACA,OACF;AACF;;;;;;AAOA,SAAgB,SAAS,KAA0B;CACjD,OAAO,EACL,OAAO,SAAS,KAAqB;EACnC,IAAI,iBAAiB,GAAG,GAAG;GACzB,IAAI,UAAU,sBAAsB,GAAG;GACvC,IAAI,KAAK,IAAI,GAAG;GAChB;EACF;EACA,IAAI,SAAS,GAAG;CAClB,EACF;AACF;;AAGA,SAAgB,cAAc,SAAiE;CAC7F,MAAM,SAAS,OAAO,QAAQ,UAAU,EAAE;CAC1C,OAAO,QAAQ,iBAAiB,UAAU,OAAO,SAAS,WAAW;AACvE;AAEA,MAAM,cAAsC;CAC1C,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;AACP;;;;;;AAOA,SAAgB,UAAU,WAAmB,QAAgB,OAA8B;CACzF,MAAM,WAAW,YAAY,YAAY,UAAU,MAAM,iBAAiB;CAC1E,MAAM,MAAM,iBAAiB,SAAS,MAAM,UAAU,MAAM,UAAU;CAEtE,OAAO,QAAQ,WAAW;EAAE;EAAQ,SADpB,UAAU,OAAO,QAAQ,IAAI,aAAa,eAAe,WAAW;CACxC,GAAG,EAAE,OAAO,CAAC;AAC3D;;;;;;;;;;ACnWA,MAAM,cAAc,UAClB,MAAM,QAAQ,MAAM,OAAO,CAAC,CAAC,QAAQ,MAAM,MAAM,CAAC,CAAC,QAAQ,MAAM,MAAM,CAAC,CAAC,QAAQ,MAAM,QAAQ;AAEjG,MAAa,mBACV,WACA,EAAE,MAAM,WACP;;;;;aAKS,WAAW,KAAK,EAAE;MACzB,KAAK;;;MAGL,KAAK;;;;;;;;AASX,SAAgB,SAAS,MAAY,KAAK,OAAe;CAIvD,OAAO,sBAAsB,GAAG,4BAHnB,KAAK,UAAU,OAAO,IAAI,MAAO,OAAO,MAAM,WAAW,EAAE,SAAS,IAAI,CAAE,CAAC,CACrF,QAAQ,MAAM,SAAS,CAAC,CACxB,QAAQ,OAAO,KAC6C,EAAE,2BAA0B,GAAG;AAChG;;AAGA,SAAgB,QAAQ,OAAuB;CAC7C,OAAO,+BAA+B,MAAM,QAAQ,QAAQ,EAAE,EAAE;AAClE;;AASA,SAAgB,aAAa,WAAmB,OAAuB;CACrE,MAAM,WAAW,KAAK,MACpB,aAAa,KAAK,WAAW,SAAS,eAAe,GAAG,MAAM,CAChE;CACA,MAAM,QAAQ,SAAS;CACvB,IAAI,CAAC,OACH,MAAM,IAAI,MACR,oBAAoB,MAAM,cAAc,UAAU,wFACpD;CAEF,MAAM,sBAAM,IAAI,IAAY;CAC5B,MAAM,0BAAU,IAAI,IAAY;CAChC,MAAM,QAAQ,KAAa,SAAkB;EAC3C,MAAM,IAAI,SAAS;EACnB,IAAI,CAAC,GAAG;EACR,KAAK,MAAM,KAAK,EAAE,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC;EACtC,IAAI,CAAC,MAAM,QAAQ,IAAI,EAAE,IAAI;EAC7B,KAAK,MAAM,KAAK,EAAE,WAAW,CAAC,GAAG,KAAK,GAAG,KAAK;CAChD;CACA,KAAK,OAAO,IAAI;CAChB,OAAO;EACL,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,MAAM,iCAAiC,EAAE,KAAK;EAC/D,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,KAAK,MAAM,oCAAoC,EAAE,KAAK;EACtE,+BAA+B,MAAM,KAAK;CAC5C,CAAC,CAAC,KAAK,QAAQ;AACjB;;;;;;;;;;;;;;;AChBA,MAAMA,QAAM,OAAO,IAAI,gBAAgB;AAgBvC,MAAM,cAAc;;;;;;;AAQpB,SAAS,eAAe,MAAyB,OAAyB;CACxE,MAAM,OAAO,KAAK,aAAa,IAAI,YAAY,cAAc,QAAQ,KAAK,OAAO,MAAM,KAAK,CAAC;CAC7F,IAAI,CAAC,MAAM,OAAO,CAAC;CACnB,MAAM,QAAkB,CAAC;CACzB,MAAM,uBAAO,IAAI,IAAoB;CACrC,MAAM,QAAQ,QAAwB;EACpC,IAAI,KAAK,IAAI,GAAG,GAAG;EACnB,KAAK,IAAI,GAAG;EACZ,IAAI,YAAY,KAAK,IAAI,GAAG,GAAG;GAC7B,MAAM,QAAQ,IAAI,KAAK,sBAAsB,IAAI,GAAG,QAAQ,MAAM,QAAQ,EAAE,KAAK;GACjF,MAAM,KAAK,gCAAgC,IAAI,IAAI,GAAG,MAAM,IAAI;GAChE;EACF;EACA,KAAK,MAAM,YAAY,IAAI,iBAAiB,KAAK,QAAQ;CAC3D;CACA,KAAK,IAAI;CACT,OAAO;AACT;AAEA,SAAS,aAAuC;CAC9C,OACG,WAAkE,uBAAuB;AAE9F;;;;;;;AAQA,SAAS,YAAY,MAAsB;CACzC,OAAO,QAAQ,QAAQ,IAAI,iBAAiB,QAAQ,IAAI,GAAG,IAAI;AACjE;AAEA,SAAS,WAAW,KAAqB,OAAqB;CAC5D,MAAM,UAAU,IAAI,UAAU,MAAM;CACpC,MAAM,QAAQ,MAAM,QAAQ,OAAO,IAAI,QAAQ,KAAK,GAAG,IAAI,OAAO,WAAW,EAAE,EAAC,CAC7E,MAAM,GAAG,CAAC,CACV,KAAK,MAAM,EAAE,KAAK,CAAC,CAAC,CACpB,OAAO,OAAO;CACjB,IAAI,KAAK,SAAS,GAAG,KAAK,KAAK,MAAM,MAAM,EAAE,YAAY,MAAM,MAAM,YAAY,CAAC,GAAG;CACrF,IAAI,UAAU,QAAQ,CAAC,GAAG,MAAM,KAAK,CAAC,CAAC,KAAK,IAAI,CAAC;AACnD;AAEA,SAAS,WAAW,MAAsB;CACxC,IAAI;EACF,OAAO,mBAAmB,IAAI;CAChC,QAAQ;EACN,OAAO;CACT;AACF;AAIA,MAAa,iBAAiB,cAAqC;CACjE,MAAM;CACN,UAAU;EACR,OAAO;EACP,WAAW;EACX,OAAO;CACT;CACA,QAAQ,WAAW;EACjB,MAAM,YAAY,YAAY,OAAO,SAAS;EAC9C,MAAM,WAAW,OAAO,YAAY,gBAAgB,OAAO,KAAK;EAChE,IAAI,gBAAqC;EAGzC,IAAI,UAAiC;EAErC,MAAM,YAA4B,KAAK,KAAK,SAAS;GACnD,IAAI,SAAS,mBAAmB,KAAK,OAAO;GAK5C,WAAW,KAAK,WAAW;GAG3B,IAAI,IAAI,QAAQ,iBAAiB,QAAQ,WAAW,KAAK,cAAc;GACvE,IAAI,IAAI,QAAQ,iBAAiB,QAAQ,OAAO,KAAK;GAErD,MAAM,UAAU,QAAQ;GACxB,IAAI,IAAI,WAAW,SAAS,WAAW,IAAI,QAAQ,yBAAyB,SAAS;IACnF,IAAI,aAAa;IACjB,IAAI,UAAU,sBAAsB,WAAW,GAAG,CAAC;IACnD,IAAI,IAAI;IACR;GACF;GAEA,IAAI,IAAI,WAAW,SAAS,IAAI,WAAW,WAAW,IAAI,WAAW,UAAU;IAG7E,MAAM,YAAY,IAAI;IACtB,IAAI,YAAY,SAAgC,QAAgB,GAAG,MAAiB;KAClF,OAAQ,UAAkD,KACxD,MACA,WAAW,MAAM,MAAM,QACvB,GAAG,IACL;IACF;GACF;GACA,KAAK;EACP;EAEA,OAAO;GACL,YAAY,EAAE,QAAQ;IACpB,MAAM,OAAO,WAAW;IACxB,IAAI;IAEJ,IAAI,MACF,aAAa,QAAQ,OAAO,KAAK;SAC5B;KACL,MAAM,OAAO,aAAa,WAAW,OAAO,KAAK;KACjD,aAAa;KACb,MAAM,OAAO,WAAW,MAAM,CAAC,CAC5B,OAAO,aAAa,KAAK,WAAW,SAAS,eAAe,CAAC,CAAC,CAAC,CAC/D,OAAO,KAAK,CAAC,CACb,MAAM,GAAG,EAAE;KACd,gBAAgB;KAGhB,IAAI,WAAW,KAAK,WAAW,cAAc,CAAC,GAC5C,MAAM,IAAI,MACR,6BAA6B,UAAU,8GACzC;KAIF,MAAM,SAAS,IAAI,IAAI,YAAY,SAAS,CAAC,CAAC,QAAQ,SAAS,KAAK,WAAW,GAAG,CAAC,CAAC;KACpF,MAAM,kBAAkC,KAAK,KAAK,SAAS;MACzD,MAAM,QAAQ,YAAY,IAAI,OAAO,IAAG,CAAE,MAAM,GAAG,CAAC,CAAC,EAAG,CAAC,CACtD,MAAM,GAAG,CAAC,CACV,KAAK,OAAO;MACf,IAAI,SAAS,OAAO,IAAI,KAAK,GAAG;OAC9B,IAAI,aAAa;OACjB,IAAI,IAAI;OACR;MACF;MACA,KAAK;KACP;KACA,KAAK,IAAI,cAAuB;KAChC,KAAK,YAAY,KAAK,SAAS;IACjC;IACA,IAAI,OAAO,YAAY,QAAW;KAChC,MAAM,IAAI,OAAO;KACjB,UAAU,OAAO,MAAM,aAAa,UAAU;IAChD;IAEA,MAAM,MAAM,OAAO;IACnB,IAAI;IACJ,MAAM,aAAa,YAAgC;KACjD,IAAI,MAGF,QAAO,MADW,KAAK,cAAc,IAAI,IAAK,MAAM,QAAQ,QAAQ,EAAE,GAAG,EAC/D,CAAC;KAEb,MAAM,MAAM,cAAc,YAAY,IAAK,UAAU,iBAAiB,CAAC,CAAC,CAAC;KAEzE,eAAe;;MAA0B;CAAI,CAAC,MAAM,QAAQ,IAAI,OAAoB;KACpF,OAAO;IACT;IACA,MAAM,SAAS,OAAO,SAA0C;KAC9D,IAAI,CAAC,KAAK,OAAO;KACjB,IAAI;MACF,OAAO,OACL,MAAM,WAAW,EAAC,CAClB,IAAI;KACR,SAAS,KAAK;MACZ,IAAI,QAAQ,eAAe,OAAO,KAAK,iBAAiB,GAAG;MAC3D,MAAI,MAAM,KAAK,kBAAkB,KAAK,UAAU,8BAA8B;MAC9E,OAAO;KACT;IACF;IAEA,UAAU;KACR,eAAe,QAAQ;KACvB,OAAO,OAAO;KACd,WAAW,OAAO;KAClB,MAAM,WAAW,MAAM,KAAK;MAC1B,MAAM,WAAW,MAAM,OAAO,IAAI;MAClC,MAAM,SAAS,QAAQ,YAAY,MAAM,eAAe,MAAM,IAAI,KAAK,IAAI,CAAC;MAC5E,MAAM,OAAO,SAAS;OACpB,MAAM;QAAC,OAAO,QAAQ;QAAI,KAAK,IAAI;QAAG,GAAG;QAAQ,GAAI,UAAU,QAAQ,CAAC;OAAE,CAAC,CACxE,OAAO,OAAO,CAAC,CACf,KAAK,QAAQ;OAChB,MAAM,UAAU,QAAQ,SAAS,IAAI;OACrC;MACF,CAAC;MACD,OAAO,OAAO,KAAK,mBAAmB,WAAW,IAAI,GAAG,GAAG,IAAI,IAAI;KACrE;IACF;GACF;GAEA,aAAa;IACX,OAAO,CAAC;KAAE,SAAS;KAAmB,OAAO;IAAuB,CAAC;GACvE;EACF;CACF;AACF,CAAC;;;;;;;;;;;;ACpRD,MAAM,MAAM,OAAO,IAAI,SAAS;;AAehC,SAAS,SAAS,SAAiD;CACjE,IAAI,CAAC,MAAM,QAAQ,OAAO,GAAG,OAAO;CACpC,MAAM,SAAiC,CAAC;CACxC,KAAK,MAAM,KAAK,SAAwD;EACtE,MAAM,QAAQ,EAAE,SAAS;EACzB,OAAO,WAAW,EAAE,WAAW;CACjC;CACA,OAAO;AACT;AAEA,SAAS,eAAe,KAAmB;CACzC,OAAO,KAAK,SAAS,iBAAiB;AACxC;;;;;;AAOA,SAAS,mBAAmB,KAAU,KAAsB,QAAgC;CAC1F,MAAM,OAAO,OAAO,IAAI,QAAQ,iCAAiC,EAAE,CAAC,CACjE,MAAM,GAAG,CAAC,CACV,KAAK,MAAM,EAAE,KAAK,CAAC,CAAC,CACpB,OAAO,OAAO;CACjB,MAAM,QAAQ,KAAK,SACf,OAAO,YACL,OAAO,QAAQ,MAAM,CAAC,CAAC,QAAQ,CAAC,WAC9B,KAAK,MAAM,MAAM,UAAU,KAAK,MAAM,WAAW,GAAG,EAAE,EAAE,CAAC,CAC3D,CACF,IACA;CACJ,IAAI,UAAU,gBAAgB,MAAM;CACpC,IAAI,OAAO,KAAK,KAAK,CAAC,CAAC,WAAW,GAAG;EACnC,IAAI,UAAU,wBAAwB,MAAM;EAC5C,IAAI,OAAO,GAAG,CAAC,CAAC,IAAI;EACpB;CACF;CACA,IAAI,OAAO,GAAG,CAAC,CAAC,KAAK;EAAE,SAAS;EAA+B,QAAQ;CAAM,CAAC;AAChF;;;;;;;;;;;;;;AAeA,MAAa,gBAAmD,KAAK,SAAS;CAC5E,IAAI,CAAC,eAAe,IAAI,GAAG,GAAG,OAAO,KAAK;CAC1C,IAAI,UAAU,gBAAgB,MAAM;CACpC,IAAI,UAAU,wBAAwB,MAAM;CAC5C,IAAI,UAAU;AAChB;;;;;;;;;;;;AAaA,SAAgB,oBAAoB,WAAyB,aAAa,GAAiB;CACzF,IAAI,SAAS;CACb,QAAQ,KAAK,KAAK,KAAK,SAAS;EAC9B,IAAI,IAAI,aAAa,OAAO,SAAS,KAAK,KAAK,KAAK,IAAI;EAExD,MAAM,SAAS,eAAe,iBAAiB,IAAI,WAAW,MAAM,SAAS,IAAI,OAAO,IAAI;EAC5F,IAAI,UAAU,eAAe,GAAG,GAAG,OAAO,mBAAmB,KAAK,KAAK,MAAM;EAC7E,IAAI,UAAU,KAAK,UAAU,iBAAiB,QAAQ;GACpD,MAAM,UAAU,IAAI;GACpB,IAAI,SAAS;IACX,MAAM,MAAM,IAAI,QAAQ;IACxB,QAAQ,KAAK,cAAc,MAAM,GAAG,MAAM,OAAO,IAAI;IAErD,OAAO,QACJ,KAAK,CAAC,CACN,WAAW;KACV,IAAI,OAAO,GAAG,CAAC,CAAC,UAAU,YAAY,YAAY,SAAS,IAAI,OAAO,CAAC;KACvE,IAAI,IAAI;IACV,CAAC,CAAC,CACD,OAAO,YAAY,SAAS,SAAS,KAAK,KAAK,IAAI,CAAC;GACzD;GACA,IAAI,CAAC,QAAQ;IACX,IAAI,KACF,4HACF;IACA,SAAS;GACX;GACA,OAAO,SAAS,KAAK,KAAK,KAAK,IAAI;EACrC;EAEA,MAAM,YAAY,kBAAkB,GAAG,CAAC,EAAE;EAC1C,IAAI,aAAa,KAAK,WAAW,cAAc,IAAI,OAAO,GAAG;GAC3D,MAAM,SAAS,eAAe,gBAAgB,IAAI,SAAS;GAC3D,IAAI,UAAU,KAAK,IAAI,MAAM,KAAK,mCAAmC;GACrE,MAAM,MAAM,IAAI,eAAe,KAAK,KAAc,MAAM,GAAG;GAC3D,OAAO,QAAQ,QAAQ,UAAU,WAAW,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,cAC7E,SAAS,WAAW,KAAK,KAAK,IAAI,CACpC;EACF;EACA,OAAO,SAAS,KAAK,KAAK,KAAK,IAAI;CACrC;AACF;;;;;;AAaA,SAAgB,kBAAmC;CACjD,QAAQ,KAAK,KAAK,SAAS;EACzB,MAAM,YAAY,kBAAkB,GAAG,CAAC,EAAE;EAC1C,MAAM,SAAS,KAAK,UAAU;EAC9B,IACE,CAAC,aACA,WAAW,SAAS,WAAW,UAChC,CAAC,cAAc,IAAI,WAAW,CAAC,CAAC,GAEhC,OAAO,KAAK;EAEd,MAAM,MAAM,IAAI,eAAe,KAAK,KAAc,MAAM,GAAG;EAC3D,OAAO,QAAQ,QAAQ,UAAU,WAAW,GAAG,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,IAAI;CAC5E;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forinda/kickjs-inertia",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Inertia.js adapter for KickJS — server-driven pages rendered by React, Vue or Svelte",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"kickjs",
|
|
@@ -24,21 +24,21 @@
|
|
|
24
24
|
"dist"
|
|
25
25
|
],
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"@fastify/middie": "^9.3.
|
|
27
|
+
"@fastify/middie": "^9.3.5",
|
|
28
28
|
"@swc/core": "^1.16.13",
|
|
29
|
-
"@types/node": "^26.6.
|
|
29
|
+
"@types/node": "^26.6.5",
|
|
30
30
|
"@types/supertest": "^7.2.1",
|
|
31
|
-
"express": "^5.
|
|
31
|
+
"express": "^5.3.0",
|
|
32
32
|
"fastify": "^5.12.5",
|
|
33
|
-
"h3": "
|
|
33
|
+
"h3": "2.0.2",
|
|
34
34
|
"reflect-metadata": "^0.2.2",
|
|
35
35
|
"supertest": "^7.3.1",
|
|
36
36
|
"typescript": "^7.0.2",
|
|
37
37
|
"vitest": "^5.0.3",
|
|
38
|
-
"@forinda/kickjs": "
|
|
38
|
+
"@forinda/kickjs": "10.1.0"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@forinda/kickjs": ">=
|
|
41
|
+
"@forinda/kickjs": ">=10.0.0"
|
|
42
42
|
},
|
|
43
43
|
"publishConfig": {
|
|
44
44
|
"access": "public"
|