litzjs 0.0.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/client.js ADDED
@@ -0,0 +1,1633 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_chunk = require("./chunk-8l464Juk.js");
3
+ const require_bindings = require("./bindings-B1P6pL93.js");
4
+ const require_internal_transport = require("./internal-transport-DR0r68ff.js");
5
+ let react = require("react");
6
+ react = require_chunk.__toESM(react);
7
+ let virtual_litzjs_route_manifest = require("virtual:litzjs:route-manifest");
8
+ let react_jsx_runtime = require("react/jsx-runtime");
9
+ //#region src/client/navigation.ts
10
+ function isHashOnlyNavigation(currentUrl, nextUrl) {
11
+ return currentUrl.origin === nextUrl.origin && currentUrl.pathname === nextUrl.pathname && currentUrl.search === nextUrl.search && currentUrl.hash !== nextUrl.hash;
12
+ }
13
+ function shouldInterceptLinkNavigation(options) {
14
+ if (options.button !== 0 || options.metaKey || options.altKey || options.ctrlKey || options.shiftKey) return false;
15
+ if (options.target && options.target !== "_self") return false;
16
+ if (options.download) return false;
17
+ if (options.nextUrl.origin !== options.currentUrl.origin) return false;
18
+ if (isHashOnlyNavigation(options.currentUrl, options.nextUrl)) return false;
19
+ return true;
20
+ }
21
+ function toNavigationHref(url) {
22
+ return url.pathname + url.search + url.hash;
23
+ }
24
+ function shouldPrefetchLink(options) {
25
+ return shouldInterceptLinkNavigation({
26
+ button: 0,
27
+ metaKey: false,
28
+ altKey: false,
29
+ ctrlKey: false,
30
+ shiftKey: false,
31
+ target: options.target,
32
+ download: options.download,
33
+ currentUrl: options.currentUrl,
34
+ nextUrl: options.nextUrl
35
+ });
36
+ }
37
+ function applySearchParams(currentUrl, updates) {
38
+ const nextUrl = new URL(currentUrl.href);
39
+ const nextSearch = new URLSearchParams(currentUrl.search);
40
+ for (const [key, value] of Object.entries(updates)) {
41
+ nextSearch.delete(key);
42
+ if (value == null) continue;
43
+ if (Array.isArray(value)) {
44
+ for (const entry of value) nextSearch.append(key, entry);
45
+ continue;
46
+ }
47
+ nextSearch.set(key, value);
48
+ }
49
+ nextUrl.search = nextSearch.toString();
50
+ return {
51
+ changed: nextUrl.search !== currentUrl.search,
52
+ href: toNavigationHref(nextUrl)
53
+ };
54
+ }
55
+ //#endregion
56
+ //#region src/client/link.tsx
57
+ function createLinkComponent(dependencies) {
58
+ return function LitzLink(props) {
59
+ const navigate = dependencies.useNavigate();
60
+ const { href, replace = false, onClick, onMouseEnter, onFocus, onTouchStart, target, download, rel, ...rest } = props;
61
+ return react.createElement("a", {
62
+ ...rest,
63
+ href,
64
+ target,
65
+ download,
66
+ rel,
67
+ onMouseEnter(event) {
68
+ onMouseEnter?.(event);
69
+ if (event.defaultPrevented) return;
70
+ dependencies.prefetchRouteModuleForHref(href, target, download);
71
+ },
72
+ onFocus(event) {
73
+ onFocus?.(event);
74
+ if (event.defaultPrevented) return;
75
+ dependencies.prefetchRouteModuleForHref(href, target, download);
76
+ },
77
+ onTouchStart(event) {
78
+ onTouchStart?.(event);
79
+ if (event.defaultPrevented) return;
80
+ dependencies.prefetchRouteModuleForHref(href, target, download);
81
+ },
82
+ onClick(event) {
83
+ onClick?.(event);
84
+ if (event.defaultPrevented) return;
85
+ const nextUrl = new URL(href, window.location.href);
86
+ const currentUrl = new URL(window.location.href);
87
+ if (!shouldInterceptLinkNavigation({
88
+ button: event.button,
89
+ metaKey: event.metaKey,
90
+ altKey: event.altKey,
91
+ ctrlKey: event.ctrlKey,
92
+ shiftKey: event.shiftKey,
93
+ target,
94
+ download,
95
+ currentUrl,
96
+ nextUrl
97
+ })) return;
98
+ event.preventDefault();
99
+ navigate(toNavigationHref(nextUrl), { replace });
100
+ }
101
+ });
102
+ };
103
+ }
104
+ //#endregion
105
+ //#region src/client/result-headers.ts
106
+ const PUBLIC_RESULT_HEADER_NAMES = new Set([
107
+ "content-type",
108
+ "x-litzjs-kind",
109
+ "x-litzjs-revalidate",
110
+ "x-litzjs-status",
111
+ "x-litzjs-view-id"
112
+ ]);
113
+ function createPublicResultHeaders(headers) {
114
+ const publicHeaders = new Headers();
115
+ headers.forEach((value, key) => {
116
+ const normalizedKey = key.toLowerCase();
117
+ if (PUBLIC_RESULT_HEADER_NAMES.has(normalizedKey) || normalizedKey.startsWith("x-litzjs-public-")) publicHeaders.append(key, value);
118
+ });
119
+ return publicHeaders;
120
+ }
121
+ //#endregion
122
+ //#region src/client/transport.tsx
123
+ async function parseLoaderResponse(response) {
124
+ const contentType = response.headers.get("content-type") ?? "";
125
+ const publicHeaders = createPublicResultHeaders(response.headers);
126
+ if (contentType.includes("text/x-component")) return createViewResult(response, publicHeaders);
127
+ const body = await response.json();
128
+ if (body.kind === "data") return {
129
+ kind: "data",
130
+ status: response.status,
131
+ headers: publicHeaders,
132
+ stale: false,
133
+ data: body.data,
134
+ render() {
135
+ return null;
136
+ }
137
+ };
138
+ if (body.kind === "error") throw createRouteLikeError(response.status, publicHeaders, body);
139
+ if (body.kind === "fault") throw createRouteLikeError(response.status, publicHeaders, body);
140
+ if (body.kind === "redirect") throw createRedirectSignal(response.status, publicHeaders, body);
141
+ throw new Error(`Unsupported loader response kind "${body.kind}".`);
142
+ }
143
+ async function parseActionResponse(response) {
144
+ const contentType = response.headers.get("content-type") ?? "";
145
+ const publicHeaders = createPublicResultHeaders(response.headers);
146
+ if (contentType.includes("text/x-component")) return createViewResult(response, publicHeaders);
147
+ const body = await response.json();
148
+ switch (body.kind) {
149
+ case "data": return {
150
+ kind: "data",
151
+ status: response.status,
152
+ headers: publicHeaders,
153
+ data: body.data
154
+ };
155
+ case "invalid": return {
156
+ kind: "invalid",
157
+ status: response.status,
158
+ headers: publicHeaders,
159
+ fields: body.fields,
160
+ formError: body.formError,
161
+ data: body.data
162
+ };
163
+ case "redirect": return {
164
+ kind: "redirect",
165
+ status: response.status,
166
+ headers: publicHeaders,
167
+ location: body.location,
168
+ replace: body.replace ?? false
169
+ };
170
+ case "error": return {
171
+ kind: "error",
172
+ status: response.status,
173
+ headers: publicHeaders,
174
+ message: body.message,
175
+ code: body.code,
176
+ data: body.data
177
+ };
178
+ case "fault": return {
179
+ kind: "fault",
180
+ status: response.status,
181
+ headers: publicHeaders,
182
+ message: body.message,
183
+ digest: body.digest
184
+ };
185
+ default: throw new Error(`Unsupported action response kind "${String(body.kind)}".`);
186
+ }
187
+ }
188
+ async function createViewResult(response, publicHeaders = createPublicResultHeaders(response.headers)) {
189
+ if (!response.body) throw new Error("Flight response body is missing.");
190
+ const { createFromReadableStream } = await import("@vitejs/plugin-rsc/browser");
191
+ const node = await createFromReadableStream(response.body);
192
+ return {
193
+ kind: "view",
194
+ status: Number(response.headers.get("x-litz-status") ?? response.status),
195
+ headers: publicHeaders,
196
+ stale: false,
197
+ node,
198
+ render() {
199
+ return node;
200
+ }
201
+ };
202
+ }
203
+ function isRouteLikeError(value) {
204
+ return typeof value === "object" && value !== null && "kind" in value && (value.kind === "error" || value.kind === "fault");
205
+ }
206
+ function isRedirectSignal(value) {
207
+ return typeof value === "object" && value !== null && "kind" in value && value.kind === "redirect";
208
+ }
209
+ function getRevalidateTargets(headers) {
210
+ const value = headers.get("x-litz-revalidate");
211
+ if (!value) return [];
212
+ return value.split(",").map((entry) => entry.trim()).filter(Boolean);
213
+ }
214
+ function createRouteLikeError(status, headers, body) {
215
+ return {
216
+ kind: body.kind,
217
+ status,
218
+ headers,
219
+ message: body.message,
220
+ code: "code" in body ? body.code : void 0,
221
+ digest: "digest" in body ? body.digest : void 0,
222
+ data: "data" in body ? body.data : void 0
223
+ };
224
+ }
225
+ function createRedirectSignal(status, headers, body) {
226
+ return {
227
+ kind: "redirect",
228
+ status,
229
+ headers,
230
+ location: body.location,
231
+ replace: body.replace ?? false
232
+ };
233
+ }
234
+ //#endregion
235
+ //#region src/client/resources.tsx
236
+ const RESOURCE_STORE_LIMIT = 200;
237
+ const resourceStore = /* @__PURE__ */ new Map();
238
+ const resourceFormComponentCache = /* @__PURE__ */ new Map();
239
+ const resourceComponentCache = /* @__PURE__ */ new Map();
240
+ let resourceLocationContext = null;
241
+ let resourceStatusContext = null;
242
+ let resourceDataContext = null;
243
+ let resourceActionsContext = null;
244
+ function createRuntimeContext$1(name) {
245
+ const createContext = react.createContext;
246
+ if (!createContext) throw new Error(`${name} is not available in this environment.`);
247
+ return createContext(null);
248
+ }
249
+ function getResourceLocationContext() {
250
+ resourceLocationContext ??= createRuntimeContext$1("Litz resource location");
251
+ return resourceLocationContext;
252
+ }
253
+ function getResourceStatusContext() {
254
+ resourceStatusContext ??= createRuntimeContext$1("Litz resource status");
255
+ return resourceStatusContext;
256
+ }
257
+ function getResourceDataContext() {
258
+ resourceDataContext ??= createRuntimeContext$1("Litz resource data");
259
+ return resourceDataContext;
260
+ }
261
+ function getResourceActionsContext() {
262
+ resourceActionsContext ??= createRuntimeContext$1("Litz resource actions");
263
+ return resourceActionsContext;
264
+ }
265
+ function requireActiveResourceSlice(resourcePath, value) {
266
+ if (!value) throw new Error(`Resource "${resourcePath}" is being used outside its resource component.`);
267
+ if (value.id !== resourcePath) throw new Error(`Resource "${resourcePath}" is not the active resource. Active resource is "${value.id}".`);
268
+ return value;
269
+ }
270
+ function ResourceRuntimeProvider(props) {
271
+ const ResourceLocationContext = getResourceLocationContext();
272
+ const ResourceStatusContext = getResourceStatusContext();
273
+ const ResourceDataContext = getResourceDataContext();
274
+ const ResourceActionsContext = getResourceActionsContext();
275
+ const locationValue = react.useMemo(() => ({
276
+ id: props.value.id,
277
+ params: props.value.params,
278
+ search: props.value.search,
279
+ setSearch: props.value.setSearch
280
+ }), [
281
+ props.value.id,
282
+ props.value.params,
283
+ props.value.search,
284
+ props.value.setSearch
285
+ ]);
286
+ const statusValue = react.useMemo(() => ({
287
+ id: props.value.id,
288
+ status: props.value.status,
289
+ pending: props.value.pending
290
+ }), [
291
+ props.value.id,
292
+ props.value.pending,
293
+ props.value.status
294
+ ]);
295
+ const dataValue = react.useMemo(() => ({
296
+ id: props.value.id,
297
+ loaderResult: props.value.loaderResult,
298
+ actionResult: props.value.actionResult,
299
+ data: props.value.data,
300
+ view: props.value.view
301
+ }), [
302
+ props.value.actionResult,
303
+ props.value.data,
304
+ props.value.id,
305
+ props.value.loaderResult,
306
+ props.value.view
307
+ ]);
308
+ const actionsValue = react.useMemo(() => ({
309
+ id: props.value.id,
310
+ submit: props.value.submit,
311
+ reload: props.value.reload,
312
+ retry: props.value.retry
313
+ }), [
314
+ props.value.id,
315
+ props.value.reload,
316
+ props.value.retry,
317
+ props.value.submit
318
+ ]);
319
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ResourceLocationContext.Provider, {
320
+ value: locationValue,
321
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ResourceStatusContext.Provider, {
322
+ value: statusValue,
323
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ResourceDataContext.Provider, {
324
+ value: dataValue,
325
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ResourceActionsContext.Provider, {
326
+ value: actionsValue,
327
+ children: props.children
328
+ })
329
+ })
330
+ })
331
+ });
332
+ }
333
+ function useRequiredResourceLocation(resourcePath) {
334
+ return requireActiveResourceSlice(resourcePath, react.useContext(getResourceLocationContext()));
335
+ }
336
+ function useRequiredResourceStatus(resourcePath) {
337
+ return requireActiveResourceSlice(resourcePath, react.useContext(getResourceStatusContext()));
338
+ }
339
+ function useRequiredResourceData(resourcePath) {
340
+ return requireActiveResourceSlice(resourcePath, react.useContext(getResourceDataContext()));
341
+ }
342
+ function useRequiredResourceActions(resourcePath) {
343
+ return requireActiveResourceSlice(resourcePath, react.useContext(getResourceActionsContext()));
344
+ }
345
+ function createResourceFormComponent(resourcePath) {
346
+ const cached = resourceFormComponentCache.get(resourcePath);
347
+ if (cached) return cached;
348
+ const MemoizedLitzResourceForm = react.memo(function LitzResourceForm(props) {
349
+ const actions = useRequiredResourceActions(resourcePath);
350
+ const { children, onSubmit, replace, revalidate, ...rest } = props;
351
+ const submitRef = react.useRef((payload, options) => actions.submit(payload, options));
352
+ react.useEffect(() => {
353
+ submitRef.current = (payload, options) => actions.submit(payload, options);
354
+ }, [actions.submit]);
355
+ const action = react.useCallback(async (formData) => {
356
+ await submitRef.current(formData, {
357
+ replace,
358
+ revalidate
359
+ });
360
+ }, [replace, revalidate]);
361
+ return react.createElement("form", {
362
+ ...rest,
363
+ action,
364
+ onSubmit
365
+ }, children);
366
+ });
367
+ MemoizedLitzResourceForm.displayName = `LitzResourceForm(${resourcePath})`;
368
+ resourceFormComponentCache.set(resourcePath, MemoizedLitzResourceForm);
369
+ return MemoizedLitzResourceForm;
370
+ }
371
+ function createResourceComponent(resourcePath, Component) {
372
+ const cached = resourceComponentCache.get(resourcePath);
373
+ if (cached) return cached;
374
+ const MemoizedLitzResourceComponent = react.memo(function LitzResourceComponent(props) {
375
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ResourceRuntimeProvider, {
376
+ value: useResourceRuntime(resourcePath, props),
377
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Component, { ...props })
378
+ });
379
+ });
380
+ MemoizedLitzResourceComponent.displayName = `LitzResource(${resourcePath})`;
381
+ resourceComponentCache.set(resourcePath, MemoizedLitzResourceComponent);
382
+ return MemoizedLitzResourceComponent;
383
+ }
384
+ function useResourceRuntime(resourcePath, request) {
385
+ const params = react.useMemo(() => request?.params ?? {}, [request?.params]);
386
+ const incomingSearchKey = react.useMemo(() => createUrlSearchParams(request?.search).toString(), [request?.search]);
387
+ const [searchState, setSearchState] = react.useState(() => createUrlSearchParams(request?.search));
388
+ react.useEffect(() => {
389
+ setSearchState((current) => current.toString() === incomingSearchKey ? current : createUrlSearchParams(request?.search));
390
+ }, [incomingSearchKey, request?.search]);
391
+ const preparedRequest = react.useMemo(() => prepareResourceRequest(resourcePath, {
392
+ params,
393
+ search: searchState
394
+ }), [
395
+ params,
396
+ resourcePath,
397
+ searchState
398
+ ]);
399
+ const snapshot = react.useSyncExternalStore(react.useCallback((listener) => subscribe(preparedRequest.key, listener), [preparedRequest.key]), react.useCallback(() => getEntry(preparedRequest.key).snapshot, [preparedRequest.key]), react.useCallback(() => getEntry(preparedRequest.key).snapshot, [preparedRequest.key]));
400
+ const reloadImpl = react.useCallback(async (mode = "loading") => {
401
+ await performPreparedResourceRequest(resourcePath, "loader", preparedRequest, void 0, mode);
402
+ }, [preparedRequest, resourcePath]);
403
+ react.useEffect(() => {
404
+ if (!snapshot.pending && !snapshot.loaderResult && !snapshot.failure) reloadImpl("loading");
405
+ }, [
406
+ reloadImpl,
407
+ snapshot.failure,
408
+ snapshot.loaderResult,
409
+ snapshot.pending
410
+ ]);
411
+ const setSearch = react.useCallback((updates) => {
412
+ const result = applySearchParams(new URL(`https://litz.local/?${searchState.toString()}`), updates);
413
+ if (!result.changed) return;
414
+ const nextUrl = new URL(result.href);
415
+ react.startTransition(() => {
416
+ setSearchState(new URLSearchParams(nextUrl.search));
417
+ });
418
+ }, [searchState]);
419
+ const submit = react.useCallback(async (payload, options) => {
420
+ const formData = require_internal_transport.createFormDataPayload(payload);
421
+ options?.onBeforeSubmit?.(formData);
422
+ const result = await performPreparedResourceRequest(resourcePath, "action", preparedRequest, formData, "submitting");
423
+ if (!result || !("kind" in result)) return;
424
+ if (result.kind === "error" || result.kind === "fault") {
425
+ options?.onError?.(result);
426
+ return;
427
+ }
428
+ options?.onSuccess?.(result);
429
+ }, [preparedRequest, resourcePath]);
430
+ const reload = react.useCallback(() => {
431
+ reloadImpl(snapshot.loaderResult ? "revalidating" : "loading");
432
+ }, [reloadImpl, snapshot.loaderResult]);
433
+ const retry = react.useCallback(() => {
434
+ reloadImpl(snapshot.loaderResult ? "revalidating" : "loading");
435
+ }, [reloadImpl, snapshot.loaderResult]);
436
+ if (snapshot.failure) throw snapshot.failure;
437
+ return react.useMemo(() => ({
438
+ id: resourcePath,
439
+ params,
440
+ search: searchState,
441
+ setSearch,
442
+ status: snapshot.status,
443
+ pending: snapshot.pending,
444
+ loaderResult: snapshot.loaderResult,
445
+ actionResult: snapshot.actionResult,
446
+ data: snapshot.data,
447
+ view: snapshot.view,
448
+ submit,
449
+ reload,
450
+ retry
451
+ }), [
452
+ params,
453
+ reload,
454
+ resourcePath,
455
+ retry,
456
+ searchState,
457
+ setSearch,
458
+ snapshot.actionResult,
459
+ snapshot.data,
460
+ snapshot.loaderResult,
461
+ snapshot.pending,
462
+ snapshot.status,
463
+ snapshot.view,
464
+ submit
465
+ ]);
466
+ }
467
+ async function performPreparedResourceRequest(resourcePath, operation, preparedRequest, payload, mode = "loading") {
468
+ const { key, normalizedRequest } = preparedRequest;
469
+ const entry = getEntry(key);
470
+ if (entry.inFlight) return entry.inFlight;
471
+ entry.snapshot = {
472
+ ...entry.snapshot,
473
+ status: mode,
474
+ pending: true,
475
+ failure: void 0
476
+ };
477
+ notify(entry);
478
+ entry.inFlight = (async () => {
479
+ try {
480
+ const response = operation === "action" ? await fetch("/_litz/resource", {
481
+ method: "POST",
482
+ ...require_internal_transport.createInternalActionRequestInit({
483
+ path: resourcePath,
484
+ operation,
485
+ request: {
486
+ params: normalizedRequest.params,
487
+ search: normalizedRequest.search
488
+ }
489
+ }, payload)
490
+ }) : await fetch("/_litz/resource", {
491
+ method: "POST",
492
+ headers: {
493
+ "content-type": "application/json",
494
+ accept: require_internal_transport.LITZ_RESULT_ACCEPT
495
+ },
496
+ body: JSON.stringify({
497
+ path: resourcePath,
498
+ operation,
499
+ request: {
500
+ params: normalizedRequest.params,
501
+ search: normalizedRequest.search
502
+ }
503
+ })
504
+ });
505
+ if (operation === "loader") {
506
+ const loaderResult = await parseLoaderResponse(response);
507
+ entry.snapshot = {
508
+ ...entry.snapshot,
509
+ loaderResult,
510
+ data: loaderResult.kind === "data" ? loaderResult.data : null,
511
+ view: loaderResult.kind === "view" ? loaderResult.node : null,
512
+ status: "idle",
513
+ pending: false,
514
+ failure: void 0
515
+ };
516
+ return loaderResult;
517
+ }
518
+ const actionResult = await parseActionResponse(response);
519
+ if (actionResult.kind === "redirect") {
520
+ entry.snapshot = {
521
+ ...entry.snapshot,
522
+ actionResult,
523
+ status: "idle",
524
+ pending: false,
525
+ failure: void 0
526
+ };
527
+ performClientRedirect(actionResult.location, actionResult.replace);
528
+ return actionResult;
529
+ }
530
+ if (actionResult.kind === "fault") {
531
+ entry.snapshot = {
532
+ ...entry.snapshot,
533
+ actionResult,
534
+ status: "error",
535
+ pending: false,
536
+ failure: actionResult
537
+ };
538
+ throw actionResult;
539
+ }
540
+ entry.snapshot = {
541
+ ...entry.snapshot,
542
+ actionResult,
543
+ data: actionResult.kind === "data" ? actionResult.data : null,
544
+ view: actionResult.kind === "view" ? actionResult.node : null,
545
+ status: actionResult.kind === "error" ? "error" : "idle",
546
+ pending: false,
547
+ failure: void 0
548
+ };
549
+ return actionResult;
550
+ } catch (error) {
551
+ if (isRedirectSignal(error)) {
552
+ performClientRedirect(error.location, error.replace);
553
+ entry.snapshot = {
554
+ ...entry.snapshot,
555
+ status: "idle",
556
+ pending: false,
557
+ failure: void 0
558
+ };
559
+ return;
560
+ }
561
+ entry.snapshot = {
562
+ ...entry.snapshot,
563
+ status: isRouteLikeError(error) ? "error" : "error",
564
+ pending: false,
565
+ failure: error
566
+ };
567
+ throw error;
568
+ } finally {
569
+ entry.inFlight = void 0;
570
+ notify(entry);
571
+ cleanupResourceEntry(key, entry);
572
+ }
573
+ })();
574
+ return entry.inFlight;
575
+ }
576
+ function createUrlSearchParams(search) {
577
+ if (!search) return new URLSearchParams();
578
+ if (search instanceof URLSearchParams) return new URLSearchParams(search);
579
+ return new URLSearchParams(search);
580
+ }
581
+ function normalizeResourceRequest(request) {
582
+ return {
583
+ params: request?.params ?? {},
584
+ search: Object.fromEntries(createUrlSearchParams(request?.search).entries())
585
+ };
586
+ }
587
+ function prepareResourceRequest(resourcePath, request) {
588
+ const normalizedRequest = normalizeResourceRequest(request);
589
+ return {
590
+ key: createResourceCacheKey(resourcePath, normalizedRequest),
591
+ normalizedRequest
592
+ };
593
+ }
594
+ function createResourceCacheKey(resourcePath, normalizedRequest) {
595
+ return JSON.stringify({
596
+ path: resourcePath,
597
+ params: sortRecord$1(normalizedRequest.params),
598
+ search: sortRecord$1(normalizedRequest.search)
599
+ });
600
+ }
601
+ function sortRecord$1(value) {
602
+ return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)));
603
+ }
604
+ function subscribe(key, listener) {
605
+ const entry = getEntry(key);
606
+ entry.listeners.add(listener);
607
+ return () => {
608
+ entry.listeners.delete(listener);
609
+ cleanupResourceEntry(key, entry);
610
+ };
611
+ }
612
+ function getInitialSnapshot() {
613
+ return {
614
+ loaderResult: null,
615
+ actionResult: null,
616
+ data: null,
617
+ view: null,
618
+ status: "idle",
619
+ pending: false
620
+ };
621
+ }
622
+ function getEntry(key) {
623
+ let entry = resourceStore.get(key);
624
+ if (!entry) {
625
+ entry = {
626
+ snapshot: getInitialSnapshot(),
627
+ listeners: /* @__PURE__ */ new Set()
628
+ };
629
+ resourceStore.set(key, entry);
630
+ pruneResourceStore();
631
+ }
632
+ return entry;
633
+ }
634
+ function notify(entry) {
635
+ for (const listener of entry.listeners) listener();
636
+ }
637
+ function cleanupResourceEntry(key, entry) {
638
+ if (entry.listeners.size > 0 || entry.inFlight || entry.snapshot.pending) return;
639
+ resourceStore.delete(key);
640
+ }
641
+ function pruneResourceStore() {
642
+ if (resourceStore.size <= RESOURCE_STORE_LIMIT) return;
643
+ for (const [key, entry] of resourceStore) {
644
+ if (resourceStore.size <= RESOURCE_STORE_LIMIT) return;
645
+ cleanupResourceEntry(key, entry);
646
+ }
647
+ }
648
+ function performClientRedirect(href, replace) {
649
+ if (typeof window === "undefined") return;
650
+ if (replace) window.history.replaceState(null, "", href);
651
+ else window.history.pushState(null, "", href);
652
+ window.dispatchEvent(new PopStateEvent("popstate"));
653
+ }
654
+ //#endregion
655
+ //#region src/client/route-transition.ts
656
+ function resolveRouteModuleLoadState(options) {
657
+ if (!options.matched) return {
658
+ kind: "not-found",
659
+ loadedRoute: null,
660
+ displayLocation: options.nextLocation,
661
+ pageState: options.createEmptyPageState()
662
+ };
663
+ if (options.cachedRoute) return {
664
+ kind: "cached",
665
+ loadedRoute: options.cachedRoute,
666
+ displayLocation: options.nextLocation,
667
+ pageState: options.createBootstrapPageState(options.cachedRoute)
668
+ };
669
+ if (options.previousRoute) return { kind: "preserve-current" };
670
+ return {
671
+ kind: "reset-before-load",
672
+ loadedRoute: null,
673
+ displayLocation: options.nextLocation,
674
+ pageState: options.createEmptyPageState()
675
+ };
676
+ }
677
+ function resolveLoadedRouteState(options) {
678
+ return {
679
+ loadedRoute: options.loadedRoute,
680
+ displayLocation: options.nextLocation,
681
+ pageState: options.createBootstrapPageState(options.loadedRoute)
682
+ };
683
+ }
684
+ //#endregion
685
+ //#region src/client/route-host-state.tsx
686
+ function useResolvedRouteState(options) {
687
+ const createEmptyPageState = options.createEmptyPageState;
688
+ const createBootstrapPageState = options.createBootstrapPageState;
689
+ const getCachedRoute = options.getCachedRoute;
690
+ const setCachedRoute = options.setCachedRoute;
691
+ const [displayLocation, setDisplayLocation] = react.useState(() => options.location);
692
+ const [renderedRoute, setRenderedRoute] = react.useState(null);
693
+ const [pageState, setPageState] = react.useState(() => createEmptyPageState());
694
+ const renderedRouteRef = react.useRef(null);
695
+ react.useEffect(() => {
696
+ renderedRouteRef.current = renderedRoute;
697
+ }, [renderedRoute]);
698
+ react.useLayoutEffect(() => {
699
+ let cancelled = false;
700
+ async function loadRouteModule() {
701
+ const routeState = resolveRouteModuleLoadState({
702
+ matched: Boolean(options.matched),
703
+ cachedRoute: options.matched ? getCachedRoute(options.matched.entry.id) : null,
704
+ previousRoute: renderedRouteRef.current,
705
+ nextLocation: options.location,
706
+ createEmptyPageState: () => createEmptyPageState(),
707
+ createBootstrapPageState: (route) => createBootstrapPageState(route)
708
+ });
709
+ if (routeState.kind === "not-found") {
710
+ setRenderedRoute(routeState.loadedRoute);
711
+ setDisplayLocation(routeState.displayLocation);
712
+ setPageState(routeState.pageState);
713
+ return;
714
+ }
715
+ if (routeState.kind === "cached") {
716
+ setPageState(routeState.pageState);
717
+ setRenderedRoute(routeState.loadedRoute);
718
+ setDisplayLocation(routeState.displayLocation);
719
+ return;
720
+ }
721
+ if (routeState.kind === "reset-before-load") {
722
+ setRenderedRoute(routeState.loadedRoute);
723
+ setDisplayLocation(routeState.displayLocation);
724
+ setPageState(routeState.pageState);
725
+ }
726
+ const matchedEntry = options.matched?.entry;
727
+ if (!matchedEntry) return;
728
+ const loaded = await matchedEntry.load();
729
+ if (cancelled) return;
730
+ if (!loaded.route) throw new Error(`Route module "${matchedEntry.id}" does not export "route".`);
731
+ setCachedRoute(matchedEntry.id, loaded.route);
732
+ const loadedRouteState = resolveLoadedRouteState({
733
+ loadedRoute: loaded.route,
734
+ nextLocation: options.location,
735
+ createBootstrapPageState: (route) => createBootstrapPageState(route)
736
+ });
737
+ setPageState(loadedRouteState.pageState);
738
+ setRenderedRoute(loadedRouteState.loadedRoute);
739
+ setDisplayLocation(loadedRouteState.displayLocation);
740
+ }
741
+ loadRouteModule();
742
+ return () => {
743
+ cancelled = true;
744
+ };
745
+ }, [
746
+ createBootstrapPageState,
747
+ createEmptyPageState,
748
+ getCachedRoute,
749
+ options.location,
750
+ options.matched,
751
+ setCachedRoute
752
+ ]);
753
+ return {
754
+ displayLocation,
755
+ renderedRoute,
756
+ pageState,
757
+ setPageState
758
+ };
759
+ }
760
+ //#endregion
761
+ //#region src/client/route-runtime.tsx
762
+ let routeLocationContext = null;
763
+ let routeStatusContext = null;
764
+ let routeDataContext = null;
765
+ let routeActionsContext = null;
766
+ const routeFormComponentCache = /* @__PURE__ */ new Map();
767
+ function createRuntimeContext(name) {
768
+ const createContext = react.createContext;
769
+ if (!createContext) throw new Error(`${name} is not available in this environment.`);
770
+ return createContext(null);
771
+ }
772
+ function getRouteLocationContext() {
773
+ routeLocationContext ??= createRuntimeContext("Litz route location");
774
+ return routeLocationContext;
775
+ }
776
+ function getRouteStatusContext() {
777
+ routeStatusContext ??= createRuntimeContext("Litz route status");
778
+ return routeStatusContext;
779
+ }
780
+ function getRouteDataContext() {
781
+ routeDataContext ??= createRuntimeContext("Litz route data");
782
+ return routeDataContext;
783
+ }
784
+ function getRouteActionsContext() {
785
+ routeActionsContext ??= createRuntimeContext("Litz route actions");
786
+ return routeActionsContext;
787
+ }
788
+ function requireActiveRouteSlice(routeId, value) {
789
+ if (!value) throw new Error(`Route "${routeId}" is being used outside the Litz runtime.`);
790
+ if (value.id !== routeId) throw new Error(`Route "${routeId}" is not the active route. Active route is "${value.id}".`);
791
+ return value;
792
+ }
793
+ function RouteRuntimeProvider(props) {
794
+ const RouteLocationContext = getRouteLocationContext();
795
+ const RouteStatusContext = getRouteStatusContext();
796
+ const RouteDataContext = getRouteDataContext();
797
+ const RouteActionsContext = getRouteActionsContext();
798
+ const locationValue = react.useMemo(() => ({
799
+ id: props.value.id,
800
+ params: props.value.params,
801
+ search: props.value.search,
802
+ setSearch: props.value.setSearch
803
+ }), [
804
+ props.value.id,
805
+ props.value.params,
806
+ props.value.search,
807
+ props.value.setSearch
808
+ ]);
809
+ const statusValue = react.useMemo(() => ({
810
+ id: props.value.id,
811
+ status: props.value.status,
812
+ pending: props.value.pending
813
+ }), [
814
+ props.value.id,
815
+ props.value.pending,
816
+ props.value.status
817
+ ]);
818
+ const dataValue = react.useMemo(() => ({
819
+ id: props.value.id,
820
+ loaderResult: props.value.loaderResult,
821
+ actionResult: props.value.actionResult,
822
+ data: props.value.data,
823
+ view: props.value.view
824
+ }), [
825
+ props.value.actionResult,
826
+ props.value.data,
827
+ props.value.id,
828
+ props.value.loaderResult,
829
+ props.value.view
830
+ ]);
831
+ const actionsValue = react.useMemo(() => ({
832
+ id: props.value.id,
833
+ submit: props.value.submit,
834
+ reload: props.value.reload,
835
+ retry: props.value.retry
836
+ }), [
837
+ props.value.id,
838
+ props.value.reload,
839
+ props.value.retry,
840
+ props.value.submit
841
+ ]);
842
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RouteLocationContext.Provider, {
843
+ value: locationValue,
844
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RouteStatusContext.Provider, {
845
+ value: statusValue,
846
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RouteDataContext.Provider, {
847
+ value: dataValue,
848
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RouteActionsContext.Provider, {
849
+ value: actionsValue,
850
+ children: props.children
851
+ })
852
+ })
853
+ })
854
+ });
855
+ }
856
+ function useRequiredRouteLocation(routeId) {
857
+ return requireActiveRouteSlice(routeId, react.useContext(getRouteLocationContext()));
858
+ }
859
+ function useRequiredRouteStatus(routeId) {
860
+ return requireActiveRouteSlice(routeId, react.useContext(getRouteStatusContext()));
861
+ }
862
+ function useRequiredRouteData(routeId) {
863
+ return requireActiveRouteSlice(routeId, react.useContext(getRouteDataContext()));
864
+ }
865
+ function useRequiredRouteActions(routeId) {
866
+ return requireActiveRouteSlice(routeId, react.useContext(getRouteActionsContext()));
867
+ }
868
+ function createRouteFormComponent(routeId) {
869
+ const cached = routeFormComponentCache.get(routeId);
870
+ if (cached) return cached;
871
+ const MemoizedLitzRouteForm = react.memo(function LitzRouteForm(props) {
872
+ const actions = useRequiredRouteActions(routeId);
873
+ const { children, onSubmit, replace, revalidate, ...rest } = props;
874
+ const submitRef = react.useRef((payload, options) => actions.submit(payload, options));
875
+ react.useEffect(() => {
876
+ submitRef.current = (payload, options) => actions.submit(payload, options);
877
+ }, [actions.submit]);
878
+ const action = react.useCallback(async (formData) => {
879
+ await submitRef.current(formData, {
880
+ replace,
881
+ revalidate
882
+ });
883
+ }, [replace, revalidate]);
884
+ return react.createElement("form", {
885
+ ...rest,
886
+ action,
887
+ onSubmit
888
+ }, children);
889
+ });
890
+ MemoizedLitzRouteForm.displayName = `LitzRouteForm(${routeId})`;
891
+ routeFormComponentCache.set(routeId, MemoizedLitzRouteForm);
892
+ return MemoizedLitzRouteForm;
893
+ }
894
+ //#endregion
895
+ //#region src/client/runtime.tsx
896
+ async function fetchRouteLoader(path, request, target) {
897
+ return parseLoaderResponse(await fetch("/_litz/route", {
898
+ method: "POST",
899
+ headers: {
900
+ "content-type": "application/json",
901
+ accept: require_internal_transport.LITZ_RESULT_ACCEPT
902
+ },
903
+ body: JSON.stringify({
904
+ path,
905
+ target,
906
+ operation: "loader",
907
+ request: normalizeRequest(request)
908
+ })
909
+ }));
910
+ }
911
+ async function fetchRouteAction(path, request, payload) {
912
+ const actionRequest = require_internal_transport.createInternalActionRequestInit({
913
+ path,
914
+ operation: "action",
915
+ request: normalizeRequest(request)
916
+ }, payload);
917
+ return parseActionResponse(await fetch("/_litz/action", {
918
+ method: "POST",
919
+ headers: actionRequest.headers,
920
+ body: actionRequest.body
921
+ }));
922
+ }
923
+ function normalizeRequest(request) {
924
+ return {
925
+ params: request.params ?? {},
926
+ search: request.search instanceof URLSearchParams ? Object.fromEntries(request.search.entries()) : request.search ?? {}
927
+ };
928
+ }
929
+ //#endregion
930
+ //#region src/client/index.ts
931
+ require_bindings.installClientBindings({
932
+ usePathname,
933
+ useLocation,
934
+ useRequiredRouteLocation,
935
+ useRequiredRouteStatus,
936
+ useRequiredRouteData,
937
+ useRequiredRouteActions,
938
+ useRequiredResourceLocation,
939
+ useRequiredResourceStatus,
940
+ useRequiredResourceData,
941
+ useRequiredResourceActions,
942
+ useMatches,
943
+ createRouteFormComponent,
944
+ createResourceFormComponent,
945
+ createResourceComponent
946
+ });
947
+ const manifest = require_internal_transport.sortByPathSpecificity(virtual_litzjs_route_manifest.routeManifest);
948
+ const exactManifestEntries = /* @__PURE__ */ new Map();
949
+ const dynamicManifestEntries = [];
950
+ const ROUTE_CACHE_LIMIT = 200;
951
+ const ROUTE_MODULE_CACHE_LIMIT = Math.max(manifest.length, 1);
952
+ let navigationContext = null;
953
+ let locationContext = null;
954
+ let matchesContext = null;
955
+ const routeCache = /* @__PURE__ */ new Map();
956
+ const routeModuleCache = /* @__PURE__ */ new Map();
957
+ const routeModulePrefetchCache = /* @__PURE__ */ new Map();
958
+ const layoutChainCache = /* @__PURE__ */ new WeakMap();
959
+ const pathParamNamesCache = /* @__PURE__ */ new Map();
960
+ for (const entry of manifest) if (entry.path.includes(":")) dynamicManifestEntries.push(entry);
961
+ else exactManifestEntries.set(entry.path, entry);
962
+ function getNavigationContext() {
963
+ if (!navigationContext) {
964
+ const createContext = react.createContext;
965
+ if (!createContext) throw new Error("Litz client navigation is not available in this environment.");
966
+ navigationContext = createContext(null);
967
+ }
968
+ return navigationContext;
969
+ }
970
+ function getMatchesContext() {
971
+ if (!matchesContext) {
972
+ const createContext = react.createContext;
973
+ if (!createContext) throw new Error("Litz client matches are not available in this environment.");
974
+ matchesContext = createContext([]);
975
+ }
976
+ return matchesContext;
977
+ }
978
+ function getLocationContext() {
979
+ if (!locationContext) {
980
+ const createContext = react.createContext;
981
+ if (!createContext) throw new Error("Litz client location is not available in this environment.");
982
+ locationContext = createContext(null);
983
+ }
984
+ return locationContext;
985
+ }
986
+ function mountApp(element, options) {
987
+ import("react-dom/client").then(({ createRoot }) => {
988
+ createRoot(element).render(react.createElement(LitzApp, {
989
+ component: options?.component,
990
+ layout: options?.layout
991
+ }));
992
+ });
993
+ }
994
+ function useNavigate() {
995
+ const context = react.useContext(getNavigationContext());
996
+ if (!context) throw new Error("useNavigate() must be used inside the Litz client runtime.");
997
+ return (href, options) => context.navigate(href, options);
998
+ }
999
+ function useMatches() {
1000
+ return react.useContext(getMatchesContext());
1001
+ }
1002
+ function usePathname() {
1003
+ const location = react.useContext(getLocationContext());
1004
+ if (!location) throw new Error("usePathname() must be used inside the Litz client runtime.");
1005
+ return location.pathname;
1006
+ }
1007
+ function useLocation() {
1008
+ const location = react.useContext(getLocationContext());
1009
+ if (!location) throw new Error("useLocation() must be used inside the Litz client runtime.");
1010
+ return location;
1011
+ }
1012
+ const Link = createLinkComponent({
1013
+ useNavigate,
1014
+ prefetchRouteModuleForHref
1015
+ });
1016
+ function LitzApp(props) {
1017
+ const [location, setLocation] = react.useState(() => window.location.href);
1018
+ react.useEffect(() => {
1019
+ function handlePopState() {
1020
+ react.startTransition(() => {
1021
+ setLocation(window.location.href);
1022
+ });
1023
+ }
1024
+ window.addEventListener("popstate", handlePopState);
1025
+ return () => window.removeEventListener("popstate", handlePopState);
1026
+ }, []);
1027
+ const navigate = react.useCallback((next, replace = false) => {
1028
+ if (replace) window.history.replaceState(null, "", next);
1029
+ else window.history.pushState(null, "", next);
1030
+ react.startTransition(() => {
1031
+ setLocation(window.location.href);
1032
+ });
1033
+ }, []);
1034
+ const navigationValue = react.useMemo(() => ({ navigate(href, options) {
1035
+ navigate(href, options?.replace);
1036
+ } }), [navigate]);
1037
+ const locationValue = react.useMemo(() => {
1038
+ const url = new URL(location);
1039
+ return {
1040
+ href: location,
1041
+ pathname: url.pathname,
1042
+ search: new URLSearchParams(url.search),
1043
+ hash: url.hash
1044
+ };
1045
+ }, [location]);
1046
+ const routeHost = react.createElement(RouteHost, {
1047
+ location,
1048
+ navigate,
1049
+ component: props.component
1050
+ });
1051
+ const content = props.layout ? react.createElement(props.layout.component, null, routeHost) : routeHost;
1052
+ return react.createElement(getNavigationContext().Provider, { value: navigationValue }, react.createElement(getLocationContext().Provider, { value: locationValue }, content));
1053
+ }
1054
+ function RouteHost(props) {
1055
+ const navigate = react.useCallback((next, replace) => {
1056
+ props.navigate(next, replace);
1057
+ }, [props.navigate]);
1058
+ const url = react.useMemo(() => new URL(props.location), [props.location]);
1059
+ const search = react.useMemo(() => new URLSearchParams(url.search), [url.search]);
1060
+ const matched = react.useMemo(() => findMatch(url.pathname), [url.pathname]);
1061
+ const createBootstrapPageStateForLocation = react.useCallback((route) => createBootstrapPageState(route, url.pathname, search), [search, url.pathname]);
1062
+ const { displayLocation, renderedRoute, pageState, setPageState } = useResolvedRouteState({
1063
+ matched,
1064
+ location: props.location,
1065
+ createEmptyPageState,
1066
+ createBootstrapPageState: createBootstrapPageStateForLocation,
1067
+ getCachedRoute: getCachedRouteModule,
1068
+ setCachedRoute: setCachedRouteModule
1069
+ });
1070
+ const displayedUrl = react.useMemo(() => new URL(displayLocation), [displayLocation]);
1071
+ const displayedSearch = react.useMemo(() => new URLSearchParams(displayedUrl.search), [displayedUrl.search]);
1072
+ const activeRouteState = react.useMemo(() => {
1073
+ if (!renderedRoute) return null;
1074
+ const activeMatches = buildActiveMatches(renderedRoute, displayedUrl.pathname, displayedSearch);
1075
+ return {
1076
+ activeMatches,
1077
+ loaderMatches: activeMatches.filter((entry) => Boolean(entry.options?.loader)),
1078
+ baseRequest: {
1079
+ params: extractRouteParams(renderedRoute.path, displayedUrl.pathname) ?? {},
1080
+ search: displayedSearch
1081
+ }
1082
+ };
1083
+ }, [
1084
+ displayedSearch,
1085
+ displayedUrl.pathname,
1086
+ renderedRoute
1087
+ ]);
1088
+ react.useEffect(() => {
1089
+ if (!renderedRoute || !activeRouteState) return;
1090
+ let cancelled = false;
1091
+ const { loaderMatches, baseRequest } = activeRouteState;
1092
+ const finalLoaderMatchId = loaderMatches[loaderMatches.length - 1]?.id;
1093
+ const reload = async (mode = "loading") => {
1094
+ if (loaderMatches.length === 0) {
1095
+ if (!cancelled) setPageState((current) => ({
1096
+ ...current,
1097
+ status: "idle",
1098
+ pending: false,
1099
+ errorInfo: void 0,
1100
+ errorTargetId: void 0
1101
+ }));
1102
+ return;
1103
+ }
1104
+ setPageState((current) => applyCachedLoaderStateToPageState(current, loaderMatches, mode));
1105
+ for (const entry of loaderMatches) try {
1106
+ const loaderResult = await fetchRouteLoader(renderedRoute.path, baseRequest, entry.id);
1107
+ if (cancelled) return;
1108
+ setCachedLoaderResult(entry.cacheKey, withLoaderStaleState(loaderResult, false));
1109
+ setPageState((current) => withMatchLoaderResult(current, entry.id, renderedRoute.id, loaderResult, entry.id === finalLoaderMatchId ? "idle" : current.status, entry.id === finalLoaderMatchId ? false : current.pending));
1110
+ } catch (error) {
1111
+ if (cancelled) return;
1112
+ if (isRedirectSignal(error)) {
1113
+ navigate(error.location, true);
1114
+ return;
1115
+ }
1116
+ if (isRouteLikeError(error)) {
1117
+ setPageState((current) => ({
1118
+ ...current,
1119
+ status: "error",
1120
+ pending: false,
1121
+ errorInfo: error,
1122
+ errorTargetId: entry.id
1123
+ }));
1124
+ return;
1125
+ }
1126
+ throw error;
1127
+ }
1128
+ };
1129
+ reload();
1130
+ return () => {
1131
+ cancelled = true;
1132
+ };
1133
+ }, [
1134
+ activeRouteState,
1135
+ navigate,
1136
+ renderedRoute
1137
+ ]);
1138
+ const activeMatches = activeRouteState?.activeMatches ?? [];
1139
+ const matchesValue = activeMatches;
1140
+ const reloadImpl = react.useCallback((mode) => renderedRoute ? reloadCurrentRoute({
1141
+ route: renderedRoute,
1142
+ pathname: displayedUrl.pathname,
1143
+ search: displayedSearch,
1144
+ navigate,
1145
+ setPageState,
1146
+ mode
1147
+ }) : Promise.resolve(), [
1148
+ displayedSearch,
1149
+ displayedUrl.pathname,
1150
+ navigate,
1151
+ renderedRoute,
1152
+ setPageState
1153
+ ]);
1154
+ if (!matched) return react.createElement(NotFoundPage);
1155
+ if (!renderedRoute) return react.createElement(react.Fragment);
1156
+ const content = renderMatchChain(renderedRoute, activeMatches, pageState, displayLocation, navigate, reloadImpl, setPageState);
1157
+ return react.createElement(getMatchesContext().Provider, { value: matchesValue }, props.component ? react.createElement(props.component, null, content) : content);
1158
+ }
1159
+ function findMatch(pathname) {
1160
+ const exactEntry = exactManifestEntries.get(pathname);
1161
+ if (exactEntry) return {
1162
+ entry: exactEntry,
1163
+ params: {}
1164
+ };
1165
+ for (const entry of dynamicManifestEntries) {
1166
+ const params = require_internal_transport.matchPathname(entry.path, pathname);
1167
+ if (params) return {
1168
+ entry,
1169
+ params
1170
+ };
1171
+ }
1172
+ return null;
1173
+ }
1174
+ function NotFoundPage() {
1175
+ return react.createElement("main", null, react.createElement("h1", null, "Not Found"));
1176
+ }
1177
+ function createEmptyPageState() {
1178
+ return {
1179
+ matchStates: {},
1180
+ actionResult: null,
1181
+ nextResultSequence: 1,
1182
+ latestDataResult: null,
1183
+ latestViewResult: null,
1184
+ status: "loading",
1185
+ pending: true
1186
+ };
1187
+ }
1188
+ function createBootstrapPageState(route, pathname, search) {
1189
+ const matches = buildActiveMatches(route, pathname, search);
1190
+ const matchStates = {};
1191
+ let hasAnyLoader = false;
1192
+ let hasCachedLoader = false;
1193
+ let missingLoader = false;
1194
+ let nextResultSequence = 1;
1195
+ let latestDataResult = null;
1196
+ let latestViewResult = null;
1197
+ for (const match of matches) {
1198
+ if (!match.options?.loader) continue;
1199
+ hasAnyLoader = true;
1200
+ const cached = getCachedLoaderResult(match.cacheKey);
1201
+ if (cached) {
1202
+ hasCachedLoader = true;
1203
+ const staleCached = withLoaderStaleState(cached, true);
1204
+ matchStates[match.id] = { loaderResult: staleCached };
1205
+ if (match.id === route.id) {
1206
+ if (staleCached.kind === "data") {
1207
+ latestDataResult = {
1208
+ sequence: nextResultSequence,
1209
+ result: staleCached
1210
+ };
1211
+ nextResultSequence += 1;
1212
+ } else if (staleCached.kind === "view") {
1213
+ latestViewResult = {
1214
+ sequence: nextResultSequence,
1215
+ result: staleCached
1216
+ };
1217
+ nextResultSequence += 1;
1218
+ }
1219
+ }
1220
+ } else {
1221
+ missingLoader = true;
1222
+ matchStates[match.id] = { loaderResult: null };
1223
+ }
1224
+ }
1225
+ if (!hasAnyLoader) return {
1226
+ matchStates,
1227
+ actionResult: null,
1228
+ nextResultSequence,
1229
+ latestDataResult,
1230
+ latestViewResult,
1231
+ status: "idle",
1232
+ pending: false
1233
+ };
1234
+ return {
1235
+ matchStates,
1236
+ actionResult: null,
1237
+ nextResultSequence,
1238
+ latestDataResult,
1239
+ latestViewResult,
1240
+ status: hasCachedLoader ? "revalidating" : "loading",
1241
+ pending: missingLoader || hasCachedLoader
1242
+ };
1243
+ }
1244
+ function withLatestDataResult(current, result) {
1245
+ const sequence = current.nextResultSequence;
1246
+ return {
1247
+ ...current,
1248
+ nextResultSequence: sequence + 1,
1249
+ latestDataResult: {
1250
+ sequence,
1251
+ result
1252
+ }
1253
+ };
1254
+ }
1255
+ function withLatestViewResult(current, result) {
1256
+ const sequence = current.nextResultSequence;
1257
+ return {
1258
+ ...current,
1259
+ nextResultSequence: sequence + 1,
1260
+ latestViewResult: {
1261
+ sequence,
1262
+ result
1263
+ }
1264
+ };
1265
+ }
1266
+ function applyCachedLoaderStateToPageState(current, matches, mode) {
1267
+ const matchStates = { ...current.matchStates };
1268
+ let hasResolvedLoader = false;
1269
+ for (const match of matches) {
1270
+ const cached = getCachedLoaderResult(match.cacheKey);
1271
+ if (cached) {
1272
+ matchStates[match.id] = { loaderResult: withLoaderStaleState(cached, true) };
1273
+ hasResolvedLoader = true;
1274
+ } else if (!matchStates[match.id]) matchStates[match.id] = { loaderResult: null };
1275
+ else if (matchStates[match.id]?.loaderResult) hasResolvedLoader = true;
1276
+ }
1277
+ return {
1278
+ ...current,
1279
+ matchStates,
1280
+ status: hasResolvedLoader ? "revalidating" : mode,
1281
+ pending: true,
1282
+ errorInfo: void 0,
1283
+ errorTargetId: void 0
1284
+ };
1285
+ }
1286
+ function withMatchLoaderResult(current, matchId, routeId, loaderResult, status, pending) {
1287
+ let nextState = {
1288
+ ...current,
1289
+ matchStates: {
1290
+ ...current.matchStates,
1291
+ [matchId]: { loaderResult }
1292
+ },
1293
+ status,
1294
+ pending,
1295
+ errorInfo: void 0,
1296
+ errorTargetId: void 0
1297
+ };
1298
+ if (matchId === routeId) {
1299
+ if (loaderResult.kind === "data") nextState = withLatestDataResult(nextState, loaderResult);
1300
+ else if (loaderResult.kind === "view") nextState = withLatestViewResult(nextState, loaderResult);
1301
+ }
1302
+ return nextState;
1303
+ }
1304
+ async function reloadCurrentRoute(options) {
1305
+ const loaderMatches = buildActiveMatches(options.route, options.pathname, options.search).filter((entry) => Boolean(entry.options?.loader));
1306
+ if (loaderMatches.length === 0) {
1307
+ options.setPageState((current) => ({
1308
+ ...current,
1309
+ status: "idle",
1310
+ pending: false,
1311
+ errorInfo: void 0,
1312
+ errorTargetId: void 0
1313
+ }));
1314
+ return;
1315
+ }
1316
+ options.setPageState((current) => applyCachedLoaderStateToPageState(current, loaderMatches, options.mode ?? "loading"));
1317
+ const baseRequest = {
1318
+ params: extractRouteParams(options.route.path, options.pathname) ?? {},
1319
+ search: options.search
1320
+ };
1321
+ for (const entry of loaderMatches) try {
1322
+ const loaderResult = await fetchRouteLoader(options.route.path, baseRequest, entry.id);
1323
+ setCachedLoaderResult(entry.cacheKey, withLoaderStaleState(loaderResult, false));
1324
+ options.setPageState((current) => withMatchLoaderResult(current, entry.id, options.route.id, loaderResult, entry === loaderMatches[loaderMatches.length - 1] ? "idle" : current.status, entry === loaderMatches[loaderMatches.length - 1] ? false : current.pending));
1325
+ } catch (error) {
1326
+ if (isRedirectSignal(error)) {
1327
+ options.navigate(error.location, true);
1328
+ return;
1329
+ }
1330
+ if (isRouteLikeError(error)) {
1331
+ options.setPageState((current) => ({
1332
+ ...current,
1333
+ status: "error",
1334
+ pending: false,
1335
+ errorInfo: error,
1336
+ errorTargetId: entry.id
1337
+ }));
1338
+ return;
1339
+ }
1340
+ throw error;
1341
+ }
1342
+ }
1343
+ function renderMatchChain(route, matches, pageState, location, navigate, reloadImpl, setPageState) {
1344
+ const errorBoundaryIndex = findErrorBoundaryIndex(matches, pageState);
1345
+ const pendingBoundaryIndex = errorBoundaryIndex === null ? findPendingBoundaryIndex(matches, pageState) : null;
1346
+ if (errorBoundaryIndex === null && pendingBoundaryIndex === null && matches.some((match) => Boolean(match.options?.loader) && !pageState.matchStates[match.id]?.loaderResult)) return react.createElement(react.Fragment);
1347
+ let node = null;
1348
+ for (let index = matches.length - 1; index >= 0; index -= 1) {
1349
+ const match = matches[index];
1350
+ if (!match) continue;
1351
+ let content;
1352
+ if (errorBoundaryIndex !== null && index === errorBoundaryIndex) {
1353
+ const ErrorComponent = match.options?.errorComponent ?? DefaultRouteErrorPage;
1354
+ content = react.createElement(ErrorComponent, { error: pageState.errorInfo });
1355
+ } else if (pendingBoundaryIndex !== null && index === pendingBoundaryIndex) content = react.createElement(match.options?.pendingComponent);
1356
+ else if (errorBoundaryIndex !== null && index > errorBoundaryIndex) continue;
1357
+ else if (pendingBoundaryIndex !== null && index > pendingBoundaryIndex) continue;
1358
+ else if (match.kind === "route") content = react.createElement(match.component);
1359
+ else content = react.createElement(match.component, null, node);
1360
+ node = react.createElement(MatchRuntimeBoundary, {
1361
+ key: match.id,
1362
+ match,
1363
+ route,
1364
+ pageState,
1365
+ location,
1366
+ navigate,
1367
+ reloadImpl,
1368
+ setPageState
1369
+ }, content);
1370
+ }
1371
+ return node ?? react.createElement(react.Fragment);
1372
+ }
1373
+ function findErrorBoundaryIndex(matches, pageState) {
1374
+ if (!pageState.errorInfo || !pageState.errorTargetId) return null;
1375
+ const targetIndex = matches.findIndex((match) => match.id === pageState.errorTargetId);
1376
+ if (targetIndex === -1) return null;
1377
+ for (let index = targetIndex; index >= 0; index -= 1) {
1378
+ const match = matches[index];
1379
+ if (match && match.options?.errorComponent) return index;
1380
+ }
1381
+ return targetIndex;
1382
+ }
1383
+ function findPendingBoundaryIndex(matches, pageState) {
1384
+ if (!pageState.pending) return null;
1385
+ const targetIndex = matches.findIndex((match) => Boolean(match.options?.loader) && !pageState.matchStates[match.id]?.loaderResult);
1386
+ if (targetIndex === -1) return null;
1387
+ for (let index = targetIndex; index >= 0; index -= 1) {
1388
+ const match = matches[index];
1389
+ if (match && match.options?.pendingComponent) return index;
1390
+ }
1391
+ return null;
1392
+ }
1393
+ function DefaultRouteErrorPage(props) {
1394
+ return react.createElement("main", null, react.createElement("h1", null, "Route Error"), react.createElement("p", null, `${props.error.kind} ${props.error.status}: ${props.error.message}`));
1395
+ }
1396
+ function MatchRuntimeBoundary(props) {
1397
+ const runtime = useMatchRuntime(props);
1398
+ return react.createElement(RouteRuntimeProvider, { value: runtime }, props.children);
1399
+ }
1400
+ function useMatchRuntime(options) {
1401
+ const { match, route, pageState, location, navigate, reloadImpl, setPageState } = options;
1402
+ const loaderResult = pageState.matchStates[match.id]?.loaderResult ?? null;
1403
+ const actionResult = match.kind === "route" ? pageState.actionResult : null;
1404
+ const data = match.kind === "route" ? pageState.latestDataResult?.result.data ?? null : loaderResult?.kind === "data" ? loaderResult.data : null;
1405
+ const view = match.kind === "route" ? pageState.latestViewResult?.result.node ?? null : loaderResult?.kind === "view" ? loaderResult.node : null;
1406
+ const setSearch = react.useCallback((updates, submitOptions) => {
1407
+ const result = applySearchParams(new URL(location), updates);
1408
+ if (!result.changed) return;
1409
+ navigate(result.href, submitOptions?.replace);
1410
+ }, [location, navigate]);
1411
+ const submit = react.useCallback(async (payload, submitOptions) => {
1412
+ if (match.kind !== "route" || !route.options?.action) throw new Error(`Route "${match.path}" does not define an action.`);
1413
+ const formData = require_internal_transport.createFormDataPayload(payload);
1414
+ submitOptions?.onBeforeSubmit?.(formData);
1415
+ setPageState((current) => ({
1416
+ ...current,
1417
+ status: "submitting",
1418
+ pending: true
1419
+ }));
1420
+ const result = await fetchRouteAction(route.path, {
1421
+ params: match.params,
1422
+ search: match.search
1423
+ }, formData);
1424
+ if (result?.kind === "redirect") {
1425
+ navigate(result.location, submitOptions?.replace ?? result.replace);
1426
+ return;
1427
+ }
1428
+ if (result?.kind === "view") setPageState((current) => withLatestViewResult({
1429
+ ...current,
1430
+ actionResult: result,
1431
+ status: "idle",
1432
+ pending: false
1433
+ }, result));
1434
+ else if (result?.kind === "data") setPageState((current) => withLatestDataResult({
1435
+ ...current,
1436
+ actionResult: result,
1437
+ status: "idle",
1438
+ pending: false
1439
+ }, result));
1440
+ else if (result) setPageState((current) => ({
1441
+ ...current,
1442
+ actionResult: result,
1443
+ status: result.kind === "error" || result.kind === "fault" ? "error" : "idle",
1444
+ pending: false,
1445
+ errorInfo: result.kind === "error" || result.kind === "fault" ? result : current.errorInfo,
1446
+ errorTargetId: result.kind === "error" || result.kind === "fault" ? route.id : current.errorTargetId
1447
+ }));
1448
+ if (result && shouldRevalidateAfterSubmit(route, result.headers, submitOptions?.revalidate)) await reloadImpl("revalidating");
1449
+ if (result?.kind === "error" || result?.kind === "fault") submitOptions?.onError?.(result);
1450
+ else if (result) submitOptions?.onSuccess?.(result);
1451
+ }, [
1452
+ match,
1453
+ navigate,
1454
+ reloadImpl,
1455
+ route,
1456
+ setPageState
1457
+ ]);
1458
+ const reload = react.useCallback(() => {
1459
+ reloadImpl("revalidating");
1460
+ }, [reloadImpl]);
1461
+ const retry = react.useCallback(() => {
1462
+ reloadImpl("loading");
1463
+ }, [reloadImpl]);
1464
+ return react.useMemo(() => ({
1465
+ id: match.id,
1466
+ params: match.params,
1467
+ search: match.search,
1468
+ setSearch,
1469
+ status: pageState.status,
1470
+ pending: pageState.pending,
1471
+ loaderResult,
1472
+ actionResult,
1473
+ data,
1474
+ view,
1475
+ submit,
1476
+ reload,
1477
+ retry
1478
+ }), [
1479
+ actionResult,
1480
+ data,
1481
+ loaderResult,
1482
+ match.id,
1483
+ match.params,
1484
+ match.search,
1485
+ pageState.pending,
1486
+ pageState.status,
1487
+ reload,
1488
+ retry,
1489
+ setSearch,
1490
+ submit,
1491
+ view
1492
+ ]);
1493
+ }
1494
+ function getLayoutChain(layout) {
1495
+ if (!layout) return [];
1496
+ const cached = layoutChainCache.get(layout);
1497
+ if (cached) return cached;
1498
+ const chain = [...getLayoutChain(layout.options?.layout), layout];
1499
+ layoutChainCache.set(layout, chain);
1500
+ return chain;
1501
+ }
1502
+ function buildActiveMatches(route, pathname, search) {
1503
+ const routeParams = extractRouteParams(route.path, pathname) ?? {};
1504
+ const layouts = getLayoutChain(route.options?.layout);
1505
+ const sortedSearch = sortRecord(Object.fromEntries(search.entries()));
1506
+ return [...layouts.map((layout) => {
1507
+ const params = extractRouteParams(layout.path, pathname) ?? filterParamsForPath(routeParams, layout.path);
1508
+ return {
1509
+ id: layout.id,
1510
+ path: layout.path,
1511
+ cacheKey: createRouteCacheKey(layout.path, params, sortedSearch),
1512
+ kind: "layout",
1513
+ component: layout.component,
1514
+ options: layout.options,
1515
+ params,
1516
+ search
1517
+ };
1518
+ }), {
1519
+ id: route.id,
1520
+ path: route.path,
1521
+ cacheKey: createRouteCacheKey(route.path, routeParams, sortedSearch),
1522
+ kind: "route",
1523
+ component: route.component,
1524
+ options: route.options,
1525
+ params: routeParams,
1526
+ search
1527
+ }];
1528
+ }
1529
+ function shouldRevalidateAfterSubmit(route, headers, revalidate) {
1530
+ if (revalidate === false) return false;
1531
+ if (revalidate === true) return true;
1532
+ const targets = new Set(getRevalidateTargets(headers));
1533
+ if (Array.isArray(revalidate)) for (const target of revalidate) targets.add(target);
1534
+ if (targets.size === 0) return false;
1535
+ return getActiveMatchPaths(route).some((path) => targets.has(path));
1536
+ }
1537
+ function extractRouteParams(pathPattern, pathname) {
1538
+ return require_internal_transport.extractRouteLikeParams(pathPattern, pathname);
1539
+ }
1540
+ function filterParamsForPath(params, pathPattern) {
1541
+ const names = getPathParamNames(pathPattern);
1542
+ return Object.fromEntries(names.map((name) => [name, params[name]]).filter((entry) => entry[1] !== void 0));
1543
+ }
1544
+ function createRouteCacheKey(path, params, sortedSearch) {
1545
+ return JSON.stringify({
1546
+ path,
1547
+ params: sortRecord(params),
1548
+ search: sortedSearch
1549
+ });
1550
+ }
1551
+ function getActiveMatchPaths(route) {
1552
+ return [...getLayoutChain(route.options?.layout).map((layout) => layout.path), route.path];
1553
+ }
1554
+ function getPathParamNames(pathPattern) {
1555
+ const cached = pathParamNamesCache.get(pathPattern);
1556
+ if (cached) return cached;
1557
+ const names = Array.from(pathPattern.matchAll(/:([A-Za-z0-9_]+)/g), (match) => match[1]).filter((name) => Boolean(name));
1558
+ pathParamNamesCache.set(pathPattern, names);
1559
+ return names;
1560
+ }
1561
+ function sortRecord(record) {
1562
+ return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
1563
+ }
1564
+ function getCachedLoaderResult(key) {
1565
+ const cached = routeCache.get(key);
1566
+ if (!cached) return;
1567
+ routeCache.delete(key);
1568
+ routeCache.set(key, cached);
1569
+ return cached;
1570
+ }
1571
+ function setCachedLoaderResult(key, result) {
1572
+ if (routeCache.has(key)) routeCache.delete(key);
1573
+ routeCache.set(key, result);
1574
+ while (routeCache.size > ROUTE_CACHE_LIMIT) {
1575
+ const oldestKey = routeCache.keys().next().value;
1576
+ if (oldestKey === void 0) return;
1577
+ routeCache.delete(oldestKey);
1578
+ }
1579
+ }
1580
+ function getCachedRouteModule(id) {
1581
+ const cached = routeModuleCache.get(id);
1582
+ if (!cached) return null;
1583
+ routeModuleCache.delete(id);
1584
+ routeModuleCache.set(id, cached);
1585
+ return cached;
1586
+ }
1587
+ function setCachedRouteModule(id, route) {
1588
+ if (routeModuleCache.has(id)) routeModuleCache.delete(id);
1589
+ routeModuleCache.set(id, route);
1590
+ while (routeModuleCache.size > ROUTE_MODULE_CACHE_LIMIT) {
1591
+ const oldestKey = routeModuleCache.keys().next().value;
1592
+ if (oldestKey === void 0) return;
1593
+ routeModuleCache.delete(oldestKey);
1594
+ }
1595
+ }
1596
+ function prefetchRouteModuleForHref(href, target, download) {
1597
+ const currentUrl = new URL(window.location.href);
1598
+ const nextUrl = new URL(href, currentUrl);
1599
+ if (!shouldPrefetchLink({
1600
+ target,
1601
+ download,
1602
+ currentUrl,
1603
+ nextUrl
1604
+ })) return;
1605
+ const matched = findMatch(nextUrl.pathname);
1606
+ if (!matched || routeModuleCache.has(matched.entry.id)) return;
1607
+ if (routeModulePrefetchCache.get(matched.entry.id)) return;
1608
+ const prefetch = matched.entry.load().then((loaded) => {
1609
+ if (loaded.route) setCachedRouteModule(matched.entry.id, loaded.route);
1610
+ }).catch(() => {}).finally(() => {
1611
+ routeModulePrefetchCache.delete(matched.entry.id);
1612
+ });
1613
+ routeModulePrefetchCache.set(matched.entry.id, prefetch);
1614
+ }
1615
+ function withLoaderStaleState(result, stale) {
1616
+ if (result.stale === stale) return result;
1617
+ if (result.kind === "data") return {
1618
+ ...result,
1619
+ stale
1620
+ };
1621
+ return {
1622
+ ...result,
1623
+ stale,
1624
+ render: result.render
1625
+ };
1626
+ }
1627
+ //#endregion
1628
+ exports.Link = Link;
1629
+ exports.mountApp = mountApp;
1630
+ exports.useLocation = useLocation;
1631
+ exports.useMatches = useMatches;
1632
+ exports.useNavigate = useNavigate;
1633
+ exports.usePathname = usePathname;