@sjcx/vorma 0.0.0-pre.2 → 0.0.0-pre.3

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.
Files changed (135) hide show
  1. package/dist/build/assemble-artifacts.d.ts +1 -1
  2. package/dist/build/bundle-client.d.ts +1 -1
  3. package/dist/build/cli.js +27 -37
  4. package/dist/build/compile-application.d.ts +2 -2
  5. package/dist/build/development-artifacts.d.ts +1 -1
  6. package/dist/build/development-build.d.ts +3 -6
  7. package/dist/build/development-work.d.ts +2 -3
  8. package/dist/build/generate-application.d.ts +2 -2
  9. package/dist/build/index.d.ts +2 -3
  10. package/dist/build/index.js +1 -1
  11. package/dist/build/inspect-projects.d.ts +3 -3
  12. package/dist/build/load-config.d.ts +0 -2
  13. package/dist/build/publish-artifacts.d.ts +1 -4
  14. package/dist/build/render-client-transport.d.ts +1 -1
  15. package/dist/build/render-server-generated.d.ts +1 -12
  16. package/dist/build/render-types.d.ts +1 -1
  17. package/dist/build/resolve-config.d.ts +2 -4
  18. package/dist/build/source-layout.d.ts +1 -1
  19. package/dist/{build-DcP5_DUV.js → build-sHSh5zaX.js} +67 -282
  20. package/package.json +5 -36
  21. package/dist/build/compile-server.d.ts +0 -42
  22. package/dist/client/assembly.d.ts +0 -40
  23. package/dist/client/browser-http-client.d.ts +0 -10
  24. package/dist/client/client-loaders.d.ts +0 -93
  25. package/dist/client/commit.d.ts +0 -41
  26. package/dist/client/contracts.d.ts +0 -192
  27. package/dist/client/core.d.ts +0 -45
  28. package/dist/client/development.d.ts +0 -16
  29. package/dist/client/document-recovery.d.ts +0 -4
  30. package/dist/client/endpoints.d.ts +0 -54
  31. package/dist/client/errors.d.ts +0 -87
  32. package/dist/client/freshness.d.ts +0 -51
  33. package/dist/client/head.d.ts +0 -14
  34. package/dist/client/history.d.ts +0 -25
  35. package/dist/client/index.d.ts +0 -80
  36. package/dist/client/index.js +0 -150
  37. package/dist/client/links.d.ts +0 -48
  38. package/dist/client/materialize.d.ts +0 -21
  39. package/dist/client/modules.d.ts +0 -2
  40. package/dist/client/navigation.d.ts +0 -47
  41. package/dist/client/payload.d.ts +0 -54
  42. package/dist/client/prefetch.d.ts +0 -28
  43. package/dist/client/prepare.d.ts +0 -47
  44. package/dist/client/projections.d.ts +0 -26
  45. package/dist/client/queries.d.ts +0 -33
  46. package/dist/client/react/application.d.ts +0 -87
  47. package/dist/client/react/hooks.d.ts +0 -36
  48. package/dist/client/react/index.d.ts +0 -5
  49. package/dist/client/react/index.js +0 -3237
  50. package/dist/client/react/link.d.ts +0 -27
  51. package/dist/client/react/view.d.ts +0 -86
  52. package/dist/client/request.d.ts +0 -16
  53. package/dist/client/route-context.d.ts +0 -31
  54. package/dist/client/route-matching.d.ts +0 -22
  55. package/dist/client/route-synchronization.d.ts +0 -30
  56. package/dist/client/route-url.d.ts +0 -5
  57. package/dist/client/runtime.d.ts +0 -82
  58. package/dist/client/scroll.d.ts +0 -16
  59. package/dist/client/services.d.ts +0 -34
  60. package/dist/client/state.d.ts +0 -102
  61. package/dist/client/static-assets.d.ts +0 -3
  62. package/dist/client/stylesheets.d.ts +0 -7
  63. package/dist/client/transport-type-entries.d.ts +0 -2
  64. package/dist/client/types.d.ts +0 -152
  65. package/dist/client/view-definition.d.ts +0 -24
  66. package/dist/client/work-indicator.d.ts +0 -18
  67. package/dist/compile-Me8EOyMK.js +0 -870
  68. package/dist/development-B_rxuwY7.js +0 -128
  69. package/dist/hash-set-BPx3iXec.js +0 -141
  70. package/dist/href-Cqyax8Y0.js +0 -357
  71. package/dist/materialize-DZ7VSLFk.js +0 -739
  72. package/dist/metadata-BoPKo-G2.js +0 -32
  73. package/dist/primitives/tasks/index.d.ts +0 -1
  74. package/dist/primitives/tasks/index.js +0 -1
  75. package/dist/route-context-CtY_9U_W.js +0 -559
  76. package/dist/route-matching-D_uSG2P9.js +0 -33
  77. package/dist/search-parse-0f7oqVsG.js +0 -375
  78. package/dist/server/config/index.d.ts +0 -36
  79. package/dist/server/config/index.js +0 -30
  80. package/dist/server/development/metadata.d.ts +0 -11
  81. package/dist/server/document.d.ts +0 -37
  82. package/dist/server/form-data.d.ts +0 -21
  83. package/dist/server/graph/compile.d.ts +0 -118
  84. package/dist/server/graph/declarations.d.ts +0 -117
  85. package/dist/server/graph/literal-error-tag.d.ts +0 -14
  86. package/dist/server/graph/pattern-conflicts.d.ts +0 -36
  87. package/dist/server/graph/search-schema.d.ts +0 -13
  88. package/dist/server/graph/transport-schema.d.ts +0 -11
  89. package/dist/server/index.d.ts +0 -310
  90. package/dist/server/index.js +0 -2311
  91. package/dist/server/public-assets.d.ts +0 -18
  92. package/dist/server/runtime/application.d.ts +0 -15
  93. package/dist/server/runtime/assets.d.ts +0 -15
  94. package/dist/server/runtime/context.d.ts +0 -96
  95. package/dist/server/runtime/domain-errors.d.ts +0 -11
  96. package/dist/server/runtime/effects.d.ts +0 -83
  97. package/dist/server/runtime/execute.d.ts +0 -13
  98. package/dist/server/runtime/finalize.d.ts +0 -44
  99. package/dist/server/runtime/handler.d.ts +0 -106
  100. package/dist/server/runtime/head.d.ts +0 -22
  101. package/dist/server/runtime/payload.d.ts +0 -50
  102. package/dist/server/runtime/pipeline.d.ts +0 -27
  103. package/dist/server/runtime/plan.d.ts +0 -98
  104. package/dist/server/runtime/render-document.d.ts +0 -16
  105. package/dist/server/runtime/startup-error.d.ts +0 -11
  106. package/dist/server/runtime/transport-type-entries.d.ts +0 -2
  107. package/dist/server/runtime/vercel-deployment.d.ts +0 -2
  108. package/dist/server/url-pathname.d.ts +0 -13
  109. package/dist/static-assets-BrH3O0M8.js +0 -199
  110. package/dist/universal/generated-types.d.ts +0 -9
  111. package/dist/universal/href.d.ts +0 -17
  112. package/dist/universal/http-method.d.ts +0 -4
  113. package/dist/universal/json-transport.d.ts +0 -14
  114. package/dist/universal/null-prototype-record.d.ts +0 -7
  115. package/dist/universal/path-wire.d.ts +0 -71
  116. package/dist/universal/pattern-language.d.ts +0 -4
  117. package/dist/universal/protocol-schemas.d.ts +0 -99
  118. package/dist/universal/protocol.d.ts +0 -162
  119. package/dist/universal/search-grammar.d.ts +0 -7
  120. package/dist/universal/search-parse.d.ts +0 -22
  121. package/dist/universal/search-serialize.d.ts +0 -16
  122. package/dist/universal/search-shape.d.ts +0 -32
  123. package/dist/universal/static-assets.d.ts +0 -2
  124. package/dist/universal/transport-types/big-decimal.d.ts +0 -13
  125. package/dist/universal/transport-types/chunk.d.ts +0 -8
  126. package/dist/universal/transport-types/date-time-utc.d.ts +0 -13
  127. package/dist/universal/transport-types/date-time-zoned.d.ts +0 -13
  128. package/dist/universal/transport-types/duration.d.ts +0 -13
  129. package/dist/universal/transport-types/hash-map.d.ts +0 -8
  130. package/dist/universal/transport-types/hash-set.d.ts +0 -8
  131. package/dist/universal/transport-types/option.d.ts +0 -10
  132. package/dist/universal/transport-types/redacted.d.ts +0 -9
  133. package/dist/universal/value-transport.d.ts +0 -117
  134. package/dist/value-transport-Bst4Hk-n.js +0 -864
  135. /package/dist/{universal → build}/content-digest.d.ts +0 -0
