@forinda/kickjs-inertia 0.0.0-stage → 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Felix Orinda
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,96 @@
1
- # Temporary Holding Version
1
+ # @forinda/kickjs-inertia
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [Inertia.js](https://inertiajs.com/) v3 adapter for [KickJS](https://kickjs.app/) —
4
+ server-driven pages rendered by React, Vue or Svelte, with server-side
5
+ rendering, on Express, Fastify or h3.
6
+
7
+ ```bash
8
+ pnpm add @forinda/kickjs-inertia @inertiajs/react react react-dom
9
+ ```
10
+
11
+ The fastest start is the template, which wires everything below:
12
+
13
+ ```bash
14
+ kick new my-app --template inertia
15
+ ```
16
+
17
+ ## Wiring
18
+
19
+ ```ts
20
+ // src/index.ts
21
+ import { bootstrap, session } from '@forinda/kickjs'
22
+ import { InertiaAdapter, inertiaErrorHandler, inertiaNotFound } from '@forinda/kickjs-inertia'
23
+
24
+ export const app = await bootstrap({
25
+ modules,
26
+ middlewares: [session({ secret: process.env.SESSION_SECRET! })],
27
+ adapters: [
28
+ InertiaAdapter({
29
+ entry: 'src/client/main.tsx', // the file that calls createInertiaApp
30
+ ssr: { entry: 'src/client/ssr.tsx' }, // optional
31
+ errorPage: 'Error', // optional — unknown URLs and thrown errors
32
+ share: (ctx) => ({ user: ctx.user }), // props every page gets
33
+ }),
34
+ ],
35
+ onError: inertiaErrorHandler(), // validation errors → back to the form
36
+ onNotFound: inertiaNotFound(), // unknown URLs → the error page
37
+ notFoundFallthrough: false, // same answers under `kick dev` as in production
38
+ })
39
+ ```
40
+
41
+ Pages mount at the site root, next to your `/api/v1` modules:
42
+
43
+ ```ts
44
+ routes() {
45
+ return { path: '/', prefix: false, version: false, controller: PagesController }
46
+ }
47
+ ```
48
+
49
+ ## Pages
50
+
51
+ ```ts
52
+ import { Controller, Get, Post, type RequestContext } from '@forinda/kickjs'
53
+ import { defer, flash, inertia, redirect } from '@forinda/kickjs-inertia'
54
+
55
+ @Controller()
56
+ export class PagesController {
57
+ @Get('/posts')
58
+ index() {
59
+ return inertia('Posts/Index', {
60
+ posts: () => this.posts.list(), // called only when sent
61
+ stats: defer(() => this.posts.stats()), // loaded after the page renders
62
+ })
63
+ }
64
+
65
+ @Post('/posts', { body: createPostSchema }) // a failed check → props.errors
66
+ async create(ctx: RequestContext) {
67
+ await this.posts.create(ctx.body)
68
+ flash(ctx, { success: 'Post created' })
69
+ return redirect('/posts') // 303
70
+ }
71
+ }
72
+ ```
73
+
74
+ | Helper | What it does |
75
+ | --------------------------------------------------------------------- | --------------------------------------------------------------- |
76
+ | `inertia(component, props, { status, encryptHistory, clearHistory })` | Render a page — HTML on a first visit, JSON on an Inertia visit |
77
+ | `optional(fn)` / `always(v)` / `defer(fn, group)` / `merge(v, mode)` | When a prop is sent |
78
+ | `redirect(url)` / `back(ctx)` | 303 after a submit; `back` stays on this site |
79
+ | `location(url)` | A full browser visit (another site) |
80
+ | `flash(ctx, data)` | Data for the next page's `flash` |
81
+
82
+ ## What the adapter handles
83
+
84
+ - The protocol: `X-Inertia` JSON responses, `Vary`, partial reloads, the asset
85
+ version check (409), 302 → 303 after PUT / PATCH / DELETE.
86
+ - Assets: Vite's dev server under `kick dev`; the build manifest, `assets/` and
87
+ `public/` files in production.
88
+ - Server-side rendering through Vite in dev and a built bundle in production,
89
+ falling back to client rendering if a render fails.
90
+ - Validation errors and flash data through the session.
91
+
92
+ Requires `@forinda/kickjs` 9.3 or later.
93
+
94
+ ## License
95
+
96
+ MIT
@@ -0,0 +1,193 @@
1
+
2
+ import { RequestContext, Respondable, RuntimeResponse } from "@forinda/kickjs";
3
+ //#region src/inertia.d.ts
4
+ /** The page object the Inertia client renders — see the protocol docs. */
5
+ interface Page {
6
+ component: string;
7
+ props: Record<string, unknown> & {
8
+ errors: Record<string, unknown>;
9
+ };
10
+ url: string;
11
+ version: string | null;
12
+ flash: Record<string, unknown>;
13
+ sharedProps?: string[];
14
+ deferredProps?: Record<string, string[]>;
15
+ mergeProps?: string[];
16
+ prependProps?: string[];
17
+ deepMergeProps?: string[];
18
+ encryptHistory?: boolean;
19
+ clearHistory?: boolean;
20
+ }
21
+ interface PageOptions {
22
+ /** Encrypt this page's history entry in the browser. */
23
+ encryptHistory?: boolean;
24
+ /** Wipe the browser's Inertia history, e.g. after logout. */
25
+ clearHistory?: boolean;
26
+ /** Response status. Default 200; error pages send theirs (404, 500). */
27
+ status?: number;
28
+ }
29
+ export declare function isInertiaRequest(ctx: RequestContext): boolean;
30
+ /** Flash data for the next page, sent as the page's top-level `flash`. */
31
+ export declare function flash(ctx: RequestContext, data: Record<string, unknown>): void;
32
+ export declare function buildPage(ctx: RequestContext, component: string, props: Record<string, unknown>, options?: PageOptions): Promise<Page>;
33
+ /**
34
+ * Render an Inertia page. Return it from a handler:
35
+ *
36
+ * ```ts
37
+ * @Get('/users')
38
+ * async index() {
39
+ * return inertia('Users/Index', { users: await this.users.list() })
40
+ * }
41
+ * ```
42
+ */
43
+ export declare function inertia(component: string, props?: Record<string, unknown>, options?: PageOptions): Respondable;
44
+ /**
45
+ * Redirect after a form submit. Uses 303 for anything but GET, so the browser
46
+ * follows with a GET, and saves the session first so flashed errors and
47
+ * messages are there when that GET arrives.
48
+ */
49
+ export declare function redirect(url: string): Respondable;
50
+ /** Redirect back to the page the request came from. */
51
+ export declare function back(ctx: RequestContext, fallback?: string): Respondable;
52
+ /**
53
+ * A full browser visit, outside Inertia — another site, or a page that is not
54
+ * an Inertia page. An Inertia request gets 409 + `X-Inertia-Location`, which
55
+ * the client turns into `window.location`.
56
+ */
57
+ export declare function location(url: string): Respondable;
58
+ /** A browser navigation or an Inertia visit — not an API or asset request. */
59
+ export declare function isPageRequest(headers: Record<string, string | string[] | undefined>): boolean;
60
+ /**
61
+ * The configured error page as a response, with `{ status, message }` as its
62
+ * props. A 5xx message is replaced in production — an exception's text is
63
+ * for the log, not the visitor.
64
+ */
65
+ export declare function errorPage(component: string, status: number, error?: unknown): Respondable;
66
+ //#endregion
67
+ //#region src/html.d.ts
68
+ interface HtmlParts {
69
+ /** Script and style tags for the client entry. Put inside `<head>`. */
70
+ head: string;
71
+ /** The page data and the mount element. Put inside `<body>`. */
72
+ body: string;
73
+ page: Page;
74
+ }
75
+ type HtmlTemplate = (parts: HtmlParts) => string;
76
+ export declare const defaultTemplate: (title: string) => HtmlTemplate;
77
+ /**
78
+ * The page as `<script data-page="app" type="application/json">` — where the
79
+ * v3 client reads it — followed by the mount element. `<` and `/` are escaped
80
+ * so a prop containing `</script>` cannot end the tag.
81
+ */
82
+ export declare function pageBody(page: Page, id?: string): string;
83
+ //#endregion
84
+ //#region src/adapter.d.ts
85
+ interface InertiaAdapterOptions {
86
+ /**
87
+ * The client entry, relative to the project root — the file that calls
88
+ * `createInertiaApp`. Served by Vite in dev, looked up in the build
89
+ * manifest in production. Default: `'src/client/main.tsx'`.
90
+ */
91
+ entry: string;
92
+ /** Where `vite build` wrote the client, with `build.manifest: true`. Default: `'dist/client'`. */
93
+ clientDir: string;
94
+ /**
95
+ * Asset version. A client on another version reloads the page on its next
96
+ * visit. Default: a hash of the build manifest in production, none in dev
97
+ * (Vite reloads changed modules itself).
98
+ */
99
+ version?: string | (() => string);
100
+ /** Props every page gets — the current user, app name. */
101
+ share?: (ctx: RequestContext) => Record<string, unknown> | Promise<Record<string, unknown>>;
102
+ /** `<title>` of the default HTML shell. */
103
+ title: string;
104
+ /**
105
+ * Tags added to every page's `<head>` — a favicon, meta tags, fonts. Per-page
106
+ * titles and meta belong in the page, with Inertia's `<Head>` component.
107
+ */
108
+ head?: string;
109
+ /** Replace the HTML shell entirely. */
110
+ template?: HtmlTemplate;
111
+ /**
112
+ * Page component for errors and unknown URLs, e.g. `'Error'`. It receives
113
+ * `{ status, message }`. Rendered for page requests by `inertiaErrorHandler()`
114
+ * (thrown errors) and `inertiaNotFound()` (unknown URLs); API and asset
115
+ * requests keep the usual JSON answers.
116
+ */
117
+ errorPage?: string;
118
+ /**
119
+ * Render pages on the server. `entry` is a module whose default export is
120
+ * `(page) => Promise<{ head: string[]; body: string }>` — what
121
+ * `createInertiaApp({ page, render: renderToString, ... })` returns. Loaded
122
+ * through Vite in dev; in production from `bundle`, the output of
123
+ * `vite build --ssr <entry>` (default `'dist/ssr/ssr.js'`). A render that
124
+ * throws falls back to client rendering for that request.
125
+ */
126
+ ssr?: {
127
+ entry: string;
128
+ bundle?: string;
129
+ };
130
+ }
131
+ /** What an SSR entry's default export returns. */
132
+ interface SsrResult {
133
+ head: string[];
134
+ body: string;
135
+ }
136
+ export declare const InertiaAdapter: import("@forinda/kickjs").AdapterFactory<InertiaAdapterOptions, unknown>;
137
+ //#endregion
138
+ //#region src/props.d.ts
139
+ /**
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.
143
+ *
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.
169
+ */
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) => void;
174
+ /**
175
+ * Pass to `bootstrap({ onError: inertiaErrorHandler() })`.
176
+ *
177
+ * - A validation 422 on an Inertia visit goes back to the form with the
178
+ * errors (needs the `session()` middleware to carry them).
179
+ * - Any other error on a page request renders the adapter's `errorPage`,
180
+ * when one is configured.
181
+ * - Everything else — API calls, assets — goes to `fallback`.
182
+ */
183
+ export declare function inertiaErrorHandler(fallback?: ErrorHandler): ErrorHandler;
184
+ type NotFoundHandler = (req: any, res: RuntimeResponse, next: (err?: unknown) => void) => void;
185
+ /**
186
+ * Pass to `bootstrap({ onNotFound: inertiaNotFound() })`. A browser or Inertia
187
+ * request for an unknown URL gets the adapter's `errorPage` with 404; anything
188
+ * else — API calls, other methods — goes on to the built-in 404 / 405.
189
+ */
190
+ export declare function inertiaNotFound(): NotFoundHandler;
191
+ //#endregion
192
+ export type { HtmlParts, HtmlTemplate, InertiaAdapterOptions, InertiaProp, Page, PageOptions, SsrResult };
193
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +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;;wBAqDc,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;;;UCtS9D;;EAEf;;EAEA;EACA,MAAM;;KAGI,gBAAgB,OAAO;qBAEtB,kBAAe,kBACT;;;;;;wBAoBH,SAAS,MAAM,MAAM;;;UCrBpB;;;;;;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;KA2CzE,mBAAmB,UAAU,KAAK,iBAAiB,OAAO;;;;;;wBAO/C,mBAAmB"}
package/dist/index.mjs ADDED
@@ -0,0 +1,581 @@
1
+ /**
2
+ * @forinda/kickjs-inertia v0.0.0
3
+ *
4
+ * Copyright (c) Felix Orinda
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ *
9
+ * @license MIT
10
+ */
11
+ import { createHash } from "node:crypto";
12
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
13
+ import { join, resolve } from "node:path";
14
+ import { pathToFileURL } from "node:url";
15
+ import { HttpException, Logger, RESPOND, RequestContext, defineAdapter, errorHandler } from "@forinda/kickjs";
16
+
17
+ //#region src/props.ts
18
+ const PROP = Symbol.for("kickjs.inertia.prop");
19
+ function prop(kind, value, extra) {
20
+ return {
21
+ [PROP]: kind,
22
+ value,
23
+ ...extra
24
+ };
25
+ }
26
+ /** Sent only when a partial reload names it (`router.reload({ only: [...] })`). */
27
+ function optional(value) {
28
+ return prop("optional", value);
29
+ }
30
+ /** Sent on every response, including partial reloads that did not name it. */
31
+ function always(value) {
32
+ return prop("always", value);
33
+ }
34
+ /**
35
+ * Left out of the first response; the client fetches it right after the page
36
+ * renders, one request per `group`.
37
+ */
38
+ function defer(value, group = "default") {
39
+ return prop("defer", value, { group });
40
+ }
41
+ /**
42
+ * Merged into the client's current value on a partial reload instead of
43
+ * replacing it — paginated lists, infinite scroll.
44
+ */
45
+ function merge(value, mode = "append") {
46
+ return prop("merge", value, { mode });
47
+ }
48
+ function isInertiaProp(value) {
49
+ return typeof value === "object" && value !== null && PROP in value;
50
+ }
51
+ function propKind(value) {
52
+ return value[PROP];
53
+ }
54
+ /** Unwrap a wrapper, call a function, await a promise. */
55
+ async function resolveProp(value) {
56
+ const raw = isInertiaProp(value) ? value.value : value;
57
+ return typeof raw === "function" ? await raw() : await raw;
58
+ }
59
+
60
+ //#endregion
61
+ //#region src/inertia.ts
62
+ /**
63
+ * Page responses. `inertia(component, props)` is a handler return value: on a
64
+ * first visit it renders the HTML shell with the page embedded, on an Inertia
65
+ * visit (`X-Inertia: true`) it sends the page object as JSON.
66
+ *
67
+ * @module @forinda/kickjs-inertia/inertia
68
+ */
69
+ const ERRORS_KEY = "__inertia_errors";
70
+ const FLASH_KEY = "__inertia_flash";
71
+ /** URL of the last page rendered — where a failed submit goes back to. */
72
+ const PREVIOUS_KEY = "__inertia_previous";
73
+ let active = null;
74
+ function setInertiaRuntime(runtime) {
75
+ active = runtime;
76
+ }
77
+ /** The active adapter's runtime, or null when no InertiaAdapter is mounted. */
78
+ function inertiaRuntime() {
79
+ return active;
80
+ }
81
+ function runtime() {
82
+ if (!active) throw new Error("inertia() needs InertiaAdapter — add it to bootstrap({ adapters: [InertiaAdapter({ entry })] }).");
83
+ return active;
84
+ }
85
+ function header(ctx, name) {
86
+ const value = ctx.headers[name];
87
+ return Array.isArray(value) ? value[0] : value;
88
+ }
89
+ function list(value) {
90
+ return value ? value.split(",").map((s) => s.trim()).filter(Boolean) : [];
91
+ }
92
+ function requestUrl(req) {
93
+ const r = req;
94
+ return r.originalUrl ?? r.url ?? "/";
95
+ }
96
+ function isInertiaRequest(ctx) {
97
+ return header(ctx, "x-inertia") === "true";
98
+ }
99
+ /** Read and remove a one-request value from the session. */
100
+ function take(ctx, key) {
101
+ const data = ctx.session?.data;
102
+ if (!data || data[key] === void 0) return {};
103
+ const value = data[key];
104
+ delete data[key];
105
+ return value;
106
+ }
107
+ /** Flash data for the next page, sent as the page's top-level `flash`. */
108
+ function flash(ctx, data) {
109
+ const session = ctx.session;
110
+ if (!session) throw new Error("flash() needs the session() middleware.");
111
+ session.data[FLASH_KEY] = {
112
+ ...session.data[FLASH_KEY],
113
+ ...data
114
+ };
115
+ }
116
+ async function buildPage(ctx, component, props, options = {}) {
117
+ const rt = runtime();
118
+ const shared = await rt.share?.(ctx) ?? {};
119
+ const all = {
120
+ ...shared,
121
+ ...props
122
+ };
123
+ const partial = header(ctx, "x-inertia-partial-component") === component;
124
+ const only = partial ? list(header(ctx, "x-inertia-partial-data")) : [];
125
+ const except = partial ? list(header(ctx, "x-inertia-partial-except")) : [];
126
+ const reset = list(header(ctx, "x-inertia-reset"));
127
+ const named = (key) => only.some((o) => o === key || o.startsWith(`${key}.`));
128
+ const resolved = {};
129
+ const deferredProps = {};
130
+ const merges = {
131
+ append: [],
132
+ prepend: [],
133
+ deep: []
134
+ };
135
+ for (const [key, value] of Object.entries(all)) {
136
+ const kind = isInertiaProp(value) ? propKind(value) : void 0;
137
+ let send;
138
+ if (kind === "always") send = true;
139
+ else if (partial) send = (only.length === 0 || named(key)) && !except.includes(key);
140
+ else send = kind !== "optional" && kind !== "defer";
141
+ if (!send) {
142
+ if (!partial && kind === "defer" && isInertiaProp(value)) {
143
+ const group = value.group ?? "default";
144
+ (deferredProps[group] ??= []).push(key);
145
+ }
146
+ continue;
147
+ }
148
+ resolved[key] = await resolveProp(value);
149
+ if (kind === "merge" && isInertiaProp(value) && !reset.includes(key)) merges[value.mode ?? "append"].push(key);
150
+ }
151
+ const page = {
152
+ component,
153
+ props: {
154
+ ...resolved,
155
+ errors: take(ctx, ERRORS_KEY)
156
+ },
157
+ url: requestUrl(ctx.req),
158
+ version: rt.version(),
159
+ flash: take(ctx, FLASH_KEY)
160
+ };
161
+ const sharedKeys = Object.keys(shared);
162
+ if (sharedKeys.length) page.sharedProps = sharedKeys;
163
+ if (Object.keys(deferredProps).length) page.deferredProps = deferredProps;
164
+ if (merges.append.length) page.mergeProps = merges.append;
165
+ if (merges.prepend.length) page.prependProps = merges.prepend;
166
+ if (merges.deep.length) page.deepMergeProps = merges.deep;
167
+ if (options.encryptHistory) page.encryptHistory = true;
168
+ if (options.clearHistory) page.clearHistory = true;
169
+ return page;
170
+ }
171
+ /**
172
+ * Render an Inertia page. Return it from a handler:
173
+ *
174
+ * ```ts
175
+ * @Get('/users')
176
+ * async index() {
177
+ * return inertia('Users/Index', { users: await this.users.list() })
178
+ * }
179
+ * ```
180
+ */
181
+ function inertia(component, props = {}, options) {
182
+ return { async [RESPOND](ctx) {
183
+ const page = await buildPage(ctx, component, props, options);
184
+ if (ctx.session) ctx.session.data[PREVIOUS_KEY] = page.url;
185
+ if (isInertiaRequest(ctx)) {
186
+ ctx.setHeader("X-Inertia", "true");
187
+ ctx.json(page, options?.status ?? 200);
188
+ return;
189
+ }
190
+ ctx.html(await runtime().renderHtml(page, ctx), options?.status ?? 200);
191
+ } };
192
+ }
193
+ /**
194
+ * Redirect after a form submit. Uses 303 for anything but GET, so the browser
195
+ * follows with a GET, and saves the session first so flashed errors and
196
+ * messages are there when that GET arrives.
197
+ */
198
+ function redirect(url) {
199
+ return { async [RESPOND](ctx) {
200
+ await ctx.session?.save();
201
+ const method = ctx.req.method ?? "GET";
202
+ ctx.redirect(url, method === "GET" ? 302 : 303);
203
+ } };
204
+ }
205
+ /** A path on this site — not `//host` or `/\\host`, which browsers treat as another origin. */
206
+ function localPath(value) {
207
+ return typeof value === "string" && /^\/(?![/\\])/.test(value) ? value : void 0;
208
+ }
209
+ /** The path of a Referer, only when it points at this host. */
210
+ function sameOriginPath(referer, host) {
211
+ if (!referer || !host) return void 0;
212
+ try {
213
+ const url = new URL(referer);
214
+ return url.host === host ? localPath(url.pathname + url.search) : void 0;
215
+ } catch {
216
+ return;
217
+ }
218
+ }
219
+ /**
220
+ * Where "back" is: the last page this session rendered, else a same-origin
221
+ * Referer, else `fallback`. Never another site — a form on another site that
222
+ * posts here must not be able to choose where the user lands.
223
+ */
224
+ function previousUrl(session, headers, fallback = "/") {
225
+ const one = (v) => Array.isArray(v) ? v[0] : v;
226
+ return localPath(session?.data["__inertia_previous"]) ?? sameOriginPath(one(headers.referer), one(headers.host)) ?? fallback;
227
+ }
228
+ /** Redirect back to the page the request came from. */
229
+ function back(ctx, fallback = "/") {
230
+ return redirect(previousUrl(ctx.session, {
231
+ referer: header(ctx, "referer"),
232
+ host: header(ctx, "host")
233
+ }, fallback));
234
+ }
235
+ /**
236
+ * A full browser visit, outside Inertia — another site, or a page that is not
237
+ * an Inertia page. An Inertia request gets 409 + `X-Inertia-Location`, which
238
+ * the client turns into `window.location`.
239
+ */
240
+ function location(url) {
241
+ return { async [RESPOND](ctx) {
242
+ if (isInertiaRequest(ctx)) {
243
+ ctx.setHeader("X-Inertia-Location", url);
244
+ ctx.html("", 409);
245
+ return;
246
+ }
247
+ ctx.redirect(url);
248
+ } };
249
+ }
250
+ /** A browser navigation or an Inertia visit — not an API or asset request. */
251
+ function isPageRequest(headers) {
252
+ const accept = String(headers.accept ?? "");
253
+ return headers["x-inertia"] === "true" || accept.includes("text/html");
254
+ }
255
+ const STATUS_TEXT = {
256
+ 400: "Bad Request",
257
+ 401: "Unauthorized",
258
+ 403: "Forbidden",
259
+ 404: "Page Not Found",
260
+ 405: "Method Not Allowed",
261
+ 419: "Page Expired",
262
+ 429: "Too Many Requests",
263
+ 500: "Server Error",
264
+ 503: "Service Unavailable"
265
+ };
266
+ /**
267
+ * The configured error page as a response, with `{ status, message }` as its
268
+ * props. A 5xx message is replaced in production — an exception's text is
269
+ * for the log, not the visitor.
270
+ */
271
+ function errorPage(component, status, error) {
272
+ const fallback = STATUS_TEXT[status] ?? (status >= 500 ? "Server Error" : "Error");
273
+ const raw = error instanceof Error && error.message ? error.message : fallback;
274
+ return inertia(component, {
275
+ status,
276
+ message: status >= 500 && process.env.NODE_ENV === "production" ? fallback : raw
277
+ }, { status });
278
+ }
279
+
280
+ //#endregion
281
+ //#region src/html.ts
282
+ /**
283
+ * The HTML shell for a first visit, and the script tags that load the client:
284
+ * Vite's dev server while `kick dev` runs, the build manifest in production.
285
+ *
286
+ * @module @forinda/kickjs-inertia/html
287
+ */
288
+ const defaultTemplate = (title) => ({ head, body }) => `<!doctype html>
289
+ <html lang="en">
290
+ <head>
291
+ <meta charset="utf-8" />
292
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
293
+ <title>${title}</title>
294
+ ${head}
295
+ </head>
296
+ <body>
297
+ ${body}
298
+ </body>
299
+ </html>`;
300
+ /**
301
+ * The page as `<script data-page="app" type="application/json">` — where the
302
+ * v3 client reads it — followed by the mount element. `<` and `/` are escaped
303
+ * so a prop containing `<\/script>` cannot end the tag.
304
+ */
305
+ function pageBody(page, id = "app") {
306
+ return `<script data-page="${id}" type="application/json">${JSON.stringify(page, (_k, v) => typeof v === "bigint" ? v.toString() : v).replace(/</g, "\\u003c").replace(/\//g, "\\/")}<\/script>\n <div id="${id}"></div>`;
307
+ }
308
+ /** Dev: one module script; Vite's `transformIndexHtml` adds its client and plugin preambles. */
309
+ function devHead(entry) {
310
+ return `<script type="module" src="/${entry.replace(/^\/+/, "")}"><\/script>`;
311
+ }
312
+ /** Production: the entry's script, its CSS, and the CSS and preloads of what it imports. */
313
+ function manifestHead(clientDir, entry) {
314
+ const manifest = JSON.parse(readFileSync(join(clientDir, ".vite", "manifest.json"), "utf8"));
315
+ const chunk = manifest[entry];
316
+ if (!chunk) throw new Error(`InertiaAdapter: '${entry}' is not in ${clientDir}/.vite/manifest.json — build the client with build.manifest and this entry as an input.`);
317
+ const css = /* @__PURE__ */ new Set();
318
+ const preload = /* @__PURE__ */ new Set();
319
+ const walk = (key, root) => {
320
+ const c = manifest[key];
321
+ if (!c) return;
322
+ for (const f of c.css ?? []) css.add(f);
323
+ if (!root) preload.add(c.file);
324
+ for (const i of c.imports ?? []) walk(i, false);
325
+ };
326
+ walk(entry, true);
327
+ return [
328
+ ...[...css].map((f) => `<link rel="stylesheet" href="/${f}" />`),
329
+ ...[...preload].map((f) => `<link rel="modulepreload" href="/${f}" />`),
330
+ `<script type="module" src="/${chunk.file}"><\/script>`
331
+ ].join("\n ");
332
+ }
333
+
334
+ //#endregion
335
+ //#region src/adapter.ts
336
+ /**
337
+ * `InertiaAdapter` — wires the protocol into the app:
338
+ *
339
+ * - makes `inertia()` able to render (HTML shell, asset tags, version);
340
+ * - answers a stale Inertia GET with 409 so the client reloads the page;
341
+ * - turns a 302 after PUT / PATCH / DELETE into 303, so the browser follows
342
+ * with a GET instead of repeating the method;
343
+ * - in production, serves the built client's `assets/`.
344
+ *
345
+ * @module @forinda/kickjs-inertia/adapter
346
+ */
347
+ const log$1 = Logger.for("InertiaAdapter");
348
+ const CSS_REQUEST = /\.(css|less|sass|scss|styl|stylus|pcss|postcss|sss)(?:$|\?)/;
349
+ /**
350
+ * Dev only: the stylesheets the SSR entry imports, as `<link>` tags. In dev
351
+ * Vite injects CSS from JavaScript, so server-rendered HTML would show
352
+ * unstyled until the scripts run. `data-vite-dev-id` lets Vite's client
353
+ * recognize these and not inject the same styles again.
354
+ */
355
+ function devStylesheets(vite, entry) {
356
+ const root = vite.environments.ssr.moduleGraph.getModuleById(resolve(vite.config.root, entry));
357
+ if (!root) return [];
358
+ const links = [];
359
+ const seen = /* @__PURE__ */ new Set();
360
+ const walk = (mod) => {
361
+ if (seen.has(mod)) return;
362
+ seen.add(mod);
363
+ if (CSS_REQUEST.test(mod.url)) {
364
+ const devId = mod.id ? ` data-vite-dev-id="${mod.id.replace(/"/g, "&quot;")}"` : "";
365
+ links.push(`<link rel="stylesheet" href="${mod.url}"${devId} />`);
366
+ return;
367
+ }
368
+ for (const imported of mod.importedModules) walk(imported);
369
+ };
370
+ walk(root);
371
+ return links;
372
+ }
373
+ function viteServer() {
374
+ return globalThis.__kickjs_viteServer ?? null;
375
+ }
376
+ function appendVary(res, field) {
377
+ const current = res.getHeader("Vary");
378
+ const list = (Array.isArray(current) ? current.join(",") : String(current ?? "")).split(",").map((v) => v.trim()).filter(Boolean);
379
+ if (list.includes("*") || list.some((v) => v.toLowerCase() === field.toLowerCase())) return;
380
+ res.setHeader("Vary", [...list, field].join(", "));
381
+ }
382
+ function safeDecode(path) {
383
+ try {
384
+ return decodeURIComponent(path);
385
+ } catch {
386
+ return path;
387
+ }
388
+ }
389
+ const InertiaAdapter = defineAdapter({
390
+ name: "InertiaAdapter",
391
+ defaults: {
392
+ entry: "src/client/main.tsx",
393
+ clientDir: "dist/client",
394
+ title: "KickJS"
395
+ },
396
+ build: (config) => {
397
+ const clientDir = resolve(config.clientDir);
398
+ const template = config.template ?? defaultTemplate(config.title);
399
+ let version = () => null;
400
+ const protocol = (req, res, next) => {
401
+ appendVary(res, "X-Inertia");
402
+ if (req.headers["x-inertia"] !== "true") return next();
403
+ const current = version();
404
+ if (req.method === "GET" && current && req.headers["x-inertia-version"] !== current) {
405
+ res.statusCode = 409;
406
+ res.setHeader("X-Inertia-Location", requestUrl(req));
407
+ res.end();
408
+ return;
409
+ }
410
+ if (req.method === "PUT" || req.method === "PATCH" || req.method === "DELETE") {
411
+ const writeHead = res.writeHead;
412
+ res.writeHead = function(status, ...rest) {
413
+ return writeHead.call(this, status === 302 ? 303 : status, ...rest);
414
+ };
415
+ }
416
+ next();
417
+ };
418
+ return {
419
+ beforeMount({ http }) {
420
+ const vite = viteServer();
421
+ let head;
422
+ if (vite) head = () => devHead(config.entry);
423
+ else {
424
+ const tags = manifestHead(clientDir, config.entry);
425
+ head = () => tags;
426
+ const hash = createHash("sha1").update(readFileSync(join(clientDir, ".vite", "manifest.json"))).digest("hex").slice(0, 12);
427
+ version = () => hash;
428
+ if (existsSync(join(clientDir, "package.json"))) throw new Error(`InertiaAdapter: clientDir ${clientDir} looks like a project root — it must be the client build output (default dist/client), which is served as-is.`);
429
+ const hidden = new Set(readdirSync(clientDir).filter((name) => name.startsWith(".")));
430
+ const hideBuildFiles = (req, res, next) => {
431
+ const first = safeDecode((req.url ?? "/").split("?")[0]).split("/").find(Boolean);
432
+ if (first && hidden.has(first)) {
433
+ res.statusCode = 404;
434
+ res.end();
435
+ return;
436
+ }
437
+ next();
438
+ };
439
+ http.use(hideBuildFiles);
440
+ http.serveStatic("/", clientDir);
441
+ }
442
+ if (config.version !== void 0) {
443
+ const v = config.version;
444
+ version = typeof v === "function" ? v : () => v;
445
+ }
446
+ const ssr = config.ssr;
447
+ let prodRender;
448
+ const loadRender = async () => {
449
+ if (vite) return (await vite.ssrLoadModule(`/${ssr.entry.replace(/^\/+/, "")}`)).default;
450
+ const url = pathToFileURL(resolve(ssr.bundle ?? "dist/ssr/ssr.js")).href;
451
+ prodRender ??= import(
452
+ /* @vite-ignore */
453
+ url
454
+ ).then((mod) => mod.default);
455
+ return prodRender;
456
+ };
457
+ const render = async (page) => {
458
+ if (!ssr) return null;
459
+ try {
460
+ return await (await loadRender())(page);
461
+ } catch (err) {
462
+ if (vite && err instanceof Error) vite.ssrFixStacktrace(err);
463
+ log$1.error(err, `SSR failed for ${page.component} — sending it client-rendered`);
464
+ return null;
465
+ }
466
+ };
467
+ setInertiaRuntime({
468
+ version: () => version(),
469
+ share: config.share,
470
+ errorPage: config.errorPage,
471
+ async renderHtml(page, ctx) {
472
+ const rendered = await render(page);
473
+ const styles = vite && rendered && ssr ? devStylesheets(vite, ssr.entry) : [];
474
+ const html = template({
475
+ head: [
476
+ config.head ?? "",
477
+ head(page),
478
+ ...styles,
479
+ ...rendered?.head ?? []
480
+ ].filter(Boolean).join("\n "),
481
+ body: rendered?.body ?? pageBody(page),
482
+ page
483
+ });
484
+ return vite ? vite.transformIndexHtml(requestUrl(ctx.req), html) : html;
485
+ }
486
+ });
487
+ },
488
+ middleware() {
489
+ return [{
490
+ handler: protocol,
491
+ phase: "afterGlobal"
492
+ }];
493
+ },
494
+ shutdown() {
495
+ setInertiaRuntime(null);
496
+ }
497
+ };
498
+ }
499
+ });
500
+
501
+ //#endregion
502
+ //#region src/errors.ts
503
+ /**
504
+ * Validation errors the Inertia way: on an Inertia request, a 422 from route
505
+ * validation becomes a redirect back with the errors in the session, and the
506
+ * next page gets them as `props.errors`. Everything else goes to the usual
507
+ * error handler.
508
+ *
509
+ * @module @forinda/kickjs-inertia/errors
510
+ */
511
+ const log = Logger.for("Inertia");
512
+ /** `[{ field, message }]` from route validation → `{ field: firstMessage }`. */
513
+ function toErrors(details) {
514
+ if (!Array.isArray(details)) return null;
515
+ const errors = {};
516
+ for (const d of details) {
517
+ const field = d.field || "form";
518
+ errors[field] ??= d.message ?? "Invalid value";
519
+ }
520
+ return errors;
521
+ }
522
+ /**
523
+ * Pass to `bootstrap({ onError: inertiaErrorHandler() })`.
524
+ *
525
+ * - A validation 422 on an Inertia visit goes back to the form with the
526
+ * errors (needs the `session()` middleware to carry them).
527
+ * - Any other error on a page request renders the adapter's `errorPage`,
528
+ * when one is configured.
529
+ * - Everything else — API calls, assets — goes to `fallback`.
530
+ */
531
+ function inertiaErrorHandler(fallback = errorHandler()) {
532
+ let warned = false;
533
+ return (err, req, res, next) => {
534
+ if (res.headersSent) return fallback(err, req, res, next);
535
+ const errors = err instanceof HttpException && err.status === 422 ? toErrors(err.details) : null;
536
+ if (errors && req?.headers?.["x-inertia"] === "true") {
537
+ const session = req.session;
538
+ if (session) {
539
+ const bag = req.headers["x-inertia-error-bag"];
540
+ session.data[ERRORS_KEY] = bag ? { [bag]: errors } : errors;
541
+ session.save().then(() => {
542
+ res.status(303).setHeader("location", previousUrl(session, req.headers));
543
+ res.end();
544
+ }).catch((saveErr) => fallback(saveErr, req, res, next));
545
+ return;
546
+ }
547
+ if (!warned) {
548
+ log.warn("Validation failed on an Inertia request, but there is no session() middleware to carry the errors — answering 422 instead.");
549
+ warned = true;
550
+ }
551
+ return fallback(err, req, res, next);
552
+ }
553
+ const component = inertiaRuntime()?.errorPage;
554
+ if (component && req?.headers && isPageRequest(req.headers)) {
555
+ const status = err instanceof HttpException ? err.status : 500;
556
+ if (status >= 500) log.error(err, "Unhandled error on a page request");
557
+ const ctx = new RequestContext(req, res, next, res);
558
+ Promise.resolve(errorPage(component, status, err)[RESPOND](ctx)).catch((renderErr) => fallback(renderErr, req, res, next));
559
+ return;
560
+ }
561
+ return fallback(err, req, res, next);
562
+ };
563
+ }
564
+ /**
565
+ * Pass to `bootstrap({ onNotFound: inertiaNotFound() })`. A browser or Inertia
566
+ * request for an unknown URL gets the adapter's `errorPage` with 404; anything
567
+ * else — API calls, other methods — goes on to the built-in 404 / 405.
568
+ */
569
+ function inertiaNotFound() {
570
+ return (req, res, next) => {
571
+ const component = inertiaRuntime()?.errorPage;
572
+ const method = req?.method ?? "GET";
573
+ if (!component || method !== "GET" && method !== "HEAD" || !isPageRequest(req.headers ?? {})) return next();
574
+ const ctx = new RequestContext(req, res, next, res);
575
+ Promise.resolve(errorPage(component, 404)[RESPOND](ctx)).catch(next);
576
+ };
577
+ }
578
+
579
+ //#endregion
580
+ export { InertiaAdapter, always, back, buildPage, defaultTemplate, defer, errorPage, flash, inertia, inertiaErrorHandler, inertiaNotFound, isInertiaRequest, isPageRequest, location, merge, optional, pageBody, redirect };
581
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":["log"],"sources":["../src/props.ts","../src/inertia.ts","../src/html.ts","../src/adapter.ts","../src/errors.ts"],"sourcesContent":["/**\n * Prop wrappers that change when a prop is sent. A plain value or a function\n * is sent on every full visit and on a partial reload that asks for it; a\n * function is only called when the prop is actually sent.\n *\n * @module @forinda/kickjs-inertia/props\n */\n\ntype Resolver<T> = T | (() => T | Promise<T>)\n\nconst PROP = Symbol.for('kickjs.inertia.prop')\n\nexport type PropKind = 'optional' | 'always' | 'defer' | 'merge'\n\nexport interface InertiaProp<T = unknown> {\n readonly [PROP]: PropKind\n readonly value: Resolver<T>\n /** `defer` only — props in one group load in one request. */\n readonly group?: string\n /** `merge` only — append, prepend or deep-merge into the client's copy. */\n readonly mode?: 'append' | 'prepend' | 'deep'\n}\n\nfunction prop<T>(kind: PropKind, value: Resolver<T>, extra?: object): InertiaProp<T> {\n return { [PROP]: kind, value, ...extra }\n}\n\n/** Sent only when a partial reload names it (`router.reload({ only: [...] })`). */\nexport function optional<T>(value: () => T | Promise<T>): InertiaProp<T> {\n return prop('optional', value)\n}\n\n/** Sent on every response, including partial reloads that did not name it. */\nexport function always<T>(value: Resolver<T>): InertiaProp<T> {\n return prop('always', value)\n}\n\n/**\n * Left out of the first response; the client fetches it right after the page\n * renders, one request per `group`.\n */\nexport function defer<T>(value: () => T | Promise<T>, group = 'default'): InertiaProp<T> {\n return prop('defer', value, { group })\n}\n\n/**\n * Merged into the client's current value on a partial reload instead of\n * replacing it — paginated lists, infinite scroll.\n */\nexport function merge<T>(\n value: Resolver<T>,\n mode: 'append' | 'prepend' | 'deep' = 'append',\n): InertiaProp<T> {\n return prop('merge', value, { mode })\n}\n\nexport function isInertiaProp(value: unknown): value is InertiaProp {\n return typeof value === 'object' && value !== null && PROP in value\n}\n\nexport function propKind(value: InertiaProp): PropKind {\n return value[PROP]\n}\n\n/** Unwrap a wrapper, call a function, await a promise. */\nexport async function resolveProp(value: unknown): Promise<unknown> {\n const raw = isInertiaProp(value) ? value.value : value\n return typeof raw === 'function' ? await (raw as () => unknown)() : await raw\n}\n","/**\n * Page responses. `inertia(component, props)` is a handler return value: on a\n * first visit it renders the HTML shell with the page embedded, on an Inertia\n * visit (`X-Inertia: true`) it sends the page object as JSON.\n *\n * @module @forinda/kickjs-inertia/inertia\n */\nimport { RESPOND, type RequestContext, type Respondable } from '@forinda/kickjs'\nimport { isInertiaProp, propKind, resolveProp } from './props'\n\nexport const ERRORS_KEY = '__inertia_errors'\nexport const FLASH_KEY = '__inertia_flash'\n/** URL of the last page rendered — where a failed submit goes back to. */\nexport const PREVIOUS_KEY = '__inertia_previous'\n\n/** The page object the Inertia client renders — see the protocol docs. */\nexport interface Page {\n component: string\n props: Record<string, unknown> & { errors: Record<string, unknown> }\n url: string\n version: string | null\n flash: Record<string, unknown>\n sharedProps?: string[]\n deferredProps?: Record<string, string[]>\n mergeProps?: string[]\n prependProps?: string[]\n deepMergeProps?: string[]\n encryptHistory?: boolean\n clearHistory?: boolean\n}\n\nexport interface PageOptions {\n /** Encrypt this page's history entry in the browser. */\n encryptHistory?: boolean\n /** Wipe the browser's Inertia history, e.g. after logout. */\n clearHistory?: boolean\n /** Response status. Default 200; error pages send theirs (404, 500). */\n status?: number\n}\n\n/** What the adapter hands the page renderer. Set in `beforeMount`. */\nexport interface InertiaRuntime {\n version(): string | null\n share?(ctx: RequestContext): Record<string, unknown> | Promise<Record<string, unknown>>\n renderHtml(page: Page, ctx: RequestContext): Promise<string>\n /** Component that renders error and not-found pages, when configured. */\n errorPage?: string\n}\n\n// ponytail: one active adapter per process, which is one app. An app booting\n// two Inertia apps in one process would need this keyed per Application.\nlet active: InertiaRuntime | null = null\n\nexport function setInertiaRuntime(runtime: InertiaRuntime | null): void {\n active = runtime\n}\n\n/** The active adapter's runtime, or null when no InertiaAdapter is mounted. */\nexport function inertiaRuntime(): InertiaRuntime | null {\n return active\n}\n\nfunction runtime(): InertiaRuntime {\n if (!active) {\n throw new Error(\n 'inertia() needs InertiaAdapter — add it to bootstrap({ adapters: [InertiaAdapter({ entry })] }).',\n )\n }\n return active\n}\n\nfunction header(ctx: RequestContext, name: string): string | undefined {\n const value = (ctx.headers as Record<string, string | string[] | undefined>)[name]\n return Array.isArray(value) ? value[0] : value\n}\n\nfunction list(value: string | undefined): string[] {\n return value\n ? value\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n : []\n}\n\nexport function requestUrl(req: unknown): string {\n const r = req as { originalUrl?: string; url?: string }\n return r.originalUrl ?? r.url ?? '/'\n}\n\nexport function isInertiaRequest(ctx: RequestContext): boolean {\n return header(ctx, 'x-inertia') === 'true'\n}\n\n/** Read and remove a one-request value from the session. */\nfunction take(ctx: RequestContext, key: string): Record<string, unknown> {\n const data = ctx.session?.data\n if (!data || data[key] === undefined) return {}\n const value = data[key] as Record<string, unknown>\n delete data[key]\n return value\n}\n\n/** Flash data for the next page, sent as the page's top-level `flash`. */\nexport function flash(ctx: RequestContext, data: Record<string, unknown>): void {\n const session = ctx.session\n if (!session) throw new Error('flash() needs the session() middleware.')\n session.data[FLASH_KEY] = { ...(session.data[FLASH_KEY] as object), ...data }\n}\n\nexport async function buildPage(\n ctx: RequestContext,\n component: string,\n props: Record<string, unknown>,\n options: PageOptions = {},\n): Promise<Page> {\n const rt = runtime()\n const shared = (await rt.share?.(ctx)) ?? {}\n const all: Record<string, unknown> = { ...shared, ...props }\n\n const partial = header(ctx, 'x-inertia-partial-component') === component\n const only = partial ? list(header(ctx, 'x-inertia-partial-data')) : []\n const except = partial ? list(header(ctx, 'x-inertia-partial-except')) : []\n const reset = list(header(ctx, 'x-inertia-reset'))\n\n // A dot path in `only` ('users.data') still selects its top-level prop.\n const named = (key: string) => only.some((o) => o === key || o.startsWith(`${key}.`))\n\n const resolved: Record<string, unknown> = {}\n const deferredProps: Record<string, string[]> = {}\n const merges = { append: [] as string[], prepend: [] as string[], deep: [] as string[] }\n\n for (const [key, value] of Object.entries(all)) {\n const kind = isInertiaProp(value) ? propKind(value) : undefined\n let send: boolean\n if (kind === 'always') send = true\n else if (partial) send = (only.length === 0 || named(key)) && !except.includes(key)\n else send = kind !== 'optional' && kind !== 'defer'\n\n if (!send) {\n if (!partial && kind === 'defer' && isInertiaProp(value)) {\n const group = value.group ?? 'default'\n ;(deferredProps[group] ??= []).push(key)\n }\n continue\n }\n resolved[key] = await resolveProp(value)\n if (kind === 'merge' && isInertiaProp(value) && !reset.includes(key)) {\n merges[value.mode ?? 'append'].push(key)\n }\n }\n\n const page: Page = {\n component,\n props: { ...resolved, errors: take(ctx, ERRORS_KEY) },\n url: requestUrl(ctx.req),\n version: rt.version(),\n flash: take(ctx, FLASH_KEY),\n }\n const sharedKeys = Object.keys(shared)\n if (sharedKeys.length) page.sharedProps = sharedKeys\n if (Object.keys(deferredProps).length) page.deferredProps = deferredProps\n if (merges.append.length) page.mergeProps = merges.append\n if (merges.prepend.length) page.prependProps = merges.prepend\n if (merges.deep.length) page.deepMergeProps = merges.deep\n if (options.encryptHistory) page.encryptHistory = true\n if (options.clearHistory) page.clearHistory = true\n return page\n}\n\n/**\n * Render an Inertia page. Return it from a handler:\n *\n * ```ts\n * @Get('/users')\n * async index() {\n * return inertia('Users/Index', { users: await this.users.list() })\n * }\n * ```\n */\nexport function inertia(\n component: string,\n props: Record<string, unknown> = {},\n options?: PageOptions,\n): Respondable {\n return {\n async [RESPOND](ctx: RequestContext) {\n const page = await buildPage(ctx, component, props, options)\n // Remembered for back(): helmet's default Referrer-Policy (no-referrer)\n // means the browser sends no Referer to go back to.\n if (ctx.session) ctx.session.data[PREVIOUS_KEY] = page.url\n // `Vary: X-Inertia` is added by the adapter's middleware, which can\n // append to a Vary another layer set instead of replacing it.\n if (isInertiaRequest(ctx)) {\n ctx.setHeader('X-Inertia', 'true')\n ctx.json(page, options?.status ?? 200)\n return\n }\n ctx.html(await runtime().renderHtml(page, ctx), options?.status ?? 200)\n },\n }\n}\n\n/**\n * Redirect after a form submit. Uses 303 for anything but GET, so the browser\n * follows with a GET, and saves the session first so flashed errors and\n * messages are there when that GET arrives.\n */\nexport function redirect(url: string): Respondable {\n return {\n async [RESPOND](ctx: RequestContext) {\n await ctx.session?.save()\n const method = (ctx.req as { method?: string }).method ?? 'GET'\n ctx.redirect(url, method === 'GET' ? 302 : 303)\n },\n }\n}\n\n/** A path on this site — not `//host` or `/\\\\host`, which browsers treat as another origin. */\nfunction localPath(value: unknown): string | undefined {\n return typeof value === 'string' && /^\\/(?![/\\\\])/.test(value) ? value : undefined\n}\n\n/** The path of a Referer, only when it points at this host. */\nfunction sameOriginPath(referer: string | undefined, host: string | undefined): string | undefined {\n if (!referer || !host) return undefined\n try {\n const url = new URL(referer)\n return url.host === host ? localPath(url.pathname + url.search) : undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * Where \"back\" is: the last page this session rendered, else a same-origin\n * Referer, else `fallback`. Never another site — a form on another site that\n * posts here must not be able to choose where the user lands.\n */\nexport function previousUrl(\n session: { data: Record<string, unknown> } | undefined,\n headers: { referer?: string | string[]; host?: string | string[] },\n fallback = '/',\n): string {\n const one = (v: string | string[] | undefined) => (Array.isArray(v) ? v[0] : v)\n return (\n localPath(session?.data[PREVIOUS_KEY]) ??\n sameOriginPath(one(headers.referer), one(headers.host)) ??\n fallback\n )\n}\n\n/** Redirect back to the page the request came from. */\nexport function back(ctx: RequestContext, fallback = '/'): Respondable {\n return redirect(\n previousUrl(\n ctx.session,\n { referer: header(ctx, 'referer'), host: header(ctx, 'host') },\n fallback,\n ),\n )\n}\n\n/**\n * A full browser visit, outside Inertia — another site, or a page that is not\n * an Inertia page. An Inertia request gets 409 + `X-Inertia-Location`, which\n * the client turns into `window.location`.\n */\nexport function location(url: string): Respondable {\n return {\n async [RESPOND](ctx: RequestContext) {\n if (isInertiaRequest(ctx)) {\n ctx.setHeader('X-Inertia-Location', url)\n ctx.html('', 409)\n return\n }\n ctx.redirect(url)\n },\n }\n}\n\n/** A browser navigation or an Inertia visit — not an API or asset request. */\nexport function isPageRequest(headers: Record<string, string | string[] | undefined>): boolean {\n const accept = String(headers.accept ?? '')\n return headers['x-inertia'] === 'true' || accept.includes('text/html')\n}\n\nconst STATUS_TEXT: Record<number, string> = {\n 400: 'Bad Request',\n 401: 'Unauthorized',\n 403: 'Forbidden',\n 404: 'Page Not Found',\n 405: 'Method Not Allowed',\n 419: 'Page Expired',\n 429: 'Too Many Requests',\n 500: 'Server Error',\n 503: 'Service Unavailable',\n}\n\n/**\n * The configured error page as a response, with `{ status, message }` as its\n * props. A 5xx message is replaced in production — an exception's text is\n * for the log, not the visitor.\n */\nexport function errorPage(component: string, status: number, error?: unknown): Respondable {\n const fallback = STATUS_TEXT[status] ?? (status >= 500 ? 'Server Error' : 'Error')\n const raw = error instanceof Error && error.message ? error.message : fallback\n const message = status >= 500 && process.env.NODE_ENV === 'production' ? fallback : raw\n return inertia(component, { status, message }, { status })\n}\n","/**\n * The HTML shell for a first visit, and the script tags that load the client:\n * Vite's dev server while `kick dev` runs, the build manifest in production.\n *\n * @module @forinda/kickjs-inertia/html\n */\nimport { readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport type { Page } from './inertia'\n\nexport interface HtmlParts {\n /** Script and style tags for the client entry. Put inside `<head>`. */\n head: string\n /** The page data and the mount element. Put inside `<body>`. */\n body: string\n page: Page\n}\n\nexport type HtmlTemplate = (parts: HtmlParts) => string\n\nexport const defaultTemplate =\n (title: string): HtmlTemplate =>\n ({ head, body }) =>\n `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>${title}</title>\n ${head}\n </head>\n <body>\n ${body}\n </body>\n</html>`\n\n/**\n * The page as `<script data-page=\"app\" type=\"application/json\">` — where the\n * v3 client reads it — followed by the mount element. `<` and `/` are escaped\n * so a prop containing `</script>` cannot end the tag.\n */\nexport function pageBody(page: Page, id = 'app'): string {\n const json = JSON.stringify(page, (_k, v) => (typeof v === 'bigint' ? v.toString() : v))\n .replace(/</g, '\\\\u003c')\n .replace(/\\//g, '\\\\/')\n return `<script data-page=\"${id}\" type=\"application/json\">${json}</script>\\n <div id=\"${id}\"></div>`\n}\n\n/** Dev: one module script; Vite's `transformIndexHtml` adds its client and plugin preambles. */\nexport function devHead(entry: string): string {\n return `<script type=\"module\" src=\"/${entry.replace(/^\\/+/, '')}\"></script>`\n}\n\ninterface ManifestChunk {\n file: string\n css?: string[]\n imports?: string[]\n}\n\n/** Production: the entry's script, its CSS, and the CSS and preloads of what it imports. */\nexport function manifestHead(clientDir: string, entry: string): string {\n const manifest = JSON.parse(\n readFileSync(join(clientDir, '.vite', 'manifest.json'), 'utf8'),\n ) as Record<string, ManifestChunk>\n const chunk = manifest[entry]\n if (!chunk) {\n throw new Error(\n `InertiaAdapter: '${entry}' is not in ${clientDir}/.vite/manifest.json — build the client with build.manifest and this entry as an input.`,\n )\n }\n const css = new Set<string>()\n const preload = new Set<string>()\n const walk = (key: string, root: boolean) => {\n const c = manifest[key]\n if (!c) return\n for (const f of c.css ?? []) css.add(f)\n if (!root) preload.add(c.file)\n for (const i of c.imports ?? []) walk(i, false)\n }\n walk(entry, true)\n return [\n ...[...css].map((f) => `<link rel=\"stylesheet\" href=\"/${f}\" />`),\n ...[...preload].map((f) => `<link rel=\"modulepreload\" href=\"/${f}\" />`),\n `<script type=\"module\" src=\"/${chunk.file}\"></script>`,\n ].join('\\n ')\n}\n","/**\n * `InertiaAdapter` — wires the protocol into the app:\n *\n * - makes `inertia()` able to render (HTML shell, asset tags, version);\n * - answers a stale Inertia GET with 409 so the client reloads the page;\n * - turns a 302 after PUT / PATCH / DELETE into 303, so the browser follows\n * with a GET instead of repeating the method;\n * - in production, serves the built client's `assets/`.\n *\n * @module @forinda/kickjs-inertia/adapter\n */\nimport { createHash } from 'node:crypto'\nimport { existsSync, readFileSync, readdirSync } from 'node:fs'\nimport { join, resolve } from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { defineAdapter, Logger, type RequestContext } from '@forinda/kickjs'\nimport { setInertiaRuntime, requestUrl, type Page } from './inertia'\nimport { defaultTemplate, devHead, manifestHead, pageBody, type HtmlTemplate } from './html'\n\nexport interface InertiaAdapterOptions {\n /**\n * The client entry, relative to the project root — the file that calls\n * `createInertiaApp`. Served by Vite in dev, looked up in the build\n * manifest in production. Default: `'src/client/main.tsx'`.\n */\n entry: string\n /** Where `vite build` wrote the client, with `build.manifest: true`. Default: `'dist/client'`. */\n clientDir: string\n /**\n * Asset version. A client on another version reloads the page on its next\n * visit. Default: a hash of the build manifest in production, none in dev\n * (Vite reloads changed modules itself).\n */\n version?: string | (() => string)\n /** Props every page gets — the current user, app name. */\n share?: (ctx: RequestContext) => Record<string, unknown> | Promise<Record<string, unknown>>\n /** `<title>` of the default HTML shell. */\n title: string\n /**\n * Tags added to every page's `<head>` — a favicon, meta tags, fonts. Per-page\n * titles and meta belong in the page, with Inertia's `<Head>` component.\n */\n head?: string\n /** Replace the HTML shell entirely. */\n template?: HtmlTemplate\n /**\n * Page component for errors and unknown URLs, e.g. `'Error'`. It receives\n * `{ status, message }`. Rendered for page requests by `inertiaErrorHandler()`\n * (thrown errors) and `inertiaNotFound()` (unknown URLs); API and asset\n * requests keep the usual JSON answers.\n */\n errorPage?: string\n /**\n * Render pages on the server. `entry` is a module whose default export is\n * `(page) => Promise<{ head: string[]; body: string }>` — what\n * `createInertiaApp({ page, render: renderToString, ... })` returns. Loaded\n * through Vite in dev; in production from `bundle`, the output of\n * `vite build --ssr <entry>` (default `'dist/ssr/ssr.js'`). A render that\n * throws falls back to client rendering for that request.\n */\n ssr?: { entry: string; bundle?: string }\n}\n\n/** What an SSR entry's default export returns. */\nexport interface SsrResult {\n head: string[]\n body: string\n}\n\ntype SsrRender = (page: Page) => Promise<SsrResult>\n\nconst log = Logger.for('InertiaAdapter')\n\ninterface ViteModuleLike {\n url: string\n id: string | null\n importedModules: Set<ViteModuleLike>\n}\n\ninterface ViteDevServerLike {\n config: { root: string }\n environments: { ssr: { moduleGraph: { getModuleById(id: string): ViteModuleLike | undefined } } }\n transformIndexHtml(url: string, html: string): Promise<string>\n ssrLoadModule(url: string): Promise<Record<string, unknown>>\n ssrFixStacktrace(err: Error): void\n}\n\nconst CSS_REQUEST = /\\.(css|less|sass|scss|styl|stylus|pcss|postcss|sss)(?:$|\\?)/\n\n/**\n * Dev only: the stylesheets the SSR entry imports, as `<link>` tags. In dev\n * Vite injects CSS from JavaScript, so server-rendered HTML would show\n * unstyled until the scripts run. `data-vite-dev-id` lets Vite's client\n * recognize these and not inject the same styles again.\n */\nfunction devStylesheets(vite: ViteDevServerLike, entry: string): string[] {\n const root = vite.environments.ssr.moduleGraph.getModuleById(resolve(vite.config.root, entry))\n if (!root) return []\n const links: string[] = []\n const seen = new Set<ViteModuleLike>()\n const walk = (mod: ViteModuleLike) => {\n if (seen.has(mod)) return\n seen.add(mod)\n if (CSS_REQUEST.test(mod.url)) {\n const devId = mod.id ? ` data-vite-dev-id=\"${mod.id.replace(/\"/g, '&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 const protocol: NodeMiddleware = (req, res, next) => {\n // The same URL answers HTML or JSON depending on X-Inertia, so a shared\n // cache must key on it. Appended, not set: replacing a Vary another\n // layer added (Origin, Accept-Encoding) lets a cache serve one\n // client's variant to another.\n appendVary(res, 'X-Inertia')\n if (req.headers['x-inertia'] !== 'true') return next()\n\n const current = version()\n if (req.method === 'GET' && current && req.headers['x-inertia-version'] !== current) {\n res.statusCode = 409\n res.setHeader('X-Inertia-Location', requestUrl(req))\n res.end()\n return\n }\n\n if (req.method === 'PUT' || req.method === 'PATCH' || req.method === 'DELETE') {\n // Node sends every status through writeHead — also the implicit one\n // behind res.end() — so this catches a 302 from any code path.\n const writeHead = res.writeHead\n res.writeHead = function (this: ServerResponse, status: number, ...rest: unknown[]) {\n return (writeHead as (...a: unknown[]) => ServerResponse).call(\n this,\n status === 302 ? 303 : status,\n ...rest,\n )\n } as typeof res.writeHead\n }\n next()\n }\n\n return {\n beforeMount({ http }) {\n const vite = viteServer()\n let head: (page: Page) => string\n\n if (vite) {\n head = () => devHead(config.entry)\n } else {\n const tags = manifestHead(clientDir, config.entry)\n head = () => tags\n const hash = createHash('sha1')\n .update(readFileSync(join(clientDir, '.vite', 'manifest.json')))\n .digest('hex')\n .slice(0, 12)\n version = () => hash\n // The whole build: hashed `assets/` plus what Vite copied from\n // `public/` — favicon, robots.txt, a static sitemap.\n if (existsSync(join(clientDir, 'package.json'))) {\n throw new Error(\n `InertiaAdapter: clientDir ${clientDir} looks like a project root — it must be the client build output (default dist/client), which is served as-is.`,\n )\n }\n // Not every engine's static handler skips dotfiles, so the build's\n // own (`.vite/manifest.json`) are refused here, before it runs.\n const hidden = new Set(readdirSync(clientDir).filter((name) => name.startsWith('.')))\n const hideBuildFiles: NodeMiddleware = (req, res, next) => {\n const first = safeDecode((req.url ?? '/').split('?')[0]!)\n .split('/')\n .find(Boolean)\n if (first && hidden.has(first)) {\n res.statusCode = 404\n res.end()\n return\n }\n next()\n }\n http.use(hideBuildFiles as never)\n http.serveStatic('/', clientDir)\n }\n if (config.version !== undefined) {\n const v = config.version\n version = typeof v === 'function' ? v : () => v\n }\n\n const ssr = config.ssr\n let prodRender: Promise<SsrRender> | undefined\n const loadRender = async (): Promise<SsrRender> => {\n if (vite) {\n // Every request in dev, so an edited page renders fresh.\n const mod = await vite.ssrLoadModule(`/${ssr!.entry.replace(/^\\/+/, '')}`)\n return mod.default as SsrRender\n }\n const url = pathToFileURL(resolve(ssr!.bundle ?? 'dist/ssr/ssr.js')).href\n // The app's own build output, chosen at runtime — nothing for Vite to analyze.\n prodRender ??= import(/* @vite-ignore */ url).then((mod) => mod.default as SsrRender)\n return prodRender\n }\n const render = async (page: Page): Promise<SsrResult | null> => {\n if (!ssr) return null\n try {\n return await (\n await loadRender()\n )(page)\n } catch (err) {\n if (vite && err instanceof Error) vite.ssrFixStacktrace(err)\n log.error(err, `SSR failed for ${page.component} — sending it client-rendered`)\n return null\n }\n }\n\n setInertiaRuntime({\n version: () => version(),\n share: config.share,\n errorPage: config.errorPage,\n async renderHtml(page, ctx) {\n const rendered = await render(page)\n const styles = vite && rendered && ssr ? devStylesheets(vite, ssr.entry) : []\n const html = template({\n head: [config.head ?? '', head(page), ...styles, ...(rendered?.head ?? [])]\n .filter(Boolean)\n .join('\\n '),\n body: rendered?.body ?? pageBody(page),\n page,\n })\n return vite ? vite.transformIndexHtml(requestUrl(ctx.req), html) : html\n },\n })\n },\n\n middleware() {\n return [{ handler: protocol as never, phase: 'afterGlobal' as const }]\n },\n\n shutdown() {\n setInertiaRuntime(null)\n },\n }\n },\n})\n","/**\n * Validation errors the Inertia way: on an Inertia request, a 422 from route\n * validation becomes a redirect back with the errors in the session, and the\n * next page gets them as `props.errors`. Everything else goes to the usual\n * error handler.\n *\n * @module @forinda/kickjs-inertia/errors\n */\nimport {\n errorHandler,\n HttpException,\n Logger,\n RequestContext,\n RESPOND,\n type RuntimeResponse,\n} from '@forinda/kickjs'\nimport { ERRORS_KEY, errorPage, inertiaRuntime, isPageRequest, previousUrl } from './inertia'\n\nconst log = Logger.for('Inertia')\n\ntype ErrorHandler = (\n err: any,\n req: any,\n res: RuntimeResponse,\n next: (err?: unknown) => void,\n) => void\n\ninterface SessionLike {\n data: Record<string, unknown>\n save(): Promise<void>\n}\n\n/** `[{ field, message }]` from route validation → `{ field: firstMessage }`. */\nfunction toErrors(details: unknown): Record<string, string> | null {\n if (!Array.isArray(details)) return null\n const errors: Record<string, string> = {}\n for (const d of details as Array<{ field?: string; message?: string }>) {\n const field = d.field || 'form'\n errors[field] ??= d.message ?? 'Invalid value'\n }\n return errors\n}\n\n/**\n * Pass to `bootstrap({ onError: inertiaErrorHandler() })`.\n *\n * - A validation 422 on an Inertia visit goes back to the form with the\n * errors (needs the `session()` middleware to carry them).\n * - Any other error on a page request renders the adapter's `errorPage`,\n * when one is configured.\n * - Everything else — API calls, assets — goes to `fallback`.\n */\nexport function inertiaErrorHandler(fallback: ErrorHandler = errorHandler()): ErrorHandler {\n let warned = false\n return (err, req, res, next) => {\n if (res.headersSent) return fallback(err, req, res, next)\n\n const errors = err instanceof HttpException && err.status === 422 ? toErrors(err.details) : null\n if (errors && req?.headers?.['x-inertia'] === 'true') {\n const session = req.session as SessionLike | undefined\n if (session) {\n const bag = req.headers['x-inertia-error-bag'] as string | undefined\n session.data[ERRORS_KEY] = bag ? { [bag]: errors } : errors\n session\n .save()\n .then(() => {\n res.status(303).setHeader('location', previousUrl(session, req.headers))\n res.end()\n })\n .catch((saveErr) => fallback(saveErr, req, res, next))\n return\n }\n if (!warned) {\n log.warn(\n 'Validation failed on an Inertia request, but there is no session() middleware to carry the errors — answering 422 instead.',\n )\n warned = true\n }\n return fallback(err, req, res, next)\n }\n\n const component = inertiaRuntime()?.errorPage\n if (component && req?.headers && isPageRequest(req.headers)) {\n const status = err instanceof HttpException ? err.status : 500\n if (status >= 500) log.error(err, 'Unhandled error on a page request')\n const ctx = new RequestContext(req, res as never, next, res)\n Promise.resolve(errorPage(component, status, err)[RESPOND](ctx)).catch((renderErr) =>\n fallback(renderErr, req, res, next),\n )\n return\n }\n return fallback(err, req, res, next)\n }\n}\n\ntype NotFoundHandler = (req: any, res: RuntimeResponse, next: (err?: unknown) => void) => void\n\n/**\n * Pass to `bootstrap({ onNotFound: inertiaNotFound() })`. A browser or Inertia\n * request for an unknown URL gets the adapter's `errorPage` with 404; anything\n * else — API calls, other methods — goes on to the built-in 404 / 405.\n */\nexport function inertiaNotFound(): NotFoundHandler {\n return (req, res, next) => {\n const component = inertiaRuntime()?.errorPage\n const method = req?.method ?? 'GET'\n if (\n !component ||\n (method !== 'GET' && method !== 'HEAD') ||\n !isPageRequest(req.headers ?? {})\n ) {\n return next()\n }\n const ctx = new RequestContext(req, res as never, next, res)\n Promise.resolve(errorPage(component, 404)[RESPOND](ctx)).catch(next)\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,68 @@
1
1
  {
2
2
  "name": "@forinda/kickjs-inertia",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.0.0",
4
+ "description": "Inertia.js adapter for KickJS — server-driven pages rendered by React, Vue or Svelte",
5
+ "keywords": [
6
+ "kickjs",
7
+ "inertia",
8
+ "inertiajs",
9
+ "react",
10
+ "vue",
11
+ "svelte",
12
+ "typescript"
13
+ ],
14
+ "type": "module",
15
+ "main": "dist/index.mjs",
16
+ "types": "dist/index.d.mts",
17
+ "exports": {
18
+ ".": {
19
+ "import": "./dist/index.mjs",
20
+ "types": "./dist/index.d.mts"
21
+ }
22
+ },
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "devDependencies": {
27
+ "@fastify/middie": "^9.3.4",
28
+ "@swc/core": "^1.16.13",
29
+ "@types/node": "^26.6.4",
30
+ "@types/supertest": "^7.2.1",
31
+ "express": "^5.1.0",
32
+ "fastify": "^5.12.5",
33
+ "h3": "1.15.11",
34
+ "reflect-metadata": "^0.2.2",
35
+ "supertest": "^7.3.1",
36
+ "typescript": "^7.0.2",
37
+ "vitest": "^5.0.3",
38
+ "@forinda/kickjs": "9.2.0"
39
+ },
40
+ "peerDependencies": {
41
+ "@forinda/kickjs": ">=9.2.0"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "license": "MIT",
47
+ "author": "Felix Orinda",
48
+ "engines": {
49
+ "node": ">=20.0"
50
+ },
51
+ "homepage": "https://kickjs.app/",
52
+ "repository": {
53
+ "type": "git",
54
+ "url": "https://github.com/forinda/kick-js.git",
55
+ "directory": "packages/inertia"
56
+ },
57
+ "bugs": {
58
+ "url": "https://github.com/forinda/kick-js/issues"
59
+ },
60
+ "scripts": {
61
+ "build": "tsdown",
62
+ "dev": "tsdown --watch",
63
+ "test": "vitest run --passWithNoTests",
64
+ "test:watch": "vitest",
65
+ "typecheck": "tsc --noEmit",
66
+ "clean": "rm -rf dist .wireit"
67
+ }
6
68
  }