@arkstack/inertia 0.15.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) 2026 Toneflix Technologies Limited
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.
@@ -0,0 +1,17 @@
1
+ import { Command } from "@h3ravel/musket";
2
+
3
+ //#region src/commands/InertiaSsrCommand.d.ts
4
+ /**
5
+ * Run the Inertia SSR server (the app's built SSR bundle) as a long-running
6
+ * process, restarting it if it crashes.
7
+ *
8
+ * The bundle is built separately (e.g. `vite build --ssr src/ssr.ts --outDir
9
+ * dist-ssr`); this command supervises it.
10
+ */
11
+ declare class InertiaSsrCommand extends Command {
12
+ protected signature: string;
13
+ protected description: string;
14
+ handle(): Promise<void>;
15
+ }
16
+ //#endregion
17
+ export { InertiaSsrCommand };
@@ -0,0 +1,43 @@
1
+ import { n as resolveSsrBundle, o as inertiaConfig, r as superviseProcess } from "../ssr-process-DkRTrGvf.js";
2
+ import { Arkstack } from "@arkstack/contract";
3
+ import { Command } from "@h3ravel/musket";
4
+ import { existsSync } from "node:fs";
5
+ //#region src/commands/InertiaSsrCommand.ts
6
+ /**
7
+ * Run the Inertia SSR server (the app's built SSR bundle) as a long-running
8
+ * process, restarting it if it crashes.
9
+ *
10
+ * The bundle is built separately (e.g. `vite build --ssr src/ssr.ts --outDir
11
+ * dist-ssr`); this command supervises it.
12
+ */
13
+ var InertiaSsrCommand = class extends Command {
14
+ signature = `inertia:ssr
15
+ {--bundle= : Path to the built SSR bundle. Defaults to inertia.ssr.bundle or dist-ssr/ssr.js.}
16
+ {--no-restart : Do not restart the SSR server when it exits.}
17
+ `;
18
+ description = "Run the Inertia SSR server as a long-running process, restarting it if it crashes";
19
+ async handle() {
20
+ const bundle = resolveSsrBundle(Arkstack.rootDir(), this.option("bundle"), inertiaConfig().ssr.bundle);
21
+ if (!existsSync(bundle)) {
22
+ this.error(`Inertia SSR bundle not found at ${bundle}. Build it first, e.g. \`vite build --ssr src/ssr.ts --outDir dist-ssr\`.`);
23
+ return;
24
+ }
25
+ const restart = this.option("restart") !== false;
26
+ this.info(`Starting Inertia SSR server (${bundle})`);
27
+ const controller = superviseProcess(process.execPath, [bundle], {
28
+ cwd: Arkstack.rootDir(),
29
+ restart,
30
+ onExit: (code, willRestart) => {
31
+ if (willRestart) this.warn(`Inertia SSR server exited (code ${code}); restarting…`);
32
+ },
33
+ onError: (error) => {
34
+ this.error(`Failed to start Inertia SSR server: ${error.message}`);
35
+ }
36
+ });
37
+ process.once("SIGINT", () => controller.stop());
38
+ process.once("SIGTERM", () => controller.stop());
39
+ await controller.done;
40
+ }
41
+ };
42
+ //#endregion
43
+ export { InertiaSsrCommand };
@@ -0,0 +1,377 @@
1
+ import { Response } from "@arkstack/http";
2
+
3
+ //#region src/types.d.ts
4
+ /**
5
+ * A bag of props passed to an Inertia page. Values may be plain JSON-serializable
6
+ * data, synchronous/asynchronous callbacks (evaluated lazily when included), or
7
+ * one of the special prop wrappers ({@link LazyPropContract}, {@link AlwaysPropContract},
8
+ * {@link DeferPropContract}).
9
+ */
10
+ type PageProps = Record<string, unknown>;
11
+ /**
12
+ * The page object Inertia exchanges with the client. It is serialized to JSON for
13
+ * Inertia XHR visits and embedded in the root template's `data-page` attribute on
14
+ * the initial full-page visit.
15
+ *
16
+ * @see https://inertiajs.com/the-protocol
17
+ */
18
+ interface InertiaPage {
19
+ /** The client-side page component to render, e.g. `Users/Index`. */
20
+ component: string;
21
+ /** The resolved props for the component. */
22
+ props: PageProps;
23
+ /** The URL of the current page (path + query). */
24
+ url: string;
25
+ /** The current asset version string. */
26
+ version: string;
27
+ /** Deferred prop keys grouped by fetch group, sent so the client can request them after load. */
28
+ deferredProps?: Record<string, string[]>;
29
+ /** Prop keys the client should merge into existing data instead of replacing. */
30
+ mergeProps?: string[];
31
+ /** Whether to clear the client-side history encryption state. */
32
+ clearHistory?: boolean;
33
+ /** Whether to encrypt the client-side history entry. */
34
+ encryptHistory?: boolean;
35
+ }
36
+ /** Configuration for the Inertia adapter, read from `config('inertia')`. */
37
+ interface InertiaConfig {
38
+ /**
39
+ * The root Edge template that wraps the SPA and renders the `data-page`
40
+ * element. Defaults to `app`. When the view does not exist a minimal
41
+ * built-in template is used.
42
+ */
43
+ root_view: string;
44
+ /**
45
+ * The id of the root DOM element the client mounts onto (the element that
46
+ * carries the `data-page` attribute). Defaults to `app`.
47
+ */
48
+ root_id: string;
49
+ /**
50
+ * The asset version. A string, or a function returning one. When the client
51
+ * reports a different version on a GET visit, the adapter responds with a
52
+ * `409` and an `X-Inertia-Location` header so the client performs a full
53
+ * reload. Defaults to `null` (versioning disabled).
54
+ */
55
+ version: string | null | (() => string | null | Promise<string | null>);
56
+ /** Server-side rendering options. */
57
+ ssr: {
58
+ /** Whether the initial visit is rendered by the external SSR server. */enabled: boolean; /** The SSR server's render endpoint. Defaults to the standard Inertia address. */
59
+ url?: string;
60
+ /**
61
+ * Path to the built SSR bundle run by `ark inertia:ssr`. Relative paths
62
+ * are resolved from the app root. Defaults to `dist-ssr/ssr.js`.
63
+ */
64
+ bundle?: string;
65
+ };
66
+ }
67
+ /**
68
+ * The normalized, driver-agnostic view of the current request that the Inertia
69
+ * adapter needs. Each driver middleware constructs one of these from its native
70
+ * request object.
71
+ */
72
+ interface InertiaRequest {
73
+ /** The HTTP method, upper-cased (e.g. `GET`, `POST`, `PUT`). */
74
+ method: string;
75
+ /** The request URL used as `page.url` (path + query string). */
76
+ url: string;
77
+ /** Read a request header by (case-insensitive) name. */
78
+ header(name: string): string | undefined;
79
+ }
80
+ /** Marker contract for a prop that is only included on matching partial reloads. */
81
+ interface LazyPropContract {
82
+ readonly __inertia: 'lazy';
83
+ call(): unknown;
84
+ }
85
+ /** Marker contract for a prop that is always included, even on partial reloads. */
86
+ interface AlwaysPropContract {
87
+ readonly __inertia: 'always';
88
+ call(): unknown;
89
+ }
90
+ /**
91
+ * Marker contract for a prop excluded from the initial response and fetched by
92
+ * the client in a follow-up request, optionally grouped with other deferred props.
93
+ */
94
+ interface DeferPropContract {
95
+ readonly __inertia: 'defer';
96
+ readonly group: string;
97
+ call(): unknown;
98
+ }
99
+ /** Any of the special Inertia prop wrappers. */
100
+ type InertiaPropWrapper = LazyPropContract | AlwaysPropContract | DeferPropContract;
101
+ //#endregion
102
+ //#region src/props.d.ts
103
+ /**
104
+ * A prop that is excluded from the initial page load and only resolved when the
105
+ * client explicitly requests it via a partial reload. Use for expensive data the
106
+ * first render does not need.
107
+ */
108
+ declare class LazyProp implements LazyPropContract {
109
+ private readonly callback;
110
+ readonly __inertia: "lazy";
111
+ constructor(callback: () => unknown);
112
+ call(): unknown;
113
+ }
114
+ /**
115
+ * A prop that is always included in the response — even on partial reloads that
116
+ * do not list it — and cannot be filtered out by `only`/`except`.
117
+ */
118
+ declare class AlwaysProp implements AlwaysPropContract {
119
+ private readonly value;
120
+ readonly __inertia: "always";
121
+ constructor(value: unknown);
122
+ call(): unknown;
123
+ }
124
+ /**
125
+ * A prop excluded from the initial response and fetched by the client in a
126
+ * follow-up request after the page loads. Deferred props sharing a `group` are
127
+ * fetched together in a single request.
128
+ */
129
+ declare class DeferProp implements DeferPropContract {
130
+ private readonly callback;
131
+ readonly group: string;
132
+ readonly __inertia: "defer";
133
+ constructor(callback: () => unknown, group?: string);
134
+ call(): unknown;
135
+ }
136
+ /** Narrow an arbitrary value to one of the Inertia prop wrappers. */
137
+ declare const isPropWrapper: (value: unknown) => value is InertiaPropWrapper;
138
+ declare const isLazyProp: (value: unknown) => value is LazyPropContract;
139
+ declare const isAlwaysProp: (value: unknown) => value is AlwaysPropContract;
140
+ declare const isDeferProp: (value: unknown) => value is DeferPropContract;
141
+ //#endregion
142
+ //#region src/Inertia.d.ts
143
+ /**
144
+ * The Inertia adapter. Build server-driven SPA responses: controllers return
145
+ * `Inertia.render('Page', props)` and the adapter emits either a JSON page object
146
+ * (for Inertia XHR visits) or a full HTML document embedding the page (for the
147
+ * initial visit). Shared props, partial reloads, asset versioning, deferred and
148
+ * lazy props, and redirect status semantics are all handled here.
149
+ *
150
+ * @see https://inertiajs.com/the-protocol
151
+ */
152
+ declare class Inertia {
153
+ /** Require an active request context, throwing a clear error otherwise. */
154
+ private static request;
155
+ /**
156
+ * Render an Inertia page. Returns a {@link Response} the runtime serializes:
157
+ * a JSON page object for Inertia visits, or a full HTML document for the
158
+ * initial page load.
159
+ *
160
+ * @param component The client component name, e.g. `Users/Index`.
161
+ * @param props Props for the component (plain values, callbacks, or prop wrappers).
162
+ */
163
+ static render(component: string, props?: PageProps): Promise<Response>;
164
+ /**
165
+ * Redirect to an external URL. On an Inertia visit this responds `409` with
166
+ * an `X-Inertia-Location` header so the client performs a full page visit;
167
+ * otherwise it issues a normal `302` redirect.
168
+ */
169
+ static location(url: string): Response;
170
+ /**
171
+ * Redirect within the app. For `PUT`/`PATCH`/`DELETE` requests the status is
172
+ * upgraded to `303 See Other` (per the Inertia protocol) unless an explicit
173
+ * status is given, so the client follows the redirect with a `GET`.
174
+ */
175
+ static redirect(url: string, status?: number): Response;
176
+ /** Redirect back to the referring URL (or `fallback`). */
177
+ static back(fallback?: string): Response;
178
+ /** A `409` response carrying an `X-Inertia-Location` header. */
179
+ private static conflict;
180
+ /**
181
+ * Share props with every Inertia response. Called inside a request the data
182
+ * is scoped to that request; called outside (e.g. at boot) it applies globally.
183
+ */
184
+ static share(key: string, value: unknown): void;
185
+ static share(data: PageProps): void;
186
+ /** Read the currently shared props (request-scoped when in a request). */
187
+ static shared(): PageProps;
188
+ /** Set the asset version used for cache-busting, overriding config. */
189
+ static version(version: InertiaConfig['version']): void;
190
+ /** Override Inertia configuration at runtime (e.g. enable SSR programmatically). */
191
+ static configure(partial: Partial<InertiaConfig>): void;
192
+ /**
193
+ * A prop only resolved on a partial reload that explicitly requests it.
194
+ * Excluded from the initial page load.
195
+ */
196
+ static lazy(callback: () => unknown): LazyProp;
197
+ /** Alias of {@link Inertia.lazy} matching the modern Inertia client naming. */
198
+ static optional(callback: () => unknown): LazyProp;
199
+ /** A prop always included in the response, even on partial reloads. */
200
+ static always(value: unknown): AlwaysProp;
201
+ /**
202
+ * A prop excluded from the initial response and fetched by the client after
203
+ * load. Deferred props sharing a `group` are fetched together.
204
+ */
205
+ static defer(callback: () => unknown, group?: string): DeferProp;
206
+ }
207
+ //#endregion
208
+ //#region src/helpers.d.ts
209
+ /**
210
+ * Render an Inertia page, or — when called with no arguments — return the
211
+ * {@link Inertia} manager for chaining (`inertia().share(...)`,
212
+ * `inertia().version(...)`).
213
+ *
214
+ * @example
215
+ * ```ts
216
+ * // In a controller
217
+ * return inertia('Users/Index', { users: await User.all() })
218
+ *
219
+ * // Share data with every response
220
+ * inertia().share('appName', config('app.name'))
221
+ * ```
222
+ */
223
+ declare function inertia(): typeof Inertia;
224
+ declare function inertia(component: string, props?: PageProps): Promise<Response>;
225
+ //#endregion
226
+ //#region src/resolve.d.ts
227
+ interface ResolvedProps {
228
+ props: PageProps;
229
+ deferredProps?: Record<string, string[]>;
230
+ }
231
+ /**
232
+ * Filter and evaluate page props for the current request, honouring Inertia's
233
+ * partial-reload semantics:
234
+ *
235
+ * - On a full visit or a non-matching partial, lazy and deferred props are
236
+ * excluded; `always` and plain props are included.
237
+ * - On a partial reload matching this component, only the requested keys
238
+ * (`X-Inertia-Partial-Data`) are kept — minus `X-Inertia-Partial-Except` —
239
+ * while `always` props are always kept.
240
+ *
241
+ * Deferred props are advertised under `deferredProps` (grouped) on the initial
242
+ * response so the client can request them afterwards.
243
+ */
244
+ declare const resolveProps: (component: string, props: PageProps, request: InertiaRequest) => Promise<ResolvedProps>;
245
+ //#endregion
246
+ //#region src/html.d.ts
247
+ /** Escape a string for safe inclusion in a double-quoted HTML attribute. */
248
+ declare const escapeHtmlAttribute: (value: string) => string;
249
+ /**
250
+ * Build the root mount element carrying the serialized page object in its
251
+ * `data-page` attribute — the element the Inertia client adapter hydrates.
252
+ */
253
+ declare const renderDataPage: (page: InertiaPage, rootId: string) => string;
254
+ /** Minimal built-in root document used when the configured root view is absent. */
255
+ declare const builtInTemplate: (mount: string, head?: string) => string;
256
+ /**
257
+ * Render the full HTML document for an initial (non-XHR) visit.
258
+ *
259
+ * When SSR is enabled the page is rendered by the external SSR server and its
260
+ * markup + head tags are embedded; if the SSR server is unavailable it falls back
261
+ * to a client-rendered mount element. The result is wrapped by the configured
262
+ * `root_view` Edge template (which receives `inertia` and `inertiaHead`
263
+ * variables), or a minimal built-in document when that view is absent.
264
+ */
265
+ declare const renderRootHtml: (page: InertiaPage, config: InertiaConfig) => Promise<string>;
266
+ //#endregion
267
+ //#region src/ssr.d.ts
268
+ /** The default address the Inertia SSR server listens on. */
269
+ declare const DEFAULT_SSR_URL = "http://127.0.0.1:13714/render";
270
+ /** The payload returned by an Inertia SSR server for a rendered page. */
271
+ interface SsrResponse {
272
+ /** HTML strings to inject into the document `<head>` (title, meta, style). */
273
+ head: string[];
274
+ /** The rendered `<div id="app" data-page="…">…</div>` mount element. */
275
+ body: string;
276
+ }
277
+ /**
278
+ * Render a page via an external Inertia SSR server.
279
+ *
280
+ * POSTs the page object to the SSR endpoint (a Node process running the app's SSR
281
+ * bundle) and returns its `{ head, body }`. Returns `null` on any failure — an
282
+ * unreachable server, a non-2xx response, or a malformed payload — so the caller
283
+ * can fall back to client-side rendering rather than failing the request.
284
+ *
285
+ * @see https://inertiajs.com/server-side-rendering
286
+ */
287
+ declare const renderViaSsr: (page: InertiaPage, url?: string) => Promise<SsrResponse | null>;
288
+ //#endregion
289
+ //#region src/ssr-process.d.ts
290
+ /** The default location of the built SSR bundle, relative to the app root. */
291
+ declare const DEFAULT_SSR_BUNDLE = "dist-ssr/ssr.js";
292
+ /**
293
+ * Resolve the SSR bundle path run by `ark inertia:ssr`.
294
+ *
295
+ * Precedence: an explicit `--bundle` option, then `inertia.ssr.bundle` config,
296
+ * then {@link DEFAULT_SSR_BUNDLE}. Relative paths are resolved from `rootDir`.
297
+ */
298
+ declare const resolveSsrBundle: (rootDir: string, option?: string, configured?: string) => string;
299
+ interface SuperviseOptions {
300
+ /** Working directory for the spawned process. */
301
+ cwd?: string;
302
+ /** Restart the process when it exits unexpectedly (default `true`). */
303
+ restart?: boolean;
304
+ /** Delay before restarting after a crash, in ms (default `1000`). */
305
+ restartDelayMs?: number;
306
+ /** Called when the child exits; `willRestart` reflects the restart decision. */
307
+ onExit?: (code: number | null, willRestart: boolean) => void;
308
+ /** Called when the child fails to spawn. */
309
+ onError?: (error: Error) => void;
310
+ }
311
+ /** Handle to a supervised process. */
312
+ interface SsrProcessController {
313
+ /** Stop the process and prevent further restarts. */
314
+ stop(): void;
315
+ /** Resolves when the process has stopped (and will not restart). */
316
+ done: Promise<void>;
317
+ }
318
+ /**
319
+ * Spawn and supervise a long-running process, restarting it when it crashes
320
+ * until {@link SsrProcessController.stop} is called. Used by `ark inertia:ssr` to
321
+ * keep the Inertia SSR server alive.
322
+ */
323
+ declare const superviseProcess: (command: string, args: string[], options?: SuperviseOptions) => SsrProcessController;
324
+ //#endregion
325
+ //#region src/config.d.ts
326
+ /** The defaults applied when an app provides no (or partial) `inertia` config. */
327
+ declare const defaultConfig: InertiaConfig;
328
+ /**
329
+ * Override Inertia configuration at runtime, taking precedence over
330
+ * `src/config/inertia.ts`. Useful for programmatic setups and tests. Merges
331
+ * shallowly, with `ssr` merged one level deep.
332
+ */
333
+ declare const configure: (partial: Partial<InertiaConfig>) => void;
334
+ /** Clear any runtime configuration overrides (primarily for tests). */
335
+ declare const resetConfig: () => void;
336
+ /**
337
+ * Read the merged Inertia configuration. Runtime overrides ({@link configure})
338
+ * are layered over the app's `src/config/inertia.ts`, which is layered over
339
+ * {@link defaultConfig}. Never throws — a missing config file falls back entirely
340
+ * to the defaults.
341
+ */
342
+ declare const inertiaConfig: () => InertiaConfig;
343
+ /** Set the asset version at runtime, taking precedence over config. */
344
+ declare const setVersion: (version: InertiaConfig["version"]) => void;
345
+ /** Resolve the current asset version to a string (empty string when disabled). */
346
+ declare const resolveVersion: () => Promise<string>;
347
+ //#endregion
348
+ //#region src/context.d.ts
349
+ /** Per-request state the Inertia adapter carries through the request lifecycle. */
350
+ interface InertiaStore {
351
+ /** The normalized current request. */
352
+ request: InertiaRequest;
353
+ /** Props shared for the duration of this request (seeded from the global bag). */
354
+ shared: PageProps;
355
+ }
356
+ /** Run `callback` with `request` bound as the active Inertia context. */
357
+ declare const runInertia: <T>(request: InertiaRequest, callback: () => T) => T;
358
+ /** The active store, or `undefined` when called outside an Inertia request. */
359
+ declare const currentStore: () => InertiaStore | undefined;
360
+ /** Merge data into the shared bag — the current request's if active, else the global one. */
361
+ declare const shareData: (data: PageProps) => void;
362
+ /** Read the effective shared bag (request-scoped when active, else global). */
363
+ declare const sharedData: () => PageProps;
364
+ /** Remove every globally shared prop (primarily for tests). */
365
+ declare const flushShared: () => void;
366
+ //#endregion
367
+ //#region src/protocol.d.ts
368
+ /** Methods whose redirects must use `303 See Other` so the Inertia client re-issues a GET. */
369
+ declare const SEE_OTHER_METHODS: Set<string>;
370
+ /**
371
+ * Whether a `302` redirect produced for the given method should be upgraded to a
372
+ * `303 See Other` on an Inertia visit. Inertia's client follows a `303` with a
373
+ * `GET`, which is required after `PUT`/`PATCH`/`DELETE` mutations.
374
+ */
375
+ declare const shouldUpgradeRedirect: (method: string, status: number) => boolean;
376
+ //#endregion
377
+ export { AlwaysProp, AlwaysPropContract, DEFAULT_SSR_BUNDLE, DEFAULT_SSR_URL, DeferProp, DeferPropContract, Inertia, InertiaConfig, InertiaPage, InertiaPropWrapper, InertiaRequest, type InertiaStore, LazyProp, LazyPropContract, PageProps, SEE_OTHER_METHODS, type SsrProcessController, type SsrResponse, type SuperviseOptions, builtInTemplate, configure, currentStore, defaultConfig, escapeHtmlAttribute, flushShared, inertia, inertiaConfig, isAlwaysProp, isDeferProp, isLazyProp, isPropWrapper, renderDataPage, renderRootHtml, renderViaSsr, resetConfig, resolveProps, resolveSsrBundle, resolveVersion, runInertia, setVersion, shareData, sharedData, shouldUpgradeRedirect, superviseProcess };
package/dist/index.js ADDED
@@ -0,0 +1,394 @@
1
+ import { a as defaultConfig, c as resolveVersion, i as configure, l as setVersion, n as resolveSsrBundle, o as inertiaConfig, r as superviseProcess, s as resetConfig, t as DEFAULT_SSR_BUNDLE } from "./ssr-process-DkRTrGvf.js";
2
+ import { AsyncLocalStorage } from "node:async_hooks";
3
+ import { Response } from "@arkstack/http";
4
+ //#region src/props.ts
5
+ /**
6
+ * A prop that is excluded from the initial page load and only resolved when the
7
+ * client explicitly requests it via a partial reload. Use for expensive data the
8
+ * first render does not need.
9
+ */
10
+ var LazyProp = class {
11
+ callback;
12
+ __inertia = "lazy";
13
+ constructor(callback) {
14
+ this.callback = callback;
15
+ }
16
+ call() {
17
+ return this.callback();
18
+ }
19
+ };
20
+ /**
21
+ * A prop that is always included in the response — even on partial reloads that
22
+ * do not list it — and cannot be filtered out by `only`/`except`.
23
+ */
24
+ var AlwaysProp = class {
25
+ value;
26
+ __inertia = "always";
27
+ constructor(value) {
28
+ this.value = value;
29
+ }
30
+ call() {
31
+ return typeof this.value === "function" ? this.value() : this.value;
32
+ }
33
+ };
34
+ /**
35
+ * A prop excluded from the initial response and fetched by the client in a
36
+ * follow-up request after the page loads. Deferred props sharing a `group` are
37
+ * fetched together in a single request.
38
+ */
39
+ var DeferProp = class {
40
+ callback;
41
+ group;
42
+ __inertia = "defer";
43
+ constructor(callback, group = "default") {
44
+ this.callback = callback;
45
+ this.group = group;
46
+ }
47
+ call() {
48
+ return this.callback();
49
+ }
50
+ };
51
+ /** Narrow an arbitrary value to one of the Inertia prop wrappers. */
52
+ const isPropWrapper = (value) => {
53
+ return Boolean(value && typeof value === "object" && "__inertia" in value && typeof value.call === "function");
54
+ };
55
+ const isLazyProp = (value) => isPropWrapper(value) && value.__inertia === "lazy";
56
+ const isAlwaysProp = (value) => isPropWrapper(value) && value.__inertia === "always";
57
+ const isDeferProp = (value) => isPropWrapper(value) && value.__inertia === "defer";
58
+ //#endregion
59
+ //#region src/context.ts
60
+ /**
61
+ * Async-local store binding the current request to the Inertia helpers. The
62
+ * driver middleware runs the downstream handler inside {@link runInertia} so that
63
+ * `inertia()` / `Inertia.*` can resolve the active request and shared props
64
+ * without threading them through every call — mirroring how resora binds its
65
+ * `{ req, res }` context.
66
+ */
67
+ const storage = new AsyncLocalStorage();
68
+ /**
69
+ * Props shared across every request (set outside a request, e.g. at boot). Each
70
+ * request seeds its own {@link InertiaStore.shared} bag from this so per-request
71
+ * sharing never leaks between requests.
72
+ */
73
+ const globalShared = {};
74
+ /** Run `callback` with `request` bound as the active Inertia context. */
75
+ const runInertia = (request, callback) => {
76
+ return storage.run({
77
+ request,
78
+ shared: { ...globalShared }
79
+ }, callback);
80
+ };
81
+ /** The active store, or `undefined` when called outside an Inertia request. */
82
+ const currentStore = () => storage.getStore();
83
+ /** Merge data into the shared bag — the current request's if active, else the global one. */
84
+ const shareData = (data) => {
85
+ const target = storage.getStore()?.shared ?? globalShared;
86
+ Object.assign(target, data);
87
+ };
88
+ /** Read the effective shared bag (request-scoped when active, else global). */
89
+ const sharedData = () => {
90
+ return storage.getStore()?.shared ?? globalShared;
91
+ };
92
+ /** Remove every globally shared prop (primarily for tests). */
93
+ const flushShared = () => {
94
+ for (const key of Object.keys(globalShared)) delete globalShared[key];
95
+ };
96
+ //#endregion
97
+ //#region src/protocol.ts
98
+ /** Methods whose redirects must use `303 See Other` so the Inertia client re-issues a GET. */
99
+ const SEE_OTHER_METHODS = new Set([
100
+ "PUT",
101
+ "PATCH",
102
+ "DELETE"
103
+ ]);
104
+ /**
105
+ * Whether a `302` redirect produced for the given method should be upgraded to a
106
+ * `303 See Other` on an Inertia visit. Inertia's client follows a `303` with a
107
+ * `GET`, which is required after `PUT`/`PATCH`/`DELETE` mutations.
108
+ */
109
+ const shouldUpgradeRedirect = (method, status) => {
110
+ return status === 302 && SEE_OTHER_METHODS.has(method.toUpperCase());
111
+ };
112
+ //#endregion
113
+ //#region src/ssr.ts
114
+ /** The default address the Inertia SSR server listens on. */
115
+ const DEFAULT_SSR_URL = "http://127.0.0.1:13714/render";
116
+ /**
117
+ * Render a page via an external Inertia SSR server.
118
+ *
119
+ * POSTs the page object to the SSR endpoint (a Node process running the app's SSR
120
+ * bundle) and returns its `{ head, body }`. Returns `null` on any failure — an
121
+ * unreachable server, a non-2xx response, or a malformed payload — so the caller
122
+ * can fall back to client-side rendering rather than failing the request.
123
+ *
124
+ * @see https://inertiajs.com/server-side-rendering
125
+ */
126
+ const renderViaSsr = async (page, url = DEFAULT_SSR_URL) => {
127
+ try {
128
+ const response = await fetch(url, {
129
+ method: "POST",
130
+ headers: { "Content-Type": "application/json" },
131
+ body: JSON.stringify(page)
132
+ });
133
+ if (!response.ok) return null;
134
+ const data = await response.json();
135
+ if (!data || typeof data.body !== "string") return null;
136
+ return {
137
+ head: Array.isArray(data.head) ? data.head : [],
138
+ body: data.body
139
+ };
140
+ } catch {
141
+ return null;
142
+ }
143
+ };
144
+ //#endregion
145
+ //#region src/html.ts
146
+ /** Escape a string for safe inclusion in a double-quoted HTML attribute. */
147
+ const escapeHtmlAttribute = (value) => {
148
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
149
+ };
150
+ /**
151
+ * Build the root mount element carrying the serialized page object in its
152
+ * `data-page` attribute — the element the Inertia client adapter hydrates.
153
+ */
154
+ const renderDataPage = (page, rootId) => {
155
+ return `<div id="${rootId}" data-page="${escapeHtmlAttribute(JSON.stringify(page))}"></div>`;
156
+ };
157
+ /** Minimal built-in root document used when the configured root view is absent. */
158
+ const builtInTemplate = (mount, head = "") => {
159
+ return [
160
+ "<!DOCTYPE html>",
161
+ "<html>",
162
+ "<head>",
163
+ "<meta charset=\"utf-8\">",
164
+ "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">",
165
+ ...head ? [head] : [],
166
+ "</head>",
167
+ "<body>",
168
+ mount,
169
+ "</body>",
170
+ "</html>"
171
+ ].join("\n");
172
+ };
173
+ /**
174
+ * Render the full HTML document for an initial (non-XHR) visit.
175
+ *
176
+ * When SSR is enabled the page is rendered by the external SSR server and its
177
+ * markup + head tags are embedded; if the SSR server is unavailable it falls back
178
+ * to a client-rendered mount element. The result is wrapped by the configured
179
+ * `root_view` Edge template (which receives `inertia` and `inertiaHead`
180
+ * variables), or a minimal built-in document when that view is absent.
181
+ */
182
+ const renderRootHtml = async (page, config) => {
183
+ let mount = renderDataPage(page, config.root_id);
184
+ let head = "";
185
+ if (config.ssr?.enabled) {
186
+ const ssr = await renderViaSsr(page, config.ssr.url);
187
+ if (ssr) {
188
+ mount = ssr.body;
189
+ head = ssr.head.join("\n");
190
+ }
191
+ }
192
+ try {
193
+ const { view } = await import("@arkstack/view");
194
+ if (view().exists(config.root_view)) return await view(config.root_view, {
195
+ page,
196
+ inertia: mount,
197
+ inertiaHead: head
198
+ }).render();
199
+ } catch {}
200
+ return builtInTemplate(mount, head);
201
+ };
202
+ //#endregion
203
+ //#region src/resolve.ts
204
+ /** Parse a comma-separated partial-reload header into a trimmed, non-empty list. */
205
+ const csv = (value) => {
206
+ if (!value) return [];
207
+ return value.split(",").map((entry) => entry.trim()).filter(Boolean);
208
+ };
209
+ /** Recursively evaluate prop wrappers and callbacks within a value. */
210
+ const evaluate = async (value) => {
211
+ if (isPropWrapper(value)) return evaluate(value.call());
212
+ if (typeof value === "function") return evaluate(value());
213
+ if (value && typeof value.then === "function") return evaluate(await value);
214
+ if (Array.isArray(value)) return Promise.all(value.map((entry) => evaluate(entry)));
215
+ if (value && typeof value === "object" && value.constructor === Object) {
216
+ const entries = await Promise.all(Object.entries(value).map(async ([key, val]) => [key, await evaluate(val)]));
217
+ return Object.fromEntries(entries);
218
+ }
219
+ return value;
220
+ };
221
+ /**
222
+ * Filter and evaluate page props for the current request, honouring Inertia's
223
+ * partial-reload semantics:
224
+ *
225
+ * - On a full visit or a non-matching partial, lazy and deferred props are
226
+ * excluded; `always` and plain props are included.
227
+ * - On a partial reload matching this component, only the requested keys
228
+ * (`X-Inertia-Partial-Data`) are kept — minus `X-Inertia-Partial-Except` —
229
+ * while `always` props are always kept.
230
+ *
231
+ * Deferred props are advertised under `deferredProps` (grouped) on the initial
232
+ * response so the client can request them afterwards.
233
+ */
234
+ const resolveProps = async (component, props, request) => {
235
+ const isPartial = request.header("x-inertia") === "true" && request.header("x-inertia-partial-component") === component;
236
+ const only = csv(request.header("x-inertia-partial-data"));
237
+ const except = csv(request.header("x-inertia-partial-except"));
238
+ const deferred = {};
239
+ const included = {};
240
+ for (const [key, value] of Object.entries(props)) {
241
+ let include;
242
+ if (!isPartial) {
243
+ if (isDeferProp(value)) (deferred[value.group] ??= []).push(key);
244
+ include = !isLazyProp(value) && !isDeferProp(value);
245
+ } else {
246
+ include = only.length ? only.includes(key) : true;
247
+ if (except.length && except.includes(key)) include = false;
248
+ if (isDeferProp(value) && !only.includes(key)) include = false;
249
+ }
250
+ if (isAlwaysProp(value)) include = true;
251
+ if (include) included[key] = await evaluate(value);
252
+ }
253
+ return {
254
+ props: included,
255
+ deferredProps: !isPartial && Object.keys(deferred).length ? deferred : void 0
256
+ };
257
+ };
258
+ //#endregion
259
+ //#region src/Inertia.ts
260
+ const isInertiaRequest = (request) => request.header("x-inertia") === "true";
261
+ const jsonResponse = (page) => {
262
+ return new Response({
263
+ statusCode: 200,
264
+ body: page
265
+ }).header("Content-Type", "application/json; charset=utf-8").header("Vary", "X-Inertia").header("X-Inertia", "true");
266
+ };
267
+ const htmlResponse = (html) => {
268
+ return new Response({
269
+ statusCode: 200,
270
+ body: html
271
+ }).header("Content-Type", "text/html; charset=utf-8").header("Vary", "X-Inertia");
272
+ };
273
+ /**
274
+ * The Inertia adapter. Build server-driven SPA responses: controllers return
275
+ * `Inertia.render('Page', props)` and the adapter emits either a JSON page object
276
+ * (for Inertia XHR visits) or a full HTML document embedding the page (for the
277
+ * initial visit). Shared props, partial reloads, asset versioning, deferred and
278
+ * lazy props, and redirect status semantics are all handled here.
279
+ *
280
+ * @see https://inertiajs.com/the-protocol
281
+ */
282
+ var Inertia = class Inertia {
283
+ /** Require an active request context, throwing a clear error otherwise. */
284
+ static request() {
285
+ const store = currentStore();
286
+ if (!store) throw new Error("Inertia called outside of a request. Ensure the Inertia middleware is registered.");
287
+ return store.request;
288
+ }
289
+ /**
290
+ * Render an Inertia page. Returns a {@link Response} the runtime serializes:
291
+ * a JSON page object for Inertia visits, or a full HTML document for the
292
+ * initial page load.
293
+ *
294
+ * @param component The client component name, e.g. `Users/Index`.
295
+ * @param props Props for the component (plain values, callbacks, or prop wrappers).
296
+ */
297
+ static async render(component, props = {}) {
298
+ const request = Inertia.request();
299
+ const config = inertiaConfig();
300
+ const version = await resolveVersion();
301
+ if (isInertiaRequest(request) && request.method === "GET" && (request.header("x-inertia-version") ?? "") !== version) return Inertia.conflict(request.url);
302
+ const { props: resolved, deferredProps } = await resolveProps(component, {
303
+ ...sharedData(),
304
+ ...props
305
+ }, request);
306
+ const page = {
307
+ component,
308
+ props: resolved,
309
+ url: request.url,
310
+ version,
311
+ ...deferredProps ? { deferredProps } : {}
312
+ };
313
+ if (isInertiaRequest(request)) return jsonResponse(page);
314
+ return htmlResponse(await renderRootHtml(page, config));
315
+ }
316
+ /**
317
+ * Redirect to an external URL. On an Inertia visit this responds `409` with
318
+ * an `X-Inertia-Location` header so the client performs a full page visit;
319
+ * otherwise it issues a normal `302` redirect.
320
+ */
321
+ static location(url) {
322
+ if (isInertiaRequest(Inertia.request())) return Inertia.conflict(url);
323
+ return Inertia.redirect(url, 302);
324
+ }
325
+ /**
326
+ * Redirect within the app. For `PUT`/`PATCH`/`DELETE` requests the status is
327
+ * upgraded to `303 See Other` (per the Inertia protocol) unless an explicit
328
+ * status is given, so the client follows the redirect with a `GET`.
329
+ */
330
+ static redirect(url, status) {
331
+ const method = Inertia.request().method;
332
+ return new Response({
333
+ statusCode: status ?? (SEE_OTHER_METHODS.has(method) ? 303 : 302),
334
+ body: null
335
+ }).header("Location", url);
336
+ }
337
+ /** Redirect back to the referring URL (or `fallback`). */
338
+ static back(fallback = "/") {
339
+ const request = Inertia.request();
340
+ return Inertia.redirect(request.header("referer") || request.header("referrer") || fallback);
341
+ }
342
+ /** A `409` response carrying an `X-Inertia-Location` header. */
343
+ static conflict(url) {
344
+ return new Response({
345
+ statusCode: 409,
346
+ body: null
347
+ }).header("X-Inertia-Location", url);
348
+ }
349
+ static share(keyOrData, value) {
350
+ shareData(typeof keyOrData === "string" ? { [keyOrData]: value } : keyOrData);
351
+ }
352
+ /** Read the currently shared props (request-scoped when in a request). */
353
+ static shared() {
354
+ return { ...sharedData() };
355
+ }
356
+ /** Set the asset version used for cache-busting, overriding config. */
357
+ static version(version) {
358
+ setVersion(version);
359
+ }
360
+ /** Override Inertia configuration at runtime (e.g. enable SSR programmatically). */
361
+ static configure(partial) {
362
+ configure(partial);
363
+ }
364
+ /**
365
+ * A prop only resolved on a partial reload that explicitly requests it.
366
+ * Excluded from the initial page load.
367
+ */
368
+ static lazy(callback) {
369
+ return new LazyProp(callback);
370
+ }
371
+ /** Alias of {@link Inertia.lazy} matching the modern Inertia client naming. */
372
+ static optional(callback) {
373
+ return new LazyProp(callback);
374
+ }
375
+ /** A prop always included in the response, even on partial reloads. */
376
+ static always(value) {
377
+ return new AlwaysProp(value);
378
+ }
379
+ /**
380
+ * A prop excluded from the initial response and fetched by the client after
381
+ * load. Deferred props sharing a `group` are fetched together.
382
+ */
383
+ static defer(callback, group = "default") {
384
+ return new DeferProp(callback, group);
385
+ }
386
+ };
387
+ //#endregion
388
+ //#region src/helpers.ts
389
+ function inertia(component, props = {}) {
390
+ if (component === void 0) return Inertia;
391
+ return Inertia.render(component, props);
392
+ }
393
+ //#endregion
394
+ export { AlwaysProp, DEFAULT_SSR_BUNDLE, DEFAULT_SSR_URL, DeferProp, Inertia, LazyProp, SEE_OTHER_METHODS, builtInTemplate, configure, currentStore, defaultConfig, escapeHtmlAttribute, flushShared, inertia, inertiaConfig, isAlwaysProp, isDeferProp, isLazyProp, isPropWrapper, renderDataPage, renderRootHtml, renderViaSsr, resetConfig, resolveProps, resolveSsrBundle, resolveVersion, runInertia, setVersion, shareData, sharedData, shouldUpgradeRedirect, superviseProcess };
@@ -0,0 +1 @@
1
+ export { };
package/dist/setup.js ADDED
@@ -0,0 +1,30 @@
1
+ import { Publisher } from "@arkstack/common";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ //#region src/setup.ts
5
+ const root = join(dirname(fileURLToPath(import.meta.url)), "..");
6
+ /**
7
+ * Register the artifacts `@arkstack/inertia` publishes into the application.
8
+ *
9
+ * Run `ark publish --package @arkstack/inertia` to copy the Inertia config and
10
+ * the root Edge template into the app, then customize them for your front-end
11
+ * (Vite tags, title, meta, etc.).
12
+ */
13
+ Publisher.publishes({
14
+ package: "@arkstack/inertia",
15
+ tag: "inertia-config",
16
+ entries: [{
17
+ from: join(root, "stubs/config/inertia.ts.stub"),
18
+ to: "src/config/inertia.ts"
19
+ }]
20
+ });
21
+ Publisher.publishes({
22
+ package: "@arkstack/inertia",
23
+ tag: "inertia-views",
24
+ entries: [{
25
+ from: join(root, "stubs/views/app.edge.stub"),
26
+ to: "src/resources/views/app.edge"
27
+ }]
28
+ });
29
+ //#endregion
30
+ export {};
@@ -0,0 +1,127 @@
1
+ import { config } from "@arkstack/common";
2
+ import { spawn } from "node:child_process";
3
+ import { isAbsolute, join } from "node:path";
4
+ //#region src/config.ts
5
+ /** The defaults applied when an app provides no (or partial) `inertia` config. */
6
+ const defaultConfig = {
7
+ root_view: "app",
8
+ root_id: "app",
9
+ version: null,
10
+ ssr: { enabled: false }
11
+ };
12
+ /** Runtime overrides applied on top of file config via {@link configure}. */
13
+ let configOverrides = {};
14
+ /**
15
+ * Override Inertia configuration at runtime, taking precedence over
16
+ * `src/config/inertia.ts`. Useful for programmatic setups and tests. Merges
17
+ * shallowly, with `ssr` merged one level deep.
18
+ */
19
+ const configure = (partial) => {
20
+ configOverrides = {
21
+ ...configOverrides,
22
+ ...partial,
23
+ ...partial.ssr ? { ssr: {
24
+ ...configOverrides.ssr,
25
+ ...partial.ssr
26
+ } } : {}
27
+ };
28
+ };
29
+ /** Clear any runtime configuration overrides (primarily for tests). */
30
+ const resetConfig = () => {
31
+ configOverrides = {};
32
+ };
33
+ /**
34
+ * Read the merged Inertia configuration. Runtime overrides ({@link configure})
35
+ * are layered over the app's `src/config/inertia.ts`, which is layered over
36
+ * {@link defaultConfig}. Never throws — a missing config file falls back entirely
37
+ * to the defaults.
38
+ */
39
+ const inertiaConfig = () => {
40
+ let userConfig;
41
+ try {
42
+ userConfig = config("inertia", {});
43
+ } catch {
44
+ userConfig = void 0;
45
+ }
46
+ return {
47
+ ...defaultConfig,
48
+ ...userConfig ?? {},
49
+ ...configOverrides,
50
+ ssr: {
51
+ ...defaultConfig.ssr,
52
+ ...userConfig?.ssr ?? {},
53
+ ...configOverrides.ssr ?? {}
54
+ }
55
+ };
56
+ };
57
+ /** A version override set at runtime via `Inertia.version(...)`, if any. */
58
+ let versionOverride;
59
+ /** Set the asset version at runtime, taking precedence over config. */
60
+ const setVersion = (version) => {
61
+ versionOverride = version;
62
+ };
63
+ /** Resolve the current asset version to a string (empty string when disabled). */
64
+ const resolveVersion = async () => {
65
+ const source = versionOverride ?? inertiaConfig().version;
66
+ const value = typeof source === "function" ? await source() : source;
67
+ return value == null ? "" : String(value);
68
+ };
69
+ //#endregion
70
+ //#region src/ssr-process.ts
71
+ /** The default location of the built SSR bundle, relative to the app root. */
72
+ const DEFAULT_SSR_BUNDLE = "dist-ssr/ssr.js";
73
+ /**
74
+ * Resolve the SSR bundle path run by `ark inertia:ssr`.
75
+ *
76
+ * Precedence: an explicit `--bundle` option, then `inertia.ssr.bundle` config,
77
+ * then {@link DEFAULT_SSR_BUNDLE}. Relative paths are resolved from `rootDir`.
78
+ */
79
+ const resolveSsrBundle = (rootDir, option, configured) => {
80
+ const bundle = option || configured || "dist-ssr/ssr.js";
81
+ return isAbsolute(bundle) ? bundle : join(rootDir, bundle);
82
+ };
83
+ /**
84
+ * Spawn and supervise a long-running process, restarting it when it crashes
85
+ * until {@link SsrProcessController.stop} is called. Used by `ark inertia:ssr` to
86
+ * keep the Inertia SSR server alive.
87
+ */
88
+ const superviseProcess = (command, args, options = {}) => {
89
+ const { cwd, restart = true, restartDelayMs = 1e3 } = options;
90
+ let child;
91
+ let timer;
92
+ let stopping = false;
93
+ let resolveDone = () => {};
94
+ const done = new Promise((resolve) => {
95
+ resolveDone = resolve;
96
+ });
97
+ const start = () => {
98
+ child = spawn(command, args, {
99
+ cwd,
100
+ stdio: "inherit"
101
+ });
102
+ child.on("error", (error) => {
103
+ options.onError?.(error);
104
+ resolveDone();
105
+ });
106
+ child.on("exit", (code) => {
107
+ const willRestart = !stopping && restart;
108
+ options.onExit?.(code, willRestart);
109
+ if (!willRestart) {
110
+ resolveDone();
111
+ return;
112
+ }
113
+ timer = setTimeout(start, restartDelayMs);
114
+ });
115
+ };
116
+ start();
117
+ return {
118
+ stop() {
119
+ stopping = true;
120
+ if (timer) clearTimeout(timer);
121
+ child?.kill("SIGTERM");
122
+ },
123
+ done
124
+ };
125
+ };
126
+ //#endregion
127
+ export { defaultConfig as a, resolveVersion as c, configure as i, setVersion as l, resolveSsrBundle as n, inertiaConfig as o, superviseProcess as r, resetConfig as s, DEFAULT_SSR_BUNDLE as t };
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@arkstack/inertia",
3
+ "version": "0.15.0",
4
+ "type": "module",
5
+ "description": "InertiaJS server-side adapter for Arkstack, building the modern monolith with server-driven SPAs.",
6
+ "homepage": "https://arkstack.toneflix.net/guide/inertia",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/arkstack-hq/arkstack.git",
10
+ "directory": "packages/inertia"
11
+ },
12
+ "keywords": [
13
+ "inertia",
14
+ "inertiajs",
15
+ "spa",
16
+ "monolith",
17
+ "vue",
18
+ "react",
19
+ "svelte",
20
+ "arkstack"
21
+ ],
22
+ "files": [
23
+ "dist",
24
+ "stubs"
25
+ ],
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "exports": {
30
+ ".": "./dist/index.js",
31
+ "./commands/InertiaSsrCommand": "./dist/commands/InertiaSsrCommand.js",
32
+ "./setup": "./dist/setup.js",
33
+ "./package.json": "./package.json"
34
+ },
35
+ "dependencies": {
36
+ "@arkstack/common": "^0.15.0"
37
+ },
38
+ "peerDependencies": {
39
+ "@h3ravel/musket": "^2.2.1",
40
+ "@arkstack/http": "^0.15.0",
41
+ "@arkstack/view": "^0.15.0",
42
+ "@arkstack/contract": "^0.15.0"
43
+ },
44
+ "peerDependenciesMeta": {
45
+ "@arkstack/view": {
46
+ "optional": true
47
+ },
48
+ "@h3ravel/musket": {
49
+ "optional": true
50
+ }
51
+ },
52
+ "scripts": {
53
+ "build": "tsdown",
54
+ "test": "vitest",
55
+ "version:patch": "pnpm version patch"
56
+ }
57
+ }
@@ -0,0 +1,40 @@
1
+ import type { InertiaConfig } from '@arkstack/inertia';
2
+ import { env } from '@arkstack/common';
3
+
4
+ /**
5
+ * Inertia adapter configuration.
6
+ *
7
+ * @see https://arkstack.toneflix.net/guide/inertia
8
+ */
9
+ export default (): InertiaConfig => {
10
+ return {
11
+ /**
12
+ * The root Edge template that wraps the SPA. It renders the `{{{ inertia }}}`
13
+ * mount element and loads your client bundle (e.g. via Vite tags).
14
+ */
15
+ root_view: 'app',
16
+
17
+ /** The id of the DOM element the Inertia client mounts onto. */
18
+ root_id: 'app',
19
+
20
+ /**
21
+ * Asset version used for cache-busting. When the client's version differs on
22
+ * a GET visit, Inertia forces a full reload so stale assets are replaced.
23
+ * Return a string (e.g. a build hash) or `null` to disable versioning.
24
+ */
25
+ version: env('INERTIA_VERSION', null),
26
+
27
+ /**
28
+ * Server-side rendering. When enabled, the initial page is rendered by an
29
+ * external SSR server (your built SSR bundle) and the markup is embedded in
30
+ * the response; if that server is unreachable the adapter falls back to
31
+ * client-side rendering. `url` is the SSR server's render endpoint, and
32
+ * `bundle` is the built SSR entry run by `ark inertia:ssr`.
33
+ */
34
+ ssr: {
35
+ enabled: env('INERTIA_SSR', false),
36
+ url: env('INERTIA_SSR_URL', 'http://127.0.0.1:13714/render'),
37
+ bundle: 'dist-ssr/ssr.js',
38
+ },
39
+ };
40
+ };
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ {{{ inertiaHead }}}
7
+ {{-- Replace with your Vite client + entry tags, e.g.: --}}
8
+ {{-- <script type="module" src="http://localhost:5173/@@vite/client"></script> --}}
9
+ {{-- <script type="module" src="http://localhost:5173/src/main.ts"></script> --}}
10
+ </head>
11
+ <body>
12
+ {{{ inertia }}}
13
+ </body>
14
+ </html>