@@ -1,3237 +0,0 @@
1
- import { _ as DOCUMENT_PROTOCOL, b as HTTP_PROTOCOL, i as encodeTransportedValue, o as canonicalizeRequestPathForMatcher, r as decodeTransportedValue } from "../../value-transport-Bst4Hk-n.js";
2
- import { A as routeStateAtom, B as RevalidationError, C as committedFrameAtom, D as projectRouteState, E as projectRouteError, F as HandlerError, H as getQueryRedirect, I as InputError, L as NavigationError, M as clientStateAtom, N as BuildMismatchError, P as CancelledByDedupeKeyError, R as PrefetchError, S as buildMismatchAtom, T as navigationStateAtom, V as TransportError, _ as CLIENT_TRANSPORT_TYPE_ENTRIES, a as readSynchronizationTarget, b as createLinkIntent, c as createWorkIndicator, d as ClientCoordinator, f as clientRuntime, g as toHref, h as universalRuntime, i as getRouteMatchAtom, j as workStateAtom, k as revalidationStateAtom, m as publishApplicationContexts, n as getClientLoaderDataAtom, o as runSynchronizationTarget, p as provideClientContext, r as getLoaderDataAtom, t as createRouteContext, u as CLIENT_UNAVAILABLE_ERROR, v as allowsInterception, x as getLinkStateAtomFor, y as attachPointerModalityTracking, z as QueryRedirected } from "../../route-context-CtY_9U_W.js";
3
- import { h as encodeScalar, i as serializeSearchParams, t as buildHref } from "../../href-Cqyax8Y0.js";
4
- import { i as readTransportedValue, n as readEndpointErrorEnvelope, r as readRedirectStatus, t as getStaticAssetDestination } from "../../static-assets-BrH3O0M8.js";
5
- import { C as readDocumentHead, S as rememberScroll, _ as satisfyDemand, a as readDocumentStylesheets, b as readBrowserPosition, c as acknowledgeQuery, d as failAttempt, f as holdAttemptForRetry, g as retireDemand, h as releaseQuery, i as prepareStylesheets, l as canStartAttempt, m as registerDemand, n as materializeRoute, o as reloadDocument, p as makeRevalidationError, r as commitFrame, s as abandonAttempt, u as expectQueries, v as startAttempt, x as recallScroll, y as createEntryKey } from "../../materialize-DZ7VSLFk.js";
6
- import { Brand, Cause, Context, Deferred, Duration, Effect, Exit, Fiber, FiberSet, Layer, Option, PubSub, RcMap, Result, Scope, Stream, identity } from "effect";
7
- import { FetchHttpClient, Headers, HttpBody, HttpClient, HttpClientRequest } from "effect/unstable/http";
8
- import { AsyncResult, Atom, AtomRegistry, Reactivity } from "effect/unstable/reactivity";
9
- import { RegistryContext, scheduleTask, useAtomValue } from "@effect/atom-react";
10
- import { createRoot } from "react-dom/client";
11
- import { deepEquals } from "@vorma/primitives/json";
12
- import { Component, createContext, memo, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
13
- import { jsx } from "react/jsx-runtime";
14
- //#region src/client/client-loaders.ts
15
- const CONFIRMED_DYNAMIC_PATH_CAPACITY = 4096;
16
- function toKnownMatch(route) {
17
- return {
18
- pattern: route.pattern,
19
- params: route.params,
20
- splatValues: route.splatValues,
21
- input: route.searchInput
22
- };
23
- }
24
- function createClientLoaderRegistry() {
25
- /** By authored pattern. */
26
- const facts = /* @__PURE__ */ new Map();
27
- const confirmedPaths = /* @__PURE__ */ new Map();
28
- let module = null;
29
- /** Built from the registrations on first use after they change. */
30
- let matcher = null;
31
- function getMatcher() {
32
- if (module === null) return null;
33
- if (matcher === null) matcher = module.createLearnedMatcher([...facts.values()].map((own) => {
34
- return {
35
- pattern: own.compiled,
36
- value: own
37
- };
38
- }));
39
- return matcher;
40
- }
41
- function matchLearned(path) {
42
- const learned = getMatcher();
43
- if (learned === null) return null;
44
- const chain = learned.matchPath(path);
45
- if (chain === null) return null;
46
- const proven = [];
47
- for (const match of chain) {
48
- const own = match.value;
49
- proven.push({
50
- pattern: own.pattern,
51
- params: match.params,
52
- splatValues: match.splatValues,
53
- facts: own
54
- });
55
- }
56
- return proven;
57
- }
58
- function selectStaticPrefix(chain) {
59
- const end = chain.findIndex((match) => !match.facts.isStatic);
60
- return end === -1 ? chain : chain.slice(0, end);
61
- }
62
- /** The chain the registry can prove for `path`: the confirmed chain while the learned patterns agree with it, else the static prefix of a learned match. */
63
- function proveChain(path) {
64
- const learned = matchLearned(path);
65
- const confirmed = confirmedPaths.get(path);
66
- if (confirmed !== void 0) {
67
- if (learned === null || learned.length === confirmed.length && learned.every((match, index) => {
68
- return confirmed[index]?.pattern === match.pattern;
69
- })) {
70
- confirmedPaths.delete(path);
71
- confirmedPaths.set(path, confirmed);
72
- return confirmed;
73
- }
74
- confirmedPaths.delete(path);
75
- }
76
- return learned === null ? [] : selectStaticPrefix(learned);
77
- }
78
- /** A position's search input parsed from the contract structure; null when parsing refuses it. */
79
- function readSearchInput(input) {
80
- const parsed = input.module.parseSearch({
81
- document: input.search.document,
82
- searchParams: input.searchParams
83
- });
84
- return Result.isFailure(parsed) ? null : { value: parsed.success };
85
- }
86
- function recordConfirmedPath(input) {
87
- confirmedPaths.delete(input.path);
88
- const firstDynamicIndex = input.matches.findIndex((match) => !match.facts.isStatic);
89
- if (firstDynamicIndex === -1) return;
90
- if (!input.matches.some((match, index) => {
91
- return index >= firstDynamicIndex && match.facts.view?.clientLoader !== void 0;
92
- })) return;
93
- confirmedPaths.set(input.path, input.matches);
94
- if (confirmedPaths.size <= CONFIRMED_DYNAMIC_PATH_CAPACITY) return;
95
- for (const leastRecentlyUsedPath of confirmedPaths.keys()) {
96
- confirmedPaths.delete(leastRecentlyUsedPath);
97
- break;
98
- }
99
- }
100
- return {
101
- replaceViewsFn: (replacements) => {
102
- for (const own of facts.values()) if (own.view !== void 0 && replacements.has(own.view)) own.view = replacements.get(own.view);
103
- },
104
- recordConfirmedChain: (chain) => {
105
- const proven = [];
106
- for (const route of chain.routes) {
107
- let own = facts.get(route.pattern);
108
- if (own === void 0) {
109
- own = {
110
- pattern: route.pattern,
111
- compiled: route.compiledPattern,
112
- isStatic: route.compiledPattern.segments.every((segment) => {
113
- return segment.kind === "static" || segment.kind === "index";
114
- }),
115
- search: route.searchContract === null ? null : { document: route.searchContract },
116
- view: route.definition
117
- };
118
- facts.set(route.pattern, own);
119
- matcher = null;
120
- }
121
- proven.push({
122
- pattern: route.pattern,
123
- params: route.params,
124
- splatValues: route.splatValues,
125
- facts: own
126
- });
127
- }
128
- const path = canonicalizeRequestPathForMatcher(chain.url.pathname);
129
- if (Result.isSuccess(path)) recordConfirmedPath({
130
- path: path.success,
131
- matches: proven
132
- });
133
- },
134
- installMatching: (installed) => {
135
- module = installed;
136
- matcher = null;
137
- },
138
- findEligibleMatches: (url) => {
139
- if (module === null) return [];
140
- const path = canonicalizeRequestPathForMatcher(url.pathname);
141
- if (Result.isFailure(path)) return [];
142
- const eligible = [];
143
- for (const match of proveChain(path.success)) {
144
- let input;
145
- if (match.facts.search !== null) {
146
- const read = readSearchInput({
147
- module,
148
- search: match.facts.search,
149
- searchParams: url.searchParams
150
- });
151
- if (read === null) break;
152
- input = read.value;
153
- }
154
- eligible.push({
155
- match: {
156
- pattern: match.pattern,
157
- params: match.params,
158
- splatValues: match.splatValues,
159
- input
160
- },
161
- view: match.facts.view
162
- });
163
- }
164
- return eligible;
165
- }
166
- };
167
- }
168
- /** Forks one client loader into the current scope, joined to its candidate's server result. */
169
- function startClientLoader(provideClientFn, input) {
170
- const confirmed = Deferred.await(input.confirmation);
171
- const context = {
172
- trigger: input.trigger,
173
- href: input.href,
174
- historyState: input.historyState,
175
- pattern: input.match.pattern,
176
- params: input.match.params,
177
- splatValues: input.match.splatValues,
178
- input: input.match.input,
179
- knownRouteMatches: input.known,
180
- serverConfirmedRouteMatches: Effect.map(confirmed, (routes) => routes.map(toKnownMatch)),
181
- loaderData: Effect.flatMap(confirmed, (c) => {
182
- const route = c.find((route) => route.pattern === input.match.pattern);
183
- return route === void 0 ? Effect.interrupt : Effect.succeed(route.loaderData);
184
- }),
185
- getLoaderData: (pattern) => {
186
- return Effect.map(confirmed, (c) => {
187
- return Option.map(Option.fromNullishOr(c.find((route) => route.pattern === pattern)), (route) => route.loaderData);
188
- });
189
- }
190
- };
191
- return Effect.map(Effect.forkScoped(input.activation === void 0 ? provideClientFn(Effect.suspend(() => input.clientLoader(context))) : Effect.provide(Effect.suspend(() => input.clientLoader(context)), input.activation.client)), (fiber) => {
192
- return {
193
- pattern: input.match.pattern,
194
- fiber
195
- };
196
- });
197
- }
198
- /**
199
- * Collects loader outcomes and reports the earliest failure in route order.
200
- * The preparation owner observes failures and interrupts later work as soon
201
- * as each loader starts, including before this collection begins.
202
- */
203
- function awaitClientLoaders(publishEventFn, running) {
204
- return Effect.gen(function* () {
205
- const settled = yield* Effect.forEach(running, ({ index, pattern, fiber }) => {
206
- return Effect.map(Fiber.await(fiber), (exit) => {
207
- return {
208
- index,
209
- pattern,
210
- exit
211
- };
212
- });
213
- }, { concurrency: "unbounded" });
214
- const results = /* @__PURE__ */ new Map();
215
- let failure = null;
216
- for (const { index, pattern, exit } of settled) if (Exit.isSuccess(exit)) results.set(index, AsyncResult.success(exit.value));
217
- else if (!Cause.hasInterruptsOnly(exit.cause)) {
218
- results.set(index, AsyncResult.failure(exit.cause));
219
- if (failure === null || index < failure.index) failure = {
220
- index,
221
- pattern,
222
- cause: exit.cause
223
- };
224
- }
225
- if (failure !== null) publishEventFn({
226
- _tag: "FailureReported",
227
- source: "clientLoader",
228
- cause: failure.cause
229
- });
230
- return {
231
- results,
232
- failure
233
- };
234
- });
235
- }
236
- //#endregion
237
- //#region src/client/browser-http-client.ts
238
- /** Executes a framework request with native Fetch controls and its caller's scope. */
239
- function executeBrowserRequest(request) {
240
- return Effect.gen(function* () {
241
- const fetch = yield* FetchHttpClient.Fetch;
242
- let followedUrl;
243
- return {
244
- response: yield* Effect.flatMap(HttpClient.HttpClient, (client) => HttpClient.withScope(client).execute(request)).pipe(Effect.provide(FetchHttpClient.layer), Effect.provideService(FetchHttpClient.Fetch, async (input, init) => {
245
- const response = await fetch(input, init);
246
- if (response.redirected) followedUrl = response.url;
247
- return response;
248
- })),
249
- followedUrl
250
- };
251
- });
252
- }
253
- /** The address without the query keys that the client adds to its own route requests. */
254
- function stripRouteQueryKeys(url) {
255
- const stripped = new URL(url);
256
- stripped.searchParams.delete(HTTP_PROTOCOL.queryKeys.viewPayload);
257
- stripped.searchParams.delete(HTTP_PROTOCOL.queryKeys.deployment);
258
- return stripped;
259
- }
260
- /** The observed redirect destination, excluding Vorma's route query keys. */
261
- function findFollowedRedirect(followedUrl, requested) {
262
- if (followedUrl === void 0 || followedUrl === "") return null;
263
- const final = stripRouteQueryKeys(new URL(followedUrl));
264
- return final.href === stripRouteQueryKeys(requested).href ? null : final.href;
265
- }
266
- //#endregion
267
- //#region src/client/services.ts
268
- /** The activation a route-local computation runs under; a mutation holds it until settlement. */
269
- const CurrentActivation = Context.Service("vorma/client/CurrentActivation");
270
- function declaresServices(view) {
271
- return view.clientOnlyServices !== void 0 || view.universalServices !== void 0;
272
- }
273
- /** Builds the activation map in the application scope. */
274
- function createActivationMap(getApplicationContextsFn) {
275
- return RcMap.make({ lookup: (key) => {
276
- return Effect.gen(function* () {
277
- const scope = yield* Effect.scope;
278
- const contexts = getApplicationContextsFn();
279
- if (Option.isNone(contexts)) return yield* Effect.fail(CLIENT_UNAVAILABLE_ERROR);
280
- const application = contexts.value;
281
- const memoMap = yield* Layer.makeMemoMap;
282
- const universalServices = key.view.universalServices === void 0 ? Layer.empty : key.view.universalServices;
283
- const universal = yield* Layer.buildWithMemoMap(Layer.provide(universalServices, Layer.succeedContext(application.universal)), memoMap, scope);
284
- const universalContext = Context.merge(application.universal, universal);
285
- const clientOnlyServices = key.view.clientOnlyServices === void 0 ? Layer.empty : key.view.clientOnlyServices;
286
- const client = yield* Layer.buildWithMemoMap(Layer.provide(clientOnlyServices, Layer.succeedContext(Context.merge(application.client, universalContext))), memoMap, scope);
287
- const clientContext = Context.add(Context.merge(Context.merge(application.client, universalContext), client), CurrentActivation, key);
288
- return {
289
- key,
290
- client: clientContext,
291
- universal: universalContext,
292
- clientRuntime: Atom.runtime(Layer.succeedContext(clientContext)),
293
- universalRuntime: Atom.runtime(Layer.succeedContext(universalContext))
294
- };
295
- });
296
- } });
297
- }
298
- /**
299
- * Acquires every entry's activation in `scope`. A live preparation keeps its
300
- * hold until this scope acquires it. A cached entry contains only the key;
301
- * re-entry builds a new activation when no owner retains that key.
302
- */
303
- function acquireActivations(activations, input) {
304
- return Effect.forEach(input.entries, (entry) => {
305
- const key = "activationKey" in entry ? entry.activationKey : entry.activation?.key;
306
- if (key === void 0) return Effect.succeed("activationKey" in entry ? toAcquiredRouteEntry(entry, void 0) : entry);
307
- return Effect.map(Effect.provideService(RcMap.get(activations, key), Scope.Scope, input.scope), (activation) => toAcquiredRouteEntry(entry, activation));
308
- });
309
- }
310
- /** Omits acquired contexts and runtimes from a reusable entry. */
311
- function toCachedRouteEntry(entry) {
312
- const { activation, ...data } = entry;
313
- return {
314
- ...data,
315
- activationKey: activation?.key
316
- };
317
- }
318
- function toAcquiredRouteEntry(entry, activation) {
319
- if ("activationKey" in entry) {
320
- const { activationKey: _activationKey, ...data } = entry;
321
- return {
322
- ...data,
323
- activation
324
- };
325
- }
326
- return {
327
- ...entry,
328
- activation
329
- };
330
- }
331
- //#endregion
332
- //#region src/client/endpoints.ts
333
- const CLIENT_BUILD_ID_HEADER = HTTP_PROTOCOL.requestHeaders.clientBuildId;
334
- const ACCEPTS_REDIRECT_HEADER = HTTP_PROTOCOL.requestHeaders.clientAcceptsRedirect;
335
- const DEPLOYMENT_ID_HEADER = HTTP_PROTOCOL.requestHeaders.vercelDeploymentId;
336
- const SERVER_BUILD_ID_HEADER = HTTP_PROTOCOL.responseHeaders.clientBuildId;
337
- const BUILD_SKEW_HEADER = HTTP_PROTOCOL.responseHeaders.buildSkew;
338
- const REDIRECT_HEADER = HTTP_PROTOCOL.responseHeaders.clientRedirect;
339
- const REDIRECT_STATUS_HEADER = HTTP_PROTOCOL.responseHeaders.clientRedirectStatus;
340
- const makeOperationId = Brand.nominal();
341
- function classifyRedirectTarget(target) {
342
- const parsed = Result.try(() => new URL(target));
343
- if (Result.isFailure(parsed)) return { kind: "invalid" };
344
- const url = parsed.success;
345
- if (url.protocol !== "http:" && url.protocol !== "https:") return { kind: "invalid" };
346
- return url.origin === window.location.origin ? {
347
- kind: "navigate",
348
- url
349
- } : {
350
- kind: "document",
351
- href: url.href
352
- };
353
- }
354
- function createEndpointOperations(core, dependencies) {
355
- /** The unfinished call per dedupe key; a later call with the key completes the earlier call's cancellation. */
356
- const unfinishedByDedupeKey = /* @__PURE__ */ new Map();
357
- /** Claims a dedupe key for this call, cancelling the earlier unfinished call that held it. */
358
- function claimDedupeKey(key) {
359
- const earlier = unfinishedByDedupeKey.get(key);
360
- if (earlier !== void 0) Deferred.doneUnsafe(earlier, Effect.void);
361
- const cancellation = Deferred.makeUnsafe();
362
- unfinishedByDedupeKey.set(key, cancellation);
363
- return cancellation;
364
- }
365
- function releaseDedupeKey(key, cancellation) {
366
- if (unfinishedByDedupeKey.get(key) === cancellation) unfinishedByDedupeKey.delete(key);
367
- }
368
- /** Runs `work` unless the call's dedupe key is claimed by a later call first. */
369
- function runUnlessCancelled(input, operationId, cancellation, work) {
370
- if (cancellation === void 0 || input.dedupeKey === void 0) return work;
371
- const dedupeKey = input.dedupeKey;
372
- return Effect.raceFirst(work, Effect.andThen(Deferred.await(cancellation), Effect.fail(new CancelledByDedupeKeyError({
373
- ...makeBoundary(input, operationId),
374
- dedupeKey
375
- }))));
376
- }
377
- function trackOperation(operation) {
378
- core.updateState((state) => {
379
- return {
380
- ...state,
381
- operations: new Map(state.operations).set(operation.operationId, operation)
382
- };
383
- });
384
- }
385
- function settleOperation(operation, outcome) {
386
- const settled = {
387
- ...operation,
388
- phase: "settled"
389
- };
390
- core.publishEvent({
391
- _tag: "OperationSettled",
392
- operation: settled,
393
- outcome
394
- });
395
- trackOperation(settled);
396
- core.updateState((state) => {
397
- const operations = new Map(state.operations);
398
- operations.delete(operation.operationId);
399
- return {
400
- ...state,
401
- operations
402
- };
403
- });
404
- }
405
- function makeBoundary(input, operationId) {
406
- return {
407
- method: input.method,
408
- pattern: input.pattern,
409
- operationId
410
- };
411
- }
412
- function prepareRequest(input, operationId, kind) {
413
- const boundary = makeBoundary(input, operationId);
414
- return Effect.gen(function* () {
415
- const isRead = input.method === "GET";
416
- let search;
417
- let formData;
418
- let json;
419
- if (input.input instanceof FormData) {
420
- if (isRead) {
421
- const params = new URLSearchParams();
422
- for (const [name, value] of input.input) {
423
- if (typeof value !== "string") return yield* new InputError({
424
- ...boundary,
425
- message: `File field ${JSON.stringify(name)} cannot be encoded in a GET query`
426
- });
427
- params.append(name, encodeScalar(value));
428
- }
429
- search = params.toString();
430
- } else formData = input.input;
431
- } else if (input.input !== void 0 && isRead) {
432
- const serialized = serializeSearchParams({
433
- value: input.input,
434
- typeEntries: CLIENT_TRANSPORT_TYPE_ENTRIES
435
- });
436
- if (Result.isFailure(serialized)) return yield* new InputError({
437
- ...boundary,
438
- message: "Endpoint input cannot be serialized as search",
439
- cause: serialized.failure
440
- });
441
- search = serialized.success;
442
- } else if (input.input !== void 0) {
443
- const transported = encodeTransportedValue({
444
- value: input.input,
445
- typeEntries: CLIENT_TRANSPORT_TYPE_ENTRIES
446
- });
447
- if (Result.isFailure(transported)) return yield* new InputError({
448
- ...boundary,
449
- message: "Endpoint input cannot be transported",
450
- cause: transported.failure
451
- });
452
- json = yield* Effect.try({
453
- try: () => JSON.stringify(transported.success),
454
- catch: (cause) => {
455
- return new InputError({
456
- ...boundary,
457
- message: "Endpoint input is not JSON serializable",
458
- cause
459
- });
460
- }
461
- });
462
- }
463
- const href = buildHref({
464
- kind: "endpoint",
465
- pattern: input.pattern,
466
- transportTypeEntries: CLIENT_TRANSPORT_TYPE_ENTRIES,
467
- ...input.params === void 0 ? {} : { params: input.params },
468
- ...input.splatValues === void 0 ? {} : { splatValues: input.splatValues },
469
- ...search === void 0 ? {} : { search }
470
- });
471
- if (Result.isFailure(href)) return yield* new InputError({
472
- ...boundary,
473
- message: href.failure.reason,
474
- cause: href.failure
475
- });
476
- const resolved = yield* Effect.try({
477
- try: () => new URL(href.success, window.location.href),
478
- catch: (cause) => {
479
- return new InputError({
480
- ...boundary,
481
- message: "Endpoint target is not a valid URL",
482
- cause
483
- });
484
- }
485
- });
486
- let headers = yield* Effect.try({
487
- try: () => {
488
- return Headers.fromInput(input.requestOptions?.headers);
489
- },
490
- catch: (cause) => {
491
- return new InputError({
492
- ...boundary,
493
- message: "Endpoint request headers are invalid",
494
- cause
495
- });
496
- }
497
- });
498
- headers = Headers.set(headers, "accept", "application/json");
499
- headers = Headers.set(headers, CLIENT_BUILD_ID_HEADER, core.getClientBuildId());
500
- headers = Headers.set(headers, ACCEPTS_REDIRECT_HEADER, "1");
501
- const deploymentId = core.getDeploymentId();
502
- if (deploymentId !== void 0) headers = Headers.set(headers, DEPLOYMENT_ID_HEADER, deploymentId);
503
- let request = HttpClientRequest.make(input.method)(resolved, { headers });
504
- if (formData !== void 0) request = HttpClientRequest.setBody(request, HttpBody.formData(formData));
505
- else if (json !== void 0) {
506
- const contentType = headers["content-type"] === void 0 ? "application/json" : headers["content-type"];
507
- request = HttpClientRequest.setBody(request, HttpBody.raw(json, { contentType }));
508
- }
509
- request = yield* core.prepareRequest({
510
- request,
511
- requestKind: kind,
512
- operationId: Option.some(operationId)
513
- }).pipe(Effect.mapError((cause) => {
514
- return new TransportError({
515
- ...boundary,
516
- message: "Endpoint request preparation failed",
517
- cause
518
- });
519
- }));
520
- return {
521
- href: resolved.href,
522
- request
523
- };
524
- });
525
- }
526
- function readErrorEnvelope(response, input, operationId) {
527
- const boundary = makeBoundary(input, operationId);
528
- return Effect.gen(function* () {
529
- const json = yield* response.json.pipe(Effect.mapError((cause) => {
530
- return new TransportError({
531
- ...boundary,
532
- message: "Endpoint error response is not valid JSON",
533
- cause,
534
- status: response.status
535
- });
536
- }));
537
- const envelope = readEndpointErrorEnvelope(json);
538
- if (Result.isFailure(envelope) || envelope.success.error.status !== response.status) return yield* new TransportError({
539
- ...boundary,
540
- message: "Endpoint error response does not satisfy the Vorma protocol",
541
- cause: Result.isFailure(envelope) ? envelope.failure : json,
542
- status: response.status
543
- });
544
- const domain = envelope.success.domain;
545
- if (domain === void 0) return yield* new HandlerError({
546
- ...boundary,
547
- status: response.status,
548
- clientMessage: envelope.success.error.client_message
549
- });
550
- const revived = decodeTransportedValue({
551
- transported: domain.data,
552
- typeEntries: CLIENT_TRANSPORT_TYPE_ENTRIES
553
- });
554
- if (Result.isFailure(revived)) return yield* new TransportError({
555
- ...boundary,
556
- message: "Endpoint error data does not satisfy the Vorma protocol",
557
- cause: revived.failure,
558
- status: response.status
559
- });
560
- return yield* Effect.fail(revived.success);
561
- });
562
- }
563
- /** A success body is the handler's output transported by value; the marks restore it. */
564
- function readSuccessBody(response, input, operationId) {
565
- const boundary = makeBoundary(input, operationId);
566
- if (!response.headers["content-type"]?.toLowerCase().includes("json")) return Effect.fail(new TransportError({
567
- ...boundary,
568
- message: "Endpoint success response is not JSON",
569
- cause: response.headers["content-type"],
570
- status: response.status
571
- }));
572
- return Effect.gen(function* () {
573
- const json = yield* response.json.pipe(Effect.mapError((cause) => {
574
- return new TransportError({
575
- ...boundary,
576
- message: "Endpoint success response is not valid JSON",
577
- cause,
578
- status: response.status
579
- });
580
- }));
581
- const transported = readTransportedValue(json);
582
- const revived = Result.flatMap(transported, (value) => {
583
- return decodeTransportedValue({
584
- transported: value,
585
- typeEntries: CLIENT_TRANSPORT_TYPE_ENTRIES
586
- });
587
- });
588
- if (Result.isFailure(revived)) return yield* new TransportError({
589
- ...boundary,
590
- message: "Endpoint success response does not satisfy the Vorma protocol",
591
- cause: revived.failure,
592
- status: response.status
593
- });
594
- return revived.success;
595
- });
596
- }
597
- function dispatchEndpointRequest(input, operationId, kind, prepared) {
598
- const boundary = makeBoundary(input, operationId);
599
- return Effect.gen(function* () {
600
- const requestOptions = input.requestOptions;
601
- const { response, followedUrl } = yield* core.provideClient(Effect.suspend(() => {
602
- const execution = executeBrowserRequest(prepared.request);
603
- return requestOptions === void 0 ? execution : execution.pipe(Effect.updateContext((context) => {
604
- return Context.add(context, FetchHttpClient.RequestInit, {
605
- ...Context.getOrUndefined(context, FetchHttpClient.RequestInit),
606
- ...requestOptions
607
- });
608
- }));
609
- })).pipe(Effect.mapError((cause) => {
610
- return new TransportError({
611
- ...boundary,
612
- message: "Endpoint request failed",
613
- cause
614
- });
615
- }));
616
- const followed = findFollowedRedirect(followedUrl, new URL(prepared.href));
617
- if (followed !== null) return {
618
- response,
619
- followed
620
- };
621
- const serverBuildId = response.headers[SERVER_BUILD_ID_HEADER];
622
- const mismatch = {
623
- clientBuildId: core.getClientBuildId(),
624
- serverBuildId,
625
- requestedHref: prepared.href,
626
- requestKind: kind,
627
- status: response.status
628
- };
629
- if (response.headers[BUILD_SKEW_HEADER] === "1") {
630
- core.reportBuildMismatch(mismatch);
631
- dependencies.retireReusableFn();
632
- return yield* new BuildMismatchError({
633
- ...boundary,
634
- message: "Endpoint request was refused by another build"
635
- });
636
- }
637
- if (serverBuildId === void 0) return yield* new TransportError({
638
- ...boundary,
639
- message: "Endpoint response is missing its Vorma build identity",
640
- cause: response.headers,
641
- status: response.status
642
- });
643
- if (serverBuildId !== core.getClientBuildId()) core.reportBuildMismatch(mismatch);
644
- return {
645
- response,
646
- followed: null
647
- };
648
- });
649
- }
650
- function classifyEndpointResponse(dispatched, input, operationId) {
651
- const { response } = dispatched;
652
- const boundary = makeBoundary(input, operationId);
653
- const responseMetadata = {
654
- status: response.status,
655
- headers: response.headers
656
- };
657
- return Effect.gen(function* () {
658
- if (dispatched.followed !== null) {
659
- const disposition = classifyRedirectTarget(dispatched.followed);
660
- if (disposition.kind === "invalid") return yield* new TransportError({
661
- ...boundary,
662
- message: "Endpoint redirect target must be an HTTP or HTTPS URL",
663
- cause: dispatched.followed,
664
- status: response.status
665
- });
666
- return {
667
- _tag: "Redirected",
668
- redirectUrl: dispatched.followed,
669
- redirectStatus: void 0,
670
- responseMetadata,
671
- disposition
672
- };
673
- }
674
- const redirect = response.headers[REDIRECT_HEADER];
675
- if (redirect !== void 0) {
676
- const redirectStatus = readRedirectStatus(Number(response.headers[REDIRECT_STATUS_HEADER]));
677
- if (Result.isFailure(redirectStatus)) return yield* new TransportError({
678
- ...boundary,
679
- message: "Endpoint redirect is missing a valid intended HTTP status",
680
- cause: redirectStatus.failure,
681
- status: response.status
682
- });
683
- const target = Result.try(() => new URL(redirect, response.request.url).href);
684
- const disposition = Result.isFailure(target) ? { kind: "invalid" } : classifyRedirectTarget(target.success);
685
- if (disposition.kind === "invalid") return yield* new TransportError({
686
- ...boundary,
687
- message: "Endpoint redirect target must be an HTTP or HTTPS URL",
688
- cause: redirect,
689
- status: response.status
690
- });
691
- return {
692
- _tag: "Redirected",
693
- redirectUrl: disposition.kind === "document" ? disposition.href : disposition.url.href,
694
- redirectStatus: redirectStatus.success,
695
- responseMetadata,
696
- disposition
697
- };
698
- }
699
- if (response.status < 200 || response.status >= 300) return yield* readErrorEnvelope(response, input, operationId);
700
- return {
701
- _tag: "Completed",
702
- data: yield* readSuccessBody(response, input, operationId),
703
- responseMetadata
704
- };
705
- });
706
- }
707
- /** Executes one query. Observer-owned; registers no freshness demand. */
708
- function runEndpointQuery(input, reportDispatchFn) {
709
- return Effect.gen(function* () {
710
- const operationId = makeOperationId(crypto.randomUUID());
711
- const operation = {
712
- operationId,
713
- kind: "Query",
714
- request: {
715
- method: input.method,
716
- pattern: input.pattern,
717
- href: Option.none()
718
- },
719
- phase: "preparing",
720
- skipWorkIndicator: input.skipWorkIndicator === true,
721
- skipAutoRevalidate: false
722
- };
723
- let current = operation;
724
- trackOperation(operation);
725
- const cancellation = input.dedupeKey === void 0 ? void 0 : claimDedupeKey(input.dedupeKey);
726
- return yield* Effect.gen(function* () {
727
- const prepared = yield* prepareRequest(input, operationId, "Query");
728
- current = {
729
- ...operation,
730
- phase: "dispatched",
731
- request: {
732
- ...operation.request,
733
- href: Option.some(prepared.href)
734
- }
735
- };
736
- trackOperation(current);
737
- reportDispatchFn?.({
738
- sequence: core.advanceSequence(),
739
- generation: core.getState().generation
740
- });
741
- const retirement = core.getGenerationRetirement();
742
- const classified = yield* runUnlessCancelled(input, operationId, cancellation, Effect.raceFirst(Effect.flatMap(dispatchEndpointRequest(input, operationId, "Query", prepared), (dispatched) => {
743
- return classifyEndpointResponse(dispatched, input, operationId);
744
- }), Effect.andThen(Deferred.await(retirement), Effect.interrupt)));
745
- if (classified._tag === "Redirected") {
746
- const navigation = startRedirect(classified);
747
- return yield* new QueryRedirected({
748
- ...makeBoundary(input, operationId),
749
- redirectUrl: classified.redirectUrl,
750
- redirectStatus: classified.redirectStatus,
751
- navigation
752
- });
753
- }
754
- return {
755
- data: classified.data,
756
- responseMetadata: classified.responseMetadata
757
- };
758
- }).pipe(Effect.scoped, Effect.onExit((outcome) => {
759
- return Effect.sync(() => {
760
- if (cancellation !== void 0 && input.dedupeKey !== void 0) releaseDedupeKey(input.dedupeKey, cancellation);
761
- settleOperation(current, outcome);
762
- });
763
- }));
764
- });
765
- }
766
- /** Starts the navigation a redirect requires and returns its observation. */
767
- function startRedirect(classified) {
768
- if (classified.disposition.kind === "document") {
769
- dependencies.navigateDocumentFn(classified.redirectUrl);
770
- return Effect.succeed({
771
- _tag: "DocumentNavigation",
772
- href: classified.redirectUrl
773
- });
774
- }
775
- return dependencies.redirectFn(classified.disposition.url);
776
- }
777
- /** Executes one mutation with native caller interruption and a scoped activation hold. */
778
- function runEndpointMutation(input) {
779
- return Effect.gen(function* () {
780
- const operationId = makeOperationId(crypto.randomUUID());
781
- const skipAutoRevalidate = input.skipAutoRevalidate === true;
782
- const skipWorkIndicator = input.skipWorkIndicator === true;
783
- const operation = {
784
- operationId,
785
- kind: "Mutation",
786
- request: {
787
- method: input.method,
788
- pattern: input.pattern,
789
- href: Option.none()
790
- },
791
- phase: "preparing",
792
- skipWorkIndicator,
793
- skipAutoRevalidate
794
- };
795
- trackOperation(operation);
796
- const cancellation = input.dedupeKey === void 0 ? void 0 : claimDedupeKey(input.dedupeKey);
797
- const prepared = yield* prepareRequest(input, operationId, "Mutation").pipe(Effect.onError((cause) => {
798
- return Effect.sync(() => {
799
- if (cancellation !== void 0 && input.dedupeKey !== void 0) releaseDedupeKey(input.dedupeKey, cancellation);
800
- settleOperation(operation, Exit.failCause(cause));
801
- });
802
- }));
803
- const dispatched = {
804
- ...operation,
805
- phase: "dispatched",
806
- request: {
807
- ...operation.request,
808
- href: Option.some(prepared.href)
809
- }
810
- };
811
- const activation = yield* Effect.serviceOption(CurrentActivation);
812
- let revalidation;
813
- function scheduleRefresh() {
814
- if (revalidation !== void 0) return revalidation;
815
- revalidation = skipAutoRevalidate ? Effect.succeed({ _tag: "Skipped" }) : dependencies.requireRefreshFn({ skipWorkIndicator });
816
- return revalidation;
817
- }
818
- function makeOwned(scope) {
819
- return Effect.gen(function* () {
820
- trackOperation(dispatched);
821
- const classified = yield* runUnlessCancelled(input, operationId, cancellation, Effect.flatMap(dispatchEndpointRequest(input, operationId, "Mutation", prepared), (sent) => {
822
- return classifyEndpointResponse(sent, input, operationId);
823
- }));
824
- if (classified._tag === "Redirected") {
825
- if (classified.disposition.kind === "document") {
826
- dependencies.navigateDocumentFn(classified.redirectUrl);
827
- revalidation = Effect.succeed({
828
- _tag: "DocumentNavigation",
829
- href: classified.redirectUrl
830
- });
831
- return {
832
- _tag: "Redirected",
833
- operationId,
834
- redirectUrl: classified.redirectUrl,
835
- redirectStatus: classified.redirectStatus,
836
- responseMetadata: classified.responseMetadata,
837
- revalidation
838
- };
839
- }
840
- const refresh = scheduleRefresh();
841
- revalidation = dependencies.redirectFn(classified.disposition.url).pipe(Effect.matchEffect({
842
- onFailure: (error) => {
843
- return Effect.fail(new RevalidationError({
844
- reason: "route",
845
- cause: Cause.fail(error)
846
- }));
847
- },
848
- onSuccess: (result) => {
849
- switch (result._tag) {
850
- case "DocumentNavigation": return Effect.succeed(result);
851
- case "Superseded": return Effect.fail(new RevalidationError({
852
- reason: "superseded",
853
- cause: Cause.fail(result)
854
- }));
855
- default: return refresh;
856
- }
857
- }
858
- }));
859
- return {
860
- _tag: "Redirected",
861
- operationId,
862
- redirectUrl: classified.redirectUrl,
863
- redirectStatus: classified.redirectStatus,
864
- responseMetadata: classified.responseMetadata,
865
- revalidation
866
- };
867
- }
868
- return {
869
- _tag: "Completed",
870
- operationId,
871
- data: classified.data,
872
- responseMetadata: classified.responseMetadata,
873
- revalidation: scheduleRefresh()
874
- };
875
- }).pipe(Scope.provide(scope), Effect.onExit((outcome) => {
876
- return Effect.andThen(Scope.close(scope, outcome), Effect.sync(() => {
877
- const refused = Exit.isFailure(outcome) && Option.exists(Cause.findErrorOption(outcome.cause), (error) => error instanceof BuildMismatchError);
878
- if (!Exit.isSuccess(outcome) && !refused) scheduleRefresh();
879
- if (cancellation !== void 0 && input.dedupeKey !== void 0) releaseDedupeKey(input.dedupeKey, cancellation);
880
- settleOperation(dispatched, outcome);
881
- }));
882
- }));
883
- }
884
- return yield* Effect.uninterruptibleMask((restore) => {
885
- return Effect.gen(function* () {
886
- const scope = yield* Scope.make();
887
- if (Option.isSome(activation)) {
888
- const held = yield* Effect.exit(Scope.provide(scope)(RcMap.get(core.activations, activation.value)));
889
- if (Exit.isFailure(held)) {
890
- yield* Scope.close(scope, held);
891
- if (cancellation !== void 0 && input.dedupeKey !== void 0) releaseDedupeKey(input.dedupeKey, cancellation);
892
- settleOperation(operation, held);
893
- return yield* Effect.failCause(held.cause);
894
- }
895
- }
896
- const fiber = yield* FiberSet.run(core.mutationFibers, restore(makeOwned(scope)));
897
- return yield* restore(Fiber.join(fiber)).pipe(Effect.ensuring(Fiber.interrupt(fiber)));
898
- });
899
- });
900
- });
901
- }
902
- return {
903
- query: (input) => runEndpointQuery(input, void 0),
904
- read: runEndpointQuery,
905
- mutate: runEndpointMutation
906
- };
907
- }
908
- //#endregion
909
- //#region src/client/modules.ts
910
- /** Writes a `modulepreload` link for each module path that has none yet. */
911
- function preloadModules(paths) {
912
- if (paths.length === 0) return;
913
- const existing = new Set([...document.head.querySelectorAll("link[rel=\"modulepreload\"]")].map((link) => link.getAttribute("href")));
914
- for (const path of paths) {
915
- if (existing.has(path)) continue;
916
- const link = document.createElement("link");
917
- link.rel = "modulepreload";
918
- link.href = path;
919
- document.head.appendChild(link);
920
- existing.add(path);
921
- }
922
- }
923
- //#endregion
924
- //#region src/client/payload.ts
925
- function readRoute$1(input) {
926
- const { record } = input;
927
- function failField(field, cause) {
928
- return Result.fail({
929
- _tag: "PayloadReadError",
930
- pattern: record.pattern,
931
- field,
932
- cause
933
- });
934
- }
935
- let searchInput;
936
- if (record.search_input !== void 0) {
937
- const revived = decodeTransportedValue({
938
- transported: record.search_input,
939
- typeEntries: CLIENT_TRANSPORT_TYPE_ENTRIES
940
- });
941
- if (Result.isFailure(revived)) return failField("search_input", revived.failure);
942
- searchInput = revived.success;
943
- }
944
- let loaderData;
945
- if ("loader_data" in record) {
946
- const transported = record.loader_data;
947
- if (transported === null || transported === void 0) loaderData = null;
948
- else {
949
- const revived = decodeTransportedValue({
950
- transported,
951
- typeEntries: CLIENT_TRANSPORT_TYPE_ENTRIES
952
- });
953
- if (Result.isFailure(revived)) return failField("loader_data", revived.failure);
954
- loaderData = revived.success;
955
- }
956
- }
957
- return Result.succeed({
958
- pattern: record.pattern,
959
- params: record.params ?? {},
960
- splatValues: record.splat_values ?? [],
961
- compiledPattern: {
962
- kind: "nested",
963
- pattern: record.compiled_pattern.pattern ?? record.pattern,
964
- segments: record.compiled_pattern.segments
965
- },
966
- searchContract: record.search_contract ?? null,
967
- searchInput,
968
- moduleReference: typeof record.import_url === "string" ? {
969
- url: record.import_url,
970
- exportNames: record.export_names
971
- } : null,
972
- loaderData
973
- });
974
- }
975
- function readRoutes(wire) {
976
- const serverError = wire.server_error;
977
- const routes = [];
978
- for (const record of wire.routes) {
979
- const route = readRoute$1({ record });
980
- if (Result.isFailure(route)) return Result.fail(route.failure);
981
- routes.push(route.success);
982
- }
983
- return Result.succeed({
984
- routes,
985
- serverError: serverError === void 0 ? void 0 : {
986
- index: serverError.index,
987
- status: serverError.status,
988
- clientMessage: serverError.client_message
989
- }
990
- });
991
- }
992
- function readNavigation(wire) {
993
- return Result.map(readRoutes(wire), (routePayload) => {
994
- return {
995
- ...routePayload,
996
- head: {
997
- title: wire.head.title,
998
- meta: wire.head.meta ?? [],
999
- rest: wire.head.rest ?? []
1000
- },
1001
- deps: wire.deps ?? [],
1002
- cssBundles: wire.css_bundles ?? [],
1003
- staticAssets: wire.static_assets ?? []
1004
- };
1005
- });
1006
- }
1007
- /** Reads a navigation payload this build's server wrote. */
1008
- function readPayload(raw) {
1009
- return readNavigation(raw);
1010
- }
1011
- /** Reads the initial document payload, including build identity. */
1012
- function readBootstrapPayload(raw) {
1013
- const wire = raw;
1014
- return Result.map(readRoutes(wire), (routePayload) => {
1015
- return {
1016
- clientBuildId: wire.client_build_id,
1017
- developmentMode: wire.is_dev === true,
1018
- deploymentId: wire.deployment_id,
1019
- ...routePayload
1020
- };
1021
- });
1022
- }
1023
- //#endregion
1024
- //#region src/client/static-assets.ts
1025
- const preloads = /* @__PURE__ */ new WeakMap();
1026
- const ATTRIBUTE = DOCUMENT_PROTOCOL.attributes.staticAsset;
1027
- /** Adds shared, scoped resource hints without awaiting resource loading. */
1028
- function preloadStaticAssets(urls) {
1029
- return Effect.gen(function* () {
1030
- for (const url of new Set(urls)) yield* Effect.acquireRelease(Effect.sync(() => {
1031
- const existing = [...document.head.querySelectorAll("link")].find((link) => link.getAttribute(ATTRIBUTE) === url);
1032
- let preload = existing === void 0 ? void 0 : preloads.get(existing);
1033
- if (existing !== void 0 && preload === void 0) return;
1034
- if (preload === void 0) {
1035
- const destination = getStaticAssetDestination(url);
1036
- const link = document.createElement("link");
1037
- link.rel = "preload";
1038
- link.as = destination;
1039
- if (destination !== "image") link.crossOrigin = "anonymous";
1040
- link.href = url;
1041
- link.setAttribute(ATTRIBUTE, url);
1042
- preload = {
1043
- link,
1044
- owners: 0
1045
- };
1046
- preloads.set(link, preload);
1047
- document.head.append(link);
1048
- }
1049
- preload.owners += 1;
1050
- return preload;
1051
- }), (retained) => {
1052
- return Effect.sync(() => {
1053
- if (retained === void 0) return;
1054
- retained.owners -= 1;
1055
- if (retained.owners === 0) retained.link.remove();
1056
- });
1057
- });
1058
- });
1059
- }
1060
- //#endregion
1061
- //#region src/client/prepare.ts
1062
- function failAt$1(stage, cause) {
1063
- return new NavigationError({
1064
- stage,
1065
- cause: Cause.isCause(cause) ? cause : Cause.fail(cause)
1066
- });
1067
- }
1068
- function readRoute(core, source) {
1069
- return Effect.gen(function* () {
1070
- const requestUrl = new URL(source.url);
1071
- requestUrl.hash = "";
1072
- const clientBuildId = core.getClientBuildId();
1073
- requestUrl.searchParams.set(HTTP_PROTOCOL.queryKeys.viewPayload, clientBuildId);
1074
- const deploymentId = core.getDeploymentId();
1075
- if (source.requestKind === "Revalidation" && deploymentId !== void 0) requestUrl.searchParams.set(HTTP_PROTOCOL.queryKeys.deployment, deploymentId);
1076
- const request = HttpClientRequest.get(requestUrl.href).pipe(HttpClientRequest.setHeaders({
1077
- accept: "application/json",
1078
- [HTTP_PROTOCOL.requestHeaders.clientAcceptsRedirect]: "1"
1079
- }));
1080
- const { response, followedUrl } = yield* Effect.flatMap(core.prepareRequest({
1081
- request,
1082
- requestKind: source.requestKind,
1083
- operationId: Option.none()
1084
- }), (preparedRequest) => {
1085
- return core.provideClient(executeBrowserRequest(preparedRequest));
1086
- }).pipe(Effect.mapError((cause) => failAt$1("request", cause)));
1087
- const followed = findFollowedRedirect(followedUrl, requestUrl);
1088
- if (followed !== null) return {
1089
- _tag: "Redirect",
1090
- href: followed
1091
- };
1092
- const serverBuildId = response.headers[HTTP_PROTOCOL.responseHeaders.clientBuildId];
1093
- if (response.headers[HTTP_PROTOCOL.responseHeaders.buildSkew] !== void 0) return {
1094
- _tag: "BuildSkew",
1095
- mismatch: {
1096
- clientBuildId,
1097
- serverBuildId,
1098
- requestedHref: source.url.href,
1099
- requestKind: source.requestKind,
1100
- status: response.status
1101
- }
1102
- };
1103
- const redirect = response.headers[HTTP_PROTOCOL.responseHeaders.clientRedirect];
1104
- if (redirect !== void 0) return {
1105
- _tag: "Redirect",
1106
- href: redirect
1107
- };
1108
- const contentType = response.headers["content-type"];
1109
- if (contentType === void 0 || !contentType.startsWith("application/json")) return yield* failAt$1("request", {
1110
- status: response.status,
1111
- contentType
1112
- });
1113
- return {
1114
- _tag: "Payload",
1115
- raw: yield* Effect.mapError(response.json, (cause) => {
1116
- return failAt$1("request", {
1117
- status: response.status,
1118
- cause
1119
- });
1120
- }),
1121
- status: response.status,
1122
- serverBuildId
1123
- };
1124
- });
1125
- }
1126
- /** Prepares one destination. Known client loaders start before the read settles when `prestart` is set. */
1127
- function prepareRoute(core, input) {
1128
- const url = input.source.url;
1129
- let announced = false;
1130
- function announceReadiness(exit) {
1131
- if (input.reportReadinessFn !== void 0 && !announced) {
1132
- announced = true;
1133
- input.reportReadinessFn(exit);
1134
- }
1135
- }
1136
- const program = Effect.gen(function* () {
1137
- const sequence = core.advanceSequence();
1138
- const generation = core.getState().generation;
1139
- const confirmation = Deferred.makeUnsafe();
1140
- const positionsByPattern = /* @__PURE__ */ new Map();
1141
- let failureBoundary = Infinity;
1142
- const scope = yield* Effect.scope;
1143
- const committed = core.getState().frame;
1144
- function getRunningLoaders() {
1145
- const running = [];
1146
- for (const position of positionsByPattern.values()) if (position.loader !== void 0 && position.confirmation !== "rejected") running.push({
1147
- index: position.index,
1148
- pattern: position.match.pattern,
1149
- fiber: position.loader
1150
- });
1151
- return running;
1152
- }
1153
- function canRunLoader(position) {
1154
- return position.confirmation !== "rejected" && position.index <= failureBoundary;
1155
- }
1156
- /** One position acquires its services once and retains all of its preparation work. */
1157
- function startPreparationPosition(inputPosition) {
1158
- return Effect.gen(function* () {
1159
- const { index, match, known, view } = inputPosition;
1160
- const work = {
1161
- index,
1162
- match,
1163
- confirmation: inputPosition.confirmation,
1164
- loader: void 0
1165
- };
1166
- const preparation = yield* Effect.forkIn(Effect.gen(function* () {
1167
- let activation;
1168
- if (view !== void 0 && declaresServices(view)) {
1169
- const retained = committed?.entries[index];
1170
- const key = retained?.pattern === match.pattern && retained.activation !== void 0 ? retained.activation.key : {
1171
- pattern: match.pattern,
1172
- view
1173
- };
1174
- activation = yield* RcMap.get(core.activations, key).pipe(Effect.provideService(Scope.Scope, scope), Effect.mapError((cause) => failAt$1("assets", cause)));
1175
- }
1176
- if (!canRunLoader(work) || view?.clientLoader === void 0) return activation;
1177
- const started = yield* startClientLoader(core.provideClient, {
1178
- clientLoader: view.clientLoader,
1179
- trigger: input.trigger,
1180
- href: url.href,
1181
- historyState: input.historyState,
1182
- match,
1183
- known,
1184
- confirmation,
1185
- activation
1186
- }).pipe(Effect.provideService(Scope.Scope, scope));
1187
- work.loader = started.fiber;
1188
- if (!canRunLoader(work)) {
1189
- yield* Fiber.interrupt(started.fiber);
1190
- work.loader = void 0;
1191
- return activation;
1192
- }
1193
- yield* Effect.forkIn(Effect.flatMap(Fiber.await(started.fiber), (exit) => {
1194
- if (Exit.isSuccess(exit) || Cause.hasInterruptsOnly(exit.cause) || work.confirmation === "rejected") return Effect.void;
1195
- failureBoundary = Math.min(failureBoundary, index);
1196
- return Fiber.interruptAll(getRunningLoaders().filter((other) => {
1197
- return other.index > failureBoundary;
1198
- }).map((other) => other.fiber));
1199
- }), scope);
1200
- return activation;
1201
- }), scope);
1202
- const position = Object.assign(work, { preparation });
1203
- positionsByPattern.set(match.pattern, position);
1204
- if (!canRunLoader(position)) position.loader?.interruptUnsafe();
1205
- return position;
1206
- });
1207
- }
1208
- let payload;
1209
- if (input.source.kind === "payload") payload = input.source.payload;
1210
- else {
1211
- const reading = yield* Effect.forkScoped(Effect.scoped(readRoute(core, input.source)));
1212
- if (input.prestart) {
1213
- const eligible = core.clientLoaders.findEligibleMatches(url);
1214
- const known = eligible.map((entry) => entry.match);
1215
- for (const [index, { match, view }] of eligible.entries()) if (view?.clientLoader !== void 0) yield* startPreparationPosition({
1216
- index,
1217
- match,
1218
- known,
1219
- view,
1220
- confirmation: "pending"
1221
- });
1222
- }
1223
- const response = yield* Fiber.join(reading);
1224
- if (response._tag !== "Payload") return response;
1225
- if (response.serverBuildId !== void 0 && response.serverBuildId !== core.getClientBuildId()) core.reportBuildMismatch({
1226
- clientBuildId: core.getClientBuildId(),
1227
- serverBuildId: response.serverBuildId,
1228
- requestedHref: url.href,
1229
- requestKind: input.source.requestKind,
1230
- status: response.status
1231
- });
1232
- const read = readPayload(response.raw);
1233
- if (Result.isFailure(read)) return yield* failAt$1("request", read.failure);
1234
- payload = read.success;
1235
- }
1236
- const stylesheetReadiness = yield* Effect.forkIn(prepareStylesheets(payload.cssBundles).pipe(Effect.provideService(Scope.Scope, scope)), scope);
1237
- yield* preloadStaticAssets(payload.staticAssets).pipe(Effect.provideService(Scope.Scope, scope));
1238
- if (!core.runsInDevelopmentMode()) preloadModules(payload.deps);
1239
- const matches = payload.routes.map(toKnownMatch);
1240
- for (const [pattern, position] of positionsByPattern) if (matches[position.index]?.pattern === pattern) position.confirmation = "confirmed";
1241
- else {
1242
- position.confirmation = "rejected";
1243
- position.preparation.interruptUnsafe();
1244
- position.loader?.interruptUnsafe();
1245
- positionsByPattern.delete(pattern);
1246
- }
1247
- Deferred.doneUnsafe(confirmation, Effect.succeed(payload.routes));
1248
- failureBoundary = Infinity;
1249
- for (const active of getRunningLoaders()) {
1250
- const exit = active.fiber.pollUnsafe();
1251
- if (exit !== void 0 && Exit.isFailure(exit) && !Cause.hasInterruptsOnly(exit.cause)) failureBoundary = Math.min(failureBoundary, active.index);
1252
- }
1253
- const positions = yield* Effect.forEach(payload.routes, (decoded, index) => {
1254
- return Effect.gen(function* () {
1255
- const route = yield* materializeRoute(decoded).pipe(Effect.mapError((cause) => failAt$1("assets", cause)));
1256
- const position = positionsByPattern.get(route.pattern) ?? (yield* startPreparationPosition({
1257
- index,
1258
- match: toKnownMatch(route),
1259
- known: matches,
1260
- view: route.definition,
1261
- confirmation: "confirmed"
1262
- }));
1263
- return {
1264
- route,
1265
- activation: yield* Fiber.join(position.preparation)
1266
- };
1267
- });
1268
- }, { concurrency: "unbounded" });
1269
- const routes = positions.map((position) => position.route);
1270
- core.clientLoaders.recordConfirmedChain({
1271
- url,
1272
- routes
1273
- });
1274
- announceReadiness(Exit.void);
1275
- const [clientLoaderOutcome] = yield* Effect.all([awaitClientLoaders(core.publishEvent, getRunningLoaders()), Fiber.join(stylesheetReadiness)], { concurrency: "unbounded" });
1276
- const entries = positions.map(({ route, activation }, index) => {
1277
- const settled = clientLoaderOutcome.results.get(index);
1278
- return {
1279
- pattern: route.pattern,
1280
- params: route.params,
1281
- splatValues: route.splatValues,
1282
- input: route.searchInput,
1283
- view: route.definition,
1284
- loaderData: route.loaderData,
1285
- clientLoaderData: settled === void 0 ? AsyncResult.initial() : settled,
1286
- activation
1287
- };
1288
- });
1289
- return {
1290
- _tag: "Prepared",
1291
- prepared: {
1292
- url,
1293
- entries,
1294
- error: selectRouteError(payload.serverError, clientLoaderOutcome.failure),
1295
- head: payload.head,
1296
- cssBundles: payload.cssBundles,
1297
- sequence,
1298
- generation
1299
- }
1300
- };
1301
- });
1302
- return input.reportReadinessFn === void 0 ? program : Effect.onExit(program, (exit) => {
1303
- return Effect.sync(() => {
1304
- if (Exit.isFailure(exit)) announceReadiness(Exit.failCause(exit.cause));
1305
- else if (exit.value._tag !== "Prepared") announceReadiness(Exit.fail(failAt$1(exit.value._tag === "Redirect" ? "target" : "request", exit.value)));
1306
- });
1307
- });
1308
- }
1309
- /** The route error a prepared route carries: the earliest client loader failure, else the failed server loader. */
1310
- function selectRouteError(serverError, clientLoaderFailure) {
1311
- if (clientLoaderFailure !== null) return {
1312
- index: clientLoaderFailure.index,
1313
- pattern: clientLoaderFailure.pattern,
1314
- source: "clientLoader",
1315
- error: clientLoaderFailure.cause
1316
- };
1317
- if (serverError === void 0) return null;
1318
- return {
1319
- index: serverError.index,
1320
- pattern: void 0,
1321
- source: "serverLoader",
1322
- error: {
1323
- status: serverError.status,
1324
- clientMessage: serverError.clientMessage
1325
- }
1326
- };
1327
- }
1328
- //#endregion
1329
- //#region src/client/prefetch.ts
1330
- /** The part of an address a preparation serves: its path and query. */
1331
- function readDestination(url) {
1332
- return url.pathname + url.search;
1333
- }
1334
- function createPrefetch(core) {
1335
- const entries = /* @__PURE__ */ new Map();
1336
- function clearPending(entry) {
1337
- if (entries.get(entry.destination) === entry) core.updateState((state) => {
1338
- const prefetch = new Map(state.prefetch);
1339
- prefetch.delete(entry.destination);
1340
- return {
1341
- ...state,
1342
- prefetch
1343
- };
1344
- });
1345
- }
1346
- function stopPreparation(entry) {
1347
- entry.fiber?.interruptUnsafe();
1348
- core.forkFiber(Scope.close(entry.scope, Exit.void));
1349
- }
1350
- function retireEntry(entry) {
1351
- Deferred.doneUnsafe(entry.observation, Effect.fail(new PrefetchError({ reason: "superseded" })));
1352
- clearPending(entry);
1353
- if (entries.get(entry.destination) === entry) entries.delete(entry.destination);
1354
- stopPreparation(entry);
1355
- }
1356
- function settlePreparation(entry, exit) {
1357
- if (Exit.isSuccess(exit)) {
1358
- Deferred.doneUnsafe(entry.result, Effect.succeed(exit.value));
1359
- if (exit.value._tag === "Prepared") return;
1360
- } else Deferred.doneUnsafe(entry.result, Effect.failCause(exit.cause));
1361
- const cause = Exit.isFailure(exit) ? exit.cause : Cause.fail(new NavigationError({
1362
- stage: "target",
1363
- cause: Cause.fail(exit.value)
1364
- }));
1365
- Deferred.doneUnsafe(entry.observation, Effect.fail(new PrefetchError({
1366
- reason: Cause.hasInterruptsOnly(cause) ? "superseded" : "preparation",
1367
- cause
1368
- })));
1369
- clearPending(entry);
1370
- if (entries.get(entry.destination) === entry) entries.delete(entry.destination);
1371
- core.forkFiber(Scope.close(entry.scope, Exit.void));
1372
- }
1373
- function openPreparation(url, handle, skipWorkIndicator) {
1374
- const entry = {
1375
- destination: readDestination(url),
1376
- handles: /* @__PURE__ */ new Set([handle]),
1377
- startSequence: core.getState().sequence,
1378
- observation: Deferred.makeUnsafe(),
1379
- result: Deferred.makeUnsafe(),
1380
- scope: Scope.makeUnsafe(),
1381
- fiber: void 0,
1382
- keptAfterObservers: false,
1383
- consumed: false
1384
- };
1385
- entries.set(entry.destination, entry);
1386
- core.updateState((state) => {
1387
- return {
1388
- ...state,
1389
- prefetch: new Map(state.prefetch).set(entry.destination, {
1390
- href: url.href,
1391
- skipWorkIndicator
1392
- })
1393
- };
1394
- });
1395
- entry.fiber = core.forkFiber(Effect.onExit(Effect.provideService(prepareRoute(core, {
1396
- source: {
1397
- kind: "request",
1398
- url,
1399
- requestKind: "Prefetch"
1400
- },
1401
- trigger: "prefetch",
1402
- historyState: void 0,
1403
- prestart: true,
1404
- reportReadinessFn: (exit) => {
1405
- if (Exit.isSuccess(exit)) {
1406
- clearPending(entry);
1407
- Deferred.doneUnsafe(entry.observation, Effect.void);
1408
- }
1409
- }
1410
- }), Scope.Scope, entry.scope), (exit) => Effect.sync(() => settlePreparation(entry, exit))));
1411
- return entry;
1412
- }
1413
- function acquirePreparationHandle(url, skipWorkIndicator) {
1414
- return Effect.suspend(() => {
1415
- const handle = {};
1416
- const destination = readDestination(url);
1417
- const state = core.getState();
1418
- const committed = state.frame === null ? null : readDestination(new URL(state.frame.href));
1419
- const pending = Option.map(state.navigation.pending === null ? Option.none() : Option.some(state.navigation.pending), (item) => {
1420
- return readDestination(new URL(item.href));
1421
- });
1422
- if (destination === committed || Option.contains(pending, destination)) return Effect.fail(new PrefetchError({ reason: "unavailable" }));
1423
- const existing = entries.get(destination);
1424
- if (existing !== void 0) {
1425
- existing.handles.add(handle);
1426
- return Effect.succeed({
1427
- entry: existing,
1428
- handle
1429
- });
1430
- }
1431
- for (const entry of entries.values()) if (entry.handles.size === 0) retireEntry(entry);
1432
- return Effect.succeed({
1433
- entry: openPreparation(url, handle, skipWorkIndicator),
1434
- handle
1435
- });
1436
- });
1437
- }
1438
- function releasePreparationHandle(entry, handle) {
1439
- if (!entry.handles.delete(handle)) return;
1440
- if (entries.get(entry.destination) !== entry) return;
1441
- if (entry.handles.size === 0 && entry.keptAfterObservers && !entry.consumed) {
1442
- for (const other of entries.values()) if (other !== entry && other.handles.size === 0) retireEntry(other);
1443
- }
1444
- if (entry.handles.size === 0 && !entry.keptAfterObservers && !entry.consumed) retireEntry(entry);
1445
- }
1446
- return {
1447
- awaitReadiness: (url, options) => {
1448
- return Effect.acquireUseRelease(acquirePreparationHandle(url, options.skipWorkIndicator), ({ entry }) => Deferred.await(entry.observation), ({ entry, handle }, exit) => {
1449
- return Effect.sync(() => {
1450
- if (Exit.isSuccess(exit)) entry.keptAfterObservers = true;
1451
- releasePreparationHandle(entry, handle);
1452
- });
1453
- });
1454
- },
1455
- holdUntilInterrupted: (url, options) => {
1456
- return Effect.acquireUseRelease(acquirePreparationHandle(url, options.skipWorkIndicator), () => Effect.never, ({ entry, handle }) => Effect.sync(() => releasePreparationHandle(entry, handle)));
1457
- },
1458
- takeForNavigation: (destination) => {
1459
- const entry = entries.get(destination);
1460
- if (entry === void 0) return null;
1461
- clearPending(entry);
1462
- entries.delete(destination);
1463
- entry.consumed = true;
1464
- entry.keptAfterObservers = false;
1465
- Deferred.doneUnsafe(entry.observation, Effect.fail(new PrefetchError({ reason: "takenByNavigation" })));
1466
- return {
1467
- result: Deferred.await(entry.result),
1468
- startSequence: entry.startSequence,
1469
- release: () => stopPreparation(entry)
1470
- };
1471
- },
1472
- retirePreparations: () => {
1473
- for (const entry of entries.values()) retireEntry(entry);
1474
- }
1475
- };
1476
- }
1477
- //#endregion
1478
- //#region src/client/scroll.ts
1479
- function readScrollPosition() {
1480
- return {
1481
- x: window.scrollX,
1482
- y: window.scrollY
1483
- };
1484
- }
1485
- function normalizeFragment(hash) {
1486
- const raw = hash.startsWith("#") ? hash.slice(1) : hash;
1487
- if (raw.length === 0) return raw;
1488
- return Result.getOrElse(Result.try(() => decodeURIComponent(raw)), () => raw);
1489
- }
1490
- /** Applies the target through the browser's scroll calls, the one platform boundary here; a failure comes back as a value. */
1491
- function applyScroll(target) {
1492
- return Result.try(() => {
1493
- if ("hash" in target) {
1494
- document.getElementById(normalizeFragment(target.hash))?.scrollIntoView();
1495
- return;
1496
- }
1497
- window.scrollTo(target.x, target.y);
1498
- });
1499
- }
1500
- //#endregion
1501
- //#region src/client/navigation.ts
1502
- /** The redirect chain length fetch itself accepts. */
1503
- const REDIRECT_LIMIT = 20;
1504
- /** The largest delay browsers accept without reducing it to a short timeout. */
1505
- const MAX_TIMER_DELAY_MILLIS = 2147483647;
1506
- function failAt(stage, cause) {
1507
- return new NavigationError({
1508
- stage,
1509
- cause: Cause.isCause(cause) ? cause : Cause.fail(cause)
1510
- });
1511
- }
1512
- /** A failed refresh read as a revalidation failure: the server's refusal is a build mismatch, anything else a route failure. */
1513
- function classifyRefreshFailure(error) {
1514
- const inner = Cause.findErrorOption(error.cause);
1515
- const mismatch = Option.isSome(inner) && inner.value instanceof BuildMismatchError;
1516
- return makeRevalidationError(mismatch ? "build-mismatch" : "route", error.cause);
1517
- }
1518
- /** The navigation options that carry a history entry's user state. */
1519
- function makeHistoryStateOptions(historyState) {
1520
- return historyState === void 0 ? {} : { historyState };
1521
- }
1522
- function matchesNavigationOptions(left, right) {
1523
- const { historyState: leftState, ...leftOptions } = left;
1524
- const { historyState: rightState, ...rightOptions } = right;
1525
- return Object.is(leftState, rightState) && deepEquals({
1526
- left: leftOptions,
1527
- right: rightOptions
1528
- });
1529
- }
1530
- function leaveDocument(href) {
1531
- window.location.assign(href);
1532
- return {
1533
- _tag: "DocumentNavigation",
1534
- href
1535
- };
1536
- }
1537
- /** The deepest entry a scroll can wait for: the last entry, or the error position that replaces the rest. */
1538
- function selectScrollEntryIndex(entryCount, errorIndex) {
1539
- if (entryCount === 0 && errorIndex !== null) return 0;
1540
- return errorIndex === null ? entryCount - 1 : Math.min(entryCount - 1, errorIndex);
1541
- }
1542
- /** Navigation follows an enabled fragment first, then a saved position, then the scroll-to-top setting. */
1543
- function decideScrollIntent(input) {
1544
- const entryIndex = selectScrollEntryIndex(input.entryCount, input.errorIndex);
1545
- if (normalizeFragment(input.url.hash).length > 0 && input.options.scrollToFragment !== false) return {
1546
- target: { hash: input.url.hash },
1547
- entryIndex
1548
- };
1549
- if (input.restore !== void 0) return {
1550
- target: input.restore,
1551
- entryIndex
1552
- };
1553
- if (input.options.scrollToTop === false) return;
1554
- return {
1555
- target: {
1556
- x: 0,
1557
- y: 0
1558
- },
1559
- entryIndex
1560
- };
1561
- }
1562
- /** The first frame adopts the browser's entry, or gives an entry without a key one. */
1563
- function selectFirstFramePosition(current) {
1564
- return current.key === void 0 ? {
1565
- kind: "write",
1566
- href: current.href,
1567
- replace: true,
1568
- userState: current.userState,
1569
- key: createEntryKey()
1570
- } : {
1571
- kind: "adopt",
1572
- position: current
1573
- };
1574
- }
1575
- /** Restores a saved position or applies the initial fragment after the first frame renders. */
1576
- function selectFirstFrameScrollIntent(restore, prepared) {
1577
- const target = restore ?? (normalizeFragment(prepared.url.hash).length > 0 ? { hash: prepared.url.hash } : void 0);
1578
- return target === void 0 ? void 0 : {
1579
- target,
1580
- entryIndex: selectScrollEntryIndex(prepared.entries.length, prepared.error === null ? null : prepared.error.index)
1581
- };
1582
- }
1583
- function createNavigation(core, prefetch, options) {
1584
- let active = null;
1585
- const snapshots = /* @__PURE__ */ new Map();
1586
- let snapshotExpirationTimer;
1587
- /** Settles when boot commits its frame or fails; a navigation requested before then waits on it. */
1588
- const firstFrame = Deferred.makeUnsafe();
1589
- /** Set by disposal; a review scheduled before it must not touch the registry afterwards. */
1590
- let retired = false;
1591
- /** Each query's current computation; a release that names another computation is a replaced one's. */
1592
- const queryComputations = /* @__PURE__ */ new Map();
1593
- /** Expectations collected while a demand registers; the record holds the demand before they apply. */
1594
- let collecting = null;
1595
- function closeScope(scope) {
1596
- core.forkFiber(Scope.close(scope, Exit.void));
1597
- }
1598
- function clearSnapshotExpirationTimer() {
1599
- if (snapshotExpirationTimer !== void 0) {
1600
- window.clearTimeout(snapshotExpirationTimer);
1601
- snapshotExpirationTimer = void 0;
1602
- }
1603
- }
1604
- function deleteExpiredSnapshots(now) {
1605
- for (const [destination, snapshot] of snapshots) if (snapshot.expiresAt <= now) snapshots.delete(destination);
1606
- }
1607
- /** Runs one timer for the earliest expiry; longer browser delays are resumed in bounded steps. */
1608
- function scheduleSnapshotExpiration() {
1609
- clearSnapshotExpirationTimer();
1610
- let earliest = Infinity;
1611
- for (const snapshot of snapshots.values()) earliest = Math.min(earliest, snapshot.expiresAt);
1612
- if (!Number.isFinite(earliest)) return;
1613
- const delay = Math.min(Math.max(0, earliest - Date.now()), MAX_TIMER_DELAY_MILLIS);
1614
- snapshotExpirationTimer = window.setTimeout(() => {
1615
- snapshotExpirationTimer = void 0;
1616
- deleteExpiredSnapshots(Date.now());
1617
- scheduleSnapshotExpiration();
1618
- }, delay);
1619
- }
1620
- function retireSnapshots() {
1621
- snapshots.clear();
1622
- clearSnapshotExpirationTimer();
1623
- }
1624
- function retireReusable() {
1625
- prefetch.retirePreparations();
1626
- retireSnapshots();
1627
- }
1628
- /** The reusable preparation for `url`: a taken prefetch or a snapshot within its reuse window, whichever read is newer. */
1629
- function takeReusable(url) {
1630
- const frame = core.getState().frame;
1631
- if (frame !== null && frame.error !== null) snapshots.delete(readDestination(new URL(frame.href)));
1632
- deleteExpiredSnapshots(Date.now());
1633
- const destination = readDestination(url);
1634
- const snapshot = snapshots.get(destination);
1635
- const consumed = prefetch.takeForNavigation(destination);
1636
- if (consumed !== null) {
1637
- if (snapshot !== void 0 && snapshot.prepared.sequence > consumed.startSequence) {
1638
- consumed.release();
1639
- return {
1640
- kind: "snapshot",
1641
- ...snapshot
1642
- };
1643
- }
1644
- return {
1645
- kind: "prefetch",
1646
- preparation: consumed
1647
- };
1648
- }
1649
- return snapshot === void 0 ? null : {
1650
- kind: "snapshot",
1651
- ...snapshot
1652
- };
1653
- }
1654
- function applyFreshness(transition) {
1655
- core.updateState((state) => {
1656
- return {
1657
- ...state,
1658
- freshness: transition.facts
1659
- };
1660
- });
1661
- for (const complete of transition.complete) complete();
1662
- if (transition.startAttempt) ensureAttempt();
1663
- }
1664
- /** The fiber scheduling refresh attempts and any application-configured retries. */
1665
- let retryRunner = null;
1666
- /** Manual and focus requests postpone route refresh until eight milliseconds after the last request. */
1667
- let refreshDebounce = null;
1668
- /**
1669
- * Starts one refresh attempt for the outstanding demand when one may run.
1670
- * Returns the candidate, or null when nothing started: no demand, a
1671
- * navigation that will settle the demand itself, or a failed start.
1672
- */
1673
- function startAttemptNow() {
1674
- const state = core.getState();
1675
- const facts = state.freshness;
1676
- if (state.lifecycle._tag === "Disposed" || refreshDebounce !== null || !canStartAttempt(facts)) return null;
1677
- const frame = state.frame;
1678
- if (frame === null) {
1679
- const position = readBrowserPosition();
1680
- if (Result.isFailure(position)) {
1681
- applyFreshness(failAttempt(facts, makeRevalidationError("route", position.failure)));
1682
- return null;
1683
- }
1684
- core.updateState((state) => {
1685
- return {
1686
- ...state,
1687
- freshness: startAttempt(facts)
1688
- };
1689
- });
1690
- return startCandidate({
1691
- kind: "revalidation",
1692
- source: "navigate",
1693
- recoverByReload: false,
1694
- href: position.success.href,
1695
- options: makeHistoryStateOptions(position.success.userState),
1696
- historyMode: "adopt",
1697
- restoreScroll: position.success.key === void 0 ? void 0 : recallScroll(position.success.key)
1698
- });
1699
- }
1700
- core.updateState((state) => {
1701
- return {
1702
- ...state,
1703
- freshness: startAttempt(facts)
1704
- };
1705
- });
1706
- if (active?.kind === "navigation") return null;
1707
- return startCandidate({
1708
- kind: "revalidation",
1709
- source: "navigate",
1710
- recoverByReload: false,
1711
- href: frame.href,
1712
- options: makeHistoryStateOptions(frame.historyState),
1713
- historyMode: "adopt",
1714
- restoreScroll: void 0
1715
- });
1716
- }
1717
- /** One attempt as the schedule sees it: a retryable failure fails, everything else ends the loop. */
1718
- function attemptOnce() {
1719
- return Effect.suspend(() => {
1720
- const candidate = startAttemptNow();
1721
- if (candidate === null) return Effect.void;
1722
- return Effect.flatMap(Effect.exit(Deferred.await(candidate.intent.done)), (exit) => {
1723
- if (Exit.isSuccess(exit) || core.getState().freshness.demand === null) return Effect.void;
1724
- const error = Cause.findErrorOption(exit.cause);
1725
- if (Option.isNone(error)) return Effect.void;
1726
- const revalidationError = classifyRefreshFailure(error.value);
1727
- return revalidationError.reason === "build-mismatch" ? Effect.void : Effect.fail(revalidationError);
1728
- });
1729
- });
1730
- }
1731
- /** Starts a refresh attempt for the outstanding demand when one may run, under the application's schedule when one is stated. */
1732
- function ensureAttempt() {
1733
- const state = core.getState();
1734
- if (state.lifecycle._tag === "Disposed" || refreshDebounce !== null || !canStartAttempt(state.freshness)) return;
1735
- const schedule = options.refreshRetrySchedule;
1736
- if (schedule === void 0) {
1737
- startAttemptNow();
1738
- return;
1739
- }
1740
- retryRunner?.interruptUnsafe();
1741
- retryRunner = core.forkFiber(Effect.retry(attemptOnce(), schedule).pipe(Effect.catchCause((cause) => {
1742
- return Effect.sync(() => {
1743
- const error = Cause.findErrorOption(cause);
1744
- if (Option.isSome(error)) applyFreshness(failAttempt(core.getState().freshness, error.value));
1745
- });
1746
- })));
1747
- }
1748
- function setPending(candidate) {
1749
- core.updateState((state) => {
1750
- return {
1751
- ...state,
1752
- navigation: {
1753
- ...state.navigation,
1754
- pending: candidate === null ? null : {
1755
- href: candidate.intent.href,
1756
- source: candidate.source,
1757
- skipWorkIndicator: candidate.intent.options.skipWorkIndicator === true
1758
- }
1759
- }
1760
- };
1761
- });
1762
- }
1763
- /** Empties the slot; the occupant's region then finds it lost and ends without effect. */
1764
- function vacateCandidateSlot() {
1765
- const occupant = active;
1766
- if (occupant === null) return;
1767
- active = null;
1768
- if (occupant.kind === "navigation") setPending(null);
1769
- occupant.fiber?.interruptUnsafe();
1770
- }
1771
- function startCandidate(input) {
1772
- const candidate = {
1773
- kind: input.kind,
1774
- source: input.source,
1775
- recoverByReload: input.recoverByReload,
1776
- restoreScroll: input.restoreScroll,
1777
- reuse: input.kind === "navigation" ? takeReusable(new URL(input.href)) : null,
1778
- intent: {
1779
- href: input.href,
1780
- options: input.options,
1781
- historyMode: input.historyMode,
1782
- done: Deferred.makeUnsafe()
1783
- },
1784
- fiber: void 0,
1785
- sequence: void 0,
1786
- outcome: void 0,
1787
- frameScope: void 0
1788
- };
1789
- const previous = active;
1790
- active = candidate;
1791
- previous?.fiber?.interruptUnsafe();
1792
- if (candidate.kind === "navigation") {
1793
- setPending(candidate);
1794
- core.updateState((state) => {
1795
- return {
1796
- ...state,
1797
- navigation: {
1798
- ...state.navigation,
1799
- latest: AsyncResult.waiting(state.navigation.latest)
1800
- }
1801
- };
1802
- });
1803
- }
1804
- candidate.fiber = core.forkFiber(Effect.onExit(runCandidate(candidate), (exit) => Effect.sync(() => finalizeCandidate(candidate, exit))));
1805
- return candidate;
1806
- }
1807
- function runCandidate(candidate) {
1808
- return Effect.gen(function* () {
1809
- let url = new URL(candidate.intent.href);
1810
- let redirects = 0;
1811
- const reuse = candidate.reuse;
1812
- if (reuse?.kind === "snapshot") {
1813
- const frame = core.getState().frame;
1814
- return yield* acceptRoute(candidate, {
1815
- ...reuse.prepared,
1816
- entries: reuse.prepared.entries.map((entry, index) => {
1817
- const retained = frame === null ? void 0 : frame.entries[index];
1818
- return entry.activationKey !== void 0 && retained !== void 0 && retained.pattern === entry.pattern && retained.activation !== void 0 ? {
1819
- ...entry,
1820
- activationKey: retained.activation.key
1821
- } : entry;
1822
- }),
1823
- generation: core.getState().generation
1824
- });
1825
- }
1826
- let promoted = reuse?.kind === "prefetch" ? reuse.preparation : null;
1827
- while (true) {
1828
- const preparationScope = Scope.makeUnsafe();
1829
- const source = promoted === null ? Effect.provideService(prepareRoute(core, {
1830
- source: {
1831
- kind: "request",
1832
- url,
1833
- requestKind: candidate.kind === "navigation" ? "Navigation" : "Revalidation"
1834
- },
1835
- trigger: candidate.kind,
1836
- historyState: candidate.intent.options.historyState,
1837
- prestart: true,
1838
- reportReadinessFn: void 0
1839
- }), Scope.Scope, preparationScope) : promoted.result;
1840
- const release = promoted === null ? Scope.close(preparationScope, Exit.void) : Effect.sync(promoted.release);
1841
- promoted = null;
1842
- const step = yield* Effect.ensuring(Effect.gen(function* () {
1843
- const outcome = yield* source;
1844
- if (outcome._tag === "Prepared") return {
1845
- _tag: "Accepted",
1846
- result: yield* acceptRoute(candidate, outcome.prepared)
1847
- };
1848
- return {
1849
- _tag: "Unprepared",
1850
- outcome
1851
- };
1852
- }), release);
1853
- if (step._tag === "Accepted") return step.result;
1854
- const outcome = step.outcome;
1855
- if (outcome._tag === "BuildSkew") {
1856
- core.reportBuildMismatch(outcome.mismatch);
1857
- retireReusable();
1858
- if (candidate.kind === "revalidation") return yield* new NavigationError({
1859
- stage: "request",
1860
- cause: Cause.fail(new BuildMismatchError({
1861
- method: "GET",
1862
- pattern: url.pathname,
1863
- message: "The server refused the loaded build during revalidation"
1864
- }))
1865
- });
1866
- return leaveDocument(url.href);
1867
- }
1868
- const target = Result.try(() => new URL(outcome.href, url.href));
1869
- if (Result.isFailure(target)) return yield* failAt("target", target.failure);
1870
- if (!/^https?:$/.test(target.success.protocol)) return yield* failAt("target", `Redirect target ${outcome.href} is not an HTTP URL`);
1871
- if (target.success.origin !== window.location.origin) return leaveDocument(target.success.href);
1872
- if (++redirects > REDIRECT_LIMIT) return yield* failAt("target", `More than ${REDIRECT_LIMIT} redirects`);
1873
- const committed = core.getState().frame;
1874
- if (candidate.kind === "navigation" && committed !== null && readDestination(target.success) === readDestination(new URL(committed.href))) return yield* completeAtCommitted(candidate, committed, target.success);
1875
- url = target.success;
1876
- candidate.source = "redirect";
1877
- candidate.intent = {
1878
- ...candidate.intent,
1879
- href: url.href,
1880
- historyMode: candidate.intent.historyMode === "adopt" ? "replace" : candidate.intent.historyMode
1881
- };
1882
- if (active === candidate && candidate.kind === "navigation") setPending(candidate);
1883
- }
1884
- });
1885
- }
1886
- /** Completes a navigation whose redirect returned to the committed destination: Unchanged, or a local commit for a new fragment or state. */
1887
- function completeAtCommitted(candidate, committed, url) {
1888
- return Effect.suspend(() => {
1889
- if (active !== candidate) return Effect.succeed({ _tag: "Superseded" });
1890
- const intent = candidate.intent;
1891
- const committedUrl = new URL(committed.href);
1892
- if (!checkBrowserPositionChanged() && normalizeFragment(url.hash) === normalizeFragment(committedUrl.hash) && Object.is(intent.options.historyState, committed.historyState)) {
1893
- candidate.outcome = {
1894
- _tag: "Unchanged",
1895
- href: committed.href
1896
- };
1897
- return Effect.succeed(candidate.outcome);
1898
- }
1899
- if (committed.key !== void 0) rememberScroll(committed.key, readScrollPosition());
1900
- const result = commitLocal({
1901
- reason: "navigation",
1902
- frame: committed,
1903
- url,
1904
- position: {
1905
- kind: "write",
1906
- href: url.href,
1907
- replace: intent.historyMode === "replace",
1908
- userState: intent.options.historyState,
1909
- key: createEntryKey()
1910
- },
1911
- options: intent.options,
1912
- restore: void 0
1913
- });
1914
- if (Result.isSuccess(result)) candidate.outcome = result.success;
1915
- return Effect.fromResult(result);
1916
- });
1917
- }
1918
- function acceptRoute(candidate, prepared) {
1919
- return Effect.scoped(Effect.gen(function* () {
1920
- const frame = core.getState().frame;
1921
- const preparationScope = yield* Effect.scope;
1922
- const outgoing = frame === null ? [] : yield* Effect.mapError(acquireActivations(core.activations, {
1923
- entries: frame.entries.filter((entry) => {
1924
- return entry.view?.beforeRouteReplacement !== void 0;
1925
- }),
1926
- scope: preparationScope
1927
- }), (cause) => failAt("transition", cause));
1928
- candidate.sequence = prepared.sequence;
1929
- const frameScope = Scope.makeUnsafe();
1930
- candidate.frameScope = frameScope;
1931
- const entries = yield* Effect.mapError(acquireActivations(core.activations, {
1932
- entries: prepared.entries,
1933
- scope: frameScope
1934
- }), (cause) => failAt("assets", cause));
1935
- const reason = candidate.kind === "revalidation" ? "revalidation" : candidate.recoverByReload ? "popstate" : "navigation";
1936
- if (frame !== null) {
1937
- const args = {
1938
- trigger: reason,
1939
- current: projectRouteState(frame),
1940
- next: projectRouteState({
1941
- href: candidate.intent.href,
1942
- historyState: candidate.intent.options.historyState,
1943
- entries: prepared.entries,
1944
- error: prepared.error
1945
- })
1946
- };
1947
- const hooks = [...outgoing.map((entry) => {
1948
- return {
1949
- entry,
1950
- hook: entry.view?.beforeRouteReplacement
1951
- };
1952
- }), ...entries.map((entry) => {
1953
- return {
1954
- entry,
1955
- hook: entry.view?.beforeRouteCommit
1956
- };
1957
- })];
1958
- yield* Effect.catchCause(Effect.forEach(hooks, ({ entry, hook }) => {
1959
- if (hook === void 0) return Effect.void;
1960
- const effect = Effect.suspend(() => hook(args));
1961
- return entry.activation === void 0 ? core.provideClient(effect) : Effect.provide(effect, entry.activation.client);
1962
- }, {
1963
- concurrency: "unbounded",
1964
- discard: true
1965
- }), (cause) => failAt("transition", cause));
1966
- }
1967
- return yield* commitRegion(candidate, {
1968
- ...prepared,
1969
- entries
1970
- }, reason, frameScope);
1971
- }));
1972
- }
1973
- function commitRegion(candidate, prepared, reason, frameScope) {
1974
- /** Synchronous, so nothing interleaves between its checks and the acceptance point. */
1975
- function runRegion() {
1976
- const state = core.getState();
1977
- const lifecycleAdmits = state.lifecycle._tag === "Ready" || candidate.kind === "revalidation" && state.lifecycle._tag === "PreBoot";
1978
- if (active !== candidate || !lifecycleAdmits || state.generation !== prepared.generation) return Result.succeed({ _tag: "Superseded" });
1979
- const intent = candidate.intent;
1980
- const firstFrame = state.frame === null;
1981
- let position;
1982
- if (intent.historyMode === "adopt") {
1983
- const current = readBrowserPosition();
1984
- if (Result.isFailure(current)) return Result.fail(failAt("transition", current.failure));
1985
- if (current.success.href !== intent.href) return Result.fail(failAt("transition", "The browser location changed during preparation"));
1986
- position = selectFirstFramePosition(current.success);
1987
- } else {
1988
- if (state.frame !== null && state.frame.key !== void 0) rememberScroll(state.frame.key, readScrollPosition());
1989
- position = {
1990
- kind: "write",
1991
- href: intent.href,
1992
- replace: intent.historyMode === "replace",
1993
- userState: intent.options.historyState,
1994
- key: createEntryKey()
1995
- };
1996
- }
1997
- const committed = commitFrame(core, {
1998
- reason: firstFrame ? "boot" : reason,
1999
- entries: prepared.entries,
2000
- error: prepared.error,
2001
- head: prepared.head,
2002
- cssBundles: prepared.cssBundles,
2003
- documentUpdate: "apply",
2004
- position,
2005
- scrollIntent: firstFrame ? selectFirstFrameScrollIntent(candidate.restoreScroll, prepared) : candidate.kind === "revalidation" ? void 0 : decideScrollIntent({
2006
- url: new URL(intent.href),
2007
- options: intent.options,
2008
- restore: candidate.restoreScroll,
2009
- entryCount: prepared.entries.length,
2010
- errorIndex: prepared.error === null ? null : prepared.error.index
2011
- }),
2012
- committedAt: candidate.reuse?.kind === "snapshot" ? candidate.reuse.committedAt : void 0,
2013
- scope: frameScope
2014
- });
2015
- if (Result.isSuccess(committed)) {
2016
- candidate.outcome = {
2017
- _tag: "Committed",
2018
- href: committed.success.href
2019
- };
2020
- return Result.succeed(candidate.outcome);
2021
- }
2022
- return Result.fail(committed.failure);
2023
- }
2024
- const transitionDocument = document;
2025
- const startViewTransition = transitionDocument.startViewTransition;
2026
- const outcome = candidate.kind === "navigation" && options.enableViewTransitions && startViewTransition !== void 0 ? Effect.callback((resume) => {
2027
- const started = Result.try(() => {
2028
- return startViewTransition.call(transitionDocument, () => resume(Effect.succeed(runRegion())));
2029
- });
2030
- if (Result.isFailure(started)) {
2031
- resume(Effect.fail(failAt("transition", started.failure)));
2032
- return;
2033
- }
2034
- started.success.ready.catch(() => void 0);
2035
- started.success.updateCallbackDone.catch((cause) => {
2036
- resume(Effect.fail(failAt("transition", cause)));
2037
- });
2038
- }) : Effect.sync(runRegion);
2039
- return Effect.flatMap(outcome, Effect.fromResult);
2040
- }
2041
- function finalizeCandidate(candidate, rawExit) {
2042
- const exit = candidate.outcome === void 0 ? rawExit : Exit.succeed(candidate.outcome);
2043
- const held = active === candidate;
2044
- if (held) active = null;
2045
- const interrupted = Exit.isFailure(exit) && Cause.hasInterruptsOnly(exit.cause);
2046
- if (candidate.frameScope !== void 0 && candidate.outcome === void 0) closeScope(candidate.frameScope);
2047
- if (candidate.kind === "navigation" && held) {
2048
- setPending(null);
2049
- if (!interrupted) core.updateState((state) => {
2050
- return {
2051
- ...state,
2052
- navigation: {
2053
- ...state.navigation,
2054
- latest: AsyncResult.fromExit(exit)
2055
- }
2056
- };
2057
- });
2058
- }
2059
- if (held && Exit.isFailure(exit) && !interrupted) {
2060
- const error = Cause.findErrorOption(exit.cause);
2061
- if (!(Option.isSome(error) && error.value.stage === "publication") && (candidate.recoverByReload || checkBrowserPositionChanged())) reloadDocument(core, exit.cause);
2062
- }
2063
- const facts = core.getState().freshness;
2064
- if (Exit.isSuccess(exit)) {
2065
- if (exit.value._tag === "Committed" && candidate.sequence !== void 0) {
2066
- const frame = core.getState().frame;
2067
- const reuseWindow = frame?.entries.at(-1)?.view?.reuseLoaderDataWithin;
2068
- const duration = reuseWindow === void 0 ? Option.none() : Duration.fromInput(reuseWindow);
2069
- const durationMillis = Option.map(duration, Duration.toMillis);
2070
- const retainedAt = Date.now();
2071
- if (frame !== null && frame.error === null && candidate.reuse?.kind !== "snapshot" && Option.isSome(durationMillis) && Number.isFinite(durationMillis.value) && durationMillis.value > 0) {
2072
- snapshots.set(readDestination(new URL(frame.href)), {
2073
- prepared: {
2074
- url: new URL(frame.href),
2075
- entries: frame.entries.map(toCachedRouteEntry),
2076
- error: frame.error,
2077
- head: frame.head,
2078
- cssBundles: frame.cssBundles,
2079
- sequence: candidate.sequence,
2080
- generation: core.getState().generation
2081
- },
2082
- committedAt: frame.committedAt,
2083
- expiresAt: frame.committedAt + durationMillis.value
2084
- });
2085
- deleteExpiredSnapshots(retainedAt);
2086
- scheduleSnapshotExpiration();
2087
- }
2088
- const error = frame === null ? null : frame.error;
2089
- if (facts.demand !== null) applyFreshness(error === null ? satisfyDemand(facts, { sequence: candidate.sequence }) : failAttempt(facts, makeRevalidationError("route", error.error)));
2090
- }
2091
- } else if (interrupted) {
2092
- if (active === null && facts.attempt.phase === "running") applyFreshness(abandonAttempt(facts));
2093
- } else {
2094
- const error = Cause.findErrorOption(exit.cause);
2095
- if (Option.isNone(error)) core.publishEvent({
2096
- _tag: "FailureReported",
2097
- source: "navigation",
2098
- cause: exit.cause
2099
- });
2100
- if (held && facts.attempt.phase === "running") {
2101
- if (candidate.kind === "revalidation") {
2102
- const revalidationError = Option.match(error, {
2103
- onNone: () => {
2104
- return makeRevalidationError("route", exit.cause);
2105
- },
2106
- onSome: classifyRefreshFailure
2107
- });
2108
- applyFreshness(options.refreshRetrySchedule !== void 0 && revalidationError.reason !== "build-mismatch" ? holdAttemptForRetry(facts, revalidationError) : failAttempt(facts, revalidationError));
2109
- } else applyFreshness(abandonAttempt(facts));
2110
- }
2111
- }
2112
- Deferred.doneUnsafe(candidate.intent.done, interrupted ? Effect.succeed({ _tag: "Superseded" }) : Exit.isSuccess(exit) ? Effect.succeed(exit.value) : Effect.failCause(exit.cause));
2113
- }
2114
- /** Commits the committed entries again at a new position, without a request. */
2115
- function commitLocal(input) {
2116
- return Result.map(commitFrame(core, {
2117
- reason: input.reason,
2118
- entries: input.frame.entries,
2119
- error: input.frame.error,
2120
- head: input.frame.head,
2121
- cssBundles: input.frame.cssBundles,
2122
- documentUpdate: "apply",
2123
- position: input.position,
2124
- scrollIntent: decideScrollIntent({
2125
- url: input.url,
2126
- options: input.options,
2127
- restore: input.restore,
2128
- entryCount: input.frame.entries.length,
2129
- errorIndex: input.frame.error === null ? null : input.frame.error.index
2130
- }),
2131
- committedAt: input.frame.committedAt,
2132
- scope: input.frame.scope
2133
- }), (frame) => {
2134
- if (active?.kind === "revalidation" && readDestination(new URL(active.intent.href)) === readDestination(new URL(frame.href))) active.intent = {
2135
- ...active.intent,
2136
- href: frame.href,
2137
- options: makeHistoryStateOptions(frame.historyState)
2138
- };
2139
- return {
2140
- _tag: "Committed",
2141
- href: frame.href
2142
- };
2143
- });
2144
- }
2145
- function navigateToHref(href, requested) {
2146
- return Effect.suspend(() => beginNavigation(href, requested));
2147
- }
2148
- function recordNavigationTargetFailure(error) {
2149
- core.updateState((state) => {
2150
- return {
2151
- ...state,
2152
- navigation: {
2153
- ...state.navigation,
2154
- latest: AsyncResult.fail(error)
2155
- }
2156
- };
2157
- });
2158
- }
2159
- function beginNavigation(href, requested) {
2160
- {
2161
- const state = core.getState();
2162
- const frame = state.frame;
2163
- if (state.lifecycle._tag === "PreBoot") {
2164
- const queued = core.forkFiber(Effect.flatMap(Deferred.await(firstFrame), () => {
2165
- return beginNavigation(href, requested);
2166
- }));
2167
- return Fiber.join(queued);
2168
- }
2169
- if (state.lifecycle._tag !== "Ready" || frame === null) return failAt("target", "The client is not ready to navigate");
2170
- const parsed = Result.try(() => new URL(href, window.location.href));
2171
- if (Result.isFailure(parsed)) return failAt("target", parsed.failure);
2172
- const url = parsed.success;
2173
- if (!/^https?:$/.test(url.protocol) || url.origin !== window.location.origin) return Effect.succeed(leaveDocument(url.href));
2174
- const captured = Result.try(() => structuredClone(requested.historyState));
2175
- if (Result.isFailure(captured)) return failAt("target", captured.failure);
2176
- const options = {
2177
- ...requested,
2178
- ...makeHistoryStateOptions(captured.success)
2179
- };
2180
- const committed = new URL(frame.href);
2181
- if (readDestination(url) === readDestination(committed)) {
2182
- if (!checkBrowserPositionChanged() && normalizeFragment(url.hash) === normalizeFragment(committed.hash) && Object.is(options.historyState, frame.historyState)) return Effect.succeed({
2183
- _tag: "Unchanged",
2184
- href: frame.href
2185
- });
2186
- if (frame.key !== void 0) rememberScroll(frame.key, readScrollPosition());
2187
- return Effect.fromResult(commitLocal({
2188
- reason: "navigation",
2189
- frame,
2190
- url,
2191
- position: {
2192
- kind: "write",
2193
- href: url.href,
2194
- replace: options.replace === true,
2195
- userState: options.historyState,
2196
- key: createEntryKey()
2197
- },
2198
- options,
2199
- restore: void 0
2200
- }));
2201
- }
2202
- const historyMode = options.replace === true ? "replace" : "push";
2203
- if (active?.kind === "navigation" && readDestination(new URL(active.intent.href)) === readDestination(url)) {
2204
- const current = active.intent;
2205
- if (current.href === url.href && matchesNavigationOptions(current.options, options)) return Deferred.await(current.done);
2206
- active.intent = {
2207
- href: url.href,
2208
- options,
2209
- historyMode,
2210
- done: Deferred.makeUnsafe()
2211
- };
2212
- setPending(active);
2213
- Deferred.doneUnsafe(current.done, Effect.succeed({ _tag: "Superseded" }));
2214
- return Deferred.await(active.intent.done);
2215
- }
2216
- return Deferred.await(startCandidate({
2217
- kind: "navigation",
2218
- source: "navigate",
2219
- recoverByReload: false,
2220
- href: url.href,
2221
- options,
2222
- historyMode,
2223
- restoreScroll: void 0
2224
- }).intent.done);
2225
- }
2226
- }
2227
- function requireRefresh(input) {
2228
- const waiter = Deferred.makeUnsafe();
2229
- retireSnapshots();
2230
- const state = core.getState();
2231
- refreshDebounce?.interruptUnsafe();
2232
- refreshDebounce = input.debounce ? core.forkFiber(Effect.sleep("8 millis").pipe(Effect.andThen(Effect.sync(() => {
2233
- refreshDebounce = null;
2234
- ensureAttempt();
2235
- })))) : null;
2236
- const expected = /* @__PURE__ */ new Set();
2237
- collecting = expected;
2238
- applyFreshness(registerDemand(state.freshness, {
2239
- sequence: state.sequence,
2240
- waiter,
2241
- skipWorkIndicator: input.skipWorkIndicator
2242
- }));
2243
- collecting = null;
2244
- applyFreshness(expectQueries(core.getState().freshness, expected));
2245
- return Deferred.await(waiter);
2246
- }
2247
- function invalidateSession() {
2248
- return Effect.suspend(() => {
2249
- core.retireGeneration();
2250
- retireReusable();
2251
- vacateCandidateSlot();
2252
- return requireRefresh({
2253
- skipWorkIndicator: false,
2254
- debounce: false
2255
- });
2256
- });
2257
- }
2258
- function settleBoot(outcome) {
2259
- Deferred.doneUnsafe(firstFrame, Result.isSuccess(outcome) ? Effect.void : Effect.fail(failAt("target", outcome.failure)));
2260
- }
2261
- /** Disposal: nothing outstanding survives the application. */
2262
- function retireAll() {
2263
- retired = true;
2264
- Deferred.doneUnsafe(firstFrame, Effect.fail(failAt("target", "The client application is disposed")));
2265
- retryRunner?.interruptUnsafe();
2266
- refreshDebounce?.interruptUnsafe();
2267
- refreshDebounce = null;
2268
- retireReusable();
2269
- vacateCandidateSlot();
2270
- applyFreshness(retireDemand(core.getState().freshness, makeRevalidationError("disposed", "The client application is disposed")));
2271
- }
2272
- const queries = {
2273
- expect: ({ id, token }) => {
2274
- queryComputations.set(id, token);
2275
- if (collecting !== null) {
2276
- collecting.add(id);
2277
- return;
2278
- }
2279
- if (!retired) applyFreshness(expectQueries(core.getState().freshness, /* @__PURE__ */ new Set([id])));
2280
- },
2281
- release: ({ id, token }) => {
2282
- if (queryComputations.get(id) !== token) return;
2283
- queryComputations.delete(id);
2284
- if (!retired) applyFreshness(releaseQuery(core.getState().freshness, id));
2285
- },
2286
- acknowledge: (acknowledgment) => {
2287
- const state = core.getState();
2288
- if (acknowledgment.dispatched !== void 0 && acknowledgment.dispatched.generation !== state.generation) return;
2289
- applyFreshness(acknowledgeQuery(state.freshness, {
2290
- id: acknowledgment.id,
2291
- sequence: acknowledgment.dispatched?.sequence,
2292
- success: acknowledgment.success
2293
- }));
2294
- }
2295
- };
2296
- /** Whether the browser moved away from the committed frame during navigation. */
2297
- function checkBrowserPositionChanged() {
2298
- const frame = core.getState().frame;
2299
- if (frame === null) return false;
2300
- const position = readBrowserPosition();
2301
- return Result.isFailure(position) || position.success.href !== frame.href || position.success.key !== frame.key;
2302
- }
2303
- function handlePopstate() {
2304
- vacateCandidateSlot();
2305
- const position = readBrowserPosition();
2306
- const frame = core.getState().frame;
2307
- if (Result.isFailure(position)) {
2308
- reloadDocument(core, Cause.fail(position.failure));
2309
- return;
2310
- }
2311
- if (frame === null) {
2312
- reloadDocument(core, Cause.fail("No committed route is available for history traversal"));
2313
- return;
2314
- }
2315
- const url = new URL(position.success.href);
2316
- if (frame.key !== void 0 && frame.key !== position.success.key) rememberScroll(frame.key, readScrollPosition());
2317
- const restore = position.success.key === void 0 ? void 0 : recallScroll(position.success.key);
2318
- if (readDestination(url) === readDestination(new URL(frame.href))) {
2319
- const committed = commitLocal({
2320
- reason: "popstate",
2321
- frame,
2322
- url,
2323
- position: {
2324
- kind: "adopt",
2325
- position: position.success
2326
- },
2327
- options: {},
2328
- restore
2329
- });
2330
- if (Result.isFailure(committed) && committed.failure.stage !== "publication") reloadDocument(core, Cause.fail(committed.failure));
2331
- return;
2332
- }
2333
- startCandidate({
2334
- kind: "navigation",
2335
- source: "popstate",
2336
- recoverByReload: true,
2337
- href: url.href,
2338
- options: makeHistoryStateOptions(position.success.userState),
2339
- historyMode: "adopt",
2340
- restoreScroll: restore
2341
- });
2342
- }
2343
- /** A reload or departure remembers the entry's scroll; boot restores it by the entry's key. */
2344
- function handlePageHide() {
2345
- const frame = core.getState().frame;
2346
- if (frame !== null && frame.key !== void 0) rememberScroll(frame.key, readScrollPosition());
2347
- }
2348
- /** A document that becomes visible again is treated as focused; a hidden one is not. */
2349
- function handleVisibilityChange() {
2350
- if (document.visibilityState === "visible") handleFocus();
2351
- }
2352
- function handleFocus() {
2353
- const focus = options.revalidateOnWindowFocus;
2354
- const state = core.getState();
2355
- const busy = active !== null || state.freshness.attempt.phase === "running" || [...state.operations.values()].some((operation) => operation.phase !== "settled");
2356
- if (focus === void 0 || state.frame === null || busy || Date.now() - state.frame.committedAt < Duration.toMillis(focus.staleTime)) return;
2357
- core.forkFiber(Effect.ignore(Effect.tapError(requireRefresh({
2358
- skipWorkIndicator: focus.skipWorkIndicator,
2359
- debounce: true
2360
- }), (error) => {
2361
- return Effect.sync(() => {
2362
- core.publishEvent({
2363
- _tag: "FailureReported",
2364
- source: "revalidation",
2365
- cause: Cause.fail(error)
2366
- });
2367
- });
2368
- })));
2369
- }
2370
- return {
2371
- navigate: navigateToHref,
2372
- recordTargetFailure: recordNavigationTargetFailure,
2373
- begin: beginNavigation,
2374
- revalidate: (input) => Effect.suspend(() => requireRefresh({
2375
- ...input,
2376
- debounce: true
2377
- })),
2378
- requireRefresh: (input) => requireRefresh({
2379
- ...input,
2380
- debounce: false
2381
- }),
2382
- settleBoot,
2383
- retireReusable,
2384
- retireAll,
2385
- invalidate: invalidateSession,
2386
- queries,
2387
- commitBoot: (prepared, scope, position) => {
2388
- const restore = position.key === void 0 ? void 0 : recallScroll(position.key);
2389
- const committed = commitFrame(core, {
2390
- reason: "boot",
2391
- entries: prepared.entries,
2392
- error: prepared.error,
2393
- head: prepared.head,
2394
- cssBundles: prepared.cssBundles,
2395
- documentUpdate: "adopt",
2396
- position: selectFirstFramePosition(position),
2397
- scrollIntent: selectFirstFrameScrollIntent(restore, prepared),
2398
- committedAt: void 0,
2399
- scope
2400
- });
2401
- if (Result.isSuccess(committed)) ensureAttempt();
2402
- return committed;
2403
- },
2404
- attachBrowserListeners: () => {
2405
- const previousScrollRestoration = window.history.scrollRestoration;
2406
- window.history.scrollRestoration = "manual";
2407
- window.addEventListener("popstate", handlePopstate);
2408
- window.addEventListener("focus", handleFocus);
2409
- document.addEventListener("visibilitychange", handleVisibilityChange);
2410
- window.addEventListener("pagehide", handlePageHide);
2411
- const detachPointerModality = attachPointerModalityTracking();
2412
- return () => {
2413
- if (window.history.scrollRestoration === "manual") window.history.scrollRestoration = previousScrollRestoration;
2414
- detachPointerModality();
2415
- window.removeEventListener("pagehide", handlePageHide);
2416
- window.removeEventListener("popstate", handlePopstate);
2417
- window.removeEventListener("focus", handleFocus);
2418
- document.removeEventListener("visibilitychange", handleVisibilityChange);
2419
- };
2420
- }
2421
- };
2422
- }
2423
- //#endregion
2424
- //#region src/client/request.ts
2425
- /** Applies the application's request transformation before dispatch. */
2426
- function prepareClientRequest(input) {
2427
- if (input.prepareRequestFn === void 0) return Effect.succeed(input.context.request);
2428
- return input.prepareRequestFn(input.context);
2429
- }
2430
- //#endregion
2431
- //#region src/client/assembly.ts
2432
- function createClientApplication(input) {
2433
- const registry = AtomRegistry.make({ scheduleTask: input.scheduleTaskFn });
2434
- const scope = Scope.makeUnsafe();
2435
- const events = Effect.runSync(PubSub.unbounded());
2436
- let contexts = Option.none();
2437
- let retirement = Deferred.makeUnsafe();
2438
- let clientBuildId = "";
2439
- let deploymentId;
2440
- let developmentMode = false;
2441
- let disposal;
2442
- function getState() {
2443
- return registry.get(clientStateAtom);
2444
- }
2445
- function updateState(updateFn) {
2446
- registry.update(clientStateAtom, updateFn);
2447
- }
2448
- function setLifecycle(lifecycle) {
2449
- updateState((state) => {
2450
- return {
2451
- ...state,
2452
- lifecycle
2453
- };
2454
- });
2455
- }
2456
- function publishEvent(event) {
2457
- if (disposal === void 0) PubSub.publishUnsafe(events, event);
2458
- }
2459
- function provideClient(effect) {
2460
- return Effect.suspend(() => {
2461
- return Option.isNone(contexts) ? Effect.fail(CLIENT_UNAVAILABLE_ERROR) : provideClientContext(contexts.value, effect);
2462
- });
2463
- }
2464
- const core = {
2465
- registry,
2466
- scope,
2467
- memoMap: Effect.runSync(Layer.makeMemoMap),
2468
- mutationFibers: Effect.runSync(Effect.provideService(FiberSet.make(), Scope.Scope, scope)),
2469
- events: Stream.fromPubSub(events),
2470
- publishEvent,
2471
- getState,
2472
- updateState,
2473
- forkFiber: (effect) => Effect.runSync(Effect.forkIn(effect, scope)),
2474
- advanceSequence: () => {
2475
- updateState((state) => {
2476
- return {
2477
- ...state,
2478
- sequence: state.sequence + 1
2479
- };
2480
- });
2481
- return getState().sequence;
2482
- },
2483
- getGenerationRetirement: () => retirement,
2484
- retireGeneration: () => {
2485
- const retired = retirement;
2486
- retirement = Deferred.makeUnsafe();
2487
- updateState((state) => {
2488
- return {
2489
- ...state,
2490
- generation: state.generation + 1
2491
- };
2492
- });
2493
- Deferred.doneUnsafe(retired, Effect.void);
2494
- },
2495
- getClientBuildId: () => clientBuildId,
2496
- getDeploymentId: () => deploymentId,
2497
- runsInDevelopmentMode: () => developmentMode,
2498
- clientLoaders: createClientLoaderRegistry(),
2499
- prepareRequest: (context) => {
2500
- return provideClient(prepareClientRequest({
2501
- context,
2502
- prepareRequestFn: input.options.prepareRequestFn
2503
- }));
2504
- },
2505
- provideClient,
2506
- reportBuildMismatch: (mismatch) => {
2507
- updateState((state) => {
2508
- return {
2509
- ...state,
2510
- buildMismatch: Option.some(mismatch)
2511
- };
2512
- });
2513
- publishEvent({
2514
- _tag: "BuildMismatchDetected",
2515
- mismatch
2516
- });
2517
- },
2518
- activations: Effect.runSync(Effect.provideService(createActivationMap(() => contexts), Scope.Scope, scope))
2519
- };
2520
- const prefetch = createPrefetch(core);
2521
- const navigation = createNavigation(core, prefetch, {
2522
- enableViewTransitions: input.options.enableViewTransitions === true,
2523
- refreshRetrySchedule: input.options.refreshRetrySchedule,
2524
- revalidateOnWindowFocus: input.options.revalidateOnWindowFocus === void 0 ? void 0 : {
2525
- staleTime: input.options.revalidateOnWindowFocus.staleTime,
2526
- skipWorkIndicator: input.options.revalidateOnWindowFocus.skipWorkIndicator === true
2527
- }
2528
- });
2529
- const workIndicator = createWorkIndicator(core, input.options.workIndicator);
2530
- const coordinator = {
2531
- trackWork: workIndicator.track,
2532
- registry,
2533
- events: core.events,
2534
- publishEvent,
2535
- navigation,
2536
- prefetch,
2537
- queries: navigation.queries,
2538
- fork: (effect) => core.forkFiber(effect),
2539
- reportRenderFailure: (failure) => {
2540
- publishEvent({
2541
- _tag: "FailureReported",
2542
- source: "render",
2543
- cause: Cause.isCause(failure.error) ? failure.error : Cause.die(failure.error)
2544
- });
2545
- updateState((state) => {
2546
- const frame = state.frame;
2547
- if (frame === null || frame.revision !== failure.revision || failure.index === void 0 || failure.pattern === void 0 || frame.error !== null && frame.error.index <= failure.index) return state;
2548
- return {
2549
- ...state,
2550
- frame: {
2551
- ...frame,
2552
- error: {
2553
- index: failure.index,
2554
- pattern: failure.pattern,
2555
- source: "render",
2556
- error: failure.error
2557
- }
2558
- }
2559
- };
2560
- });
2561
- },
2562
- endpoints: createEndpointOperations(core, {
2563
- retireReusableFn: navigation.retireReusable,
2564
- requireRefreshFn: navigation.requireRefresh,
2565
- redirectFn: (url) => {
2566
- return navigation.begin(url.href, { replace: true });
2567
- },
2568
- navigateDocumentFn: (href) => {
2569
- window.location.assign(href);
2570
- }
2571
- })
2572
- };
2573
- const assembleServices = Effect.gen(function* () {
2574
- const universalServices = input.options.universalServices === void 0 ? Layer.empty : input.options.universalServices;
2575
- const universal = yield* Layer.buildWithMemoMap(universalServices, core.memoMap, scope);
2576
- const universalLayer = Layer.succeedContext(universal);
2577
- const clientOnly = input.options.clientOnlyServices === void 0 ? Layer.empty : Layer.provide(input.options.clientOnlyServices, universalLayer);
2578
- const framework = Layer.mergeAll(Layer.succeed(ClientCoordinator, coordinator), Layer.succeed(AtomRegistry.AtomRegistry, registry), Reactivity.layer, FetchHttpClient.layer);
2579
- return {
2580
- universal,
2581
- client: yield* Layer.buildWithMemoMap(Layer.merge(Layer.merge(framework, universalLayer), clientOnly), core.memoMap, scope)
2582
- };
2583
- });
2584
- function startBrowserProgram() {
2585
- const program = input.options.browserProgram;
2586
- if (program === void 0) return;
2587
- core.forkFiber(core.provideClient(program).pipe(Effect.tapCause((cause) => {
2588
- return Cause.hasInterruptsOnly(cause) ? Effect.void : Effect.sync(() => {
2589
- publishEvent({
2590
- _tag: "FailureReported",
2591
- source: "browserProgram",
2592
- cause
2593
- });
2594
- });
2595
- }), Effect.ignore));
2596
- }
2597
- /**
2598
- * Imports the matching module in the background, after the first frame.
2599
- * Once it is installed, client loaders start before the server answers
2600
- * for every destination the registry can prove; until then, and if it
2601
- * never arrives, nothing starts early and every navigation proceeds as
2602
- * it would without the module.
2603
- */
2604
- function loadRouteMatching() {
2605
- core.forkFiber(Effect.tryPromise(() => import("../../route-matching-D_uSG2P9.js")).pipe(Effect.map((module) => {
2606
- core.clientLoaders.installMatching(module);
2607
- }), Effect.tapCause((cause) => {
2608
- return Cause.hasInterruptsOnly(cause) ? Effect.void : Effect.sync(() => {
2609
- publishEvent({
2610
- _tag: "FailureReported",
2611
- source: "clientLoaderEligibility",
2612
- cause
2613
- });
2614
- });
2615
- }), Effect.ignore));
2616
- }
2617
- /** Creates a typed boot failure; the outer boundary records it after cleanup. */
2618
- function failBoot(message, cause) {
2619
- const error = {
2620
- _tag: "ClientBootError",
2621
- message,
2622
- ...cause === void 0 ? {} : { cause }
2623
- };
2624
- return Exit.fail(error);
2625
- }
2626
- let bootStarted = false;
2627
- let bootFiber;
2628
- function bootApplication() {
2629
- if (disposal !== void 0) return Promise.resolve(Exit.fail({
2630
- _tag: "ClientBootError",
2631
- message: "The client application is disposed"
2632
- }));
2633
- if (getState().lifecycle._tag !== "PreBoot" || bootStarted) return Promise.resolve(Exit.fail({
2634
- _tag: "ClientBootError",
2635
- message: "The client application boot already started"
2636
- }));
2637
- bootStarted = true;
2638
- bootFiber = core.forkFiber(Effect.flatMap(Effect.scoped(prepareBoot()), (document) => {
2639
- return Effect.suspend(() => finishBoot(document));
2640
- }));
2641
- return Effect.runPromise(Effect.map(Fiber.await(bootFiber), (exit) => {
2642
- if (Exit.isSuccess(exit)) return exit;
2643
- const failure = Exit.findErrorOption(exit);
2644
- const error = Option.isSome(failure) ? failure.value : {
2645
- _tag: "ClientBootError",
2646
- message: "Client application boot did not complete",
2647
- cause: exit.cause
2648
- };
2649
- if (disposal === void 0 && getState().lifecycle._tag === "PreBoot") {
2650
- setLifecycle({
2651
- _tag: "BootFailed",
2652
- error
2653
- });
2654
- navigation.settleBoot(Result.fail(error));
2655
- }
2656
- return Exit.fail(error);
2657
- }));
2658
- }
2659
- function prepareBoot() {
2660
- return Effect.gen(function* () {
2661
- const root = document.getElementById(DOCUMENT_PROTOCOL.elementIds.root);
2662
- const dataElement = document.getElementById(DOCUMENT_PROTOCOL.elementIds.data);
2663
- if (root === null || dataElement === null) return yield* failBoot("The document carries no Vorma root or bootstrap payload");
2664
- const raw = Result.try(() => {
2665
- return JSON.parse(dataElement.textContent);
2666
- });
2667
- if (Result.isFailure(raw)) return yield* failBoot("The bootstrap payload is not JSON", raw.failure);
2668
- const url = new URL(window.location.href);
2669
- const bootstrapRead = readBootstrapPayload(raw.success);
2670
- if (Result.isFailure(bootstrapRead)) return yield* failBoot("The bootstrap payload's values do not restore", bootstrapRead.failure);
2671
- const bootstrap = bootstrapRead.success;
2672
- clientBuildId = bootstrap.clientBuildId;
2673
- deploymentId = bootstrap.deploymentId;
2674
- developmentMode = bootstrap.developmentMode;
2675
- if (import.meta.env?.DEV && developmentMode) yield* (yield* Effect.tryPromise(() => import("../../development-B_rxuwY7.js")).pipe(Effect.catch((cause) => failBoot("Could not load development updates", cause)))).installDevelopmentUpdates({
2676
- core,
2677
- navigation
2678
- });
2679
- const documentHead = readDocumentHead();
2680
- if (Result.isFailure(documentHead)) return yield* failBoot("The document's managed head could not be read", documentHead.failure);
2681
- const payload = {
2682
- routes: bootstrap.routes,
2683
- serverError: bootstrap.serverError,
2684
- head: documentHead.success,
2685
- deps: [],
2686
- cssBundles: readDocumentStylesheets(),
2687
- staticAssets: []
2688
- };
2689
- const assembled = yield* Effect.result(assembleServices);
2690
- if (getState().lifecycle._tag === "Disposed") return yield* failBoot("The client application was disposed during boot");
2691
- if (Result.isFailure(assembled)) return yield* failBoot("Client application services could not be acquired", assembled.failure);
2692
- contexts = Option.some(assembled.success);
2693
- publishApplicationContexts({
2694
- registry,
2695
- contexts: assembled.success
2696
- });
2697
- registry.mount(clientRuntime);
2698
- yield* workIndicator.start();
2699
- startBrowserProgram();
2700
- const read = readBrowserPosition();
2701
- if (Result.isFailure(read)) publishEvent({
2702
- _tag: "FailureReported",
2703
- source: "navigation",
2704
- cause: Cause.fail(read.failure)
2705
- });
2706
- const position = Result.isSuccess(read) ? read.success : {
2707
- href: url.href,
2708
- userState: void 0,
2709
- key: void 0
2710
- };
2711
- const prepared = yield* Effect.result(prepareRoute(core, {
2712
- source: {
2713
- kind: "payload",
2714
- url,
2715
- payload
2716
- },
2717
- trigger: "boot",
2718
- historyState: position.userState,
2719
- prestart: false,
2720
- reportReadinessFn: void 0
2721
- }));
2722
- if (getState().lifecycle._tag === "Disposed") return yield* failBoot("The client application was disposed during boot");
2723
- if (Result.isFailure(prepared) || prepared.success._tag !== "Prepared") return yield* failBoot("The initial route could not be prepared", Result.isFailure(prepared) ? prepared.failure : prepared.success);
2724
- if (getState().frame !== null) return {
2725
- root,
2726
- dataElement
2727
- };
2728
- const preparedRoute = prepared.success.prepared;
2729
- return yield* Effect.acquireUseRelease(Scope.fork(scope), (frameScope) => {
2730
- return Effect.gen(function* () {
2731
- const entries = yield* Effect.result(acquireActivations(core.activations, {
2732
- entries: preparedRoute.entries,
2733
- scope: frameScope
2734
- }));
2735
- if (getState().lifecycle._tag === "Disposed") return yield* failBoot("The client application was disposed during boot");
2736
- if (Result.isFailure(entries)) return yield* failBoot("The initial route's services could not be built", entries.failure);
2737
- const committed = navigation.commitBoot({
2738
- ...preparedRoute,
2739
- entries: entries.success
2740
- }, frameScope, position);
2741
- if (Result.isFailure(committed)) return yield* failBoot("The initial route could not be committed", committed.failure);
2742
- return {
2743
- root,
2744
- dataElement
2745
- };
2746
- });
2747
- }, (frameScope, exit) => {
2748
- return getState().frame?.scope === frameScope ? Effect.void : Scope.close(frameScope, exit);
2749
- });
2750
- });
2751
- }
2752
- /** Renders the committed first frame, attaches the browser listeners, and marks the client ready. */
2753
- function finishBoot({ root, dataElement }) {
2754
- input.renderFn(root);
2755
- const detach = navigation.attachBrowserListeners();
2756
- Effect.runSync(Scope.addFinalizer(scope, Effect.sync(detach)));
2757
- loadRouteMatching();
2758
- dataElement.remove();
2759
- setLifecycle({ _tag: "Ready" });
2760
- navigation.settleBoot(Result.succeed(void 0));
2761
- return Exit.void;
2762
- }
2763
- function disposeApplication() {
2764
- if (disposal !== void 0) return disposal;
2765
- setLifecycle({ _tag: "Disposed" });
2766
- navigation.retireAll();
2767
- const frame = getState().frame;
2768
- disposal = Effect.runPromise((bootFiber === void 0 ? Effect.void : Fiber.interrupt(bootFiber)).pipe(Effect.andThen(Effect.sync(input.disposeRendererFn)), Effect.ensuring(frame === null ? Effect.void : Scope.close(frame.scope, Exit.void)), Effect.ensuring(Scope.close(scope, Exit.void)), Effect.ensuring(PubSub.shutdown(events)), Effect.ensuring(Effect.sync(() => registry.dispose()))));
2769
- return disposal;
2770
- }
2771
- return {
2772
- core,
2773
- coordinator,
2774
- navigation,
2775
- prefetch,
2776
- checkDisposed: () => disposal !== void 0,
2777
- boot: bootApplication,
2778
- dispose: disposeApplication
2779
- };
2780
- }
2781
- //#endregion
2782
- //#region src/client/react/view.tsx
2783
- const Context$1 = createContext(null);
2784
- const ApplicationProvider = Context$1.Provider;
2785
- /** The mounted application. A Vorma component outside one cannot work, and React's protocol for that is a throw to the caller. */
2786
- function useApplication() {
2787
- const application = useContext(Context$1);
2788
- if (application === null) throw new Error("Vorma React components render only inside a Vorma client application");
2789
- return application;
2790
- }
2791
- function makeUiView(definition) {
2792
- return definition;
2793
- }
2794
- const UiView = { make: makeUiView };
2795
- const RENDER_FAILURE = Symbol("vorma/client/RenderFailure");
2796
- function matchesTaggedFailure(error) {
2797
- return typeof error === "object" && error !== null && RENDER_FAILURE in error;
2798
- }
2799
- /** Observes the navigation a query redirect started; renders its failure in the boundary and nothing otherwise. */
2800
- function RedirectObservation(props) {
2801
- const observation = useMemo(() => {
2802
- return clientRuntime.atom(props.redirect.navigation);
2803
- }, [props.redirect]);
2804
- const result = useAtomValue(observation);
2805
- const Boundary = props.boundary;
2806
- if (result._tag === "Failure") return /* @__PURE__ */ jsx(Boundary, { error: projectRouteError({
2807
- pattern: props.pattern,
2808
- source: "render",
2809
- error: result.cause
2810
- }) });
2811
- return null;
2812
- }
2813
- /** React's catch protocol, attributed to the matched entry that owns the boundary. */
2814
- var RouteBoundary = class RouteBoundary extends Component {
2815
- state = {
2816
- revision: this.props.revision,
2817
- caught: null
2818
- };
2819
- static getDerivedStateFromProps(props, state) {
2820
- return props.revision === state.revision ? null : {
2821
- revision: props.revision,
2822
- caught: null
2823
- };
2824
- }
2825
- static getDerivedStateFromError(error) {
2826
- return { caught: {
2827
- error,
2828
- index: void 0,
2829
- pattern: void 0
2830
- } };
2831
- }
2832
- /** The failure with the position it belongs to: a rethrown one keeps its own, a new one is this boundary's. */
2833
- attributeFailure(error) {
2834
- return matchesTaggedFailure(error) ? error[RENDER_FAILURE] : {
2835
- error,
2836
- index: this.props.index,
2837
- pattern: this.props.pattern
2838
- };
2839
- }
2840
- componentDidCatch(error, _info) {
2841
- const failure = this.attributeFailure(error);
2842
- if (getQueryRedirect(failure.error) !== void 0) return;
2843
- this.props.reportFn({
2844
- index: failure.index,
2845
- pattern: failure.pattern,
2846
- revision: this.props.revision,
2847
- error: failure.error
2848
- });
2849
- }
2850
- render() {
2851
- if (this.state.caught === null) return this.props.children;
2852
- const failure = this.attributeFailure(this.state.caught.error);
2853
- const Boundary = this.props.boundary;
2854
- if (Boundary === void 0) throw { [RENDER_FAILURE]: failure };
2855
- const redirect = getQueryRedirect(failure.error);
2856
- return /* @__PURE__ */ jsx(RouteBoundary, {
2857
- index: this.props.index,
2858
- pattern: this.props.pattern,
2859
- revision: this.props.revision,
2860
- boundary: void 0,
2861
- reportFn: this.props.reportFn,
2862
- children: redirect === void 0 ? /* @__PURE__ */ jsx(Boundary, { error: projectRouteError({
2863
- ...failure,
2864
- source: "render"
2865
- }) }) : /* @__PURE__ */ jsx(RedirectObservation, {
2866
- redirect,
2867
- boundary: Boundary,
2868
- pattern: failure.pattern
2869
- })
2870
- });
2871
- }
2872
- };
2873
- /** The nearest authored boundary strictly above an error position, or the application boundary outside the route tree. */
2874
- function selectBoundary(frame, application) {
2875
- const error = frame.error;
2876
- if (error === null) return null;
2877
- for (let index = error.index - 1; index >= 0; index--) {
2878
- const entry = frame.entries[index];
2879
- const boundary = (entry?.view)?.descendantErrorBoundary;
2880
- if (entry !== void 0 && boundary !== void 0) return {
2881
- index,
2882
- component: boundary
2883
- };
2884
- }
2885
- return {
2886
- index: -1,
2887
- component: application.defaultErrorBoundary
2888
- };
2889
- }
2890
- /** An owning view's Outlet contains its descendant boundary, not the owning component. */
2891
- function RouteOutlet(props) {
2892
- const application = useApplication();
2893
- const frame = useAtomValue(committedFrameAtom);
2894
- const boundary = frame === null ? null : selectBoundary(frame, application);
2895
- const scrollIntent = frame?.scrollIntent;
2896
- const revision = frame?.revision ?? 0;
2897
- const scrollDepth = boundary === null ? scrollIntent?.entryIndex : boundary.index + 1;
2898
- useLayoutEffect(() => {
2899
- if (scrollIntent === void 0 || scrollDepth !== props.index) return;
2900
- const applied = applyScroll(scrollIntent.target);
2901
- if (Result.isFailure(applied)) application.coordinator.publishEvent({
2902
- _tag: "FailureReported",
2903
- source: "navigation",
2904
- cause: Cause.die(applied.failure)
2905
- });
2906
- }, [
2907
- application,
2908
- props.index,
2909
- revision,
2910
- scrollDepth,
2911
- scrollIntent
2912
- ]);
2913
- if (frame === null) return null;
2914
- const ownerIndex = props.index - 1;
2915
- const owner = frame.entries[ownerIndex];
2916
- const rendersFailure = boundary !== null && boundary.index === ownerIndex;
2917
- const ownerView = owner?.view;
2918
- const Boundary = boundary?.component;
2919
- return /* @__PURE__ */ jsx(RouteBoundary, {
2920
- index: ownerIndex < 0 ? void 0 : ownerIndex,
2921
- pattern: owner?.pattern,
2922
- revision: frame.revision,
2923
- boundary: rendersFailure ? void 0 : ownerIndex < 0 ? application.defaultErrorBoundary : ownerView?.descendantErrorBoundary,
2924
- reportFn: application.coordinator.reportRenderFailure,
2925
- children: rendersFailure && Boundary !== void 0 && frame.error !== null ? /* @__PURE__ */ jsx(Boundary, { error: projectRouteError(frame.error) }) : /* @__PURE__ */ jsx(EntryAtIndex, { index: props.index })
2926
- }, rendersFailure ? "failure" : "children");
2927
- }
2928
- /** Renders one successful position. Its own rendering failures propagate to an enclosing Outlet. */
2929
- function EntryAtIndex(props) {
2930
- const frame = useAtomValue(committedFrameAtom);
2931
- const index = props.index;
2932
- const entry = frame?.entries[index];
2933
- const Outlet = useMemo(() => {
2934
- return function Outlet() {
2935
- return /* @__PURE__ */ jsx(RouteOutlet, { index: index + 1 });
2936
- };
2937
- }, [index]);
2938
- if (frame === null || entry === void 0) return null;
2939
- const view = entry.view;
2940
- if (view === void 0) return /* @__PURE__ */ jsx(RouteOutlet, { index: index + 1 });
2941
- return /* @__PURE__ */ jsx(MatchedView, {
2942
- entry,
2943
- frame,
2944
- index,
2945
- view,
2946
- Outlet
2947
- }, entry.pattern);
2948
- }
2949
- function MatchedView(props) {
2950
- const application = useApplication();
2951
- const [routeContext] = useState(() => {
2952
- return createRouteContext(props.index, props.entry);
2953
- });
2954
- const activation = props.entry.activation;
2955
- const viewProps = {
2956
- ...routeContext,
2957
- Outlet: props.Outlet,
2958
- clientRuntime: activation === void 0 ? clientRuntime : activation.clientRuntime,
2959
- universalRuntime: activation === void 0 ? universalRuntime : activation.universalRuntime
2960
- };
2961
- const View = props.view.component;
2962
- return /* @__PURE__ */ jsx(RouteBoundary, {
2963
- index: props.index,
2964
- pattern: props.entry.pattern,
2965
- revision: props.frame.revision,
2966
- boundary: void 0,
2967
- reportFn: application.coordinator.reportRenderFailure,
2968
- children: /* @__PURE__ */ jsx(View, { ...viewProps })
2969
- });
2970
- }
2971
- /** The application boundary encloses every route. */
2972
- function RootOutlet() {
2973
- return /* @__PURE__ */ jsx(RouteOutlet, { index: 0 });
2974
- }
2975
- //#endregion
2976
- //#region src/client/react/application.tsx
2977
- function makeClientApplication(options) {
2978
- let root;
2979
- function renderIntoRoot(rootElement, node) {
2980
- if (root === void 0) root = createRoot(rootElement);
2981
- root.render(node);
2982
- }
2983
- function Providers(props) {
2984
- return /* @__PURE__ */ jsx(RegistryContext.Provider, {
2985
- value: application.core.registry,
2986
- children: /* @__PURE__ */ jsx(ApplicationProvider, {
2987
- value: {
2988
- coordinator: application.coordinator,
2989
- defaultErrorBoundary: options.defaultErrorBoundary,
2990
- defaultLinkProps: options.defaultLinkProps
2991
- },
2992
- children: props.children
2993
- })
2994
- });
2995
- }
2996
- const application = createClientApplication({
2997
- options,
2998
- scheduleTaskFn: scheduleTask,
2999
- renderFn: (rootElement) => {
3000
- renderIntoRoot(rootElement, /* @__PURE__ */ jsx(Providers, { children: /* @__PURE__ */ jsx(RootOutlet, {}) }));
3001
- },
3002
- disposeRendererFn: () => {
3003
- const mounted = root;
3004
- root = void 0;
3005
- mounted?.unmount();
3006
- }
3007
- });
3008
- async function boot() {
3009
- const exit = await application.boot();
3010
- if (Exit.isSuccess(exit)) return exit;
3011
- application.core.publishEvent({
3012
- _tag: "FailureReported",
3013
- source: "navigation",
3014
- cause: exit.cause
3015
- });
3016
- const rendered = Result.try(() => {
3017
- const rootElement = document.getElementById(DOCUMENT_PROTOCOL.elementIds.root);
3018
- const Boundary = options.defaultErrorBoundary;
3019
- if (rootElement !== null && !application.checkDisposed()) renderIntoRoot(rootElement, /* @__PURE__ */ jsx(Providers, { children: /* @__PURE__ */ jsx(Boundary, { error: {
3020
- source: "boot",
3021
- cause: exit.cause,
3022
- status: 500,
3023
- clientMessage: Cause.hasInterruptsOnly(exit.cause) ? "The application stopped before it started." : "The application could not start."
3024
- } }) }));
3025
- });
3026
- if (Result.isFailure(rendered)) application.core.publishEvent({
3027
- _tag: "FailureReported",
3028
- source: "render",
3029
- cause: Cause.die(rendered.failure)
3030
- });
3031
- return exit;
3032
- }
3033
- return {
3034
- _tag: "ClientApplication",
3035
- boot,
3036
- dispose: () => application.dispose()
3037
- };
3038
- }
3039
- const ClientApplication = { make: makeClientApplication };
3040
- //#endregion
3041
- //#region src/client/react/hooks.ts
3042
- /** Reads a projection; without a selector the value itself is the selection. */
3043
- function useProjection(atom, selectFn) {
3044
- return useAtomValue(atom, selectFn === void 0 ? identity : selectFn);
3045
- }
3046
- /** Keeps the browser URL synchronized with one typed React target. */
3047
- function useRouteSynchronization(input) {
3048
- const application = useApplication();
3049
- const committedHref = useAtomValue(routeStateAtom, (route) => Option.isSome(route) ? route.value.href : void 0);
3050
- const resolvedTarget = toHref(input);
3051
- const targetReference = useRef(resolvedTarget);
3052
- targetReference.current = resolvedTarget;
3053
- const targetIdentity = Result.isSuccess(resolvedTarget) ? `success\0${resolvedTarget.success}` : `failure\0${input.pattern}\0${resolvedTarget.failure.message}`;
3054
- const enabled = input.enabled;
3055
- const debounceDelay = input.debounceDelay;
3056
- const replace = input.replace;
3057
- const scrollToTop = input.scrollToTop;
3058
- const scrollToFragment = input.scrollToFragment;
3059
- const skipWorkIndicator = input.skipWorkIndicator;
3060
- useEffect(() => {
3061
- if (enabled === false) return;
3062
- const fiber = application.coordinator.fork(Effect.ignore(Effect.flatMap(readSynchronizationTarget({
3063
- coordinator: application.coordinator,
3064
- href: targetReference.current,
3065
- options: {
3066
- ...replace === void 0 ? {} : { replace },
3067
- ...scrollToTop === void 0 ? {} : { scrollToTop },
3068
- ...scrollToFragment === void 0 ? {} : { scrollToFragment },
3069
- ...skipWorkIndicator === void 0 ? {} : { skipWorkIndicator }
3070
- }
3071
- }), (target) => {
3072
- return runSynchronizationTarget({
3073
- coordinator: application.coordinator,
3074
- target,
3075
- debounceDelay
3076
- });
3077
- })));
3078
- return () => fiber.interruptUnsafe();
3079
- }, [
3080
- application.coordinator,
3081
- committedHref,
3082
- debounceDelay,
3083
- enabled,
3084
- replace,
3085
- scrollToTop,
3086
- scrollToFragment,
3087
- skipWorkIndicator,
3088
- targetIdentity
3089
- ]);
3090
- }
3091
- function useLoaderData(input, selectFn) {
3092
- return useProjection(getLoaderDataAtom(input), selectFn);
3093
- }
3094
- function useClientLoaderData(input, selectFn) {
3095
- return useProjection(getClientLoaderDataAtom(input), selectFn);
3096
- }
3097
- function useRouteMatch(input, selectFn) {
3098
- return useProjection(getRouteMatchAtom(input), selectFn);
3099
- }
3100
- function useRouteState(selectFn) {
3101
- return useProjection(routeStateAtom, selectFn);
3102
- }
3103
- function useNavigationState(selectFn) {
3104
- return useProjection(navigationStateAtom, selectFn);
3105
- }
3106
- function useRevalidationState(selectFn) {
3107
- return useProjection(revalidationStateAtom, selectFn);
3108
- }
3109
- function useWorkState(selectFn) {
3110
- return useProjection(workStateAtom, selectFn);
3111
- }
3112
- function useBuildMismatch(selectFn) {
3113
- return useProjection(buildMismatchAtom, selectFn);
3114
- }
3115
- //#endregion
3116
- //#region src/client/react/link.tsx
3117
- const NO_LINK_STATE = Atom.make({
3118
- active: {
3119
- exact: false,
3120
- ancestor: false
3121
- },
3122
- pending: {
3123
- exact: false,
3124
- ancestor: false
3125
- }
3126
- });
3127
- const ATTRIBUTES = DOCUMENT_PROTOCOL.attributes;
3128
- /** The active and pending facts of a target under its match rules; an invalid target follows the applicable route boundary. */
3129
- function useLinkState(input) {
3130
- const href = toHref(input);
3131
- if (Result.isFailure(href)) throw href.failure;
3132
- return useAtomValue(getLinkStateAtomFor({
3133
- href: href.success,
3134
- pattern: input.pattern,
3135
- rules: input.matchRules
3136
- }));
3137
- }
3138
- function LinkComponent(rawProps) {
3139
- const application = useApplication();
3140
- const props = {
3141
- ...application.defaultLinkProps,
3142
- ...rawProps
3143
- };
3144
- const { href: _href, pattern, params: _params, splatValues: _splatValues, input: _input, hash: _hash, prefetch, prefetchDelayMs, visitOnPointerDown, matchRules, replace, scrollToTop, scrollToFragment, historyState, skipWorkIndicator, children, ...anchorProps } = props;
3145
- const resolved = toHref(props);
3146
- if (Result.isFailure(resolved)) throw resolved.failure;
3147
- const href = resolved.success;
3148
- const external = Result.map(Result.try(() => new URL(href, window.location.href)), (url) => {
3149
- return !/^https?:$/.test(url.protocol) || url.origin !== window.location.origin;
3150
- });
3151
- const isExternal = Result.getOrElse(external, () => true);
3152
- const state = useAtomValue(isExternal || matchRules?.skip === true ? NO_LINK_STATE : getLinkStateAtomFor({
3153
- href,
3154
- pattern,
3155
- rules: matchRules
3156
- }));
3157
- const wantsPrefetch = prefetch === "intent" && !isExternal;
3158
- const intent = useMemo(() => {
3159
- return createLinkIntent({
3160
- coordinator: application.coordinator,
3161
- getHrefFn: () => href,
3162
- skipWorkIndicator: skipWorkIndicator === true
3163
- });
3164
- }, [
3165
- application.coordinator,
3166
- href,
3167
- skipWorkIndicator
3168
- ]);
3169
- useEffect(() => () => intent.reset(), [intent]);
3170
- /** Whether the browser would handle this activation itself. */
3171
- function leavesToBrowser(event) {
3172
- return isExternal || event.defaultPrevented || anchorProps.download !== void 0 || !allowsInterception(event, anchorProps.target);
3173
- }
3174
- function navigateToLinkHref() {
3175
- application.coordinator.fork(Effect.ignore(application.coordinator.navigation.navigate(href, {
3176
- ...replace === void 0 ? {} : { replace },
3177
- ...scrollToTop === void 0 ? {} : { scrollToTop },
3178
- ...scrollToFragment === void 0 ? {} : { scrollToFragment },
3179
- ...historyState === void 0 ? {} : { historyState },
3180
- ...skipWorkIndicator === void 0 ? {} : { skipWorkIndicator }
3181
- })));
3182
- }
3183
- function handleClick(event) {
3184
- anchorProps.onClick?.(event);
3185
- if (leavesToBrowser(event)) return;
3186
- event.preventDefault();
3187
- navigateToLinkHref();
3188
- }
3189
- function handlePointerDown(event) {
3190
- anchorProps.onPointerDown?.(event);
3191
- if (visitOnPointerDown !== true || event.pointerType !== "mouse" && event.pointerType !== "pen" || leavesToBrowser(event)) return;
3192
- event.preventDefault();
3193
- event.currentTarget.addEventListener("click", (click) => click.preventDefault(), { once: true });
3194
- navigateToLinkHref();
3195
- }
3196
- const attributes = {};
3197
- if (!isExternal && matchRules?.skip !== true) {
3198
- if (state.active.exact) {
3199
- attributes[ATTRIBUTES.linkActiveExact] = "";
3200
- attributes["aria-current"] = anchorProps["aria-current"] === void 0 ? "page" : anchorProps["aria-current"];
3201
- }
3202
- if (state.active.ancestor) attributes[ATTRIBUTES.linkActiveAncestor] = "";
3203
- if (state.pending.exact) attributes[ATTRIBUTES.linkPendingExact] = "";
3204
- if (state.pending.ancestor) attributes[ATTRIBUTES.linkPendingAncestor] = "";
3205
- }
3206
- return /* @__PURE__ */ jsx("a", {
3207
- ...anchorProps,
3208
- ...attributes,
3209
- href,
3210
- onClick: handleClick,
3211
- onPointerDown: handlePointerDown,
3212
- onPointerEnter: (event) => {
3213
- if (wantsPrefetch) intent.begin("pointer", prefetchDelayMs);
3214
- anchorProps.onPointerEnter?.(event);
3215
- },
3216
- onPointerLeave: (event) => {
3217
- if (wantsPrefetch) intent.end("pointer");
3218
- anchorProps.onPointerLeave?.(event);
3219
- },
3220
- onFocus: (event) => {
3221
- if (wantsPrefetch) intent.begin("focus", prefetchDelayMs);
3222
- anchorProps.onFocus?.(event);
3223
- },
3224
- onBlur: (event) => {
3225
- if (wantsPrefetch) intent.end("focus");
3226
- anchorProps.onBlur?.(event);
3227
- },
3228
- onTouchCancel: (event) => {
3229
- if (wantsPrefetch) intent.reset();
3230
- anchorProps.onTouchCancel?.(event);
3231
- },
3232
- children
3233
- });
3234
- }
3235
- const Link = memo(LinkComponent);
3236
- //#endregion
3237
- export { ClientApplication, Link, UiView, useBuildMismatch, useClientLoaderData, useLinkState, useLoaderData, useNavigationState, useRevalidationState, useRouteMatch, useRouteState, useRouteSynchronization, useWorkState };