@stratal/inertia 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -0
- package/dist/index.d.mts +259 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +784 -0
- package/dist/index.mjs.map +1 -0
- package/dist/inertia-dev-css-plugin-BYromyO_.mjs +70 -0
- package/dist/inertia-dev-css-plugin-BYromyO_.mjs.map +1 -0
- package/dist/inertia-types-plugin-NO_uxhxQ.mjs +39 -0
- package/dist/inertia-types-plugin-NO_uxhxQ.mjs.map +1 -0
- package/dist/rolldown-runtime-wcPFST8Q.mjs +13 -0
- package/dist/type-generator-DlXIc6e2.mjs +193 -0
- package/dist/type-generator-DlXIc6e2.mjs.map +1 -0
- package/dist/vite.d.mts +14 -0
- package/dist/vite.d.mts.map +1 -0
- package/dist/vite.mjs +4 -0
- package/global.d.ts +11 -0
- package/package.json +107 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,784 @@
|
|
|
1
|
+
import { n as runTypeGeneration, t as findPagesDir } from "./type-generator-DlXIc6e2.mjs";
|
|
2
|
+
import { Module } from "stratal/module";
|
|
3
|
+
import { Route, RouterContext } from "stratal/router";
|
|
4
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { join, relative } from "node:path";
|
|
6
|
+
import { Command } from "stratal/quarry";
|
|
7
|
+
import { watch } from "node:fs/promises";
|
|
8
|
+
import { Transient, inject } from "stratal/di";
|
|
9
|
+
import { z } from "stratal/validation";
|
|
10
|
+
//#region src/augment/router-context.ts
|
|
11
|
+
function augmentRouterContext(resolveService) {
|
|
12
|
+
const proto = RouterContext.prototype;
|
|
13
|
+
const originalRedirect = proto.redirect;
|
|
14
|
+
proto.redirect = function(url, status) {
|
|
15
|
+
if (!status || status === 302) {
|
|
16
|
+
const method = this.c.req.method;
|
|
17
|
+
if (method !== "GET" && method !== "HEAD") return originalRedirect.call(this, url, 303);
|
|
18
|
+
}
|
|
19
|
+
return originalRedirect.call(this, url, status);
|
|
20
|
+
};
|
|
21
|
+
proto.inertia = function(component, props, options) {
|
|
22
|
+
return resolveService(this).render(this, component, props, options);
|
|
23
|
+
};
|
|
24
|
+
proto.defer = function(callback, group) {
|
|
25
|
+
return resolveService(this).defer(callback, group);
|
|
26
|
+
};
|
|
27
|
+
proto.optional = function(callback) {
|
|
28
|
+
return resolveService(this).optional(callback);
|
|
29
|
+
};
|
|
30
|
+
proto.merge = function(callback) {
|
|
31
|
+
return resolveService(this).merge(callback);
|
|
32
|
+
};
|
|
33
|
+
proto.withoutSsr = function() {
|
|
34
|
+
this.c.set("withoutSsr", true);
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region src/vite/create-vite-config.ts
|
|
39
|
+
async function createInertiaViteConfig(options) {
|
|
40
|
+
const { mergeConfig } = await import("vite");
|
|
41
|
+
let userConfig = {};
|
|
42
|
+
const viteConfigPath = join(options.cwd, "vite.config.ts");
|
|
43
|
+
if (existsSync(viteConfigPath)) {
|
|
44
|
+
const loaded = await import(
|
|
45
|
+
/* @vite-ignore */
|
|
46
|
+
viteConfigPath
|
|
47
|
+
);
|
|
48
|
+
userConfig = loaded.default ?? loaded;
|
|
49
|
+
}
|
|
50
|
+
const { cloudflare } = await import("@cloudflare/vite-plugin");
|
|
51
|
+
const { stratalInertiaDevCss } = await import("./inertia-dev-css-plugin-BYromyO_.mjs").then((n) => n.t);
|
|
52
|
+
const { stratalInertiaTypes } = await import("./inertia-types-plugin-NO_uxhxQ.mjs").then((n) => n.t);
|
|
53
|
+
const hasInertiaPlugin = (Array.isArray(userConfig.plugins) ? userConfig.plugins.flat() : []).some((p) => p && typeof p === "object" && "name" in p && p.name === "inertia");
|
|
54
|
+
const inertiaPlugins = [];
|
|
55
|
+
if (!hasInertiaPlugin) try {
|
|
56
|
+
const { default: inertia } = await import("@inertiajs/vite");
|
|
57
|
+
inertiaPlugins.push(inertia({ pages: {
|
|
58
|
+
path: "./src/inertia/pages",
|
|
59
|
+
extension: ".tsx"
|
|
60
|
+
} }));
|
|
61
|
+
} catch {}
|
|
62
|
+
const optimizeDepsExclude = [
|
|
63
|
+
"@cloudflare/vite-plugin",
|
|
64
|
+
"wrangler",
|
|
65
|
+
"blake3-wasm"
|
|
66
|
+
];
|
|
67
|
+
return mergeConfig({
|
|
68
|
+
plugins: [
|
|
69
|
+
cloudflare(),
|
|
70
|
+
...inertiaPlugins,
|
|
71
|
+
stratalInertiaDevCss({ entries: ["/" + options.entryPath] }),
|
|
72
|
+
stratalInertiaTypes(),
|
|
73
|
+
{
|
|
74
|
+
name: "stratal:optimize-deps-fix",
|
|
75
|
+
configEnvironment(_name, env) {
|
|
76
|
+
const existing = env.optimizeDeps?.exclude ?? [];
|
|
77
|
+
env.optimizeDeps = {
|
|
78
|
+
...env.optimizeDeps,
|
|
79
|
+
exclude: [...existing, ...optimizeDepsExclude]
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
],
|
|
84
|
+
publicDir: join(options.cwd, "src", "inertia", "public"),
|
|
85
|
+
build: {
|
|
86
|
+
...options.outDir ? { outDir: options.outDir } : {},
|
|
87
|
+
rolldownOptions: { input: options.entryPath }
|
|
88
|
+
},
|
|
89
|
+
...options.server ? { server: {
|
|
90
|
+
port: options.server.port,
|
|
91
|
+
host: options.server.host ?? void 0
|
|
92
|
+
} } : {}
|
|
93
|
+
}, userConfig);
|
|
94
|
+
}
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region src/commands/inertia-build.command.ts
|
|
97
|
+
var InertiaBuildCommand = class extends Command {
|
|
98
|
+
static command = "inertia:build {--outDir=dist : Output directory} {--ssr : Also build SSR bundle}";
|
|
99
|
+
static description = "Build Inertia.js frontend for production";
|
|
100
|
+
async handle() {
|
|
101
|
+
const outDir = this.string("outDir") || "dist";
|
|
102
|
+
const shouldBuildSsr = this.boolean("ssr");
|
|
103
|
+
const cwd = process.cwd();
|
|
104
|
+
const entryPath = "src/inertia/app.tsx";
|
|
105
|
+
if (!existsSync(join(cwd, entryPath))) {
|
|
106
|
+
this.fail("src/inertia/app.tsx not found. Run `quarry inertia:install` first.");
|
|
107
|
+
return 1;
|
|
108
|
+
}
|
|
109
|
+
this.info("Building Inertia.js frontend for production...");
|
|
110
|
+
try {
|
|
111
|
+
const { build } = await import("vite");
|
|
112
|
+
const config = await createInertiaViteConfig({
|
|
113
|
+
cwd,
|
|
114
|
+
entryPath,
|
|
115
|
+
outDir
|
|
116
|
+
});
|
|
117
|
+
await build(config);
|
|
118
|
+
this.success("Client build complete!");
|
|
119
|
+
if (shouldBuildSsr) {
|
|
120
|
+
this.info("Building SSR bundle...");
|
|
121
|
+
await build({
|
|
122
|
+
...config,
|
|
123
|
+
build: {
|
|
124
|
+
...config.build,
|
|
125
|
+
ssr: true
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
this.success("SSR build complete!");
|
|
129
|
+
}
|
|
130
|
+
this.success(`Output in ${outDir}/`);
|
|
131
|
+
} catch (err) {
|
|
132
|
+
this.fail(`Build failed: ${err.message}`);
|
|
133
|
+
return 1;
|
|
134
|
+
}
|
|
135
|
+
return 0;
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
//#endregion
|
|
139
|
+
//#region src/commands/inertia-dev.command.ts
|
|
140
|
+
var InertiaDevCommand = class extends Command {
|
|
141
|
+
static command = "inertia:dev {--port=5173 : Dev server port} {--host : Expose to network}";
|
|
142
|
+
static description = "Start Inertia.js Vite development server";
|
|
143
|
+
async handle() {
|
|
144
|
+
const port = this.number("port") || 5173;
|
|
145
|
+
const host = this.boolean("host");
|
|
146
|
+
const cwd = process.cwd();
|
|
147
|
+
const entryPath = "src/inertia/app.tsx";
|
|
148
|
+
if (!existsSync(join(cwd, entryPath))) {
|
|
149
|
+
this.fail("src/inertia/app.tsx not found. Run `quarry inertia:install` first.");
|
|
150
|
+
return 1;
|
|
151
|
+
}
|
|
152
|
+
this.info("Starting Vite dev server...");
|
|
153
|
+
try {
|
|
154
|
+
const { createServer } = await import("vite");
|
|
155
|
+
const server = await createServer(await createInertiaViteConfig({
|
|
156
|
+
cwd,
|
|
157
|
+
entryPath,
|
|
158
|
+
server: {
|
|
159
|
+
port,
|
|
160
|
+
host
|
|
161
|
+
}
|
|
162
|
+
}));
|
|
163
|
+
await server.listen();
|
|
164
|
+
server.printUrls();
|
|
165
|
+
} catch (err) {
|
|
166
|
+
this.fail(`Failed to start dev server: ${err.message}`);
|
|
167
|
+
return 1;
|
|
168
|
+
}
|
|
169
|
+
return 0;
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
//#endregion
|
|
173
|
+
//#region src/commands/inertia-install.command.ts
|
|
174
|
+
const ROOT_HTML = `<!DOCTYPE html>
|
|
175
|
+
<html lang="en">
|
|
176
|
+
<head>
|
|
177
|
+
<meta charset="utf-8" />
|
|
178
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
179
|
+
@viteHead
|
|
180
|
+
@inertiaHead
|
|
181
|
+
</head>
|
|
182
|
+
<body>
|
|
183
|
+
@inertia
|
|
184
|
+
@viteScripts
|
|
185
|
+
</body>
|
|
186
|
+
</html>`;
|
|
187
|
+
const APP_TSX = `import { createInertiaApp } from '@inertiajs/react'
|
|
188
|
+
|
|
189
|
+
createInertiaApp()`;
|
|
190
|
+
const HOME_TSX = `export default function Home({ message }: { message: string }) {
|
|
191
|
+
return (
|
|
192
|
+
<div>
|
|
193
|
+
<h1>{message}</h1>
|
|
194
|
+
<p>This page is rendered with Inertia.js and Stratal.</p>
|
|
195
|
+
</div>
|
|
196
|
+
)
|
|
197
|
+
}`;
|
|
198
|
+
var InertiaInstallCommand = class extends Command {
|
|
199
|
+
static command = "inertia:install {--skip-deps : Skip installing npm dependencies}";
|
|
200
|
+
static description = "Scaffold Inertia.js files for a Stratal project";
|
|
201
|
+
async handle() {
|
|
202
|
+
const skipDeps = this.boolean("skip-deps");
|
|
203
|
+
const cwd = process.cwd();
|
|
204
|
+
const inertiaDir = join(cwd, "src", "inertia");
|
|
205
|
+
const pagesDir = join(inertiaDir, "pages");
|
|
206
|
+
this.info("Creating src/inertia/ directory...");
|
|
207
|
+
mkdirSync(pagesDir, { recursive: true });
|
|
208
|
+
const publicDir = join(inertiaDir, "public");
|
|
209
|
+
mkdirSync(publicDir, { recursive: true });
|
|
210
|
+
const gitkeepPath = join(publicDir, ".gitkeep");
|
|
211
|
+
if (!existsSync(gitkeepPath)) writeFileSync(gitkeepPath, "", "utf-8");
|
|
212
|
+
this.success("Created src/inertia/public/");
|
|
213
|
+
const files = [
|
|
214
|
+
{
|
|
215
|
+
path: join(inertiaDir, "root.html"),
|
|
216
|
+
content: ROOT_HTML,
|
|
217
|
+
name: "root.html"
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
path: join(inertiaDir, "app.tsx"),
|
|
221
|
+
content: APP_TSX,
|
|
222
|
+
name: "app.tsx"
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
path: join(pagesDir, "Home.tsx"),
|
|
226
|
+
content: HOME_TSX,
|
|
227
|
+
name: "pages/Home.tsx"
|
|
228
|
+
}
|
|
229
|
+
];
|
|
230
|
+
for (const file of files) if (existsSync(file.path)) this.warn(`Skipping ${file.name} (already exists)`);
|
|
231
|
+
else {
|
|
232
|
+
writeFileSync(file.path, file.content, "utf-8");
|
|
233
|
+
this.success(`Created src/inertia/${file.name}`);
|
|
234
|
+
}
|
|
235
|
+
const appModulePath = join(cwd, "src", "app.module.ts");
|
|
236
|
+
if (existsSync(appModulePath)) {
|
|
237
|
+
this.info("Updating src/app.module.ts...");
|
|
238
|
+
try {
|
|
239
|
+
if (await this.updateAppModule(appModulePath)) this.success("Updated src/app.module.ts with InertiaModule");
|
|
240
|
+
else this.info("InertiaModule already configured in app.module.ts");
|
|
241
|
+
} catch (err) {
|
|
242
|
+
this.warn(`Could not auto-update app.module.ts: ${err.message}`);
|
|
243
|
+
this.info("Please manually add InertiaModule.forRoot() to your module imports");
|
|
244
|
+
}
|
|
245
|
+
} else this.info("No src/app.module.ts found — please manually configure InertiaModule");
|
|
246
|
+
try {
|
|
247
|
+
const { outputPath, pageCount } = await runTypeGeneration(cwd);
|
|
248
|
+
const relPath = relative(cwd, outputPath);
|
|
249
|
+
this.success(`Generated ${relPath} (${pageCount} page${pageCount !== 1 ? "s" : ""})`);
|
|
250
|
+
} catch {
|
|
251
|
+
this.warn("Could not generate initial type definitions. Run `quarry inertia:types` manually.");
|
|
252
|
+
}
|
|
253
|
+
if (!skipDeps) {
|
|
254
|
+
this.newLine();
|
|
255
|
+
this.info("Install the following dependencies:");
|
|
256
|
+
this.line(" npm install @stratal/inertia @inertiajs/react @inertiajs/vite react react-dom");
|
|
257
|
+
this.line(" npm install -D @types/react @types/react-dom vite @cloudflare/vite-plugin");
|
|
258
|
+
}
|
|
259
|
+
this.newLine();
|
|
260
|
+
this.success("Inertia.js scaffolding complete!");
|
|
261
|
+
this.info("Run `quarry inertia:dev` to start the dev server");
|
|
262
|
+
return 0;
|
|
263
|
+
}
|
|
264
|
+
async updateAppModule(modulePath) {
|
|
265
|
+
const { Project, SyntaxKind } = await import("ts-morph");
|
|
266
|
+
const sourceFile = new Project({ useInMemoryFileSystem: false }).addSourceFileAtPath(modulePath);
|
|
267
|
+
if (sourceFile.getImportDeclaration((decl) => decl.getModuleSpecifierValue() === "@stratal/inertia")) return false;
|
|
268
|
+
sourceFile.addImportDeclaration({
|
|
269
|
+
defaultImport: "rootView",
|
|
270
|
+
moduleSpecifier: "./inertia/root.html?raw"
|
|
271
|
+
});
|
|
272
|
+
sourceFile.addImportDeclaration({
|
|
273
|
+
namedImports: ["InertiaModule"],
|
|
274
|
+
moduleSpecifier: "@stratal/inertia"
|
|
275
|
+
});
|
|
276
|
+
const classes = sourceFile.getClasses();
|
|
277
|
+
for (const cls of classes) {
|
|
278
|
+
const moduleDecorator = cls.getDecorator("Module");
|
|
279
|
+
if (!moduleDecorator) continue;
|
|
280
|
+
const args = moduleDecorator.getArguments();
|
|
281
|
+
if (args.length === 0) continue;
|
|
282
|
+
const objLiteral = args[0].asKind(SyntaxKind.ObjectLiteralExpression);
|
|
283
|
+
if (!objLiteral) continue;
|
|
284
|
+
const importsProp = objLiteral.getProperty("imports");
|
|
285
|
+
if (importsProp) {
|
|
286
|
+
const arrayLiteral = (importsProp.asKind(SyntaxKind.PropertyAssignment)?.getInitializer())?.asKind(SyntaxKind.ArrayLiteralExpression);
|
|
287
|
+
if (arrayLiteral) arrayLiteral.addElement(`InertiaModule.forRoot({\n rootView,\n })`);
|
|
288
|
+
} else objLiteral.addPropertyAssignment({
|
|
289
|
+
name: "imports",
|
|
290
|
+
initializer: `[\n InertiaModule.forRoot({\n rootView,\n }),\n ]`
|
|
291
|
+
});
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
await sourceFile.save();
|
|
295
|
+
return true;
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
//#endregion
|
|
299
|
+
//#region src/commands/inertia-types.command.ts
|
|
300
|
+
var InertiaTypesCommand = class extends Command {
|
|
301
|
+
static command = "inertia:types {--watch : Watch for changes and regenerate}";
|
|
302
|
+
static description = "Generate Inertia.js page type definitions";
|
|
303
|
+
async handle() {
|
|
304
|
+
const cwd = process.cwd();
|
|
305
|
+
const pagesDir = findPagesDir(cwd);
|
|
306
|
+
if (!existsSync(pagesDir)) {
|
|
307
|
+
this.fail("src/inertia/pages/ not found. Run `quarry inertia:install` first.");
|
|
308
|
+
return 1;
|
|
309
|
+
}
|
|
310
|
+
if (!await this.generate(cwd)) return 1;
|
|
311
|
+
if (this.boolean("watch")) {
|
|
312
|
+
this.info("Watching for changes...");
|
|
313
|
+
await this.watchForChanges(cwd, pagesDir);
|
|
314
|
+
}
|
|
315
|
+
return 0;
|
|
316
|
+
}
|
|
317
|
+
async generate(cwd) {
|
|
318
|
+
try {
|
|
319
|
+
const { outputPath, pageCount } = await runTypeGeneration(cwd);
|
|
320
|
+
const relPath = relative(cwd, outputPath);
|
|
321
|
+
this.success(`Generated ${relPath} (${pageCount} page${pageCount !== 1 ? "s" : ""})`);
|
|
322
|
+
return true;
|
|
323
|
+
} catch (err) {
|
|
324
|
+
this.fail(`Type generation failed: ${err.message}`);
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
async watchForChanges(cwd, pagesDir) {
|
|
329
|
+
try {
|
|
330
|
+
const watcher = watch(pagesDir, { recursive: true });
|
|
331
|
+
for await (const event of watcher) if (event.filename && /\.(tsx|ts)$/.test(event.filename)) {
|
|
332
|
+
this.info(`Change detected: ${event.filename}`);
|
|
333
|
+
await this.generate(cwd);
|
|
334
|
+
}
|
|
335
|
+
} catch (err) {
|
|
336
|
+
this.fail(`Watch failed: ${err.message}`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
//#endregion
|
|
341
|
+
//#region src/inertia.tokens.ts
|
|
342
|
+
const INERTIA_TOKENS = {
|
|
343
|
+
Options: Symbol.for("stratal:inertia:options"),
|
|
344
|
+
InertiaService: Symbol.for("stratal:inertia:service"),
|
|
345
|
+
TemplateService: Symbol.for("stratal:inertia:template"),
|
|
346
|
+
ManifestService: Symbol.for("stratal:inertia:manifest"),
|
|
347
|
+
SsrRenderer: Symbol.for("stratal:inertia:ssr-renderer")
|
|
348
|
+
};
|
|
349
|
+
//#endregion
|
|
350
|
+
//#region \0@oxc-project+runtime@0.115.0/helpers/decorateMetadata.js
|
|
351
|
+
function __decorateMetadata(k, v) {
|
|
352
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
353
|
+
}
|
|
354
|
+
//#endregion
|
|
355
|
+
//#region \0@oxc-project+runtime@0.115.0/helpers/decorateParam.js
|
|
356
|
+
function __decorateParam(paramIndex, decorator) {
|
|
357
|
+
return function(target, key) {
|
|
358
|
+
decorator(target, key, paramIndex);
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
//#endregion
|
|
362
|
+
//#region \0@oxc-project+runtime@0.115.0/helpers/decorate.js
|
|
363
|
+
function __decorate(decorators, target, key, desc) {
|
|
364
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
365
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
366
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
367
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
368
|
+
}
|
|
369
|
+
//#endregion
|
|
370
|
+
//#region src/middleware/inertia.middleware.ts
|
|
371
|
+
let InertiaMiddleware = class InertiaMiddleware {
|
|
372
|
+
constructor(options) {
|
|
373
|
+
this.options = options;
|
|
374
|
+
}
|
|
375
|
+
async handle(ctx, next) {
|
|
376
|
+
const isInertia = ctx.header("x-inertia") === "true";
|
|
377
|
+
const isPrefetch = ctx.header("purpose") === "prefetch";
|
|
378
|
+
ctx.c.set("inertia", isInertia);
|
|
379
|
+
ctx.c.set("inertiaPrefetch", isPrefetch);
|
|
380
|
+
ctx.c.set("withoutSsr", false);
|
|
381
|
+
if (isInertia && ctx.c.req.method === "GET") {
|
|
382
|
+
const clientVersion = ctx.header("x-inertia-version");
|
|
383
|
+
const serverVersion = this.options.version ?? "";
|
|
384
|
+
if (clientVersion && serverVersion && clientVersion !== serverVersion) {
|
|
385
|
+
ctx.c.header("X-Inertia-Location", ctx.c.req.url);
|
|
386
|
+
ctx.c.status(409);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
await next();
|
|
391
|
+
ctx.c.header("Vary", "X-Inertia");
|
|
392
|
+
if (isInertia) {
|
|
393
|
+
const method = ctx.c.req.method;
|
|
394
|
+
if (ctx.c.res.status === 302 && method !== "GET" && method !== "HEAD") ctx.c.status(303);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
InertiaMiddleware = __decorate([
|
|
399
|
+
Transient(),
|
|
400
|
+
__decorateParam(0, inject(INERTIA_TOKENS.Options)),
|
|
401
|
+
__decorateMetadata("design:paramtypes", [Object])
|
|
402
|
+
], InertiaMiddleware);
|
|
403
|
+
//#endregion
|
|
404
|
+
//#region src/types.ts
|
|
405
|
+
const INERTIA_PROP_OPTIONAL = Symbol.for("stratal:inertia:prop:optional");
|
|
406
|
+
const INERTIA_PROP_DEFERRED = Symbol.for("stratal:inertia:prop:deferred");
|
|
407
|
+
const INERTIA_PROP_MERGE = Symbol.for("stratal:inertia:prop:merge");
|
|
408
|
+
//#endregion
|
|
409
|
+
//#region src/services/inertia.service.ts
|
|
410
|
+
let InertiaService = class InertiaService {
|
|
411
|
+
sharedData = {};
|
|
412
|
+
constructor(options, template, ssr) {
|
|
413
|
+
this.options = options;
|
|
414
|
+
this.template = template;
|
|
415
|
+
this.ssr = ssr;
|
|
416
|
+
}
|
|
417
|
+
share(key, value) {
|
|
418
|
+
this.sharedData[key] = value;
|
|
419
|
+
}
|
|
420
|
+
location(url) {
|
|
421
|
+
return new Response("", {
|
|
422
|
+
status: 409,
|
|
423
|
+
headers: { "X-Inertia-Location": url }
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
optional(callback) {
|
|
427
|
+
return {
|
|
428
|
+
[INERTIA_PROP_OPTIONAL]: true,
|
|
429
|
+
callback
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
defer(callback, group = "default") {
|
|
433
|
+
return {
|
|
434
|
+
[INERTIA_PROP_DEFERRED]: true,
|
|
435
|
+
callback,
|
|
436
|
+
group
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
merge(callback) {
|
|
440
|
+
return {
|
|
441
|
+
[INERTIA_PROP_MERGE]: true,
|
|
442
|
+
callback
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
async render(ctx, component, props = {}, renderOptions = {}) {
|
|
446
|
+
const url = new URL(ctx.c.req.url).pathname;
|
|
447
|
+
const isInertia = ctx.c.get("inertia");
|
|
448
|
+
const allProps = {
|
|
449
|
+
...await this.resolveSharedData(ctx),
|
|
450
|
+
...this.sharedData,
|
|
451
|
+
...props
|
|
452
|
+
};
|
|
453
|
+
const { resolvedProps, mergeProps, deferredProps } = await this.processProps(allProps, ctx, component, isInertia);
|
|
454
|
+
const page = {
|
|
455
|
+
component,
|
|
456
|
+
props: resolvedProps,
|
|
457
|
+
url,
|
|
458
|
+
version: this.options.version ?? "",
|
|
459
|
+
mergeProps,
|
|
460
|
+
deferredProps,
|
|
461
|
+
...renderOptions.encryptHistory ? { encryptHistory: true } : {},
|
|
462
|
+
...renderOptions.clearHistory ? { clearHistory: true } : {}
|
|
463
|
+
};
|
|
464
|
+
if (isInertia) return new Response(JSON.stringify(page), {
|
|
465
|
+
status: 200,
|
|
466
|
+
headers: {
|
|
467
|
+
"Content-Type": "application/json",
|
|
468
|
+
"X-Inertia": "true",
|
|
469
|
+
"Vary": "X-Inertia"
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
const ssrResult = ctx.c.get("withoutSsr") || this.isSsrDisabled(url) ? {
|
|
473
|
+
head: [],
|
|
474
|
+
body: ""
|
|
475
|
+
} : await this.ssr.render(page);
|
|
476
|
+
const html = this.template.render(page, ssrResult.head, ssrResult.body);
|
|
477
|
+
return new Response(html, {
|
|
478
|
+
status: 200,
|
|
479
|
+
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
async resolveSharedData(ctx) {
|
|
483
|
+
const shared = {};
|
|
484
|
+
const configShared = this.options.sharedData;
|
|
485
|
+
if (!configShared) return shared;
|
|
486
|
+
for (const [key, value] of Object.entries(configShared)) if (typeof value === "function") shared[key] = await value(ctx);
|
|
487
|
+
else shared[key] = value;
|
|
488
|
+
return shared;
|
|
489
|
+
}
|
|
490
|
+
async processProps(allProps, ctx, component, isInertia) {
|
|
491
|
+
const resolvedProps = {};
|
|
492
|
+
const mergeProps = [];
|
|
493
|
+
const deferredProps = {};
|
|
494
|
+
const partialComponent = ctx.header("x-inertia-partial-component");
|
|
495
|
+
const partialDataHeader = ctx.header("x-inertia-partial-data");
|
|
496
|
+
const isPartialReload = isInertia && partialComponent === component && partialDataHeader;
|
|
497
|
+
const requestedProps = partialDataHeader?.split(",").map((s) => s.trim()) ?? [];
|
|
498
|
+
for (const [key, value] of Object.entries(allProps)) {
|
|
499
|
+
if (this.isDeferredProp(value)) {
|
|
500
|
+
if (isPartialReload && this.isRequested(key, requestedProps)) resolvedProps[key] = await value.callback();
|
|
501
|
+
else if (!isPartialReload) {
|
|
502
|
+
deferredProps[value.group] ??= [];
|
|
503
|
+
deferredProps[value.group].push(key);
|
|
504
|
+
}
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
if (this.isMergeProp(value)) {
|
|
508
|
+
if (isPartialReload && !this.isRequested(key, requestedProps)) continue;
|
|
509
|
+
mergeProps.push(key);
|
|
510
|
+
resolvedProps[key] = await value.callback();
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
if (this.isOptionalProp(value)) {
|
|
514
|
+
if (isPartialReload && this.isRequested(key, requestedProps)) resolvedProps[key] = await value.callback();
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
if (isPartialReload) {
|
|
518
|
+
if (this.isRequested(key, requestedProps)) resolvedProps[key] = value;
|
|
519
|
+
} else resolvedProps[key] = value;
|
|
520
|
+
}
|
|
521
|
+
return {
|
|
522
|
+
resolvedProps,
|
|
523
|
+
mergeProps,
|
|
524
|
+
deferredProps
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Check if a prop key is requested — supports dot-notation (e.g., `user.permissions`
|
|
529
|
+
* matches the top-level `user` key).
|
|
530
|
+
*/
|
|
531
|
+
isRequested(key, requestedProps) {
|
|
532
|
+
return requestedProps.some((prop) => prop === key || prop.startsWith(`${key}.`));
|
|
533
|
+
}
|
|
534
|
+
isOptionalProp(value) {
|
|
535
|
+
return typeof value === "object" && value !== null && INERTIA_PROP_OPTIONAL in value;
|
|
536
|
+
}
|
|
537
|
+
isDeferredProp(value) {
|
|
538
|
+
return typeof value === "object" && value !== null && INERTIA_PROP_DEFERRED in value;
|
|
539
|
+
}
|
|
540
|
+
isMergeProp(value) {
|
|
541
|
+
return typeof value === "object" && value !== null && INERTIA_PROP_MERGE in value;
|
|
542
|
+
}
|
|
543
|
+
isSsrDisabled(pathname) {
|
|
544
|
+
const patterns = this.options.ssr?.disabled;
|
|
545
|
+
if (!patterns || patterns.length === 0) return false;
|
|
546
|
+
return patterns.some((pattern) => {
|
|
547
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
548
|
+
return new RegExp(`^/${escaped.replace(/\*/g, "[^/]*")}$`).test(pathname);
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
InertiaService = __decorate([
|
|
553
|
+
Transient(),
|
|
554
|
+
__decorateParam(0, inject(INERTIA_TOKENS.Options)),
|
|
555
|
+
__decorateParam(1, inject(INERTIA_TOKENS.TemplateService)),
|
|
556
|
+
__decorateParam(2, inject(INERTIA_TOKENS.SsrRenderer)),
|
|
557
|
+
__decorateMetadata("design:paramtypes", [
|
|
558
|
+
Object,
|
|
559
|
+
Object,
|
|
560
|
+
Object
|
|
561
|
+
])
|
|
562
|
+
], InertiaService);
|
|
563
|
+
//#endregion
|
|
564
|
+
//#region src/services/manifest.service.ts
|
|
565
|
+
const DEFAULT_ENTRY_CLIENT_PATH = "src/inertia/app.tsx";
|
|
566
|
+
let ManifestService = class ManifestService {
|
|
567
|
+
manifest;
|
|
568
|
+
entryClientPath;
|
|
569
|
+
constructor(options) {
|
|
570
|
+
this.manifest = options.manifest ?? null;
|
|
571
|
+
this.entryClientPath = (options.entryClientPath ?? DEFAULT_ENTRY_CLIENT_PATH).replace(/^\/+/, "");
|
|
572
|
+
}
|
|
573
|
+
get isDev() {
|
|
574
|
+
return this.manifest === null;
|
|
575
|
+
}
|
|
576
|
+
getHeadTags() {
|
|
577
|
+
if (this.isDev) return "<link rel=\"stylesheet\" href=\"/__inertia/ssr-css\" data-ssr-css />";
|
|
578
|
+
const tags = [];
|
|
579
|
+
const seen = /* @__PURE__ */ new Set();
|
|
580
|
+
for (const entry of Object.values(this.manifest)) if (entry.css) for (const cssFile of entry.css) {
|
|
581
|
+
if (seen.has(cssFile)) continue;
|
|
582
|
+
seen.add(cssFile);
|
|
583
|
+
tags.push(`<link rel="stylesheet" href="/${cssFile}" />`);
|
|
584
|
+
}
|
|
585
|
+
return tags.join("\n");
|
|
586
|
+
}
|
|
587
|
+
getScriptTags() {
|
|
588
|
+
if (this.isDev) return [
|
|
589
|
+
"<script type=\"module\" src=\"/@vite/client\"><\/script>",
|
|
590
|
+
`<script type="module">
|
|
591
|
+
import { createHotContext } from "/@vite/client";
|
|
592
|
+
const hot = createHotContext("/__ssr_css");
|
|
593
|
+
hot.on("vite:afterUpdate", () => {
|
|
594
|
+
document.querySelectorAll("[data-ssr-css]").forEach(el => el.remove());
|
|
595
|
+
});
|
|
596
|
+
<\/script>`,
|
|
597
|
+
`<script type="module" src="/${this.entryClientPath}"><\/script>`
|
|
598
|
+
].join("\n");
|
|
599
|
+
const tags = [];
|
|
600
|
+
for (const entry of Object.values(this.manifest)) if (entry.isEntry) tags.push(`<script type="module" src="/${entry.file}"><\/script>`);
|
|
601
|
+
return tags.join("\n");
|
|
602
|
+
}
|
|
603
|
+
};
|
|
604
|
+
ManifestService = __decorate([
|
|
605
|
+
Transient(),
|
|
606
|
+
__decorateParam(0, inject(INERTIA_TOKENS.Options)),
|
|
607
|
+
__decorateMetadata("design:paramtypes", [Object])
|
|
608
|
+
], ManifestService);
|
|
609
|
+
//#endregion
|
|
610
|
+
//#region src/services/ssr-renderer.service.ts
|
|
611
|
+
let SsrRendererService = class SsrRendererService {
|
|
612
|
+
bundle = null;
|
|
613
|
+
loadPromise = null;
|
|
614
|
+
constructor(options) {
|
|
615
|
+
this.options = options;
|
|
616
|
+
}
|
|
617
|
+
async render(page) {
|
|
618
|
+
if (!this.options.ssr) return {
|
|
619
|
+
head: [],
|
|
620
|
+
body: ""
|
|
621
|
+
};
|
|
622
|
+
await this.ensureBundle();
|
|
623
|
+
if (!this.bundle) return {
|
|
624
|
+
head: [],
|
|
625
|
+
body: ""
|
|
626
|
+
};
|
|
627
|
+
return this.bundle.render(page);
|
|
628
|
+
}
|
|
629
|
+
async ensureBundle() {
|
|
630
|
+
if (this.bundle) return;
|
|
631
|
+
this.loadPromise ??= this.loadBundle();
|
|
632
|
+
try {
|
|
633
|
+
await this.loadPromise;
|
|
634
|
+
} catch {}
|
|
635
|
+
}
|
|
636
|
+
async loadBundle() {
|
|
637
|
+
if (!this.options.ssr) return;
|
|
638
|
+
try {
|
|
639
|
+
const mod = await this.options.ssr.bundle();
|
|
640
|
+
this.bundle = "default" in mod ? mod.default : mod;
|
|
641
|
+
} catch {
|
|
642
|
+
this.loadPromise = null;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
SsrRendererService = __decorate([
|
|
647
|
+
Transient(),
|
|
648
|
+
__decorateParam(0, inject(INERTIA_TOKENS.Options)),
|
|
649
|
+
__decorateMetadata("design:paramtypes", [Object])
|
|
650
|
+
], SsrRendererService);
|
|
651
|
+
//#endregion
|
|
652
|
+
//#region src/services/template.service.ts
|
|
653
|
+
let TemplateService = class TemplateService {
|
|
654
|
+
constructor(options, manifest) {
|
|
655
|
+
this.options = options;
|
|
656
|
+
this.manifest = manifest;
|
|
657
|
+
}
|
|
658
|
+
render(page, ssrHead, ssrBody) {
|
|
659
|
+
const appHtml = ssrBody || this.buildClientOnlyBody(page);
|
|
660
|
+
const headTags = ssrHead.length > 0 ? ssrHead.join("\n") : "";
|
|
661
|
+
const viteHead = this.manifest.getHeadTags();
|
|
662
|
+
const viteScripts = this.manifest.getScriptTags();
|
|
663
|
+
let html = this.options.rootView;
|
|
664
|
+
html = html.replace("@inertiaHead", headTags);
|
|
665
|
+
html = html.replace("@inertia", appHtml);
|
|
666
|
+
html = html.replace("@viteHead", viteHead);
|
|
667
|
+
html = html.replace("@viteScripts", viteScripts);
|
|
668
|
+
return html;
|
|
669
|
+
}
|
|
670
|
+
buildClientOnlyBody(page) {
|
|
671
|
+
return `<script data-page="app" type="application/json">${JSON.stringify(page).replace(/\//g, "\\/")}<\/script><div id="app"></div>`;
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
TemplateService = __decorate([
|
|
675
|
+
Transient(),
|
|
676
|
+
__decorateParam(0, inject(INERTIA_TOKENS.Options)),
|
|
677
|
+
__decorateParam(1, inject(INERTIA_TOKENS.ManifestService)),
|
|
678
|
+
__decorateMetadata("design:paramtypes", [Object, Object])
|
|
679
|
+
], TemplateService);
|
|
680
|
+
//#endregion
|
|
681
|
+
//#region src/inertia.module.ts
|
|
682
|
+
var _InertiaModule;
|
|
683
|
+
let InertiaModule = _InertiaModule = class InertiaModule {
|
|
684
|
+
static forRoot(options) {
|
|
685
|
+
return {
|
|
686
|
+
module: _InertiaModule,
|
|
687
|
+
providers: [{
|
|
688
|
+
provide: INERTIA_TOKENS.Options,
|
|
689
|
+
useValue: options
|
|
690
|
+
}]
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
static forRootAsync(options) {
|
|
694
|
+
return {
|
|
695
|
+
module: _InertiaModule,
|
|
696
|
+
providers: [{
|
|
697
|
+
provide: INERTIA_TOKENS.Options,
|
|
698
|
+
useFactory: options.useFactory,
|
|
699
|
+
inject: options.inject
|
|
700
|
+
}]
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
configure(consumer) {
|
|
704
|
+
consumer.apply(InertiaMiddleware).forRoutes("*");
|
|
705
|
+
}
|
|
706
|
+
onInitialize() {
|
|
707
|
+
augmentRouterContext((ctx) => {
|
|
708
|
+
return ctx.getContainer().resolve(INERTIA_TOKENS.InertiaService);
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
InertiaModule = _InertiaModule = __decorate([Module({ providers: [
|
|
713
|
+
{
|
|
714
|
+
provide: INERTIA_TOKENS.InertiaService,
|
|
715
|
+
useClass: InertiaService
|
|
716
|
+
},
|
|
717
|
+
{
|
|
718
|
+
provide: INERTIA_TOKENS.TemplateService,
|
|
719
|
+
useClass: TemplateService
|
|
720
|
+
},
|
|
721
|
+
{
|
|
722
|
+
provide: INERTIA_TOKENS.ManifestService,
|
|
723
|
+
useClass: ManifestService
|
|
724
|
+
},
|
|
725
|
+
{
|
|
726
|
+
provide: INERTIA_TOKENS.SsrRenderer,
|
|
727
|
+
useClass: SsrRendererService
|
|
728
|
+
},
|
|
729
|
+
InertiaInstallCommand,
|
|
730
|
+
InertiaTypesCommand,
|
|
731
|
+
InertiaDevCommand,
|
|
732
|
+
InertiaBuildCommand
|
|
733
|
+
] })], InertiaModule);
|
|
734
|
+
//#endregion
|
|
735
|
+
//#region src/decorators/inertia-route.decorator.ts
|
|
736
|
+
/**
|
|
737
|
+
* Zod schema for the Inertia page JSON response (returned for X-Inertia XHR requests)
|
|
738
|
+
*/
|
|
739
|
+
const inertiaPageSchema = z.object({
|
|
740
|
+
component: z.string(),
|
|
741
|
+
props: z.record(z.string(), z.unknown()),
|
|
742
|
+
url: z.string(),
|
|
743
|
+
version: z.string(),
|
|
744
|
+
mergeProps: z.array(z.string()),
|
|
745
|
+
deferredProps: z.record(z.string(), z.array(z.string())),
|
|
746
|
+
encryptHistory: z.boolean(),
|
|
747
|
+
clearHistory: z.boolean()
|
|
748
|
+
});
|
|
749
|
+
/**
|
|
750
|
+
* Decorator for Inertia page routes.
|
|
751
|
+
*
|
|
752
|
+
* Wraps `@Route()` with:
|
|
753
|
+
* - Auto-applied Inertia page response schema
|
|
754
|
+
* - `hideFromDocs: true` by default (overridable)
|
|
755
|
+
*
|
|
756
|
+
* Accepts `query`, `params`, `body`, `tags`, `summary`, `description`, `security`.
|
|
757
|
+
*
|
|
758
|
+
* @example
|
|
759
|
+
* ```typescript
|
|
760
|
+
* @Controller('/notes')
|
|
761
|
+
* export class NotesController implements IController {
|
|
762
|
+
* @InertiaRoute({ query: z.object({ page: z.string().optional() }) })
|
|
763
|
+
* async index(ctx: RouterContext) {
|
|
764
|
+
* return ctx.inertia('notes/Index', { notes: [] })
|
|
765
|
+
* }
|
|
766
|
+
* }
|
|
767
|
+
* ```
|
|
768
|
+
*/
|
|
769
|
+
function InertiaRoute(config = {}) {
|
|
770
|
+
const { hideFromDocs = true, ...rest } = config;
|
|
771
|
+
return Route({
|
|
772
|
+
...rest,
|
|
773
|
+
response: {
|
|
774
|
+
schema: inertiaPageSchema,
|
|
775
|
+
description: "Inertia page response",
|
|
776
|
+
contentType: "text/html"
|
|
777
|
+
},
|
|
778
|
+
hideFromDocs
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
//#endregion
|
|
782
|
+
export { INERTIA_TOKENS, InertiaBuildCommand, InertiaDevCommand, InertiaInstallCommand, InertiaMiddleware, InertiaModule, InertiaRoute, InertiaService, InertiaTypesCommand, ManifestService, SsrRendererService, TemplateService, runTypeGeneration };
|
|
783
|
+
|
|
784
|
+
//# sourceMappingURL=index.mjs.map
|