@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 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, the asset
85
- version check (409), 302 → 303 after PUT / PATCH / DELETE.
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/props.d.ts
235
+ //#region src/errors.d.ts
236
+ type ErrorHandler = (err: any, req: any, res: RuntimeResponse, next: (err?: unknown) => void) => unknown;
139
237
  /**
140
- * Prop wrappers that change when a prop is sent. A plain value or a function
141
- * is sent on every full visit and on a partial reload that asks for it; a
142
- * function is only called when the prop is actually sent.
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
- * @module @forinda/kickjs-inertia/props
145
- */
146
- type Resolver<T> = T | (() => T | Promise<T>);
147
- declare const PROP: unique symbol;
148
- type PropKind = 'optional' | 'always' | 'defer' | 'merge';
149
- interface InertiaProp<T = unknown> {
150
- readonly [PROP]: PropKind;
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 function merge<T>(value: Resolver<T>, mode?: 'append' | 'prepend' | 'deep'): InertiaProp<T>;
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
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/inertia.ts","../src/html.ts","../src/adapter.ts","../src/props.ts","../src/errors.ts"],"mappings":";;;;UAgBiB;EACf;EACA,OAAO;IAA4B,QAAQ;;EAC3C;EACA;EACA,OAAO;EACP;EACA,gBAAgB;EAChB;EACA;EACA;EACA;EACA;;UAGe;;EAEf;;EAEA;;EAEA;;wBAyDc,iBAAiB,KAAK;;wBActB,MAAM,KAAK,gBAAgB,MAAM;wBAM3B,UACpB,KAAK,gBACL,mBACA,OAAO,yBACP,UAAS,cACR,QAAQ;;;;;;;;;;;wBAiEK,QACd,mBACA,QAAO,yBACP,UAAU,cACT;;;;;;wBAwBa,SAAS,cAAc;;wBA6CvB,KAAK,KAAK,gBAAgB,oBAAiB;;;;;;wBAe3C,SAAS,cAAc;;wBAcvB,cAAc,SAAS;;;;;;wBAsBvB,UAAU,mBAAmB,gBAAgB,kBAAkB;;;UC1S9D;;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;;qBA0EW,0CAAc,eAAA;;;;;;;;;;KCrItB,SAAS,KAAK,WAAW,IAAI,QAAQ;cAEpC;KAEM;UAEK,YAAY;YACjB,OAAO;WACR,OAAO,SAAS;;WAEhB;;WAEA;;;wBAQK,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,uCACC,YAAY;;;KChCV,gBACH,UACA,UACA,KAAK,iBACL,OAAO;;;;;;;;;;wBA4BO,oBAAoB,WAAU,eAAgC;KA0CzE,mBACH,UACA,KAAK,iBACL,OAAO,kCACG;;;;;;wBAOI,mBAAmB"}
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.1.0
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, { mode });
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)) merges[value.mode ?? "append"].push(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 = resolve(config.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(resolve(ssr.bundle ?? "dist/ssr/ssr.js")).href;
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
@@ -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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;')\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, '&quot;')}\"` : ''\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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;')\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, '&quot;')}\"` : ''\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.1.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.0.0"
38
+ "@forinda/kickjs": "10.1.0"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "@forinda/kickjs": ">=10.0.0"