@stratal/inertia 0.0.18 → 0.0.19
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/dist/index.d.mts +472 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +1158 -0
- package/dist/index.mjs.map +1 -0
- package/dist/react.d.mts +83 -0
- package/dist/react.d.mts.map +1 -0
- package/dist/react.mjs +158 -0
- package/dist/react.mjs.map +1 -0
- package/dist/testing.d.mts +36 -0
- package/dist/testing.d.mts.map +1 -0
- package/dist/testing.mjs +78 -0
- package/dist/testing.mjs.map +1 -0
- package/dist/type-generator-C5JljyzK.mjs +391 -0
- package/dist/type-generator-C5JljyzK.mjs.map +1 -0
- package/dist/vite.d.mts +21 -0
- package/dist/vite.d.mts.map +1 -0
- package/dist/vite.mjs +152 -0
- package/dist/vite.mjs.map +1 -0
- package/package.json +20 -20
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1158 @@
|
|
|
1
|
+
import { n as runTypeGeneration, t as findPagesDir } from "./type-generator-C5JljyzK.mjs";
|
|
2
|
+
import { Scope, Transient, inject } from "stratal/di";
|
|
3
|
+
import { ApplicationError } from "stratal/errors";
|
|
4
|
+
import { I18N_TOKENS } from "stratal/i18n";
|
|
5
|
+
import { Module } from "stratal/module";
|
|
6
|
+
import { Delete, Get, Patch, Post, Put, ROUTER_TOKENS, Route, RouterContext, SchemaValidationError } from "stratal/router";
|
|
7
|
+
import { spawn } from "node:child_process";
|
|
8
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { dirname, join, relative } from "node:path";
|
|
10
|
+
import { Command } from "stratal/quarry";
|
|
11
|
+
import { watch } from "node:fs/promises";
|
|
12
|
+
import { LOGGER_TOKENS } from "stratal/logger";
|
|
13
|
+
import { deleteCookie, getSignedCookie, setSignedCookie } from "hono/cookie";
|
|
14
|
+
import { z } from "stratal/validation";
|
|
15
|
+
//#region src/augment/router-context.ts
|
|
16
|
+
function augmentRouterContext(resolveService) {
|
|
17
|
+
const originalRedirect = RouterContext.prototype.redirect;
|
|
18
|
+
RouterContext.macro("redirect", function(url, status) {
|
|
19
|
+
if (!status || status === 302) {
|
|
20
|
+
const method = this.c.req.method;
|
|
21
|
+
if (method !== "GET" && method !== "HEAD") return originalRedirect.call(this, url, 303);
|
|
22
|
+
}
|
|
23
|
+
return originalRedirect.call(this, url, status);
|
|
24
|
+
});
|
|
25
|
+
RouterContext.macro("inertia", function(component, props, options) {
|
|
26
|
+
return resolveService(this).render(this, component, props, options);
|
|
27
|
+
});
|
|
28
|
+
RouterContext.macro("defer", function(callback, group) {
|
|
29
|
+
return resolveService(this).defer(callback, group);
|
|
30
|
+
});
|
|
31
|
+
RouterContext.macro("optional", function(callback) {
|
|
32
|
+
return resolveService(this).optional(callback);
|
|
33
|
+
});
|
|
34
|
+
RouterContext.macro("merge", function(callback, options) {
|
|
35
|
+
return resolveService(this).merge(callback, options);
|
|
36
|
+
});
|
|
37
|
+
RouterContext.macro("once", function(callback, options) {
|
|
38
|
+
return resolveService(this).once(callback, options);
|
|
39
|
+
});
|
|
40
|
+
RouterContext.macro("always", function(callback) {
|
|
41
|
+
return resolveService(this).always(callback);
|
|
42
|
+
});
|
|
43
|
+
RouterContext.macro("flash", function(key, value) {
|
|
44
|
+
const flashOut = this.c.get("inertiaFlashOut");
|
|
45
|
+
if (flashOut) flashOut[key] = value;
|
|
46
|
+
});
|
|
47
|
+
RouterContext.macro("withoutSsr", function() {
|
|
48
|
+
this.c.set("withoutSsr", true);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region src/vite/create-vite-config.ts
|
|
53
|
+
function writeTempViteConfig(options) {
|
|
54
|
+
const configPath = join(join(options.cwd, "node_modules", ".stratal"), "vite.config.mjs");
|
|
55
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
56
|
+
const hasUserConfig = existsSync(join(options.cwd, "vite.config.ts"));
|
|
57
|
+
const serverConfig = options.server ? `server: { port: ${options.server.port}, host: ${options.server.host ? "true" : "undefined"} },` : "";
|
|
58
|
+
const outDirConfig = options.outDir ? `outDir: '${options.outDir}',` : "";
|
|
59
|
+
writeFileSync(configPath, `
|
|
60
|
+
import { mergeConfig } from 'vite'
|
|
61
|
+
import { cloudflare } from '@cloudflare/vite-plugin'
|
|
62
|
+
import { stratalInertia } from '@stratal/inertia/vite'
|
|
63
|
+
|
|
64
|
+
let inertiaPlugin = null
|
|
65
|
+
try {
|
|
66
|
+
const mod = await import('@inertiajs/vite')
|
|
67
|
+
const inertia = mod.default ?? mod
|
|
68
|
+
inertiaPlugin = inertia()
|
|
69
|
+
} catch {}
|
|
70
|
+
|
|
71
|
+
const baseConfig = {
|
|
72
|
+
plugins: [
|
|
73
|
+
cloudflare(${options.persistTo ? `{ persistState: { path: ${JSON.stringify(options.persistTo)} } }` : ""}),
|
|
74
|
+
...(inertiaPlugin ? [inertiaPlugin] : []),
|
|
75
|
+
...stratalInertia(),
|
|
76
|
+
],
|
|
77
|
+
publicDir: '${join(options.cwd, "src", "inertia", "public").replace(/\\/g, "/")}',
|
|
78
|
+
build: {
|
|
79
|
+
${outDirConfig}
|
|
80
|
+
},
|
|
81
|
+
${serverConfig}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
${hasUserConfig ? `const userModule = await import('${join(options.cwd, "vite.config.ts").replace(/\\/g, "/")}')
|
|
85
|
+
const userConfig = userModule.default ?? userModule
|
|
86
|
+
export default mergeConfig(baseConfig, userConfig)` : "export default baseConfig"}
|
|
87
|
+
`, "utf-8");
|
|
88
|
+
return configPath;
|
|
89
|
+
}
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region src/commands/inertia-build.command.ts
|
|
92
|
+
var InertiaBuildCommand = class extends Command {
|
|
93
|
+
static command = "inertia:build {--outDir=dist : Output directory} {--ssr : Also build SSR bundle}";
|
|
94
|
+
static description = "Build Inertia.js frontend for production";
|
|
95
|
+
async handle() {
|
|
96
|
+
const outDir = this.string("outDir") || "dist";
|
|
97
|
+
const shouldBuildSsr = this.boolean("ssr");
|
|
98
|
+
const cwd = process.cwd();
|
|
99
|
+
if (!existsSync(join(cwd, "src/inertia/app.tsx"))) {
|
|
100
|
+
this.fail("src/inertia/app.tsx not found. Run `quarry inertia:install` first.");
|
|
101
|
+
return 1;
|
|
102
|
+
}
|
|
103
|
+
const configPath = writeTempViteConfig({
|
|
104
|
+
cwd,
|
|
105
|
+
outDir
|
|
106
|
+
});
|
|
107
|
+
this.info("Building Inertia.js frontend for production...");
|
|
108
|
+
const clientCode = await this.spawnVite(cwd, configPath, ["build"]);
|
|
109
|
+
if (clientCode !== 0) {
|
|
110
|
+
this.fail("Client build failed.");
|
|
111
|
+
return clientCode;
|
|
112
|
+
}
|
|
113
|
+
this.success("Client build complete!");
|
|
114
|
+
if (shouldBuildSsr) {
|
|
115
|
+
this.info("Building SSR bundle...");
|
|
116
|
+
const ssrCode = await this.spawnVite(cwd, configPath, ["build", "--ssr"]);
|
|
117
|
+
if (ssrCode !== 0) {
|
|
118
|
+
this.fail("SSR build failed.");
|
|
119
|
+
return ssrCode;
|
|
120
|
+
}
|
|
121
|
+
this.success("SSR build complete!");
|
|
122
|
+
}
|
|
123
|
+
this.success(`Output in ${outDir}/`);
|
|
124
|
+
this.info("Deploy with: npx wrangler deploy");
|
|
125
|
+
return 0;
|
|
126
|
+
}
|
|
127
|
+
spawnVite(cwd, configPath, args) {
|
|
128
|
+
return new Promise((resolve) => {
|
|
129
|
+
const child = spawn("npx", [
|
|
130
|
+
"vite",
|
|
131
|
+
"--config",
|
|
132
|
+
configPath,
|
|
133
|
+
...args
|
|
134
|
+
], {
|
|
135
|
+
cwd,
|
|
136
|
+
stdio: "inherit",
|
|
137
|
+
shell: true
|
|
138
|
+
});
|
|
139
|
+
child.on("error", (err) => {
|
|
140
|
+
this.fail(`Vite process error: ${err.message}`);
|
|
141
|
+
resolve(1);
|
|
142
|
+
});
|
|
143
|
+
child.on("close", (code) => {
|
|
144
|
+
resolve(code ?? 0);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
//#endregion
|
|
150
|
+
//#region src/commands/inertia-dev.command.ts
|
|
151
|
+
var InertiaDevCommand = class extends Command {
|
|
152
|
+
static command = "inertia:dev {--port= : Dev server port} {--host : Expose to network} {--persist-to= : Shared persist directory for @cloudflare/vite-plugin (relative to cwd; the plugin appends /v3). Use to share R2/KV/cache emulator state across multiple workers in dev.}";
|
|
153
|
+
static description = "Start Inertia.js Vite development server";
|
|
154
|
+
async handle() {
|
|
155
|
+
const port = this.number("port");
|
|
156
|
+
const host = this.boolean("host");
|
|
157
|
+
const persistTo = this.string("persist-to");
|
|
158
|
+
const cwd = process.cwd();
|
|
159
|
+
if (!existsSync(join(cwd, "src/inertia/app.tsx"))) {
|
|
160
|
+
this.fail("src/inertia/app.tsx not found. Run `quarry inertia:install` first.");
|
|
161
|
+
return 1;
|
|
162
|
+
}
|
|
163
|
+
const configPath = writeTempViteConfig({
|
|
164
|
+
cwd,
|
|
165
|
+
server: {
|
|
166
|
+
port,
|
|
167
|
+
host
|
|
168
|
+
},
|
|
169
|
+
persistTo
|
|
170
|
+
});
|
|
171
|
+
this.info("Starting Vite dev server...");
|
|
172
|
+
const args = [
|
|
173
|
+
"vite",
|
|
174
|
+
"dev",
|
|
175
|
+
"--config",
|
|
176
|
+
configPath
|
|
177
|
+
];
|
|
178
|
+
if (host) args.push("--host");
|
|
179
|
+
return new Promise((resolve) => {
|
|
180
|
+
const child = spawn("npx", args, {
|
|
181
|
+
cwd,
|
|
182
|
+
stdio: "inherit",
|
|
183
|
+
shell: true
|
|
184
|
+
});
|
|
185
|
+
child.on("error", (err) => {
|
|
186
|
+
this.fail(`Failed to start dev server: ${err.message}`);
|
|
187
|
+
resolve(1);
|
|
188
|
+
});
|
|
189
|
+
child.on("close", (code) => {
|
|
190
|
+
resolve(code ?? 0);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
//#endregion
|
|
196
|
+
//#region src/commands/inertia-install.command.ts
|
|
197
|
+
const ROOT_HTML = `<!DOCTYPE html>
|
|
198
|
+
<html lang="en">
|
|
199
|
+
<head>
|
|
200
|
+
<meta charset="utf-8" />
|
|
201
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
202
|
+
@viteHead
|
|
203
|
+
@inertiaHead
|
|
204
|
+
</head>
|
|
205
|
+
<body>
|
|
206
|
+
@inertia
|
|
207
|
+
@viteScripts
|
|
208
|
+
</body>
|
|
209
|
+
</html>`;
|
|
210
|
+
const APP_TSX = `import { createInertiaApp } from '@inertiajs/react'
|
|
211
|
+
|
|
212
|
+
createInertiaApp({
|
|
213
|
+
resolve: async (name) => {
|
|
214
|
+
const pages = import.meta.glob('./pages/**/*.tsx')
|
|
215
|
+
const page = await pages[\`./pages/\${name}.tsx\`]?.()
|
|
216
|
+
if (!page) throw new Error(\`Page not found: \${name}\`)
|
|
217
|
+
return page
|
|
218
|
+
},
|
|
219
|
+
})`;
|
|
220
|
+
const HOME_TSX = `export default function Home({ message }: { message: string }) {
|
|
221
|
+
return (
|
|
222
|
+
<div>
|
|
223
|
+
<h1>{message}</h1>
|
|
224
|
+
<p>This page is rendered with Inertia.js and Stratal.</p>
|
|
225
|
+
</div>
|
|
226
|
+
)
|
|
227
|
+
}`;
|
|
228
|
+
var InertiaInstallCommand = class extends Command {
|
|
229
|
+
static command = "inertia:install {--skip-deps : Skip installing npm dependencies}";
|
|
230
|
+
static description = "Scaffold Inertia.js files for a Stratal project";
|
|
231
|
+
async handle() {
|
|
232
|
+
const skipDeps = this.boolean("skip-deps");
|
|
233
|
+
const cwd = process.cwd();
|
|
234
|
+
const inertiaDir = join(cwd, "src", "inertia");
|
|
235
|
+
const pagesDir = join(inertiaDir, "pages");
|
|
236
|
+
this.info("Creating src/inertia/ directory...");
|
|
237
|
+
mkdirSync(pagesDir, { recursive: true });
|
|
238
|
+
const publicDir = join(inertiaDir, "public");
|
|
239
|
+
mkdirSync(publicDir, { recursive: true });
|
|
240
|
+
const gitkeepPath = join(publicDir, ".gitkeep");
|
|
241
|
+
if (!existsSync(gitkeepPath)) writeFileSync(gitkeepPath, "", "utf-8");
|
|
242
|
+
this.success("Created src/inertia/public/");
|
|
243
|
+
const files = [
|
|
244
|
+
{
|
|
245
|
+
path: join(inertiaDir, "root.html"),
|
|
246
|
+
content: ROOT_HTML,
|
|
247
|
+
name: "root.html"
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
path: join(inertiaDir, "app.tsx"),
|
|
251
|
+
content: APP_TSX,
|
|
252
|
+
name: "app.tsx"
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
path: join(pagesDir, "Home.tsx"),
|
|
256
|
+
content: HOME_TSX,
|
|
257
|
+
name: "pages/Home.tsx"
|
|
258
|
+
}
|
|
259
|
+
];
|
|
260
|
+
for (const file of files) if (existsSync(file.path)) this.warn(`Skipping ${file.name} (already exists)`);
|
|
261
|
+
else {
|
|
262
|
+
writeFileSync(file.path, file.content, "utf-8");
|
|
263
|
+
this.success(`Created src/inertia/${file.name}`);
|
|
264
|
+
}
|
|
265
|
+
const appModulePath = join(cwd, "src", "app.module.ts");
|
|
266
|
+
if (existsSync(appModulePath)) {
|
|
267
|
+
this.info("Updating src/app.module.ts...");
|
|
268
|
+
try {
|
|
269
|
+
if (await this.updateAppModule(appModulePath)) this.success("Updated src/app.module.ts with InertiaModule");
|
|
270
|
+
else this.info("InertiaModule already configured in app.module.ts");
|
|
271
|
+
} catch (err) {
|
|
272
|
+
this.warn(`Could not auto-update app.module.ts: ${err.message}`);
|
|
273
|
+
this.info("Please manually add InertiaModule.forRoot() to your module imports");
|
|
274
|
+
}
|
|
275
|
+
} else this.info("No src/app.module.ts found — please manually configure InertiaModule");
|
|
276
|
+
try {
|
|
277
|
+
const { outputPath, pageCount } = await runTypeGeneration(cwd);
|
|
278
|
+
const relPath = relative(cwd, outputPath);
|
|
279
|
+
this.success(`Generated ${relPath} (${pageCount} page${pageCount !== 1 ? "s" : ""})`);
|
|
280
|
+
} catch {
|
|
281
|
+
this.warn("Could not generate initial type definitions. Run `quarry inertia:types` manually.");
|
|
282
|
+
}
|
|
283
|
+
if (!skipDeps) {
|
|
284
|
+
this.newLine();
|
|
285
|
+
this.info("Install the following dependencies:");
|
|
286
|
+
this.line(" npm install @stratal/inertia @inertiajs/react @inertiajs/vite react react-dom");
|
|
287
|
+
this.line(" npm install -D @types/react @types/react-dom vite @cloudflare/vite-plugin");
|
|
288
|
+
}
|
|
289
|
+
this.newLine();
|
|
290
|
+
this.success("Inertia.js scaffolding complete!");
|
|
291
|
+
this.info("Run `quarry inertia:dev` to start the dev server");
|
|
292
|
+
return 0;
|
|
293
|
+
}
|
|
294
|
+
async updateAppModule(modulePath) {
|
|
295
|
+
const { Project, SyntaxKind } = await import("ts-morph");
|
|
296
|
+
const sourceFile = new Project({ useInMemoryFileSystem: false }).addSourceFileAtPath(modulePath);
|
|
297
|
+
if (sourceFile.getImportDeclaration((decl) => decl.getModuleSpecifierValue() === "@stratal/inertia")) return false;
|
|
298
|
+
sourceFile.addImportDeclaration({
|
|
299
|
+
defaultImport: "rootView",
|
|
300
|
+
moduleSpecifier: "./inertia/root.html?raw"
|
|
301
|
+
});
|
|
302
|
+
sourceFile.addImportDeclaration({
|
|
303
|
+
namedImports: ["InertiaModule"],
|
|
304
|
+
moduleSpecifier: "@stratal/inertia"
|
|
305
|
+
});
|
|
306
|
+
const classes = sourceFile.getClasses();
|
|
307
|
+
for (const cls of classes) {
|
|
308
|
+
const moduleDecorator = cls.getDecorator("Module");
|
|
309
|
+
if (!moduleDecorator) continue;
|
|
310
|
+
const args = moduleDecorator.getArguments();
|
|
311
|
+
if (args.length === 0) continue;
|
|
312
|
+
const objLiteral = args[0].asKind(SyntaxKind.ObjectLiteralExpression);
|
|
313
|
+
if (!objLiteral) continue;
|
|
314
|
+
const importsProp = objLiteral.getProperty("imports");
|
|
315
|
+
if (importsProp) {
|
|
316
|
+
const arrayLiteral = (importsProp.asKind(SyntaxKind.PropertyAssignment)?.getInitializer())?.asKind(SyntaxKind.ArrayLiteralExpression);
|
|
317
|
+
if (arrayLiteral) arrayLiteral.addElement(`InertiaModule.forRoot({\n rootView,\n })`);
|
|
318
|
+
} else objLiteral.addPropertyAssignment({
|
|
319
|
+
name: "imports",
|
|
320
|
+
initializer: `[\n InertiaModule.forRoot({\n rootView,\n }),\n ]`
|
|
321
|
+
});
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
await sourceFile.save();
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
//#endregion
|
|
329
|
+
//#region src/commands/inertia-types.command.ts
|
|
330
|
+
var InertiaTypesCommand = class extends Command {
|
|
331
|
+
static command = "inertia:types {--watch : Watch for changes and regenerate}";
|
|
332
|
+
static description = "Generate Inertia.js page type definitions";
|
|
333
|
+
async handle() {
|
|
334
|
+
const cwd = process.cwd();
|
|
335
|
+
if (!existsSync(findPagesDir(cwd))) {
|
|
336
|
+
this.fail("src/inertia/pages/ not found. Run `quarry inertia:install` first.");
|
|
337
|
+
return 1;
|
|
338
|
+
}
|
|
339
|
+
if (!await this.generate(cwd)) return 1;
|
|
340
|
+
if (this.boolean("watch")) {
|
|
341
|
+
this.info("Watching for changes...");
|
|
342
|
+
await this.watchForChanges(cwd);
|
|
343
|
+
}
|
|
344
|
+
return 0;
|
|
345
|
+
}
|
|
346
|
+
async generate(cwd) {
|
|
347
|
+
try {
|
|
348
|
+
const { outputPath, pageCount } = await runTypeGeneration(cwd);
|
|
349
|
+
const relPath = relative(cwd, outputPath);
|
|
350
|
+
this.success(`Generated ${relPath} (${pageCount} page${pageCount !== 1 ? "s" : ""})`);
|
|
351
|
+
return true;
|
|
352
|
+
} catch (err) {
|
|
353
|
+
this.fail(`Type generation failed: ${err.message}`);
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
async watchForChanges(cwd) {
|
|
358
|
+
const srcDir = join(cwd, "src");
|
|
359
|
+
try {
|
|
360
|
+
const watcher = watch(srcDir, { recursive: true });
|
|
361
|
+
for await (const event of watcher) if (event.filename && /\.(tsx|ts)$/.test(event.filename)) {
|
|
362
|
+
this.info(`Change detected: ${event.filename}`);
|
|
363
|
+
await this.generate(cwd);
|
|
364
|
+
}
|
|
365
|
+
} catch (err) {
|
|
366
|
+
this.fail(`Watch failed: ${err.message}`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
//#endregion
|
|
371
|
+
//#region src/inertia.tokens.ts
|
|
372
|
+
const INERTIA_TOKENS = {
|
|
373
|
+
Options: Symbol.for("stratal:inertia:options"),
|
|
374
|
+
InertiaService: Symbol.for("stratal:inertia:service"),
|
|
375
|
+
TemplateService: Symbol.for("stratal:inertia:template"),
|
|
376
|
+
ManifestService: Symbol.for("stratal:inertia:manifest"),
|
|
377
|
+
SsrRenderer: Symbol.for("stratal:inertia:ssr-renderer")
|
|
378
|
+
};
|
|
379
|
+
//#endregion
|
|
380
|
+
//#region \0@oxc-project+runtime@0.127.0/helpers/decorateMetadata.js
|
|
381
|
+
function __decorateMetadata(k, v) {
|
|
382
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
383
|
+
}
|
|
384
|
+
//#endregion
|
|
385
|
+
//#region \0@oxc-project+runtime@0.127.0/helpers/decorateParam.js
|
|
386
|
+
function __decorateParam(paramIndex, decorator) {
|
|
387
|
+
return function(target, key) {
|
|
388
|
+
decorator(target, key, paramIndex);
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
//#endregion
|
|
392
|
+
//#region \0@oxc-project+runtime@0.127.0/helpers/decorate.js
|
|
393
|
+
function __decorate(decorators, target, key, desc) {
|
|
394
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
395
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
396
|
+
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;
|
|
397
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
398
|
+
}
|
|
399
|
+
//#endregion
|
|
400
|
+
//#region src/middleware/inertia.middleware.ts
|
|
401
|
+
let InertiaMiddleware = class InertiaMiddleware {
|
|
402
|
+
constructor(options) {
|
|
403
|
+
this.options = options;
|
|
404
|
+
}
|
|
405
|
+
async handle(ctx, next) {
|
|
406
|
+
const isInertia = ctx.header("x-inertia") === "true";
|
|
407
|
+
const isPrefetch = ctx.header("purpose") === "prefetch";
|
|
408
|
+
ctx.c.set("inertia", isInertia);
|
|
409
|
+
ctx.c.set("inertiaPrefetch", isPrefetch);
|
|
410
|
+
ctx.c.set("withoutSsr", false);
|
|
411
|
+
ctx.c.set("inertiaFlashOut", {});
|
|
412
|
+
let hadFlash = false;
|
|
413
|
+
if (this.options.flash) {
|
|
414
|
+
const flashData = await this.options.flash.store.read(ctx);
|
|
415
|
+
hadFlash = Object.keys(flashData).length > 0;
|
|
416
|
+
ctx.c.set("inertiaFlash", flashData);
|
|
417
|
+
} else ctx.c.set("inertiaFlash", {});
|
|
418
|
+
if (isInertia && ctx.c.req.method === "GET") {
|
|
419
|
+
const clientVersion = ctx.header("x-inertia-version");
|
|
420
|
+
const serverVersion = this.options.version ?? "";
|
|
421
|
+
if (clientVersion && serverVersion && clientVersion !== serverVersion) {
|
|
422
|
+
ctx.c.header("X-Inertia-Location", ctx.c.req.url);
|
|
423
|
+
ctx.c.status(409);
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
await next();
|
|
428
|
+
if (this.options.flash) {
|
|
429
|
+
const flashOut = ctx.c.get("inertiaFlashOut");
|
|
430
|
+
if (Object.keys(flashOut).length > 0) await this.options.flash.store.write(ctx, flashOut);
|
|
431
|
+
else if (hadFlash) await this.options.flash.store.clear(ctx);
|
|
432
|
+
}
|
|
433
|
+
ctx.c.header("Vary", "X-Inertia");
|
|
434
|
+
if (isInertia) {
|
|
435
|
+
const method = ctx.c.req.method;
|
|
436
|
+
if (ctx.c.res.status === 302 && method !== "GET" && method !== "HEAD") ctx.c.status(303);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
InertiaMiddleware = __decorate([
|
|
441
|
+
Transient(),
|
|
442
|
+
__decorateParam(0, inject(INERTIA_TOKENS.Options)),
|
|
443
|
+
__decorateMetadata("design:paramtypes", [Object])
|
|
444
|
+
], InertiaMiddleware);
|
|
445
|
+
//#endregion
|
|
446
|
+
//#region src/types.ts
|
|
447
|
+
const INERTIA_PROP_OPTIONAL = Symbol.for("stratal:inertia:prop:optional");
|
|
448
|
+
const INERTIA_PROP_DEFERRED = Symbol.for("stratal:inertia:prop:deferred");
|
|
449
|
+
const INERTIA_PROP_MERGE = Symbol.for("stratal:inertia:prop:merge");
|
|
450
|
+
const INERTIA_PROP_ONCE = Symbol.for("stratal:inertia:prop:once");
|
|
451
|
+
const INERTIA_PROP_ALWAYS = Symbol.for("stratal:inertia:prop:always");
|
|
452
|
+
//#endregion
|
|
453
|
+
//#region src/services/inertia.service.ts
|
|
454
|
+
let InertiaService = class InertiaService {
|
|
455
|
+
sharedData = {};
|
|
456
|
+
constructor(options, template, ssr) {
|
|
457
|
+
this.options = options;
|
|
458
|
+
this.template = template;
|
|
459
|
+
this.ssr = ssr;
|
|
460
|
+
}
|
|
461
|
+
share(key, value) {
|
|
462
|
+
this.sharedData[key] = value;
|
|
463
|
+
}
|
|
464
|
+
location(url) {
|
|
465
|
+
return new Response("", {
|
|
466
|
+
status: 409,
|
|
467
|
+
headers: { "X-Inertia-Location": url }
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
optional(callback) {
|
|
471
|
+
return {
|
|
472
|
+
[INERTIA_PROP_OPTIONAL]: true,
|
|
473
|
+
callback
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
defer(callback, group = "default") {
|
|
477
|
+
return {
|
|
478
|
+
[INERTIA_PROP_DEFERRED]: true,
|
|
479
|
+
callback,
|
|
480
|
+
group
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
merge(callback, options) {
|
|
484
|
+
return {
|
|
485
|
+
[INERTIA_PROP_MERGE]: true,
|
|
486
|
+
callback,
|
|
487
|
+
strategy: options?.strategy ?? "append",
|
|
488
|
+
matchOn: options?.matchOn
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
once(callback, options) {
|
|
492
|
+
return {
|
|
493
|
+
[INERTIA_PROP_ONCE]: true,
|
|
494
|
+
callback,
|
|
495
|
+
expiresAt: options?.expiresAt ?? null,
|
|
496
|
+
key: options?.key
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
always(callback) {
|
|
500
|
+
return {
|
|
501
|
+
[INERTIA_PROP_ALWAYS]: true,
|
|
502
|
+
callback
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
async render(ctx, component, props = {}, renderOptions = {}) {
|
|
506
|
+
const reqUrl = new URL(ctx.c.req.url);
|
|
507
|
+
const url = reqUrl.search ? `${reqUrl.pathname}${reqUrl.search}` : reqUrl.pathname;
|
|
508
|
+
const isInertia = ctx.c.get("inertia");
|
|
509
|
+
const { shared: resolvedShared, sharedKeys } = await this.resolveSharedData(ctx);
|
|
510
|
+
const allProps = {
|
|
511
|
+
...resolvedShared,
|
|
512
|
+
...this.sharedData,
|
|
513
|
+
...props
|
|
514
|
+
};
|
|
515
|
+
const allSharedKeys = [...sharedKeys, ...Object.keys(this.sharedData)];
|
|
516
|
+
const result = await this.processProps(allProps, ctx, component, isInertia);
|
|
517
|
+
const { errors: flashErrors, ...flash } = ctx.c.get("inertiaFlash") ?? {};
|
|
518
|
+
const errors = flashErrors && typeof flashErrors === "object" && !Array.isArray(flashErrors) ? flashErrors : {};
|
|
519
|
+
const page = {
|
|
520
|
+
component,
|
|
521
|
+
props: {
|
|
522
|
+
...result.resolvedProps,
|
|
523
|
+
errors
|
|
524
|
+
},
|
|
525
|
+
url,
|
|
526
|
+
version: this.options.version ?? null,
|
|
527
|
+
flash,
|
|
528
|
+
rememberedState: {},
|
|
529
|
+
...result.mergeProps.length > 0 ? { mergeProps: result.mergeProps } : {},
|
|
530
|
+
...result.prependProps.length > 0 ? { prependProps: result.prependProps } : {},
|
|
531
|
+
...result.deepMergeProps.length > 0 ? { deepMergeProps: result.deepMergeProps } : {},
|
|
532
|
+
...result.matchPropsOn.length > 0 ? { matchPropsOn: result.matchPropsOn } : {},
|
|
533
|
+
...Object.keys(result.deferredProps).length > 0 ? { deferredProps: result.deferredProps } : {},
|
|
534
|
+
...Object.keys(result.deferredProps).length > 0 && !this.isPartialReload(ctx, component) ? { initialDeferredProps: result.deferredProps } : {},
|
|
535
|
+
...Object.keys(result.onceProps).length > 0 ? { onceProps: result.onceProps } : {},
|
|
536
|
+
...allSharedKeys.length > 0 ? { sharedProps: allSharedKeys } : {},
|
|
537
|
+
...renderOptions.encryptHistory ? { encryptHistory: true } : {},
|
|
538
|
+
...renderOptions.clearHistory ? { clearHistory: true } : {},
|
|
539
|
+
...renderOptions.preserveFragment ? { preserveFragment: true } : {}
|
|
540
|
+
};
|
|
541
|
+
if (isInertia) return new Response(JSON.stringify(page), {
|
|
542
|
+
status: 200,
|
|
543
|
+
headers: {
|
|
544
|
+
"Content-Type": "application/json",
|
|
545
|
+
"X-Inertia": "true",
|
|
546
|
+
"Vary": "X-Inertia"
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
const ssrResult = ctx.c.get("withoutSsr") || this.isSsrDisabled(url) ? {
|
|
550
|
+
head: [],
|
|
551
|
+
body: ""
|
|
552
|
+
} : await this.ssr.render(page);
|
|
553
|
+
const html = this.template.render(page, ssrResult.head, ssrResult.body);
|
|
554
|
+
return new Response(html, {
|
|
555
|
+
status: 200,
|
|
556
|
+
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Resolve shared data from module options and i18n configuration.
|
|
561
|
+
*
|
|
562
|
+
* Processes static values and resolver functions from `sharedData` config.
|
|
563
|
+
* When `i18n` option is set, auto-injects `locale` and `translations` props
|
|
564
|
+
* using the core {@link MessageLoaderService} resolved from the request container.
|
|
565
|
+
*/
|
|
566
|
+
async resolveSharedData(ctx) {
|
|
567
|
+
const shared = {};
|
|
568
|
+
const configShared = this.options.sharedData;
|
|
569
|
+
if (configShared) for (const [key, value] of Object.entries(configShared)) if (typeof value === "function") shared[key] = await value(ctx);
|
|
570
|
+
else shared[key] = value;
|
|
571
|
+
if (this.options.i18n) {
|
|
572
|
+
const loader = ctx.getContainer().resolve(I18N_TOKENS.MessageLoader);
|
|
573
|
+
const locale = ctx.getLocale();
|
|
574
|
+
shared.locale = locale;
|
|
575
|
+
shared.translations = loader.getFilteredMessages(locale, { only: this.options.i18n.only });
|
|
576
|
+
}
|
|
577
|
+
if (this.options.routes) {
|
|
578
|
+
const registry = ctx.getContainer().resolve(ROUTER_TOKENS.RouteRegistry);
|
|
579
|
+
shared.routes = this.serializeRoutes(registry.named());
|
|
580
|
+
}
|
|
581
|
+
return {
|
|
582
|
+
shared,
|
|
583
|
+
sharedKeys: Object.keys(shared)
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
isPartialReload(ctx, component) {
|
|
587
|
+
const isInertia = ctx.c.get("inertia");
|
|
588
|
+
const partialComponent = ctx.header("x-inertia-partial-component");
|
|
589
|
+
const partialDataHeader = ctx.header("x-inertia-partial-data");
|
|
590
|
+
return !!(isInertia && partialComponent === component && partialDataHeader);
|
|
591
|
+
}
|
|
592
|
+
async processProps(allProps, ctx, component, isInertia) {
|
|
593
|
+
const resolvedProps = {};
|
|
594
|
+
const mergeProps = [];
|
|
595
|
+
const prependProps = [];
|
|
596
|
+
const deepMergeProps = [];
|
|
597
|
+
const matchPropsOn = [];
|
|
598
|
+
const deferredProps = {};
|
|
599
|
+
const onceProps = {};
|
|
600
|
+
const partialComponent = ctx.header("x-inertia-partial-component");
|
|
601
|
+
const partialDataHeader = ctx.header("x-inertia-partial-data");
|
|
602
|
+
const partialExceptHeader = ctx.header("x-inertia-partial-except");
|
|
603
|
+
const resetHeader = ctx.header("x-inertia-reset");
|
|
604
|
+
const isPartialReload = isInertia && partialComponent === component && partialDataHeader;
|
|
605
|
+
const requestedProps = partialDataHeader?.split(",").map((s) => s.trim()) ?? [];
|
|
606
|
+
const exceptProps = partialExceptHeader?.split(",").map((s) => s.trim()) ?? [];
|
|
607
|
+
resetHeader?.split(",").map((s) => s.trim());
|
|
608
|
+
for (const [key, value] of Object.entries(allProps)) {
|
|
609
|
+
if (this.isAlwaysProp(value)) {
|
|
610
|
+
resolvedProps[key] = await value.callback();
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
if (this.isOnceProp(value)) {
|
|
614
|
+
if (isPartialReload && this.isRequested(key, requestedProps)) resolvedProps[key] = await value.callback();
|
|
615
|
+
else if (!isPartialReload) {
|
|
616
|
+
resolvedProps[key] = await value.callback();
|
|
617
|
+
onceProps[key] = {
|
|
618
|
+
prop: value.key ?? key,
|
|
619
|
+
...value.expiresAt != null ? { expiresAt: value.expiresAt } : {}
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
if (this.isDeferredProp(value)) {
|
|
625
|
+
if (isPartialReload && this.isRequested(key, requestedProps)) resolvedProps[key] = await value.callback();
|
|
626
|
+
else if (!isPartialReload) {
|
|
627
|
+
deferredProps[value.group] ??= [];
|
|
628
|
+
deferredProps[value.group].push(key);
|
|
629
|
+
}
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
if (this.isMergeProp(value)) {
|
|
633
|
+
if (isPartialReload && !this.isRequested(key, requestedProps)) continue;
|
|
634
|
+
switch (value.strategy) {
|
|
635
|
+
case "prepend":
|
|
636
|
+
prependProps.push(key);
|
|
637
|
+
break;
|
|
638
|
+
case "deep":
|
|
639
|
+
deepMergeProps.push(key);
|
|
640
|
+
break;
|
|
641
|
+
default:
|
|
642
|
+
mergeProps.push(key);
|
|
643
|
+
break;
|
|
644
|
+
}
|
|
645
|
+
if (value.matchOn) matchPropsOn.push(`${key}:${value.matchOn}`);
|
|
646
|
+
resolvedProps[key] = await value.callback();
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
if (this.isOptionalProp(value)) {
|
|
650
|
+
if (isPartialReload && this.isRequested(key, requestedProps)) resolvedProps[key] = await value.callback();
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
653
|
+
if (isPartialReload) {
|
|
654
|
+
if (this.isRequested(key, requestedProps) && !this.isExcepted(key, exceptProps)) resolvedProps[key] = value;
|
|
655
|
+
} else resolvedProps[key] = value;
|
|
656
|
+
}
|
|
657
|
+
return {
|
|
658
|
+
resolvedProps,
|
|
659
|
+
mergeProps,
|
|
660
|
+
prependProps,
|
|
661
|
+
deepMergeProps,
|
|
662
|
+
matchPropsOn,
|
|
663
|
+
deferredProps,
|
|
664
|
+
onceProps
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Check if a prop key is requested — supports dot-notation (e.g., `user.permissions`
|
|
669
|
+
* matches the top-level `user` key).
|
|
670
|
+
*/
|
|
671
|
+
isRequested(key, requestedProps) {
|
|
672
|
+
return requestedProps.some((prop) => prop === key || prop.startsWith(`${key}.`));
|
|
673
|
+
}
|
|
674
|
+
isExcepted(key, exceptProps) {
|
|
675
|
+
return exceptProps.some((prop) => prop === key || prop.startsWith(`${key}.`));
|
|
676
|
+
}
|
|
677
|
+
isOptionalProp(value) {
|
|
678
|
+
return typeof value === "object" && value !== null && INERTIA_PROP_OPTIONAL in value;
|
|
679
|
+
}
|
|
680
|
+
isDeferredProp(value) {
|
|
681
|
+
return typeof value === "object" && value !== null && INERTIA_PROP_DEFERRED in value;
|
|
682
|
+
}
|
|
683
|
+
isMergeProp(value) {
|
|
684
|
+
return typeof value === "object" && value !== null && INERTIA_PROP_MERGE in value;
|
|
685
|
+
}
|
|
686
|
+
isOnceProp(value) {
|
|
687
|
+
return typeof value === "object" && value !== null && INERTIA_PROP_ONCE in value;
|
|
688
|
+
}
|
|
689
|
+
isAlwaysProp(value) {
|
|
690
|
+
return typeof value === "object" && value !== null && INERTIA_PROP_ALWAYS in value;
|
|
691
|
+
}
|
|
692
|
+
serializeRoutes(routes) {
|
|
693
|
+
const serialized = {};
|
|
694
|
+
for (const route of routes) if (route.name) serialized[route.name] = {
|
|
695
|
+
path: route.path,
|
|
696
|
+
paramNames: route.paramNames,
|
|
697
|
+
domainParamNames: route.domainParamNames,
|
|
698
|
+
...route.domain ? { domain: route.domain } : {},
|
|
699
|
+
...route.localePaths?.length ? { localePaths: route.localePaths } : {}
|
|
700
|
+
};
|
|
701
|
+
return serialized;
|
|
702
|
+
}
|
|
703
|
+
isSsrDisabled(pathname) {
|
|
704
|
+
const patterns = this.options.ssr?.disabled;
|
|
705
|
+
if (!patterns || patterns.length === 0) return false;
|
|
706
|
+
return patterns.some((pattern) => {
|
|
707
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
708
|
+
return new RegExp(`^/${escaped.replace(/\*/g, "[^/]*")}$`).test(pathname);
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
InertiaService = __decorate([
|
|
713
|
+
Transient(INERTIA_TOKENS.InertiaService),
|
|
714
|
+
__decorateParam(0, inject(INERTIA_TOKENS.Options)),
|
|
715
|
+
__decorateParam(1, inject(INERTIA_TOKENS.TemplateService)),
|
|
716
|
+
__decorateParam(2, inject(INERTIA_TOKENS.SsrRenderer)),
|
|
717
|
+
__decorateMetadata("design:paramtypes", [
|
|
718
|
+
Object,
|
|
719
|
+
Object,
|
|
720
|
+
Object
|
|
721
|
+
])
|
|
722
|
+
], InertiaService);
|
|
723
|
+
//#endregion
|
|
724
|
+
//#region src/services/manifest.service.ts
|
|
725
|
+
const DEFAULT_ENTRY_CLIENT_PATH = "src/inertia/app.tsx";
|
|
726
|
+
let ManifestService = class ManifestService {
|
|
727
|
+
manifest;
|
|
728
|
+
entryClientPath;
|
|
729
|
+
constructor(options) {
|
|
730
|
+
this.manifest = options.manifest ?? null;
|
|
731
|
+
this.entryClientPath = (options.entryClientPath ?? DEFAULT_ENTRY_CLIENT_PATH).replace(/^\/+/, "");
|
|
732
|
+
}
|
|
733
|
+
get isDev() {
|
|
734
|
+
return this.manifest === null;
|
|
735
|
+
}
|
|
736
|
+
getHeadTags() {
|
|
737
|
+
if (this.isDev) return "<link rel=\"stylesheet\" href=\"/__inertia/ssr-css\" data-ssr-css />";
|
|
738
|
+
const tags = [];
|
|
739
|
+
const seen = /* @__PURE__ */ new Set();
|
|
740
|
+
for (const entry of Object.values(this.manifest)) if (entry.css) for (const cssFile of entry.css) {
|
|
741
|
+
if (seen.has(cssFile)) continue;
|
|
742
|
+
seen.add(cssFile);
|
|
743
|
+
tags.push(`<link rel="stylesheet" href="/${cssFile}" />`);
|
|
744
|
+
}
|
|
745
|
+
return tags.join("\n");
|
|
746
|
+
}
|
|
747
|
+
getScriptTags() {
|
|
748
|
+
if (this.isDev) return [
|
|
749
|
+
"<script type=\"module\" src=\"/@vite/client\"><\/script>",
|
|
750
|
+
`<script type="module">
|
|
751
|
+
import { createHotContext } from "/@vite/client";
|
|
752
|
+
const hot = createHotContext("/__ssr_css");
|
|
753
|
+
hot.on("vite:afterUpdate", () => {
|
|
754
|
+
document.querySelectorAll("[data-ssr-css]").forEach(el => el.remove());
|
|
755
|
+
});
|
|
756
|
+
<\/script>`,
|
|
757
|
+
`<script type="module" src="/${this.entryClientPath}"><\/script>`
|
|
758
|
+
].join("\n");
|
|
759
|
+
const tags = [];
|
|
760
|
+
for (const entry of Object.values(this.manifest)) if (entry.isEntry) tags.push(`<script type="module" src="/${entry.file}"><\/script>`);
|
|
761
|
+
return tags.join("\n");
|
|
762
|
+
}
|
|
763
|
+
};
|
|
764
|
+
ManifestService = __decorate([
|
|
765
|
+
Transient(),
|
|
766
|
+
__decorateParam(0, inject(INERTIA_TOKENS.Options)),
|
|
767
|
+
__decorateMetadata("design:paramtypes", [Object])
|
|
768
|
+
], ManifestService);
|
|
769
|
+
//#endregion
|
|
770
|
+
//#region src/services/ssr-renderer.service.ts
|
|
771
|
+
let SsrRendererService = class SsrRendererService {
|
|
772
|
+
bundle = null;
|
|
773
|
+
loadPromise = null;
|
|
774
|
+
constructor(options, logger) {
|
|
775
|
+
this.options = options;
|
|
776
|
+
this.logger = logger;
|
|
777
|
+
}
|
|
778
|
+
async render(page) {
|
|
779
|
+
if (!this.options.ssr) return {
|
|
780
|
+
head: [],
|
|
781
|
+
body: ""
|
|
782
|
+
};
|
|
783
|
+
await this.ensureBundle();
|
|
784
|
+
if (!this.bundle) return {
|
|
785
|
+
head: [],
|
|
786
|
+
body: ""
|
|
787
|
+
};
|
|
788
|
+
return this.bundle.render(page);
|
|
789
|
+
}
|
|
790
|
+
async ensureBundle() {
|
|
791
|
+
if (this.bundle) return;
|
|
792
|
+
this.loadPromise ??= this.loadBundle();
|
|
793
|
+
try {
|
|
794
|
+
await this.loadPromise;
|
|
795
|
+
} catch {}
|
|
796
|
+
}
|
|
797
|
+
async loadBundle() {
|
|
798
|
+
if (!this.options.ssr) return;
|
|
799
|
+
try {
|
|
800
|
+
const mod = await this.options.ssr.bundle();
|
|
801
|
+
const resolved = "default" in mod ? mod.default : mod;
|
|
802
|
+
this.bundle = resolved;
|
|
803
|
+
} catch (error) {
|
|
804
|
+
this.logger.warn("[stratal:inertia] Failed to load SSR bundle. Falling back to client-side rendering.", { error });
|
|
805
|
+
this.loadPromise = null;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
};
|
|
809
|
+
SsrRendererService = __decorate([
|
|
810
|
+
Transient(),
|
|
811
|
+
__decorateParam(0, inject(INERTIA_TOKENS.Options)),
|
|
812
|
+
__decorateParam(1, inject(LOGGER_TOKENS.LoggerService)),
|
|
813
|
+
__decorateMetadata("design:paramtypes", [Object, Object])
|
|
814
|
+
], SsrRendererService);
|
|
815
|
+
//#endregion
|
|
816
|
+
//#region src/services/template.service.ts
|
|
817
|
+
let TemplateService = class TemplateService {
|
|
818
|
+
constructor(options, manifest) {
|
|
819
|
+
this.options = options;
|
|
820
|
+
this.manifest = manifest;
|
|
821
|
+
}
|
|
822
|
+
render(page, ssrHead, ssrBody) {
|
|
823
|
+
const appHtml = ssrBody || this.buildClientOnlyBody(page);
|
|
824
|
+
const headTags = ssrHead.length > 0 ? ssrHead.join("\n") : "";
|
|
825
|
+
const viteHead = this.manifest.getHeadTags();
|
|
826
|
+
const viteScripts = this.manifest.getScriptTags();
|
|
827
|
+
let html = this.options.rootView;
|
|
828
|
+
html = html.replace("@inertiaHead", headTags);
|
|
829
|
+
html = html.replace("@inertia", appHtml);
|
|
830
|
+
html = html.replace("@viteHead", viteHead);
|
|
831
|
+
html = html.replace("@viteScripts", viteScripts);
|
|
832
|
+
return html;
|
|
833
|
+
}
|
|
834
|
+
buildClientOnlyBody(page) {
|
|
835
|
+
return `<script data-page="app" type="application/json">${JSON.stringify(page).replace(/\//g, "\\/")}<\/script><div id="app"></div>`;
|
|
836
|
+
}
|
|
837
|
+
};
|
|
838
|
+
TemplateService = __decorate([
|
|
839
|
+
Transient(),
|
|
840
|
+
__decorateParam(0, inject(INERTIA_TOKENS.Options)),
|
|
841
|
+
__decorateParam(1, inject(INERTIA_TOKENS.ManifestService)),
|
|
842
|
+
__decorateMetadata("design:paramtypes", [Object, Object])
|
|
843
|
+
], TemplateService);
|
|
844
|
+
//#endregion
|
|
845
|
+
//#region src/inertia.module.ts
|
|
846
|
+
var _InertiaModule;
|
|
847
|
+
let InertiaModule = _InertiaModule = class InertiaModule {
|
|
848
|
+
static forRoot(options) {
|
|
849
|
+
return {
|
|
850
|
+
module: _InertiaModule,
|
|
851
|
+
providers: [{
|
|
852
|
+
provide: INERTIA_TOKENS.Options,
|
|
853
|
+
useValue: options
|
|
854
|
+
}]
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
static forRootAsync(options) {
|
|
858
|
+
return {
|
|
859
|
+
module: _InertiaModule,
|
|
860
|
+
providers: [{
|
|
861
|
+
provide: INERTIA_TOKENS.Options,
|
|
862
|
+
useFactory: options.useFactory,
|
|
863
|
+
inject: options.inject
|
|
864
|
+
}]
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
configureRoutes(router) {
|
|
868
|
+
router.use(InertiaMiddleware);
|
|
869
|
+
}
|
|
870
|
+
onException(handler) {
|
|
871
|
+
handler.renderable(SchemaValidationError, (error, context) => {
|
|
872
|
+
if (context.type !== "http") return void 0;
|
|
873
|
+
if (this.isPrecognitionRequest(context)) return this.handlePrecognitionValidationError(error, context);
|
|
874
|
+
if (!this.isInertiaRequest(context)) return void 0;
|
|
875
|
+
const issues = error.metadata?.issues ?? [];
|
|
876
|
+
const errors = {};
|
|
877
|
+
for (const issue of issues) errors[issue.path] = issue.message;
|
|
878
|
+
context.ctx.flash("errors", errors);
|
|
879
|
+
return this.redirectBack(context);
|
|
880
|
+
});
|
|
881
|
+
handler.renderable(ApplicationError, (error, context) => {
|
|
882
|
+
if (context.type !== "http") return void 0;
|
|
883
|
+
const message = context.ctx.getContainer().resolve(I18N_TOKENS.I18nService).t(error.message, error.metadata);
|
|
884
|
+
if (this.isPrecognitionRequest(context)) return this.createPrecognitionErrorResponse({ _form: message });
|
|
885
|
+
if (!this.isInertiaRequest(context)) return void 0;
|
|
886
|
+
context.ctx.flash("errors", { _form: message });
|
|
887
|
+
return this.redirectBack(context);
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
onInitialize() {
|
|
891
|
+
augmentRouterContext((ctx) => {
|
|
892
|
+
return ctx.getContainer().resolve(INERTIA_TOKENS.InertiaService);
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
isInertiaRequest(context) {
|
|
896
|
+
return context.ctx.header("x-inertia") === "true";
|
|
897
|
+
}
|
|
898
|
+
isPrecognitionRequest(context) {
|
|
899
|
+
return context.ctx.header("precognition") === "true";
|
|
900
|
+
}
|
|
901
|
+
handlePrecognitionValidationError(error, context) {
|
|
902
|
+
const issues = error.metadata?.issues ?? [];
|
|
903
|
+
let errors = {};
|
|
904
|
+
for (const issue of issues) errors[issue.path] = issue.message;
|
|
905
|
+
const validateOnly = context.ctx.header("precognition-validate-only");
|
|
906
|
+
if (validateOnly) {
|
|
907
|
+
const fields = validateOnly.split(",").map((f) => f.trim());
|
|
908
|
+
const filtered = {};
|
|
909
|
+
for (const field of fields) if (errors[field]) filtered[field] = errors[field];
|
|
910
|
+
errors = filtered;
|
|
911
|
+
}
|
|
912
|
+
if (Object.keys(errors).length === 0) return new Response(null, {
|
|
913
|
+
status: 204,
|
|
914
|
+
headers: {
|
|
915
|
+
"Precognition": "true",
|
|
916
|
+
"Precognition-Success": "true",
|
|
917
|
+
"Vary": "Precognition"
|
|
918
|
+
}
|
|
919
|
+
});
|
|
920
|
+
return this.createPrecognitionErrorResponse(errors);
|
|
921
|
+
}
|
|
922
|
+
createPrecognitionErrorResponse(errors) {
|
|
923
|
+
return new Response(JSON.stringify({ errors }), {
|
|
924
|
+
status: 422,
|
|
925
|
+
headers: {
|
|
926
|
+
"Content-Type": "application/json",
|
|
927
|
+
"Precognition": "true",
|
|
928
|
+
"Vary": "Precognition"
|
|
929
|
+
}
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
redirectBack(context) {
|
|
933
|
+
const referer = context.ctx.header("referer");
|
|
934
|
+
if (referer) {
|
|
935
|
+
const parsed = new URL(referer);
|
|
936
|
+
const url = parsed.search ? `${parsed.pathname}${parsed.search}` : parsed.pathname;
|
|
937
|
+
return context.ctx.redirect(url, 303);
|
|
938
|
+
}
|
|
939
|
+
return context.ctx.redirect("/", 303);
|
|
940
|
+
}
|
|
941
|
+
};
|
|
942
|
+
InertiaModule = _InertiaModule = __decorate([Module({ providers: [
|
|
943
|
+
{
|
|
944
|
+
provide: INERTIA_TOKENS.InertiaService,
|
|
945
|
+
useClass: InertiaService,
|
|
946
|
+
scope: Scope.Request
|
|
947
|
+
},
|
|
948
|
+
{
|
|
949
|
+
provide: INERTIA_TOKENS.TemplateService,
|
|
950
|
+
useClass: TemplateService
|
|
951
|
+
},
|
|
952
|
+
{
|
|
953
|
+
provide: INERTIA_TOKENS.ManifestService,
|
|
954
|
+
useClass: ManifestService
|
|
955
|
+
},
|
|
956
|
+
{
|
|
957
|
+
provide: INERTIA_TOKENS.SsrRenderer,
|
|
958
|
+
useClass: SsrRendererService,
|
|
959
|
+
scope: Scope.Singleton
|
|
960
|
+
},
|
|
961
|
+
InertiaInstallCommand,
|
|
962
|
+
InertiaTypesCommand,
|
|
963
|
+
InertiaDevCommand,
|
|
964
|
+
InertiaBuildCommand
|
|
965
|
+
] })], InertiaModule);
|
|
966
|
+
//#endregion
|
|
967
|
+
//#region src/flash/cookie-flash-store.ts
|
|
968
|
+
var CookieFlashStore = class {
|
|
969
|
+
cookieName;
|
|
970
|
+
secret;
|
|
971
|
+
cookieOptions;
|
|
972
|
+
constructor(options) {
|
|
973
|
+
this.secret = options.secret;
|
|
974
|
+
this.cookieName = options.cookie ?? "stratal_flash";
|
|
975
|
+
this.cookieOptions = {
|
|
976
|
+
path: "/",
|
|
977
|
+
httpOnly: true,
|
|
978
|
+
sameSite: "Lax",
|
|
979
|
+
...options.cookieOptions
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
async read(ctx) {
|
|
983
|
+
const value = await getSignedCookie(ctx.c, this.secret, this.cookieName);
|
|
984
|
+
if (!value) return {};
|
|
985
|
+
try {
|
|
986
|
+
return JSON.parse(atob(value));
|
|
987
|
+
} catch {
|
|
988
|
+
return {};
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
async write(ctx, data) {
|
|
992
|
+
const encoded = btoa(JSON.stringify(data));
|
|
993
|
+
await setSignedCookie(ctx.c, this.cookieName, encoded, this.secret, this.cookieOptions);
|
|
994
|
+
}
|
|
995
|
+
clear(ctx) {
|
|
996
|
+
deleteCookie(ctx.c, this.cookieName, { path: this.cookieOptions.path });
|
|
997
|
+
return Promise.resolve();
|
|
998
|
+
}
|
|
999
|
+
};
|
|
1000
|
+
const inertiaResponse = {
|
|
1001
|
+
schema: z.object({
|
|
1002
|
+
component: z.string(),
|
|
1003
|
+
props: z.record(z.string(), z.unknown()),
|
|
1004
|
+
url: z.string(),
|
|
1005
|
+
version: z.string().nullable(),
|
|
1006
|
+
flash: z.record(z.string(), z.unknown()),
|
|
1007
|
+
rememberedState: z.record(z.string(), z.unknown()),
|
|
1008
|
+
mergeProps: z.array(z.string()).optional(),
|
|
1009
|
+
prependProps: z.array(z.string()).optional(),
|
|
1010
|
+
deepMergeProps: z.array(z.string()).optional(),
|
|
1011
|
+
matchPropsOn: z.array(z.string()).optional(),
|
|
1012
|
+
deferredProps: z.record(z.string(), z.array(z.string())).optional(),
|
|
1013
|
+
initialDeferredProps: z.record(z.string(), z.array(z.string())).optional(),
|
|
1014
|
+
onceProps: z.record(z.string(), z.object({
|
|
1015
|
+
prop: z.string(),
|
|
1016
|
+
expiresAt: z.number().nullable().optional()
|
|
1017
|
+
})).optional(),
|
|
1018
|
+
sharedProps: z.array(z.string()).optional(),
|
|
1019
|
+
encryptHistory: z.boolean().optional(),
|
|
1020
|
+
clearHistory: z.boolean().optional(),
|
|
1021
|
+
preserveFragment: z.boolean().optional()
|
|
1022
|
+
}),
|
|
1023
|
+
description: "Inertia page response",
|
|
1024
|
+
contentType: "text/html"
|
|
1025
|
+
};
|
|
1026
|
+
/**
|
|
1027
|
+
* Builds a full RouteConfig from InertiaRouteConfig by applying inertia defaults.
|
|
1028
|
+
*/
|
|
1029
|
+
function buildInertiaConfig(config) {
|
|
1030
|
+
const { hideFromDocs = true, ...rest } = config;
|
|
1031
|
+
return {
|
|
1032
|
+
...rest,
|
|
1033
|
+
response: inertiaResponse,
|
|
1034
|
+
hideFromDocs
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* Decorator for Inertia page routes using convention-based routing.
|
|
1039
|
+
*
|
|
1040
|
+
* Wraps `@Route()` with:
|
|
1041
|
+
* - Auto-applied Inertia page response schema
|
|
1042
|
+
* - `hideFromDocs: true` by default (overridable)
|
|
1043
|
+
*
|
|
1044
|
+
* **Cannot be mixed with HTTP method decorators** (`@Get`, `@Post`, `@InertiaGet`, etc.)
|
|
1045
|
+
* in the same controller.
|
|
1046
|
+
*
|
|
1047
|
+
* @example
|
|
1048
|
+
* ```typescript
|
|
1049
|
+
* @Controller('/notes')
|
|
1050
|
+
* export class NotesController implements IController {
|
|
1051
|
+
* @InertiaRoute({ query: z.object({ page: z.string().optional() }) })
|
|
1052
|
+
* async index(ctx: RouterContext) {
|
|
1053
|
+
* return ctx.inertia('notes/Index', { notes: [] })
|
|
1054
|
+
* }
|
|
1055
|
+
* }
|
|
1056
|
+
* ```
|
|
1057
|
+
*/
|
|
1058
|
+
function InertiaRoute(config = {}) {
|
|
1059
|
+
return Route(buildInertiaConfig(config));
|
|
1060
|
+
}
|
|
1061
|
+
/**
|
|
1062
|
+
* Registers a GET route for an Inertia page.
|
|
1063
|
+
*
|
|
1064
|
+
* Wraps `@Get()` with auto-applied Inertia page response schema
|
|
1065
|
+
* and `hideFromDocs: true` by default.
|
|
1066
|
+
*
|
|
1067
|
+
* @param path - Route path relative to the controller base path
|
|
1068
|
+
* @param config - Optional route configuration (query, params, tags, etc.)
|
|
1069
|
+
*
|
|
1070
|
+
* @example
|
|
1071
|
+
* ```typescript
|
|
1072
|
+
* @Controller('/notes')
|
|
1073
|
+
* export class NotesController {
|
|
1074
|
+
* @InertiaGet('/')
|
|
1075
|
+
* async index(ctx: RouterContext) {
|
|
1076
|
+
* return ctx.inertia('notes/Index', { notes: [] })
|
|
1077
|
+
* }
|
|
1078
|
+
*
|
|
1079
|
+
* @InertiaGet('/:id', { params: z.object({ id: z.string() }) })
|
|
1080
|
+
* async show(ctx: RouterContext) {
|
|
1081
|
+
* return ctx.inertia('notes/Show', { note })
|
|
1082
|
+
* }
|
|
1083
|
+
* }
|
|
1084
|
+
* ```
|
|
1085
|
+
*/
|
|
1086
|
+
function InertiaGet(path, config = {}) {
|
|
1087
|
+
return Get(path, buildInertiaConfig(config));
|
|
1088
|
+
}
|
|
1089
|
+
/**
|
|
1090
|
+
* Registers a POST route for an Inertia form submission.
|
|
1091
|
+
*
|
|
1092
|
+
* Wraps `@Post()` with auto-applied Inertia page response schema
|
|
1093
|
+
* and `hideFromDocs: true` by default.
|
|
1094
|
+
*
|
|
1095
|
+
* @param path - Route path relative to the controller base path
|
|
1096
|
+
* @param config - Optional route configuration (body, params, tags, etc.)
|
|
1097
|
+
*/
|
|
1098
|
+
function InertiaPost(path, config = {}) {
|
|
1099
|
+
return Post(path, buildInertiaConfig(config));
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Registers a PUT route for an Inertia form submission.
|
|
1103
|
+
*
|
|
1104
|
+
* Wraps `@Put()` with auto-applied Inertia page response schema
|
|
1105
|
+
* and `hideFromDocs: true` by default.
|
|
1106
|
+
*
|
|
1107
|
+
* @param path - Route path relative to the controller base path
|
|
1108
|
+
* @param config - Optional route configuration (body, params, tags, etc.)
|
|
1109
|
+
*/
|
|
1110
|
+
function InertiaPut(path, config = {}) {
|
|
1111
|
+
return Put(path, buildInertiaConfig(config));
|
|
1112
|
+
}
|
|
1113
|
+
/**
|
|
1114
|
+
* Registers a PATCH route for an Inertia form submission.
|
|
1115
|
+
*
|
|
1116
|
+
* Wraps `@Patch()` with auto-applied Inertia page response schema
|
|
1117
|
+
* and `hideFromDocs: true` by default.
|
|
1118
|
+
*
|
|
1119
|
+
* @param path - Route path relative to the controller base path
|
|
1120
|
+
* @param config - Optional route configuration (body, params, tags, etc.)
|
|
1121
|
+
*/
|
|
1122
|
+
function InertiaPatch(path, config = {}) {
|
|
1123
|
+
return Patch(path, buildInertiaConfig(config));
|
|
1124
|
+
}
|
|
1125
|
+
/**
|
|
1126
|
+
* Registers a DELETE route for an Inertia form submission.
|
|
1127
|
+
*
|
|
1128
|
+
* Wraps `@Delete()` with auto-applied Inertia page response schema
|
|
1129
|
+
* and `hideFromDocs: true` by default.
|
|
1130
|
+
*
|
|
1131
|
+
* @param path - Route path relative to the controller base path
|
|
1132
|
+
* @param config - Optional route configuration (params, tags, etc.)
|
|
1133
|
+
*/
|
|
1134
|
+
function InertiaDelete(path, config = {}) {
|
|
1135
|
+
return Delete(path, buildInertiaConfig(config));
|
|
1136
|
+
}
|
|
1137
|
+
//#endregion
|
|
1138
|
+
//#region src/middleware/handle-precognitive-requests.middleware.ts
|
|
1139
|
+
let HandlePrecognitiveRequests = class HandlePrecognitiveRequests {
|
|
1140
|
+
async handle(ctx, next) {
|
|
1141
|
+
const isPrecognition = ctx.header("precognition") === "true";
|
|
1142
|
+
ctx.c.set("precognition", isPrecognition);
|
|
1143
|
+
if (isPrecognition) ctx.c.set("validationSuccessResponse", new Response(null, {
|
|
1144
|
+
status: 204,
|
|
1145
|
+
headers: {
|
|
1146
|
+
"Precognition": "true",
|
|
1147
|
+
"Precognition-Success": "true",
|
|
1148
|
+
"Vary": "Precognition"
|
|
1149
|
+
}
|
|
1150
|
+
}));
|
|
1151
|
+
await next();
|
|
1152
|
+
}
|
|
1153
|
+
};
|
|
1154
|
+
HandlePrecognitiveRequests = __decorate([Transient()], HandlePrecognitiveRequests);
|
|
1155
|
+
//#endregion
|
|
1156
|
+
export { CookieFlashStore, HandlePrecognitiveRequests, INERTIA_TOKENS, InertiaBuildCommand, InertiaDelete, InertiaDevCommand, InertiaGet, InertiaInstallCommand, InertiaMiddleware, InertiaModule, InertiaPatch, InertiaPost, InertiaPut, InertiaRoute, InertiaService, InertiaTypesCommand, ManifestService, SsrRendererService, TemplateService, runTypeGeneration };
|
|
1157
|
+
|
|
1158
|
+
//# sourceMappingURL=index.mjs.map
|