@forinda/kickjs-inertia 0.1.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 +6 -2
- package/dist/index.d.mts +116 -37
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +149 -10
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -75,14 +75,18 @@ 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,
|
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) => unknown;
|
|
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`,
|
|
@@ -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,6 +109,8 @@ 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";
|
|
112
|
+
/** Set by `redirect(url, { preserveFragment: true })` for the next page. */
|
|
113
|
+
const FRAGMENT_KEY = "__inertia_fragment";
|
|
73
114
|
/**
|
|
74
115
|
* Where the adapter's middleware leaves its runtime on each request, so every
|
|
75
116
|
* helper uses the adapter of the app that is handling that request — two apps
|
|
@@ -130,6 +171,8 @@ async function buildPage(ctx, component, props, options = {}) {
|
|
|
130
171
|
const only = partial ? list(header(ctx, "x-inertia-partial-data")) : [];
|
|
131
172
|
const except = partial ? list(header(ctx, "x-inertia-partial-except")) : [];
|
|
132
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";
|
|
133
176
|
const named = (key) => only.some((o) => o === key || o.startsWith(`${key}.`));
|
|
134
177
|
const resolved = {};
|
|
135
178
|
const deferredProps = {};
|
|
@@ -138,12 +181,23 @@ async function buildPage(ctx, component, props, options = {}) {
|
|
|
138
181
|
prepend: [],
|
|
139
182
|
deep: []
|
|
140
183
|
};
|
|
184
|
+
const matchPropsOn = [];
|
|
185
|
+
const scrollProps = {};
|
|
186
|
+
const onceProps = {};
|
|
141
187
|
for (const [key, value] of Object.entries(all)) {
|
|
142
188
|
const kind = isInertiaProp(value) ? propKind(value) : void 0;
|
|
143
189
|
let send;
|
|
144
190
|
if (kind === "always") send = true;
|
|
145
191
|
else if (partial) send = (only.length === 0 || named(key)) && !except.includes(key);
|
|
146
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
|
+
}
|
|
147
201
|
if (!send) {
|
|
148
202
|
if (!partial && kind === "defer" && isInertiaProp(value)) {
|
|
149
203
|
const group = value.group ?? "default";
|
|
@@ -152,7 +206,21 @@ async function buildPage(ctx, component, props, options = {}) {
|
|
|
152
206
|
continue;
|
|
153
207
|
}
|
|
154
208
|
resolved[key] = await resolveProp(value);
|
|
155
|
-
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
|
+
}
|
|
156
224
|
}
|
|
157
225
|
const page = {
|
|
158
226
|
component,
|
|
@@ -170,6 +238,13 @@ async function buildPage(ctx, component, props, options = {}) {
|
|
|
170
238
|
if (merges.append.length) page.mergeProps = merges.append;
|
|
171
239
|
if (merges.prepend.length) page.prependProps = merges.prepend;
|
|
172
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
|
+
}
|
|
173
248
|
if (options.encryptHistory) page.encryptHistory = true;
|
|
174
249
|
if (options.clearHistory) page.clearHistory = true;
|
|
175
250
|
return page;
|
|
@@ -200,10 +275,20 @@ function inertia(component, props = {}, options) {
|
|
|
200
275
|
* Redirect after a form submit. Uses 303 for anything but GET, so the browser
|
|
201
276
|
* follows with a GET, and saves the session first so flashed errors and
|
|
202
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.
|
|
203
282
|
*/
|
|
204
|
-
function redirect(url) {
|
|
283
|
+
function redirect(url, options = {}) {
|
|
205
284
|
return { async [RESPOND](ctx) {
|
|
285
|
+
if (options.preserveFragment && ctx.session) ctx.session.data[FRAGMENT_KEY] = true;
|
|
206
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
|
+
}
|
|
207
292
|
const method = ctx.req.method ?? "GET";
|
|
208
293
|
ctx.redirect(url, method === "GET" ? 302 : 303);
|
|
209
294
|
} };
|
|
@@ -232,11 +317,11 @@ function previousUrl(session, headers, fallback = "/") {
|
|
|
232
317
|
return localPath(session?.data["__inertia_previous"]) ?? sameOriginPath(one(headers.referer), one(headers.host)) ?? fallback;
|
|
233
318
|
}
|
|
234
319
|
/** Redirect back to the page the request came from. */
|
|
235
|
-
function back(ctx, fallback = "/") {
|
|
320
|
+
function back(ctx, fallback = "/", options) {
|
|
236
321
|
return redirect(previousUrl(ctx.session, {
|
|
237
322
|
referer: header(ctx, "referer"),
|
|
238
323
|
host: header(ctx, "host")
|
|
239
|
-
}, fallback));
|
|
324
|
+
}, fallback), options);
|
|
240
325
|
}
|
|
241
326
|
/**
|
|
242
327
|
* A full browser visit, outside Inertia — another site, or a page that is not
|
|
@@ -380,6 +465,15 @@ function devStylesheets(vite, entry) {
|
|
|
380
465
|
function viteServer() {
|
|
381
466
|
return globalThis.__kickjs_viteServer ?? null;
|
|
382
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
|
+
}
|
|
383
477
|
function appendVary(res, field) {
|
|
384
478
|
const current = res.getHeader("Vary");
|
|
385
479
|
const list = (Array.isArray(current) ? current.join(",") : String(current ?? "")).split(",").map((v) => v.trim()).filter(Boolean);
|
|
@@ -401,13 +495,14 @@ const InertiaAdapter = defineAdapter({
|
|
|
401
495
|
title: "KickJS"
|
|
402
496
|
},
|
|
403
497
|
build: (config) => {
|
|
404
|
-
const clientDir =
|
|
498
|
+
const clientDir = fromAppRoot(config.clientDir);
|
|
405
499
|
const template = config.template ?? defaultTemplate(config.title);
|
|
406
500
|
let version = () => null;
|
|
407
501
|
let runtime = null;
|
|
408
502
|
const protocol = (req, res, next) => {
|
|
409
503
|
if (runtime) bindInertiaRuntime(req, runtime);
|
|
410
504
|
appendVary(res, "X-Inertia");
|
|
505
|
+
if (req.headers.precognition === "true") appendVary(res, "Precognition");
|
|
411
506
|
if (req.headers["x-inertia"] !== "true") return next();
|
|
412
507
|
const current = version();
|
|
413
508
|
if (req.method === "GET" && current && req.headers["x-inertia-version"] !== current) {
|
|
@@ -456,7 +551,7 @@ const InertiaAdapter = defineAdapter({
|
|
|
456
551
|
let prodRender;
|
|
457
552
|
const loadRender = async () => {
|
|
458
553
|
if (vite) return (await vite.ssrLoadModule(`/${ssr.entry.replace(/^\/+/, "")}`)).default;
|
|
459
|
-
const url = pathToFileURL(
|
|
554
|
+
const url = pathToFileURL(fromAppRoot(ssr.bundle ?? "dist/ssr/ssr.js")).href;
|
|
460
555
|
prodRender ??= import(
|
|
461
556
|
/* @vite-ignore */
|
|
462
557
|
url
|
|
@@ -525,9 +620,52 @@ function toErrors(details) {
|
|
|
525
620
|
}
|
|
526
621
|
return errors;
|
|
527
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
|
+
};
|
|
528
664
|
/**
|
|
529
665
|
* Pass to `bootstrap({ onError: inertiaErrorHandler() })`.
|
|
530
666
|
*
|
|
667
|
+
* - A validation 422 on a Precognition request is answered with the errors
|
|
668
|
+
* of the fields it asked about (see `precognitive`).
|
|
531
669
|
* - A validation 422 on an Inertia visit goes back to the form with the
|
|
532
670
|
* errors (needs the `session()` middleware to carry them).
|
|
533
671
|
* - Any other error on a page request renders the adapter's `errorPage`,
|
|
@@ -539,6 +677,7 @@ function inertiaErrorHandler(fallback = errorHandler()) {
|
|
|
539
677
|
return (err, req, res, next) => {
|
|
540
678
|
if (res.headersSent) return fallback(err, req, res, next);
|
|
541
679
|
const errors = err instanceof HttpException && err.status === 422 ? toErrors(err.details) : null;
|
|
680
|
+
if (errors && isPrecognitive(req)) return precognitionResult(req, res, errors);
|
|
542
681
|
if (errors && req?.headers?.["x-inertia"] === "true") {
|
|
543
682
|
const session = req.session;
|
|
544
683
|
if (session) {
|
|
@@ -581,5 +720,5 @@ function inertiaNotFound() {
|
|
|
581
720
|
}
|
|
582
721
|
|
|
583
722
|
//#endregion
|
|
584
|
-
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 };
|
|
585
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/**\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\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(ctx).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\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\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 // 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 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 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 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\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 // 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":"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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",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"supertest": "^7.3.1",
|
|
36
36
|
"typescript": "^7.0.2",
|
|
37
37
|
"vitest": "^5.0.3",
|
|
38
|
-
"@forinda/kickjs": "10.
|
|
38
|
+
"@forinda/kickjs": "10.1.0"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"@forinda/kickjs": ">=10.0.0"
|