@arponascension/express-inertia 0.1.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/dist/index.mjs ADDED
@@ -0,0 +1,1218 @@
1
+ import fs from 'fs';
2
+ import ejs from 'ejs';
3
+ import path from 'path';
4
+
5
+ // src/utils.ts
6
+ function lazy(callback) {
7
+ return {
8
+ __inertia_lazy: true,
9
+ callback
10
+ };
11
+ }
12
+ function always(value) {
13
+ return {
14
+ __inertia_always: true,
15
+ value
16
+ };
17
+ }
18
+ function defer(callback, options) {
19
+ return {
20
+ __inertia_deferred: true,
21
+ callback,
22
+ group: options?.group
23
+ };
24
+ }
25
+ function merge(value) {
26
+ return {
27
+ __inertia_merge: true,
28
+ value
29
+ };
30
+ }
31
+ function optional(callback) {
32
+ return {
33
+ __inertia_optional: true,
34
+ callback
35
+ };
36
+ }
37
+ function isLazy(val) {
38
+ return val && typeof val === "object" && val.__inertia_lazy === true;
39
+ }
40
+ function isAlways(val) {
41
+ return val && typeof val === "object" && val.__inertia_always === true;
42
+ }
43
+ function isDeferred(val) {
44
+ return val && typeof val === "object" && val.__inertia_deferred === true;
45
+ }
46
+ function isMerge(val) {
47
+ return val && typeof val === "object" && val.__inertia_merge === true;
48
+ }
49
+ function isOptional(val) {
50
+ return val && typeof val === "object" && val.__inertia_optional === true;
51
+ }
52
+ async function resolveProps(rawProps, req, component) {
53
+ const isPartial = req.header("X-Inertia-Partial-Component") === component && Boolean(req.header("X-Inertia-Partial-Data"));
54
+ const partialKeys = isPartial ? (req.header("X-Inertia-Partial-Data") || "").split(",").map((k) => k.trim()).filter(Boolean) : [];
55
+ const resolvedProps = {};
56
+ const deferredMap = {};
57
+ const mergeKeys = [];
58
+ for (const [key, rawValue] of Object.entries(rawProps)) {
59
+ if (isMerge(rawValue)) {
60
+ mergeKeys.push(key);
61
+ }
62
+ if (isDeferred(rawValue)) {
63
+ const group = rawValue.group || "default";
64
+ if (!deferredMap[group]) {
65
+ deferredMap[group] = [];
66
+ }
67
+ deferredMap[group].push(key);
68
+ }
69
+ if (isPartial) {
70
+ const isRequested = partialKeys.includes(key);
71
+ const isAlwaysIncluded = isAlways(rawValue);
72
+ if (!isRequested && !isAlwaysIncluded) {
73
+ continue;
74
+ }
75
+ } else {
76
+ if (isLazy(rawValue) || isDeferred(rawValue)) {
77
+ continue;
78
+ }
79
+ }
80
+ resolvedProps[key] = await unwrapValue(rawValue, req);
81
+ }
82
+ return {
83
+ resolvedProps,
84
+ deferredProps: Object.keys(deferredMap).length > 0 ? deferredMap : void 0,
85
+ mergeProps: mergeKeys.length > 0 ? mergeKeys : void 0
86
+ };
87
+ }
88
+ async function unwrapValue(val, req) {
89
+ if (val === null || val === void 0) {
90
+ return val;
91
+ }
92
+ if (isLazy(val)) {
93
+ val = val.callback;
94
+ } else if (isAlways(val)) {
95
+ val = val.value;
96
+ } else if (isDeferred(val)) {
97
+ val = val.callback;
98
+ } else if (isMerge(val)) {
99
+ val = val.value;
100
+ } else if (isOptional(val)) {
101
+ val = val.callback;
102
+ }
103
+ if (typeof val === "function") {
104
+ val = val(req);
105
+ }
106
+ if (val && typeof val.then === "function") {
107
+ val = await val;
108
+ }
109
+ return val;
110
+ }
111
+ function escapeHtmlAttr(str) {
112
+ return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
113
+ }
114
+ function serializePage(page) {
115
+ return escapeHtmlAttr(safeStringify(page));
116
+ }
117
+ function safeStringify(obj) {
118
+ const seen = /* @__PURE__ */ new WeakSet();
119
+ return JSON.stringify(obj, (_key, value) => {
120
+ if (typeof value === "object" && value !== null) {
121
+ if (seen.has(value)) {
122
+ return null;
123
+ }
124
+ seen.add(value);
125
+ }
126
+ return value;
127
+ });
128
+ }
129
+ var DEFAULT_COMPONENT_NAME_PATTERN = /^[\w-]+(?:\/[\w-]+)*$/;
130
+ function validateComponentName(component, pattern = DEFAULT_COMPONENT_NAME_PATTERN) {
131
+ if (!pattern.test(component)) {
132
+ throw new TypeError(
133
+ `Invalid component name "${component}". Component names must match the pattern ${pattern}.`
134
+ );
135
+ }
136
+ }
137
+ function sanitizeViewData(data) {
138
+ const seen = /* @__PURE__ */ new WeakSet();
139
+ function sanitize(value) {
140
+ if (typeof value === "function" || value === void 0) {
141
+ return void 0;
142
+ }
143
+ if (value === null || typeof value !== "object" || Buffer.isBuffer(value)) {
144
+ return value;
145
+ }
146
+ if (seen.has(value)) {
147
+ return void 0;
148
+ }
149
+ seen.add(value);
150
+ if (Array.isArray(value)) {
151
+ return value.map((item) => sanitize(item));
152
+ }
153
+ const sanitized = {};
154
+ for (const [key, val] of Object.entries(value)) {
155
+ const cleaned = sanitize(val);
156
+ if (cleaned !== void 0) {
157
+ sanitized[key] = cleaned;
158
+ }
159
+ }
160
+ return sanitized;
161
+ }
162
+ return sanitize(data);
163
+ }
164
+ async function generateSriHash(content) {
165
+ const crypto = await import('crypto');
166
+ const hash = crypto.createHash("sha384").update(content);
167
+ return `sha384-${hash.digest("base64")}`;
168
+ }
169
+
170
+ // src/logger.ts
171
+ var DEFAULT_PREFIX = "express-inertia";
172
+ function createConsoleLogger(prefix) {
173
+ return {
174
+ warn(message, meta) {
175
+ const payload = meta ? { ...meta } : void 0;
176
+ if (payload && Object.keys(payload).length > 0) {
177
+ console.warn(`[${prefix}] ${message}`, JSON.stringify(payload));
178
+ } else {
179
+ console.warn(`[${prefix}] ${message}`);
180
+ }
181
+ },
182
+ error(message, meta) {
183
+ const payload = meta ? { ...meta } : void 0;
184
+ if (payload && Object.keys(payload).length > 0) {
185
+ console.error(`[${prefix}] ${message}`, JSON.stringify(payload));
186
+ } else {
187
+ console.error(`[${prefix}] ${message}`);
188
+ }
189
+ },
190
+ info(message, meta) {
191
+ const payload = meta ? { ...meta } : void 0;
192
+ if (payload && Object.keys(payload).length > 0) {
193
+ console.info(`[${prefix}] ${message}`, JSON.stringify(payload));
194
+ } else {
195
+ console.info(`[${prefix}] ${message}`);
196
+ }
197
+ },
198
+ debug(message, meta) {
199
+ const payload = meta ? { ...meta } : void 0;
200
+ if (payload && Object.keys(payload).length > 0) {
201
+ console.debug(`[${prefix}] ${message}`, JSON.stringify(payload));
202
+ } else {
203
+ console.debug(`[${prefix}] ${message}`);
204
+ }
205
+ }
206
+ };
207
+ }
208
+ var globalLogger = createConsoleLogger(DEFAULT_PREFIX);
209
+ function setGlobalLogger(logger2) {
210
+ globalLogger = logger2;
211
+ }
212
+ function getGlobalLogger() {
213
+ return globalLogger;
214
+ }
215
+ function createLogger(options = {}) {
216
+ const prefix = options.prefix || DEFAULT_PREFIX;
217
+ if (options.logger) {
218
+ return options.logger;
219
+ }
220
+ return createConsoleLogger(prefix);
221
+ }
222
+
223
+ // src/circuit-breaker.ts
224
+ var DEFAULT_CIRCUIT_OPTIONS = {
225
+ failureThreshold: 5,
226
+ cooldownMs: 3e4,
227
+ probeDelayMs: 1e3
228
+ };
229
+ var CircuitBreaker = class {
230
+ constructor(options = DEFAULT_CIRCUIT_OPTIONS) {
231
+ this.options = options;
232
+ }
233
+ options;
234
+ state = "CLOSED";
235
+ failures = 0;
236
+ openedAt = 0;
237
+ probeTimer = null;
238
+ pendingProbe = null;
239
+ getState() {
240
+ if (this.state === "OPEN" && Date.now() - this.openedAt >= this.options.cooldownMs) {
241
+ this.state = "HALF_OPEN";
242
+ }
243
+ return this.state;
244
+ }
245
+ async execute(fn) {
246
+ const state = this.getState();
247
+ if (state === "OPEN") {
248
+ throw new Error("Circuit breaker is open");
249
+ }
250
+ try {
251
+ const result = await fn();
252
+ this.onSuccess();
253
+ return result;
254
+ } catch (err) {
255
+ this.onFailure();
256
+ throw err;
257
+ }
258
+ }
259
+ onSuccess() {
260
+ this.failures = 0;
261
+ this.state = "CLOSED";
262
+ if (this.probeTimer) {
263
+ clearTimeout(this.probeTimer);
264
+ this.probeTimer = null;
265
+ }
266
+ }
267
+ onFailure() {
268
+ this.failures++;
269
+ if (this.failures >= this.options.failureThreshold) {
270
+ this.state = "OPEN";
271
+ this.openedAt = Date.now();
272
+ }
273
+ }
274
+ async allowProbe(fn) {
275
+ if (this.getState() !== "HALF_OPEN") {
276
+ throw new Error("Circuit breaker is not in HALF_OPEN state");
277
+ }
278
+ if (this.pendingProbe) {
279
+ return this.pendingProbe.then(() => fn());
280
+ }
281
+ this.pendingProbe = new Promise((resolve) => {
282
+ this.probeTimer = setTimeout(() => {
283
+ this.pendingProbe = null;
284
+ resolve();
285
+ }, this.options.probeDelayMs);
286
+ });
287
+ return this.pendingProbe.then(() => fn());
288
+ }
289
+ reset() {
290
+ this.failures = 0;
291
+ this.state = "CLOSED";
292
+ this.openedAt = 0;
293
+ if (this.probeTimer) {
294
+ clearTimeout(this.probeTimer);
295
+ this.probeTimer = null;
296
+ }
297
+ this.pendingProbe = null;
298
+ }
299
+ };
300
+ function shouldRetry(error, statusCode, retryableStatusCodes) {
301
+ if (error?.name === "AbortError" || error?.name === "TypeError") {
302
+ return true;
303
+ }
304
+ if (typeof statusCode === "number" && retryableStatusCodes.includes(statusCode)) {
305
+ return true;
306
+ }
307
+ return false;
308
+ }
309
+ function calculateBackoff(attempt, baseDelayMs, maxDelayMs) {
310
+ const exponential = baseDelayMs * Math.pow(2, attempt);
311
+ const jitter = Math.random() * baseDelayMs;
312
+ return Math.min(exponential + jitter, maxDelayMs);
313
+ }
314
+
315
+ // src/ssr.ts
316
+ var DEFAULT_SSR_URL = "http://127.0.0.1:13714/render";
317
+ var DEFAULT_TIMEOUT = 2e3;
318
+ var logger = createLogger({ prefix: "express-inertia:ssr" });
319
+ var DEFAULT_RETRY_OPTIONS = {
320
+ maxRetries: 2,
321
+ baseDelayMs: 200,
322
+ maxDelayMs: 2e3,
323
+ retryableStatusCodes: [408, 429, 500, 502, 503, 504]
324
+ };
325
+ var circuitBreakers = /* @__PURE__ */ new Map();
326
+ function getCircuitBreaker(endpoint, options) {
327
+ const key = options?.circuitBreaker ? `${endpoint}:${JSON.stringify(options.circuitBreaker)}` : endpoint;
328
+ let breaker = circuitBreakers.get(key);
329
+ if (!breaker) {
330
+ const cbOptions = {
331
+ ...DEFAULT_CIRCUIT_OPTIONS,
332
+ ...options?.circuitBreaker
333
+ };
334
+ breaker = new CircuitBreaker(cbOptions);
335
+ circuitBreakers.set(key, breaker);
336
+ }
337
+ return breaker;
338
+ }
339
+ function resetAllCircuitBreakers() {
340
+ for (const breaker of circuitBreakers.values()) {
341
+ breaker.reset();
342
+ }
343
+ circuitBreakers.clear();
344
+ }
345
+ async function renderSSR(page, options) {
346
+ if (!options) {
347
+ return null;
348
+ }
349
+ const ssrConfig = typeof options === "boolean" ? { enabled: options } : options;
350
+ if (ssrConfig.enabled === false) {
351
+ return null;
352
+ }
353
+ if (typeof ssrConfig.render === "function") {
354
+ try {
355
+ return await renderWithRetry(async () => {
356
+ return await ssrConfig.render(page);
357
+ }, ssrConfig.retry, page.component);
358
+ } catch (err) {
359
+ if (ssrConfig.fallback === false) {
360
+ throw err;
361
+ }
362
+ logger.warn("Custom SSR render failed, falling back to client-side", {
363
+ error: err.message,
364
+ component: page.component
365
+ });
366
+ return null;
367
+ }
368
+ }
369
+ const endpoint = ssrConfig.url || DEFAULT_SSR_URL;
370
+ const timeout = ssrConfig.timeout || DEFAULT_TIMEOUT;
371
+ const retryOptions = {
372
+ ...DEFAULT_RETRY_OPTIONS,
373
+ ...ssrConfig.retry
374
+ };
375
+ const breaker = getCircuitBreaker(endpoint, ssrConfig);
376
+ try {
377
+ const result = await breaker.execute(
378
+ () => renderWithRetry(
379
+ () => renderHttpSSR(page, endpoint, timeout),
380
+ retryOptions,
381
+ page.component
382
+ )
383
+ );
384
+ return result;
385
+ } catch (err) {
386
+ if (ssrConfig.fallback === false) {
387
+ throw err;
388
+ }
389
+ logger.warn("SSR rendering failed, falling back to client rendering", {
390
+ endpoint,
391
+ component: page.component,
392
+ error: err.message
393
+ });
394
+ return null;
395
+ }
396
+ }
397
+ async function renderHttpSSR(page, endpoint, timeout) {
398
+ const controller = new AbortController();
399
+ const timer = setTimeout(() => controller.abort(), timeout);
400
+ let pageJson;
401
+ try {
402
+ pageJson = JSON.stringify(page);
403
+ } catch (err) {
404
+ throw new Error(`Failed to serialize page for SSR: ${err.message}`);
405
+ }
406
+ const response = await fetch(endpoint, {
407
+ method: "POST",
408
+ headers: {
409
+ "Content-Type": "application/json",
410
+ Accept: "application/json"
411
+ },
412
+ body: pageJson,
413
+ signal: controller.signal
414
+ });
415
+ clearTimeout(timer);
416
+ if (!response.ok) {
417
+ throw new Error(`SSR server responded with status: ${response.status} ${response.statusText}`);
418
+ }
419
+ const data = await response.json();
420
+ return {
421
+ head: Array.isArray(data.head) ? data.head : [],
422
+ body: typeof data.body === "string" ? data.body : ""
423
+ };
424
+ }
425
+ async function renderWithRetry(fn, retryOptions, context) {
426
+ const options = {
427
+ ...DEFAULT_RETRY_OPTIONS,
428
+ ...retryOptions
429
+ };
430
+ let lastError;
431
+ for (let attempt = 0; attempt <= options.maxRetries; attempt++) {
432
+ try {
433
+ return await fn();
434
+ } catch (err) {
435
+ lastError = err;
436
+ if (attempt >= options.maxRetries) {
437
+ break;
438
+ }
439
+ const statusCode = err.status || err.response?.status;
440
+ if (!shouldRetry(err, statusCode, options.retryableStatusCodes)) {
441
+ break;
442
+ }
443
+ const delay = calculateBackoff(attempt, options.baseDelayMs, options.maxDelayMs);
444
+ logger.warn("SSR request failed, retrying", {
445
+ attempt: attempt + 1,
446
+ maxRetries: options.maxRetries,
447
+ delayMs: Math.round(delay),
448
+ error: err.message,
449
+ component: context
450
+ });
451
+ await new Promise((resolve) => setTimeout(resolve, delay));
452
+ }
453
+ }
454
+ throw lastError;
455
+ }
456
+
457
+ // src/response.ts
458
+ function createInertiaResponse(req, res, options, globalSharedProps = {}) {
459
+ const requestSharedProps = {};
460
+ let currentRootView = options.rootView || "base.ejs";
461
+ let currentVersion = options.version;
462
+ let historyEncryption = options.encryptHistory;
463
+ let clearHistorySetting;
464
+ const flashMessages = {};
465
+ async function resolveVersionValue() {
466
+ if (!currentVersion) {
467
+ return null;
468
+ }
469
+ if (typeof currentVersion === "function") {
470
+ const result = await currentVersion();
471
+ return result || null;
472
+ }
473
+ return currentVersion;
474
+ }
475
+ const renderFn = async (component, props = {}, viewData = {}) => {
476
+ const securityOptions = options.security ?? { validateComponentNames: true };
477
+ if (securityOptions.validateComponentNames !== false) {
478
+ const pattern = securityOptions.componentNamePattern ?? DEFAULT_COMPONENT_NAME_PATTERN;
479
+ validateComponentName(component, pattern);
480
+ }
481
+ let dynamicGlobal = {};
482
+ if (typeof options.shared === "function") {
483
+ dynamicGlobal = await options.shared(req);
484
+ } else if (options.shared) {
485
+ dynamicGlobal = options.shared;
486
+ }
487
+ const allProps = {
488
+ ...globalSharedProps,
489
+ ...dynamicGlobal,
490
+ ...requestSharedProps,
491
+ ...Object.keys(flashMessages).length > 0 ? { flash: flashMessages } : {},
492
+ ...props
493
+ };
494
+ const version = await resolveVersionValue();
495
+ const { resolvedProps, deferredProps, mergeProps } = await resolveProps(
496
+ allProps,
497
+ req,
498
+ component
499
+ );
500
+ const pageUrl = req.originalUrl || req.url;
501
+ const page = {
502
+ component,
503
+ props: resolvedProps,
504
+ url: pageUrl,
505
+ version
506
+ };
507
+ if (historyEncryption !== void 0) {
508
+ page.encryptHistory = historyEncryption;
509
+ }
510
+ if (clearHistorySetting !== void 0) {
511
+ page.clearHistory = clearHistorySetting;
512
+ }
513
+ if (deferredProps) {
514
+ page.deferredProps = deferredProps;
515
+ }
516
+ if (mergeProps) {
517
+ page.mergeProps = mergeProps;
518
+ }
519
+ if (req.header("X-Inertia")) {
520
+ res.setHeader("X-Inertia", "true");
521
+ res.setHeader("Vary", "Accept, X-Inertia");
522
+ if (historyEncryption) {
523
+ res.setHeader("X-Inertia-Encrypt-History", "true");
524
+ }
525
+ if (clearHistorySetting) {
526
+ res.setHeader("X-Inertia-Clear-History", "true");
527
+ }
528
+ res.status(200).setHeader("Content-Type", "application/json").send(safeStringify(page));
529
+ return;
530
+ }
531
+ let ssrResult = null;
532
+ if (options.ssr) {
533
+ ssrResult = await renderSSR(page, options.ssr);
534
+ }
535
+ let globalViewData = {};
536
+ if (typeof options.viewData === "function") {
537
+ globalViewData = await options.viewData(req);
538
+ } else if (options.viewData) {
539
+ globalViewData = options.viewData;
540
+ }
541
+ const sanitizedViewData = securityOptions.validateComponentNames !== false ? sanitizeViewData(viewData) : viewData;
542
+ const sanitizedGlobalViewData = securityOptions.validateComponentNames !== false ? sanitizeViewData(globalViewData) : globalViewData;
543
+ const templateData = {
544
+ ...res.locals,
545
+ ...sanitizedGlobalViewData,
546
+ ...sanitizedViewData,
547
+ page,
548
+ ssr: ssrResult,
549
+ inertiaVersion: options.inertiaVersion,
550
+ rootElement: options.rootElement,
551
+ req
552
+ };
553
+ res.setHeader("Vary", "Accept, X-Inertia");
554
+ res.render(currentRootView, templateData);
555
+ };
556
+ const handler = renderFn;
557
+ handler.render = renderFn;
558
+ handler.inertia = renderFn;
559
+ handler.share = (keyOrObject, value) => {
560
+ if (typeof keyOrObject === "string") {
561
+ requestSharedProps[keyOrObject] = value;
562
+ } else if (typeof keyOrObject === "object" && keyOrObject !== null) {
563
+ Object.assign(requestSharedProps, keyOrObject);
564
+ }
565
+ return handler;
566
+ };
567
+ handler.getShared = (key) => {
568
+ if (key) {
569
+ return requestSharedProps[key] ?? globalSharedProps[key];
570
+ }
571
+ return { ...globalSharedProps, ...requestSharedProps };
572
+ };
573
+ handler.location = (url) => {
574
+ if (req.header("X-Inertia")) {
575
+ res.setHeader("X-Inertia-Location", url);
576
+ res.status(409).end();
577
+ } else {
578
+ res.redirect(url);
579
+ }
580
+ };
581
+ handler.back = (fallbackUrl = "/") => {
582
+ const referer = req.header("Referer") || fallbackUrl;
583
+ res.redirect(303, referer);
584
+ };
585
+ handler.version = (ver) => {
586
+ currentVersion = ver;
587
+ return handler;
588
+ };
589
+ handler.rootView = (view) => {
590
+ currentRootView = view;
591
+ return handler;
592
+ };
593
+ handler.encryptHistory = (encrypt = true) => {
594
+ historyEncryption = encrypt;
595
+ return handler;
596
+ };
597
+ handler.clearHistory = (clear = true) => {
598
+ clearHistorySetting = clear;
599
+ return handler;
600
+ };
601
+ handler.withFlash = (key, value) => {
602
+ flashMessages[key] = value;
603
+ return handler;
604
+ };
605
+ handler.postForm = (component, props, viewData) => {
606
+ return renderFn(component, props, viewData);
607
+ };
608
+ handler.putForm = (component, props, viewData) => {
609
+ return renderFn(component, props, viewData);
610
+ };
611
+ handler.patchForm = (component, props, viewData) => {
612
+ return renderFn(component, props, viewData);
613
+ };
614
+ handler.deleteForm = (component, props, viewData) => {
615
+ return renderFn(component, props, viewData);
616
+ };
617
+ return handler;
618
+ }
619
+
620
+ // src/middleware.ts
621
+ function createInertia(options = {}) {
622
+ const globalShared = typeof options.shared === "object" && options.shared !== null ? options.shared : {};
623
+ return async function inertiaMiddleware(req, res, next) {
624
+ try {
625
+ const isInertia = Boolean(req.header("X-Inertia"));
626
+ const versionHeader = req.header("X-Inertia-Version") || null;
627
+ const partialComponent = req.header("X-Inertia-Partial-Component") || null;
628
+ const partialDataHeader = req.header("X-Inertia-Partial-Data");
629
+ const resetDataHeader = req.header("X-Inertia-Reset");
630
+ const partialData = partialDataHeader ? partialDataHeader.split(",").map((s) => s.trim()).filter(Boolean) : [];
631
+ const resetData = resetDataHeader ? resetDataHeader.split(",").map((s) => s.trim()).filter(Boolean) : [];
632
+ req.inertia = {
633
+ isInertia,
634
+ version: versionHeader,
635
+ partialComponent,
636
+ partialData,
637
+ resetData
638
+ };
639
+ res.inertia = createInertiaResponse(req, res, options, { ...globalShared });
640
+ const securityOptions = options.security ?? { validateComponentNames: true };
641
+ if (securityOptions.validateComponentNames !== false && isInertia) {
642
+ const pattern = securityOptions.componentNamePattern ?? DEFAULT_COMPONENT_NAME_PATTERN;
643
+ const partialComponent2 = req.header("X-Inertia-Partial-Component");
644
+ if (partialComponent2 && !pattern.test(partialComponent2)) {
645
+ res.setHeader("X-Inertia-Location", req.originalUrl || req.url);
646
+ res.status(409).end();
647
+ return;
648
+ }
649
+ }
650
+ if (isInertia && req.method === "GET" && options.version) {
651
+ let currentVersion = null;
652
+ if (typeof options.version === "function") {
653
+ currentVersion = await options.version();
654
+ } else {
655
+ currentVersion = options.version;
656
+ }
657
+ if (versionHeader && currentVersion && versionHeader !== currentVersion) {
658
+ res.setHeader("X-Inertia-Location", req.originalUrl || req.url);
659
+ res.status(409).end();
660
+ return;
661
+ }
662
+ }
663
+ const originalRedirect = res.redirect.bind(res);
664
+ res.redirect = function(...args) {
665
+ const method = req.method.toUpperCase();
666
+ const isInertiaMethod = method === "PUT" || method === "PATCH" || method === "DELETE";
667
+ if (isInertia && isInertiaMethod) {
668
+ if (args.length === 1) {
669
+ return originalRedirect(303, args[0]);
670
+ } else if (args.length === 2 && typeof args[0] === "number") {
671
+ const status = args[0] === 301 || args[0] === 302 ? 303 : args[0];
672
+ return originalRedirect(status, args[1]);
673
+ }
674
+ }
675
+ return originalRedirect(...args);
676
+ };
677
+ next();
678
+ } catch (err) {
679
+ next(err);
680
+ }
681
+ };
682
+ }
683
+ function requestIdMiddleware() {
684
+ return (req, _res, next) => {
685
+ req.id = req.header("X-Request-ID") || generateRequestId();
686
+ next();
687
+ };
688
+ }
689
+ function generateRequestId() {
690
+ const timestamp = Date.now().toString(36);
691
+ const random = Math.random().toString(36).slice(2, 8);
692
+ return `${timestamp}-${random}`;
693
+ }
694
+
695
+ // src/directives.ts
696
+ var customDirectives = {};
697
+ function escapeJsString(str) {
698
+ return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
699
+ }
700
+ function registerDirective(name, handler) {
701
+ customDirectives[name] = handler;
702
+ }
703
+ function compileBladeDirectives(templateSource, localDirectives) {
704
+ let compiled = templateSource;
705
+ compiled = compiled.replace(
706
+ /<x-inertia(\s[^>]*)?\s*(\/|><\/x-inertia>)/gi,
707
+ (_match, attrs) => {
708
+ const idMatch = attrs?.match(/id=(?:"([^"]*)"|'([^']*)')/);
709
+ const id = idMatch ? idMatch[1] || idMatch[2] : void 0;
710
+ return id ? `<%- inertia('${escapeJsString(id)}') %>` : `<%- inertia() %>`;
711
+ }
712
+ );
713
+ compiled = compiled.replace(
714
+ /<x-inertia-(?:script|data)(\s[^>]*)?\s*(\/|><\/x-inertia-(?:script|data)>)/gi,
715
+ (_match, attrs) => {
716
+ const idMatch = attrs?.match(/id=(?:"([^"]*)"|'([^']*)')/);
717
+ const id = idMatch ? idMatch[1] || idMatch[2] : void 0;
718
+ return id ? `<%- inertiaScript('${escapeJsString(id)}') %>` : `<%- inertiaScript() %>`;
719
+ }
720
+ );
721
+ compiled = compiled.replace(
722
+ /<x-inertia-head\s*(?:\/>|><\/x-inertia-head>)|<x-inertiaHead\s*(?:\/>|><\/x-inertiaHead>)/gi,
723
+ "<%- inertiaHead() %>"
724
+ );
725
+ compiled = compiled.replace(
726
+ /<x-vite(\s[^>]*)?\s*(?:\/>|><\/x-vite>)/gi,
727
+ (_match, attrs) => {
728
+ const srcMatch = attrs?.match(/(?:src|entries)=(?:"([^"]*)"|'([^']*)')/);
729
+ const val = srcMatch ? srcMatch[1] || srcMatch[2] : "";
730
+ if (val.startsWith("[") && val.endsWith("]")) {
731
+ return `<%- vite(${val}) %>`;
732
+ }
733
+ return `<%- vite('${escapeJsString(val)}') %>`;
734
+ }
735
+ );
736
+ compiled = compiled.replace(
737
+ /<x-vite-react-refresh\s*(?:\/>|><\/x-vite-react-refresh>)|<x-viteReactRefresh\s*(?:\/>|><\/x-viteReactRefresh>)/gi,
738
+ "<%- viteReactRefresh() %>"
739
+ );
740
+ compiled = compiled.replace(/<x-csrf\s*(?:\/>|><\/x-csrf>)/gi, "<%- csrf() %>");
741
+ compiled = compiled.replace(
742
+ /<x-routes(\s[^>]*)?\s*(?:\/>|><\/x-routes>)/gi,
743
+ (_match, attrs) => {
744
+ const groupMatch = attrs?.match(/group=(?:"([^"]*)"|'([^']*)')/);
745
+ const group = groupMatch ? groupMatch[1] || groupMatch[2] : void 0;
746
+ return group ? `<%- routes('${escapeJsString(group)}') %>` : `<%- routes() %>`;
747
+ }
748
+ );
749
+ compiled = compiled.replace(/@inertiaHead\b/g, "<%- inertiaHead() %>");
750
+ compiled = compiled.replace(/@(inertiaScript|inertiaData)(\([^)]*\))?(?!\w)/g, (_match, _d, args) => {
751
+ return args ? `<%- inertiaScript${args} %>` : `<%- inertiaScript() %>`;
752
+ });
753
+ compiled = compiled.replace(/@inertia(\([^)]*\))?(?!\w)/g, (_match, args) => {
754
+ return args ? `<%- inertia${args} %>` : `<%- inertia() %>`;
755
+ });
756
+ compiled = compiled.replace(/@viteReactRefresh\b/g, "<%- viteReactRefresh() %>");
757
+ compiled = compiled.replace(/@vite\(([^)]*(?:\([^)]*\)[^)]*)*)\)/g, (_match, args) => {
758
+ return `<%- vite(${args.trim()}) %>`;
759
+ });
760
+ compiled = compiled.replace(/@csrf\b/g, "<%- csrf() %>");
761
+ compiled = compiled.replace(/@routes(\([^)]*\))?(?!\w)/g, (_match, args) => {
762
+ return args ? `<%- routes${args} %>` : `<%- routes() %>`;
763
+ });
764
+ compiled = compiled.replace(/@json\(([^)]*(?:\([^)]*\)[^)]*)*)\)/g, (_match, args) => {
765
+ return `<%- json(${args.trim()}) %>`;
766
+ });
767
+ const allDirectives = { ...customDirectives, ...localDirectives || {} };
768
+ for (const [name, handler] of Object.entries(allDirectives)) {
769
+ const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
770
+ const pattern = new RegExp(`@${escapedName}(\\([^)]*(?:\\([^)]*\\)[^)]*)*\\))?(?!\\w)`, "g");
771
+ compiled = compiled.replace(pattern, (_match, args) => {
772
+ return handler(args ? args.slice(1, -1).trim() : "", {});
773
+ });
774
+ }
775
+ return compiled;
776
+ }
777
+ function resolveDevServerUrl(config, override) {
778
+ if (override) return override.replace(/\/$/, "");
779
+ const server = config?.server || {};
780
+ if (typeof server.origin === "string" && server.origin) {
781
+ return server.origin.replace(/\/$/, "");
782
+ }
783
+ const scheme = server.https ? "https" : "http";
784
+ let host = server.host;
785
+ if (!host || host === "0.0.0.0" || host === "::" || host === "::0" || host === true) {
786
+ host = "localhost";
787
+ }
788
+ if (host.includes(":") && !host.startsWith("[")) {
789
+ host = `[${host}]`;
790
+ }
791
+ const port = server.port ?? 5173;
792
+ return `${scheme}://${host}:${port}`;
793
+ }
794
+ function inertiaVitePlugin(options = {}) {
795
+ const logger2 = createLogger({ prefix: "express-inertia:vite" });
796
+ const write = (config, server) => {
797
+ try {
798
+ const hotFile = path.resolve(config.root, options.hotFile || "public/hot");
799
+ const actual = server?.resolvedUrls?.local?.[0];
800
+ const url = actual || resolveDevServerUrl(config, options.devServerUrl);
801
+ fs.mkdirSync(path.dirname(hotFile), { recursive: true });
802
+ fs.writeFileSync(hotFile, url.replace(/\/$/, ""));
803
+ } catch (err) {
804
+ logger2.warn("Failed to write Vite hot file", { error: err.message });
805
+ }
806
+ };
807
+ const remove = (config) => {
808
+ try {
809
+ const hotFile = path.resolve(config.root, options.hotFile || "public/hot");
810
+ fs.rmSync(hotFile, { force: true });
811
+ } catch {
812
+ }
813
+ };
814
+ return {
815
+ name: "express-inertia-hot-file",
816
+ configResolved(config) {
817
+ if (config.command === "build") {
818
+ remove(config);
819
+ } else {
820
+ write(config);
821
+ }
822
+ },
823
+ configureServer(server) {
824
+ write(server.config, server);
825
+ server.httpServer?.once("listening", () => write(server.config, server));
826
+ server.httpServer?.on("close", () => remove(server.config));
827
+ process.once("exit", () => remove(server.config));
828
+ },
829
+ buildStart() {
830
+ }
831
+ };
832
+ }
833
+ var ViteHelper = class {
834
+ config;
835
+ manifestCache = null;
836
+ logger = createLogger({ prefix: "express-inertia:vite" });
837
+ constructor(config) {
838
+ const publicDir = config?.publicDir ? path.resolve(config.publicDir) : path.resolve("public");
839
+ const buildDir = config?.buildDir || "build";
840
+ this.config = {
841
+ publicDir,
842
+ buildDir,
843
+ devServerUrl: config?.devServerUrl || "http://localhost:5173",
844
+ hotFile: path.resolve(config?.hotFile || path.join(publicDir, "hot")),
845
+ manifestPath: path.resolve(
846
+ config?.manifestPath || path.join(publicDir, buildDir, ".vite", "manifest.json")
847
+ ),
848
+ base: config?.base || `/${buildDir}/`,
849
+ manifest: config?.manifest || null,
850
+ isDev: config?.isDev ?? false,
851
+ devServerUrlOverride: config?.devServerUrlOverride || null
852
+ };
853
+ }
854
+ /**
855
+ * Detect whether the Vite dev server is running.
856
+ *
857
+ * Mirrors Laravel: the presence of the `hot` file is the sole signal. Vite's
858
+ * dev server writes a `hot` file on startup and removes it on shutdown, so no
859
+ * NODE_ENV switch is required to toggle between dev and build assets.
860
+ */
861
+ isDev() {
862
+ if (this.config.isDev === true) {
863
+ return true;
864
+ }
865
+ const candidatePaths = [
866
+ this.config.hotFile,
867
+ path.resolve("hot"),
868
+ path.join(this.config.publicDir, "hot")
869
+ ];
870
+ return candidatePaths.some((p) => fs.existsSync(p));
871
+ }
872
+ /**
873
+ * Get development server URL from hot file or config.
874
+ */
875
+ getDevServerUrl() {
876
+ if (this.config.devServerUrlOverride) {
877
+ return this.config.devServerUrlOverride.replace(/\/$/, "");
878
+ }
879
+ const candidatePaths = [
880
+ this.config.hotFile,
881
+ path.resolve("hot"),
882
+ path.join(this.config.publicDir, "hot")
883
+ ];
884
+ for (const p of candidatePaths) {
885
+ if (fs.existsSync(p)) {
886
+ try {
887
+ const content = fs.readFileSync(p, "utf-8").trim();
888
+ if (content.startsWith("http://") || content.startsWith("https://")) {
889
+ return content;
890
+ }
891
+ } catch {
892
+ }
893
+ }
894
+ }
895
+ return this.config.devServerUrl;
896
+ }
897
+ /**
898
+ * Check if manifest file exists.
899
+ */
900
+ manifestExists() {
901
+ if (this.config.manifest) {
902
+ return true;
903
+ }
904
+ const candidatePaths = this.getManifestCandidatePaths();
905
+ return candidatePaths.some((p) => fs.existsSync(p));
906
+ }
907
+ getManifestCandidatePaths() {
908
+ const { publicDir, buildDir } = this.config;
909
+ return [
910
+ this.config.manifestPath,
911
+ path.resolve(publicDir, buildDir, ".vite", "manifest.json"),
912
+ path.resolve(publicDir, buildDir, "manifest.json"),
913
+ path.resolve(buildDir, ".vite", "manifest.json"),
914
+ path.resolve(buildDir, "manifest.json")
915
+ ];
916
+ }
917
+ /**
918
+ * Load and cache the production manifest.
919
+ */
920
+ getManifest() {
921
+ if (this.manifestCache) {
922
+ return this.manifestCache;
923
+ }
924
+ if (this.config.manifest) {
925
+ this.manifestCache = this.config.manifest;
926
+ return this.config.manifest;
927
+ }
928
+ const candidatePaths = this.getManifestCandidatePaths();
929
+ for (const p of candidatePaths) {
930
+ if (fs.existsSync(p)) {
931
+ try {
932
+ const raw = fs.readFileSync(p, "utf-8");
933
+ const parsed = JSON.parse(raw);
934
+ this.manifestCache = parsed;
935
+ return parsed;
936
+ } catch (err) {
937
+ throw new Error(`[express-inertia] Failed to parse Vite manifest at ${p}: ${err.message}`);
938
+ }
939
+ }
940
+ }
941
+ throw new Error(
942
+ `[express-inertia] Vite manifest not found. Looked in:
943
+ ${candidatePaths.join("\n")}
944
+ Run your build step (e.g. vite build) to generate it.`
945
+ );
946
+ }
947
+ /**
948
+ * Generate HTML script & link tags for the given entrypoint(s).
949
+ * @param entrypoints Single entry string or array of entry paths (e.g. 'src/main.ts' or ['src/main.ts', 'src/style.css'])
950
+ */
951
+ renderTags(entrypoints) {
952
+ const entries = Array.isArray(entrypoints) ? entrypoints : [entrypoints];
953
+ const tags = [];
954
+ if (this.isDev()) {
955
+ const devUrl = this.getDevServerUrl().replace(/\/$/, "");
956
+ tags.push(`<script type="module" src="${devUrl}/@vite/client"></script>`);
957
+ for (const entry of entries) {
958
+ const cleanEntry = entry.replace(/^\//, "");
959
+ if (cleanEntry.endsWith(".css") || cleanEntry.endsWith(".scss") || cleanEntry.endsWith(".less")) {
960
+ tags.push(`<link rel="stylesheet" href="${devUrl}/${cleanEntry}">`);
961
+ } else {
962
+ tags.push(`<script type="module" src="${devUrl}/${cleanEntry}"></script>`);
963
+ }
964
+ }
965
+ return tags.join("\n");
966
+ }
967
+ const manifest = this.getManifest();
968
+ const basePath = this.config.base.endsWith("/") ? this.config.base : `${this.config.base}/`;
969
+ const renderedCss = /* @__PURE__ */ new Set();
970
+ const renderedPreload = /* @__PURE__ */ new Set();
971
+ for (const entry of entries) {
972
+ const cleanEntry = entry.replace(/^\//, "");
973
+ const chunk = manifest[cleanEntry] || Object.values(manifest).find(
974
+ (c) => c.src === cleanEntry || c.file === cleanEntry || c.src && (c.src.endsWith(cleanEntry) || c.src.endsWith(`/${cleanEntry}`)) || c.file && (c.file.endsWith(cleanEntry) || c.file.endsWith(`/${cleanEntry}`))
975
+ );
976
+ if (!chunk) {
977
+ if (!/\.(css|scss|sass|less)$/.test(cleanEntry)) {
978
+ this.logger.warn("Entry not found in Vite manifest", { entry, manifestPath: this.config.manifestPath });
979
+ }
980
+ continue;
981
+ }
982
+ if (chunk.css) {
983
+ for (const cssFile of chunk.css) {
984
+ if (!renderedCss.has(cssFile)) {
985
+ renderedCss.add(cssFile);
986
+ tags.push(`<link rel="stylesheet" href="${basePath}${cssFile}">`);
987
+ }
988
+ }
989
+ }
990
+ if (chunk.file.endsWith(".css")) {
991
+ if (!renderedCss.has(chunk.file)) {
992
+ renderedCss.add(chunk.file);
993
+ tags.push(`<link rel="stylesheet" href="${basePath}${chunk.file}">`);
994
+ }
995
+ } else {
996
+ tags.push(`<script type="module" src="${basePath}${chunk.file}"></script>`);
997
+ }
998
+ if (chunk.imports) {
999
+ for (const imp of chunk.imports) {
1000
+ const impChunk = manifest[imp];
1001
+ if (impChunk && !renderedPreload.has(impChunk.file)) {
1002
+ renderedPreload.add(impChunk.file);
1003
+ tags.push(`<link rel="modulepreload" href="${basePath}${impChunk.file}">`);
1004
+ }
1005
+ }
1006
+ }
1007
+ }
1008
+ return tags.join("\n");
1009
+ }
1010
+ /**
1011
+ * Generates the React Fast Refresh preamble for development.
1012
+ */
1013
+ renderReactRefresh() {
1014
+ if (!this.isDev()) {
1015
+ return "";
1016
+ }
1017
+ const devUrl = this.getDevServerUrl().replace(/\/$/, "");
1018
+ return `<script type="module">
1019
+ import RefreshRuntime from "${devUrl}/@react-refresh";
1020
+ RefreshRuntime.injectIntoGlobalHook(window);
1021
+ window.$RefreshReg$ = () => {};
1022
+ window.$RefreshSig$ = () => (type) => type;
1023
+ window.__vite_plugin_react_preamble_installed__ = true;
1024
+ </script>`;
1025
+ }
1026
+ /**
1027
+ * Generates an SRI hash for the given asset file content.
1028
+ * Useful for adding integrity attributes to script/link tags.
1029
+ */
1030
+ async generateSriHashForAsset(assetPath) {
1031
+ const fullPath = path.resolve(this.config.publicDir, assetPath);
1032
+ const content = fs.readFileSync(fullPath);
1033
+ return generateSriHash(content);
1034
+ }
1035
+ };
1036
+ function createViteHelper(config) {
1037
+ return new ViteHelper(config);
1038
+ }
1039
+ var defaultViteHelper = new ViteHelper();
1040
+
1041
+ // src/engine.ts
1042
+ var engineCaches = /* @__PURE__ */ new Set();
1043
+ function createInertiaEngine(options) {
1044
+ const viteHelper = options?.vite ? createViteHelper(options.vite) : createViteHelper();
1045
+ const shouldCache = options?.cache ?? !viteHelper.isDev();
1046
+ const inertiaVersion = options?.inertiaVersion ?? 2;
1047
+ const defaultRootElement = options?.rootElement ?? true;
1048
+ const templateCache = /* @__PURE__ */ new Map();
1049
+ engineCaches.add(templateCache);
1050
+ return function inertiaBladeEngine(filePath, rawOptions, callback) {
1051
+ try {
1052
+ let compiledTemplate = shouldCache ? templateCache.get(filePath) : void 0;
1053
+ if (!compiledTemplate) {
1054
+ const templateSource = options?.templateSource || fs.readFileSync(filePath, "utf-8");
1055
+ const compiledSource = options?.compileTemplate ? options.compileTemplate(templateSource) : compileBladeDirectives(templateSource, options?.directives);
1056
+ compiledTemplate = ejs.compile(compiledSource, {
1057
+ filename: filePath,
1058
+ cache: shouldCache,
1059
+ async: false
1060
+ });
1061
+ if (shouldCache) {
1062
+ templateCache.set(filePath, compiledTemplate);
1063
+ }
1064
+ }
1065
+ const locals = { ...rawOptions };
1066
+ const page = locals.page;
1067
+ const ssr = locals.ssr;
1068
+ const effectiveVersion = locals.inertiaVersion ?? inertiaVersion;
1069
+ const shouldRenderRootElement = locals.rootElement ?? defaultRootElement;
1070
+ locals.inertiaScript = (id = "app") => {
1071
+ if (!page) return "";
1072
+ const jsonSafe = JSON.stringify(page).replace(/</g, "\\u003c");
1073
+ return `<script data-page="${id}" type="application/json">${jsonSafe}</script>`;
1074
+ };
1075
+ locals.inertiaData = locals.inertiaScript;
1076
+ locals.inertia = (idOrOptions = "app", version) => {
1077
+ const id = typeof idOrOptions === "string" ? idOrOptions : "app";
1078
+ const renderElement = typeof idOrOptions === "boolean" ? idOrOptions : shouldRenderRootElement !== false;
1079
+ const ver = version ?? effectiveVersion;
1080
+ if (!page) {
1081
+ return renderElement ? `<div id="${id}"></div>` : "";
1082
+ }
1083
+ const ssrBody = ssr && ssr.body ? ssr.body : "";
1084
+ const ssrFullBody = /<div[^>]*data-server-rendered/.test(ssrBody);
1085
+ if (ver === 1) {
1086
+ const pageData = serializePage(page);
1087
+ return renderElement ? `<div id="${id}" data-page="${pageData}">${ssrBody}</div>` : "";
1088
+ }
1089
+ const jsonSafe = JSON.stringify(page).replace(/</g, "\\u003c");
1090
+ const scriptTag = `<script data-page="${id}" type="application/json">${jsonSafe}</script>`;
1091
+ if (ssrFullBody) {
1092
+ return ssrBody;
1093
+ }
1094
+ if (!renderElement) {
1095
+ return scriptTag;
1096
+ }
1097
+ return `<div id="${id}">${ssrBody}</div>
1098
+ ${scriptTag}`;
1099
+ };
1100
+ locals.inertiaHead = () => {
1101
+ if (ssr && Array.isArray(ssr.head) && ssr.head.length > 0) {
1102
+ return ssr.head.join("\n");
1103
+ }
1104
+ return "";
1105
+ };
1106
+ locals.vite = (entrypoints) => {
1107
+ return viteHelper.renderTags(entrypoints);
1108
+ };
1109
+ locals.viteReactRefresh = () => {
1110
+ return viteHelper.renderReactRefresh();
1111
+ };
1112
+ locals.csrf = () => {
1113
+ const token = locals._csrf || locals.csrfToken || (typeof locals.req?.csrfToken === "function" ? locals.req.csrfToken() : "") || "";
1114
+ return token ? `<input type="hidden" name="_token" value="${token}">` : "";
1115
+ };
1116
+ locals.json = (value) => {
1117
+ return JSON.stringify(value);
1118
+ };
1119
+ if (!locals.routes) {
1120
+ locals.routes = (group) => {
1121
+ if (locals.routeConfig || locals.ziggy) {
1122
+ const routesData = JSON.stringify(locals.routeConfig || locals.ziggy);
1123
+ return `<script>const Ziggy = ${routesData};</script>`;
1124
+ }
1125
+ return "";
1126
+ };
1127
+ }
1128
+ const html = compiledTemplate(locals);
1129
+ callback(null, html);
1130
+ } catch (err) {
1131
+ callback(err);
1132
+ }
1133
+ };
1134
+ }
1135
+ var inertiaEngine = createInertiaEngine();
1136
+ function clearEngineCache() {
1137
+ for (const cache of engineCaches) {
1138
+ cache.clear();
1139
+ }
1140
+ }
1141
+
1142
+ // src/prefetch.ts
1143
+ var PrefetchHelper = class {
1144
+ constructor(viteHelper) {
1145
+ this.viteHelper = viteHelper;
1146
+ }
1147
+ viteHelper;
1148
+ /**
1149
+ * Generates prefetch tags for the given entrypoints.
1150
+ * Uses the Vite manifest to resolve actual asset paths in production.
1151
+ */
1152
+ prefetch(entries, options = {}) {
1153
+ const entrypoints = Array.isArray(entries) ? entries : [entries];
1154
+ const mode = options.mode || "prefetch";
1155
+ const tags = [];
1156
+ if (this.viteHelper.isDev()) {
1157
+ const devUrl = this.viteHelper["getDevServerUrl"]().replace(/\/$/, "");
1158
+ for (const entry of entrypoints) {
1159
+ const cleanEntry = entry.replace(/^\//, "");
1160
+ tags.push(`<link rel="${mode}" href="${devUrl}/${cleanEntry}" crossorigin>`);
1161
+ }
1162
+ return tags.join("\n");
1163
+ }
1164
+ const manifest = this.viteHelper["getManifest"]();
1165
+ const basePath = this.viteHelper["config"].base.endsWith("/") ? this.viteHelper["config"].base : `${this.viteHelper["config"].base}/`;
1166
+ const rendered = /* @__PURE__ */ new Set();
1167
+ for (const entry of entrypoints) {
1168
+ const cleanEntry = entry.replace(/^\//, "");
1169
+ const chunk = this.resolveChunk(manifest, cleanEntry);
1170
+ if (!chunk) continue;
1171
+ const files = [chunk.file, ...chunk.css || [], ...chunk.assets || []].filter(Boolean);
1172
+ for (const file of files) {
1173
+ if (rendered.has(file)) continue;
1174
+ rendered.add(file);
1175
+ const attrs = [`rel="${mode}"`, `href="${basePath}${file}"`];
1176
+ if (options.crossorigin) {
1177
+ attrs.push("crossorigin");
1178
+ }
1179
+ tags.push(`<link ${attrs.join(" ")}>`);
1180
+ }
1181
+ if (chunk.imports) {
1182
+ for (const imp of chunk.imports) {
1183
+ const impChunk = manifest[imp];
1184
+ if (impChunk?.file && !rendered.has(impChunk.file)) {
1185
+ rendered.add(impChunk.file);
1186
+ const attrs = [`rel="${mode}"`, `href="${basePath}${impChunk.file}"`];
1187
+ if (options.crossorigin) {
1188
+ attrs.push("crossorigin");
1189
+ }
1190
+ tags.push(`<link ${attrs.join(" ")}>`);
1191
+ }
1192
+ }
1193
+ }
1194
+ }
1195
+ return tags.join("\n");
1196
+ }
1197
+ /**
1198
+ * Generates preload tags for critical assets.
1199
+ */
1200
+ preload(entries, options = {}) {
1201
+ return this.prefetch(entries, { ...options, mode: "preload" });
1202
+ }
1203
+ resolveChunk(manifest, entry) {
1204
+ return manifest[entry] || Object.values(manifest).find(
1205
+ (c) => c.src === entry || c.file === entry || c.src && (c.src.endsWith(entry) || c.src.endsWith(`/${entry}`)) || c.file && (c.file.endsWith(entry) || c.file.endsWith(`/${entry}`))
1206
+ );
1207
+ }
1208
+ };
1209
+ function createPrefetchHelper(viteHelper) {
1210
+ return new PrefetchHelper(viteHelper);
1211
+ }
1212
+
1213
+ // src/index.ts
1214
+ var src_default = createInertia;
1215
+
1216
+ export { CircuitBreaker, DEFAULT_COMPONENT_NAME_PATTERN, PrefetchHelper, ViteHelper, always, calculateBackoff, clearEngineCache, compileBladeDirectives, createInertia, createInertiaEngine, createLogger, createPrefetchHelper, createViteHelper, src_default as default, defaultViteHelper, defer, escapeHtmlAttr, generateSriHash, getGlobalLogger, createInertia as inertia, inertiaEngine, inertiaVitePlugin, isAlways, isDeferred, isLazy, isMerge, isOptional, lazy, merge, optional, registerDirective, renderSSR, requestIdMiddleware, resetAllCircuitBreakers, resolveProps, safeStringify, sanitizeViewData, serializePage, setGlobalLogger, shouldRetry, validateComponentName };
1217
+ //# sourceMappingURL=index.mjs.map
1218
+ //# sourceMappingURL=index.mjs.map