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