vorma 0.83.0 → 0.85.0-pre.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,7 +5,7 @@ import { findNestedMatches as findNestedMatches2 } from "vorma/kit/matcher/find-
5
5
  import { getIsGETRequest as getIsGETRequest2 } from "vorma/kit/url";
6
6
 
7
7
  // internal/framework/_typescript/client/src/vorma_ctx/vorma_ctx.ts
8
- var VORMA_SYMBOL = Symbol.for("__vorma_internal__");
8
+ var VORMA_SYMBOL = /* @__PURE__ */ Symbol.for("__vorma_internal__");
9
9
  function __getVormaClientGlobal() {
10
10
  const dangerousGlobalThis = globalThis;
11
11
  function get(key) {
@@ -1139,8 +1139,114 @@ async function __reRenderAppInner(props) {
1139
1139
  }
1140
1140
 
1141
1141
  // internal/framework/_typescript/client/src/client.ts
1142
+ function hasServerLoaderRemoval(ctx) {
1143
+ for (const pattern of ctx.currentMatchedPatterns) {
1144
+ const hasServerLoader = ctx.routeManifest[pattern] === 1;
1145
+ if (hasServerLoader) {
1146
+ const stillMatched = ctx.matchResult.matches.some(
1147
+ (m) => m.registeredPattern.originalPattern === pattern
1148
+ );
1149
+ if (!stillMatched) {
1150
+ return true;
1151
+ }
1152
+ }
1153
+ }
1154
+ return false;
1155
+ }
1156
+ function hasNewClientLoader(ctx) {
1157
+ for (const m of ctx.matchResult.matches) {
1158
+ const pattern = m.registeredPattern.originalPattern;
1159
+ const hasClientLoader = !!ctx.patternToWaitFnMap[pattern];
1160
+ const wasAlreadyMatched = ctx.currentMatchedPatterns.includes(pattern);
1161
+ if (hasClientLoader && !wasAlreadyMatched) {
1162
+ return true;
1163
+ }
1164
+ }
1165
+ return false;
1166
+ }
1167
+ function findOutermostLoaderIndex(ctx) {
1168
+ for (let i = ctx.matchResult.matches.length - 1; i >= 0; i--) {
1169
+ const match = ctx.matchResult.matches[i];
1170
+ if (!match) continue;
1171
+ const pattern = match.registeredPattern.originalPattern;
1172
+ const hasServerLoader = ctx.routeManifest[pattern] === 1;
1173
+ const hasClientLoader = !!ctx.patternToWaitFnMap[pattern];
1174
+ if (hasServerLoader || hasClientLoader) {
1175
+ return i;
1176
+ }
1177
+ }
1178
+ return -1;
1179
+ }
1180
+ function didSearchParamsChange(ctx) {
1181
+ const currentUrlObj = new URL(window.location.href);
1182
+ const currentParamsSorted = Array.from(
1183
+ currentUrlObj.searchParams.entries()
1184
+ ).sort();
1185
+ const targetParamsSorted = Array.from(
1186
+ ctx.url.searchParams.entries()
1187
+ ).sort();
1188
+ return !jsonDeepEquals2(currentParamsSorted, targetParamsSorted);
1189
+ }
1190
+ function didOutermostParamsChange(ctx, outermostLoaderIndex) {
1191
+ const outermostMatch = ctx.matchResult.matches[outermostLoaderIndex];
1192
+ if (!outermostMatch) return false;
1193
+ for (const seg of outermostMatch.registeredPattern.normalizedSegments) {
1194
+ if (seg.segType === "dynamic") {
1195
+ const paramName = seg.normalizedVal.substring(1);
1196
+ if (ctx.matchResult.params[paramName] !== ctx.currentParams[paramName]) {
1197
+ return true;
1198
+ }
1199
+ }
1200
+ }
1201
+ const hasSplat = outermostMatch.registeredPattern.lastSegType === "splat";
1202
+ if (hasSplat) {
1203
+ if (!jsonDeepEquals2(ctx.matchResult.splatValues, ctx.currentSplatValues)) {
1204
+ return true;
1205
+ }
1206
+ }
1207
+ return false;
1208
+ }
1209
+ function buildSkipResult(ctx) {
1210
+ const importURLs = [];
1211
+ const exportKeys = [];
1212
+ const loadersData = [];
1213
+ for (let i = 0; i < ctx.matchResult.matches.length; i++) {
1214
+ const match = ctx.matchResult.matches[i];
1215
+ if (!match) continue;
1216
+ const pattern = match.registeredPattern.originalPattern;
1217
+ const moduleInfo = ctx.clientModuleMap[pattern];
1218
+ if (!moduleInfo) {
1219
+ return { canSkip: false };
1220
+ }
1221
+ importURLs.push(moduleInfo.importURL);
1222
+ exportKeys.push(moduleInfo.exportKey);
1223
+ const hasServerLoader = ctx.routeManifest[pattern] === 1;
1224
+ if (!hasServerLoader) {
1225
+ loadersData.push(void 0);
1226
+ } else {
1227
+ const currentPatternIndex = ctx.currentMatchedPatterns.indexOf(pattern);
1228
+ if (currentPatternIndex === -1) {
1229
+ return { canSkip: false };
1230
+ }
1231
+ loadersData.push(ctx.currentLoadersData[currentPatternIndex]);
1232
+ }
1233
+ }
1234
+ return {
1235
+ canSkip: true,
1236
+ matchResult: ctx.matchResult,
1237
+ importURLs,
1238
+ exportKeys,
1239
+ loadersData
1240
+ };
1241
+ }
1142
1242
  var NavigationStateManager = class {
1143
- _navigations = /* @__PURE__ */ new Map();
1243
+ // Single slot for active user/browser/redirect navigation
1244
+ _activeNavigation = null;
1245
+ // Separate cache for prefetches (can have multiple to different URLs)
1246
+ _prefetchCache = /* @__PURE__ */ new Map();
1247
+ // Single slot for pending revalidation (at most one, coalesced)
1248
+ _pendingRevalidation = null;
1249
+ // Submissions tracked separately
1144
1250
  _submissions = /* @__PURE__ */ new Map();
1145
1251
  lastDispatchedStatus = null;
1146
1252
  dispatchStatusEventDebounced;
@@ -1153,22 +1259,49 @@ var NavigationStateManager = class {
1153
1259
  async navigate(props) {
1154
1260
  const control = this.beginNavigation(props);
1155
1261
  try {
1156
- const result = await control.promise;
1157
- if (!result) {
1158
- return { didNavigate: false };
1159
- }
1160
- const targetUrl = new URL(props.href, window.location.href).href;
1161
- const entry = this._navigations.get(targetUrl);
1162
- if (!entry) {
1163
- return { didNavigate: false };
1164
- }
1165
- if (entry.intent === "navigate" || entry.intent === "revalidate") {
1166
- const now = Date.now();
1167
- lastTriggeredNavOrRevalidateTimestampMS = now;
1168
- }
1169
- await this.processNavigationResult(result, entry);
1170
- if (entry.intent === "none" && entry.type === "prefetch") {
1171
- return { didNavigate: false };
1262
+ const outcome = await control.promise;
1263
+ switch (outcome.type) {
1264
+ case "aborted":
1265
+ return { didNavigate: false };
1266
+ case "redirect": {
1267
+ const targetUrl = new URL(props.href, window.location.href).href;
1268
+ const entry = this.findNavigationEntry(targetUrl);
1269
+ if (!entry) {
1270
+ return { didNavigate: false };
1271
+ }
1272
+ if (entry.type === "prefetch" && entry.intent === "none") {
1273
+ this.deleteNavigation(targetUrl);
1274
+ return { didNavigate: false };
1275
+ }
1276
+ this.deleteNavigation(targetUrl);
1277
+ await effectuateRedirectDataResult(
1278
+ outcome.redirectData,
1279
+ props.redirectCount || 0,
1280
+ props
1281
+ );
1282
+ return { didNavigate: false };
1283
+ }
1284
+ case "success": {
1285
+ const targetUrl = new URL(props.href, window.location.href).href;
1286
+ const entry = this.findNavigationEntry(targetUrl);
1287
+ if (!entry) {
1288
+ return { didNavigate: false };
1289
+ }
1290
+ if (entry.intent === "navigate" || entry.intent === "revalidate") {
1291
+ lastTriggeredNavOrRevalidateTimestampMS = Date.now();
1292
+ }
1293
+ await this.processSuccessfulNavigation(outcome, entry);
1294
+ if (entry.intent === "none" && entry.type === "prefetch") {
1295
+ return { didNavigate: false };
1296
+ }
1297
+ return { didNavigate: true };
1298
+ }
1299
+ default: {
1300
+ const _exhaustive = outcome;
1301
+ throw new Error(
1302
+ `Unexpected navigation outcome type: ${_exhaustive.type}`
1303
+ );
1304
+ }
1172
1305
  }
1173
1306
  } catch (error) {
1174
1307
  const targetUrl = new URL(props.href, window.location.href).href;
@@ -1178,47 +1311,72 @@ var NavigationStateManager = class {
1178
1311
  }
1179
1312
  return { didNavigate: false };
1180
1313
  }
1181
- return { didNavigate: true };
1182
1314
  }
1183
1315
  beginNavigation(props) {
1184
- const existing = this._navigations.get(
1185
- new URL(props.href, window.location.href).href
1186
- );
1316
+ const targetUrl = new URL(props.href, window.location.href).href;
1187
1317
  switch (props.navigationType) {
1188
1318
  case "userNavigation":
1189
- return this.beginUserNavigation(props, existing);
1319
+ return this.beginUserNavigation(props, targetUrl);
1190
1320
  case "prefetch":
1191
- return this.beginPrefetch(props, existing);
1321
+ return this.beginPrefetch(props, targetUrl);
1192
1322
  case "revalidation":
1193
1323
  return this.beginRevalidation(props);
1194
1324
  case "browserHistory":
1195
1325
  case "redirect":
1196
1326
  default:
1197
- return this.createNavigation(props, "navigate");
1327
+ return this.createActiveNavigation(props, "navigate");
1198
1328
  }
1199
1329
  }
1200
- beginUserNavigation(props, existing) {
1201
- const targetUrl = new URL(props.href, window.location.href).href;
1202
- this.abortAllNavigationsExcept(targetUrl);
1203
- if (existing) {
1204
- if (existing.type === "prefetch") {
1205
- this.upgradeNavigation(targetUrl, {
1206
- type: "userNavigation",
1207
- intent: "navigate",
1208
- scrollToTop: props.scrollToTop,
1209
- replace: props.replace,
1210
- state: props.state
1211
- });
1212
- return existing.control;
1330
+ beginUserNavigation(props, targetUrl) {
1331
+ if (this._activeNavigation && this._activeNavigation.targetUrl !== targetUrl) {
1332
+ this._activeNavigation.control.abortController?.abort();
1333
+ this._activeNavigation = null;
1334
+ }
1335
+ for (const [url, prefetch] of this._prefetchCache.entries()) {
1336
+ if (url !== targetUrl) {
1337
+ prefetch.control.abortController?.abort();
1338
+ this._prefetchCache.delete(url);
1213
1339
  }
1214
- return existing.control;
1215
1340
  }
1216
- return this.createNavigation(props, "navigate");
1341
+ if (this._pendingRevalidation && this._pendingRevalidation.targetUrl !== targetUrl) {
1342
+ this._pendingRevalidation.control.abortController?.abort();
1343
+ this._pendingRevalidation = null;
1344
+ }
1345
+ if (this._activeNavigation?.targetUrl === targetUrl) {
1346
+ return this._activeNavigation.control;
1347
+ }
1348
+ const existingPrefetch = this._prefetchCache.get(targetUrl);
1349
+ if (existingPrefetch) {
1350
+ this._prefetchCache.delete(targetUrl);
1351
+ existingPrefetch.type = "userNavigation";
1352
+ existingPrefetch.intent = "navigate";
1353
+ existingPrefetch.scrollToTop = props.scrollToTop;
1354
+ existingPrefetch.replace = props.replace;
1355
+ existingPrefetch.state = props.state;
1356
+ this._activeNavigation = existingPrefetch;
1357
+ this.scheduleStatusUpdate();
1358
+ return existingPrefetch.control;
1359
+ }
1360
+ if (this._pendingRevalidation?.targetUrl === targetUrl) {
1361
+ this._pendingRevalidation.type = "userNavigation";
1362
+ this._pendingRevalidation.intent = "navigate";
1363
+ this._pendingRevalidation.scrollToTop = props.scrollToTop;
1364
+ this._pendingRevalidation.replace = props.replace;
1365
+ this._pendingRevalidation.state = props.state;
1366
+ return this._pendingRevalidation.control;
1367
+ }
1368
+ return this.createActiveNavigation(props, "navigate");
1217
1369
  }
1218
- beginPrefetch(props, existing) {
1219
- const targetUrl = new URL(props.href, window.location.href).href;
1220
- if (existing) {
1221
- return existing.control;
1370
+ beginPrefetch(props, targetUrl) {
1371
+ if (this._activeNavigation?.targetUrl === targetUrl) {
1372
+ return this._activeNavigation.control;
1373
+ }
1374
+ const existingPrefetch = this._prefetchCache.get(targetUrl);
1375
+ if (existingPrefetch) {
1376
+ return existingPrefetch.control;
1377
+ }
1378
+ if (this._pendingRevalidation?.targetUrl === targetUrl) {
1379
+ return this._pendingRevalidation.control;
1222
1380
  }
1223
1381
  const currentUrl = new URL(window.location.href);
1224
1382
  const targetUrlObj = new URL(targetUrl);
@@ -1227,29 +1385,23 @@ var NavigationStateManager = class {
1227
1385
  if (currentUrl.href === targetUrlObj.href) {
1228
1386
  return {
1229
1387
  abortController: new AbortController(),
1230
- promise: Promise.resolve(void 0)
1388
+ promise: Promise.resolve({ type: "aborted" })
1231
1389
  };
1232
1390
  }
1233
- return this.createNavigation(props, "none");
1391
+ return this.createPrefetch(props, targetUrl);
1234
1392
  }
1235
1393
  beginRevalidation(props) {
1236
1394
  const currentUrl = window.location.href;
1237
- const existing = this._navigations.get(currentUrl);
1238
- if (existing?.type === "revalidation" && Date.now() - existing.startTime < this.REVALIDATION_COALESCE_MS) {
1239
- return existing.control;
1395
+ if (this._pendingRevalidation && Date.now() - this._pendingRevalidation.startTime < this.REVALIDATION_COALESCE_MS) {
1396
+ return this._pendingRevalidation.control;
1240
1397
  }
1241
- for (const [key, nav] of this._navigations.entries()) {
1242
- if (nav.type === "revalidation") {
1243
- nav.control.abortController?.abort();
1244
- this.deleteNavigation(key);
1245
- }
1398
+ if (this._pendingRevalidation) {
1399
+ this._pendingRevalidation.control.abortController?.abort();
1400
+ this._pendingRevalidation = null;
1246
1401
  }
1247
- return this.createNavigation(
1248
- { ...props, href: currentUrl },
1249
- "revalidate"
1250
- );
1402
+ return this.createRevalidation({ ...props, href: currentUrl });
1251
1403
  }
1252
- createNavigation(props, intent) {
1404
+ createActiveNavigation(props, intent) {
1253
1405
  const controller = new AbortController();
1254
1406
  const targetUrl = new URL(props.href, window.location.href).href;
1255
1407
  const entry = {
@@ -1272,24 +1424,80 @@ var NavigationStateManager = class {
1272
1424
  replace: props.replace,
1273
1425
  state: props.state
1274
1426
  };
1275
- this.setNavigation(targetUrl, entry);
1427
+ this._activeNavigation = entry;
1428
+ this.scheduleStatusUpdate();
1276
1429
  return entry.control;
1277
1430
  }
1278
- upgradeNavigation(href, updates) {
1279
- const existing = this._navigations.get(href);
1280
- if (!existing) return;
1281
- this.setNavigation(href, {
1282
- ...existing,
1283
- ...updates
1284
- });
1431
+ createPrefetch(props, targetUrl) {
1432
+ const controller = new AbortController();
1433
+ const entry = {
1434
+ control: {
1435
+ abortController: controller,
1436
+ promise: this.fetchRouteData(controller, props).catch(
1437
+ (error) => {
1438
+ this._prefetchCache.delete(targetUrl);
1439
+ throw error;
1440
+ }
1441
+ )
1442
+ },
1443
+ type: "prefetch",
1444
+ intent: "none",
1445
+ phase: "fetching",
1446
+ startTime: Date.now(),
1447
+ targetUrl,
1448
+ originUrl: window.location.href,
1449
+ scrollToTop: props.scrollToTop,
1450
+ replace: props.replace,
1451
+ state: props.state
1452
+ };
1453
+ this._prefetchCache.set(targetUrl, entry);
1454
+ return entry.control;
1285
1455
  }
1286
- transitionPhase(href, phase) {
1287
- const existing = this._navigations.get(href);
1288
- if (!existing) return;
1289
- this.setNavigation(href, {
1290
- ...existing,
1291
- phase
1292
- });
1456
+ createRevalidation(props) {
1457
+ const controller = new AbortController();
1458
+ const targetUrl = new URL(props.href, window.location.href).href;
1459
+ const entry = {
1460
+ control: {
1461
+ abortController: controller,
1462
+ promise: this.fetchRouteData(controller, props).catch(
1463
+ (error) => {
1464
+ if (this._pendingRevalidation?.targetUrl === targetUrl) {
1465
+ this._pendingRevalidation = null;
1466
+ this.scheduleStatusUpdate();
1467
+ }
1468
+ throw error;
1469
+ }
1470
+ )
1471
+ },
1472
+ type: "revalidation",
1473
+ intent: "revalidate",
1474
+ phase: "fetching",
1475
+ startTime: Date.now(),
1476
+ targetUrl,
1477
+ originUrl: window.location.href,
1478
+ scrollToTop: props.scrollToTop,
1479
+ replace: props.replace,
1480
+ state: props.state
1481
+ };
1482
+ this._pendingRevalidation = entry;
1483
+ this.scheduleStatusUpdate();
1484
+ return entry.control;
1485
+ }
1486
+ transitionPhase(targetUrl, phase) {
1487
+ if (this._activeNavigation?.targetUrl === targetUrl) {
1488
+ this._activeNavigation.phase = phase;
1489
+ this.scheduleStatusUpdate();
1490
+ return;
1491
+ }
1492
+ const prefetch = this._prefetchCache.get(targetUrl);
1493
+ if (prefetch) {
1494
+ prefetch.phase = phase;
1495
+ return;
1496
+ }
1497
+ if (this._pendingRevalidation?.targetUrl === targetUrl) {
1498
+ this._pendingRevalidation.phase = phase;
1499
+ this.scheduleStatusUpdate();
1500
+ }
1293
1501
  }
1294
1502
  canSkipServerFetch(targetUrl) {
1295
1503
  const routeManifest = __vormaClientGlobal.get("routeManifest");
@@ -1300,182 +1508,49 @@ var NavigationStateManager = class {
1300
1508
  if (!patternRegistry) {
1301
1509
  return { canSkip: false };
1302
1510
  }
1303
- const patternToWaitFnMap = __vormaClientGlobal.get("patternToWaitFnMap") || {};
1304
1511
  const url = new URL(targetUrl);
1305
1512
  const matchResult = findNestedMatches2(patternRegistry, url.pathname);
1306
1513
  if (!matchResult) {
1307
1514
  return { canSkip: false };
1308
1515
  }
1309
- const clientModuleMap = __vormaClientGlobal.get("clientModuleMap") || {};
1310
- const currentMatchedPatterns = __vormaClientGlobal.get("matchedPatterns") || [];
1311
- const currentParams = __vormaClientGlobal.get("params") || {};
1312
- const currentSplatValues = __vormaClientGlobal.get("splatValues") || [];
1313
- const currentLoadersData = __vormaClientGlobal.get("loadersData") || [];
1314
- for (const pattern of currentMatchedPatterns) {
1315
- const hasServerLoader = routeManifest[pattern] === 1;
1316
- if (hasServerLoader) {
1317
- const stillMatched = matchResult.matches.some(
1318
- (m) => m.registeredPattern.originalPattern === pattern
1319
- );
1320
- if (!stillMatched) {
1321
- return { canSkip: false };
1322
- }
1323
- }
1324
- }
1325
- for (const m of matchResult.matches) {
1326
- const pattern = m.registeredPattern.originalPattern;
1327
- const hasClientLoader = !!patternToWaitFnMap[pattern];
1328
- const wasAlreadyMatched = currentMatchedPatterns.includes(pattern);
1329
- if (hasClientLoader && !wasAlreadyMatched) {
1330
- return { canSkip: false };
1331
- }
1516
+ const ctx = {
1517
+ routeManifest,
1518
+ patternRegistry,
1519
+ patternToWaitFnMap: __vormaClientGlobal.get("patternToWaitFnMap") || {},
1520
+ clientModuleMap: __vormaClientGlobal.get("clientModuleMap") || {},
1521
+ currentMatchedPatterns: __vormaClientGlobal.get("matchedPatterns") || [],
1522
+ currentParams: __vormaClientGlobal.get("params") || {},
1523
+ currentSplatValues: __vormaClientGlobal.get("splatValues") || [],
1524
+ currentLoadersData: __vormaClientGlobal.get("loadersData") || [],
1525
+ url,
1526
+ matchResult
1527
+ };
1528
+ if (hasServerLoaderRemoval(ctx)) {
1529
+ return { canSkip: false };
1332
1530
  }
1333
- let outermostLoaderIndex = -1;
1334
- for (let i = matchResult.matches.length - 1; i >= 0; i--) {
1335
- const match = matchResult.matches[i];
1336
- if (!match) continue;
1337
- const pattern = match.registeredPattern.originalPattern;
1338
- const hasServerLoader = routeManifest[pattern] === 1;
1339
- const hasClientLoader = !!patternToWaitFnMap[pattern];
1340
- if (hasServerLoader || hasClientLoader) {
1341
- outermostLoaderIndex = i;
1342
- break;
1343
- }
1344
- }
1345
- const currentUrlObj = new URL(window.location.href);
1346
- const currentParamsSorted = Array.from(
1347
- currentUrlObj.searchParams.entries()
1348
- ).sort();
1349
- const targetParamsSorted = Array.from(
1350
- url.searchParams.entries()
1351
- ).sort();
1352
- const searchChanged = !jsonDeepEquals2(
1353
- currentParamsSorted,
1354
- targetParamsSorted
1355
- );
1356
- if (searchChanged && outermostLoaderIndex !== -1) {
1531
+ if (hasNewClientLoader(ctx)) {
1357
1532
  return { canSkip: false };
1358
1533
  }
1359
- if (outermostLoaderIndex !== -1) {
1360
- const outermostMatch = matchResult.matches[outermostLoaderIndex];
1361
- if (outermostMatch) {
1362
- for (const seg of outermostMatch.registeredPattern.normalizedSegments) {
1363
- if (seg.segType === "dynamic") {
1364
- const paramName = seg.normalizedVal.substring(1);
1365
- if (matchResult.params[paramName] !== currentParams[paramName]) {
1366
- return { canSkip: false };
1367
- }
1368
- }
1369
- }
1370
- const hasSplat = outermostMatch.registeredPattern.lastSegType === "splat";
1371
- if (hasSplat) {
1372
- if (!jsonDeepEquals2(
1373
- matchResult.splatValues,
1374
- currentSplatValues
1375
- )) {
1376
- return { canSkip: false };
1377
- }
1378
- }
1379
- }
1534
+ const outermostLoaderIndex = findOutermostLoaderIndex(ctx);
1535
+ if (outermostLoaderIndex !== -1 && didSearchParamsChange(ctx)) {
1536
+ return { canSkip: false };
1380
1537
  }
1381
- const importURLs = [];
1382
- const exportKeys = [];
1383
- const loadersData = [];
1384
- for (let i = 0; i < matchResult.matches.length; i++) {
1385
- const match = matchResult.matches[i];
1386
- if (!match) continue;
1387
- const pattern = match.registeredPattern.originalPattern;
1388
- const moduleInfo = clientModuleMap[pattern];
1389
- if (!moduleInfo) {
1390
- return { canSkip: false };
1391
- }
1392
- importURLs.push(moduleInfo.importURL);
1393
- exportKeys.push(moduleInfo.exportKey);
1394
- const hasServerLoader = routeManifest[pattern] === 1;
1395
- if (!hasServerLoader) {
1396
- loadersData.push(void 0);
1397
- } else {
1398
- const currentPatternIndex = currentMatchedPatterns.indexOf(pattern);
1399
- if (currentPatternIndex === -1) {
1400
- return { canSkip: false };
1401
- }
1402
- loadersData.push(currentLoadersData[currentPatternIndex]);
1403
- }
1538
+ if (outermostLoaderIndex !== -1 && didOutermostParamsChange(ctx, outermostLoaderIndex)) {
1539
+ return { canSkip: false };
1404
1540
  }
1405
- return {
1406
- canSkip: true,
1407
- matchResult,
1408
- importURLs,
1409
- exportKeys,
1410
- loadersData
1411
- };
1541
+ return buildSkipResult(ctx);
1412
1542
  }
1413
1543
  async fetchRouteData(controller, props) {
1414
1544
  try {
1415
1545
  const url = new URL(props.href, window.location.href);
1416
1546
  if (props.navigationType !== "revalidation" && props.navigationType !== "action") {
1417
1547
  const skipCheck = this.canSkipServerFetch(url.href);
1418
- if (skipCheck.canSkip && skipCheck.matchResult) {
1419
- const { importURLs, exportKeys, loadersData } = skipCheck;
1420
- const json2 = {
1421
- matchedPatterns: skipCheck.matchResult.matches.map(
1422
- (m) => m.registeredPattern.originalPattern
1423
- ),
1424
- loadersData,
1425
- importURLs,
1426
- exportKeys,
1427
- hasRootData: __vormaClientGlobal.get("hasRootData"),
1428
- params: skipCheck.matchResult.params,
1429
- splatValues: skipCheck.matchResult.splatValues,
1430
- deps: [],
1431
- cssBundles: [],
1432
- outermostServerError: void 0,
1433
- outermostServerErrorIdx: void 0,
1434
- errorExportKeys: [],
1435
- title: void 0,
1436
- metaHeadEls: void 0,
1437
- restHeadEls: void 0,
1438
- activeComponents: void 0
1439
- };
1440
- const response2 = new Response(JSON.stringify(json2), {
1441
- status: 200,
1442
- headers: {
1443
- "Content-Type": "application/json",
1444
- "X-Vorma-Build-Id": __vormaClientGlobal.get("buildID") || "1"
1445
- }
1446
- });
1447
- const currentClientLoadersData = __vormaClientGlobal.get("clientLoadersData") || [];
1448
- const patternToWaitFnMap2 = __vormaClientGlobal.get("patternToWaitFnMap") || {};
1449
- const runningLoaders2 = /* @__PURE__ */ new Map();
1450
- for (let i = 0; i < json2.matchedPatterns.length; i++) {
1451
- const pattern = json2.matchedPatterns[i];
1452
- if (!pattern) continue;
1453
- if (patternToWaitFnMap2[pattern]) {
1454
- const currentMatchedPatterns = __vormaClientGlobal.get("matchedPatterns") || [];
1455
- const currentPatternIndex = currentMatchedPatterns.indexOf(pattern);
1456
- if (currentPatternIndex !== -1 && currentClientLoadersData[currentPatternIndex] !== void 0) {
1457
- runningLoaders2.set(
1458
- pattern,
1459
- Promise.resolve(
1460
- currentClientLoadersData[currentPatternIndex]
1461
- )
1462
- );
1463
- }
1464
- }
1465
- }
1466
- const waitFnPromise2 = completeClientLoaders(
1467
- json2,
1468
- __vormaClientGlobal.get("buildID") || "1",
1469
- runningLoaders2,
1470
- controller.signal
1471
- );
1472
- return {
1473
- response: response2,
1548
+ if (skipCheck.canSkip) {
1549
+ return this.buildClientOnlyOutcome(
1550
+ skipCheck,
1474
1551
  props,
1475
- json: json2,
1476
- cssBundlePromises: [],
1477
- waitFnPromise: waitFnPromise2
1478
- };
1552
+ controller
1553
+ );
1479
1554
  }
1480
1555
  }
1481
1556
  url.searchParams.set(
@@ -1557,7 +1632,7 @@ var NavigationStateManager = class {
1557
1632
  const responseNotOK = !response?.ok && response?.status !== 304;
1558
1633
  if (redirected || !response) {
1559
1634
  controller.abort();
1560
- return void 0;
1635
+ return { type: "aborted" };
1561
1636
  }
1562
1637
  if (responseNotOK) {
1563
1638
  controller.abort();
@@ -1565,7 +1640,7 @@ var NavigationStateManager = class {
1565
1640
  }
1566
1641
  if (redirectData?.status === "should") {
1567
1642
  controller.abort();
1568
- return { response, redirectData, props };
1643
+ return { type: "redirect", redirectData, props };
1569
1644
  }
1570
1645
  if (!json) {
1571
1646
  controller.abort();
@@ -1586,7 +1661,14 @@ var NavigationStateManager = class {
1586
1661
  for (const bundle of json.cssBundles ?? []) {
1587
1662
  cssBundlePromises.push(AssetManager.preloadCSS(bundle));
1588
1663
  }
1589
- return { response, json, props, cssBundlePromises, waitFnPromise };
1664
+ return {
1665
+ type: "success",
1666
+ response,
1667
+ json,
1668
+ props,
1669
+ cssBundlePromises,
1670
+ waitFnPromise
1671
+ };
1590
1672
  } catch (error) {
1591
1673
  if (!isAbortError(error)) {
1592
1674
  logError("Navigation failed", error);
@@ -1594,34 +1676,80 @@ var NavigationStateManager = class {
1594
1676
  throw error;
1595
1677
  }
1596
1678
  }
1597
- async processNavigationResult(result, entry) {
1598
- try {
1599
- if (!result) return;
1600
- if ("redirectData" in result) {
1601
- if (entry.type === "prefetch" && entry.intent === "none") {
1602
- this.deleteNavigation(entry.targetUrl);
1603
- return;
1604
- }
1605
- this.deleteNavigation(entry.targetUrl);
1606
- await effectuateRedirectDataResult(
1607
- result.redirectData,
1608
- result.props.redirectCount || 0,
1609
- result.props
1610
- );
1611
- return;
1679
+ buildClientOnlyOutcome(skipCheck, props, controller) {
1680
+ const { matchResult, importURLs, exportKeys, loadersData } = skipCheck;
1681
+ const json = {
1682
+ matchedPatterns: matchResult.matches.map(
1683
+ (m) => m.registeredPattern.originalPattern
1684
+ ),
1685
+ loadersData,
1686
+ importURLs,
1687
+ exportKeys,
1688
+ hasRootData: __vormaClientGlobal.get("hasRootData"),
1689
+ params: matchResult.params,
1690
+ splatValues: matchResult.splatValues,
1691
+ deps: [],
1692
+ cssBundles: [],
1693
+ outermostServerError: void 0,
1694
+ outermostServerErrorIdx: void 0,
1695
+ errorExportKeys: [],
1696
+ title: void 0,
1697
+ metaHeadEls: void 0,
1698
+ restHeadEls: void 0,
1699
+ activeComponents: void 0
1700
+ };
1701
+ const response = new Response(JSON.stringify(json), {
1702
+ status: 200,
1703
+ headers: {
1704
+ "Content-Type": "application/json",
1705
+ "X-Vorma-Build-Id": __vormaClientGlobal.get("buildID") || "1"
1612
1706
  }
1613
- if (!("json" in result)) {
1614
- logError("Invalid navigation result: no JSON or redirect");
1615
- return;
1707
+ });
1708
+ const currentClientLoadersData = __vormaClientGlobal.get("clientLoadersData") || [];
1709
+ const patternToWaitFnMap = __vormaClientGlobal.get("patternToWaitFnMap") || {};
1710
+ const runningLoaders = /* @__PURE__ */ new Map();
1711
+ for (let i = 0; i < json.matchedPatterns.length; i++) {
1712
+ const pattern = json.matchedPatterns[i];
1713
+ if (!pattern) continue;
1714
+ if (patternToWaitFnMap[pattern]) {
1715
+ const currentMatchedPatterns = __vormaClientGlobal.get("matchedPatterns") || [];
1716
+ const currentPatternIndex = currentMatchedPatterns.indexOf(pattern);
1717
+ if (currentPatternIndex !== -1 && currentClientLoadersData[currentPatternIndex] !== void 0) {
1718
+ runningLoaders.set(
1719
+ pattern,
1720
+ Promise.resolve(
1721
+ currentClientLoadersData[currentPatternIndex]
1722
+ )
1723
+ );
1724
+ }
1616
1725
  }
1726
+ }
1727
+ const waitFnPromise = completeClientLoaders(
1728
+ json,
1729
+ __vormaClientGlobal.get("buildID") || "1",
1730
+ runningLoaders,
1731
+ controller.signal
1732
+ );
1733
+ return {
1734
+ type: "success",
1735
+ response,
1736
+ props,
1737
+ json,
1738
+ cssBundlePromises: [],
1739
+ waitFnPromise
1740
+ };
1741
+ }
1742
+ async processSuccessfulNavigation(outcome, entry) {
1743
+ try {
1744
+ const { response, json, props, cssBundlePromises, waitFnPromise } = outcome;
1617
1745
  const currentBuildID = __vormaClientGlobal.get("buildID");
1618
- const responseBuildID = getBuildIDFromResponse(result.response);
1746
+ const responseBuildID = getBuildIDFromResponse(response);
1619
1747
  if (responseBuildID === currentBuildID) {
1620
1748
  const clientModuleMap = __vormaClientGlobal.get("clientModuleMap") || {};
1621
- const matchedPatterns = result.json.matchedPatterns || [];
1622
- const importURLs = result.json.importURLs || [];
1623
- const exportKeys = result.json.exportKeys || [];
1624
- const errorExportKeys = result.json.errorExportKeys || [];
1749
+ const matchedPatterns = json.matchedPatterns || [];
1750
+ const importURLs = json.importURLs || [];
1751
+ const exportKeys = json.exportKeys || [];
1752
+ const errorExportKeys = json.errorExportKeys || [];
1625
1753
  for (let i = 0; i < matchedPatterns.length; i++) {
1626
1754
  const pattern = matchedPatterns[i];
1627
1755
  const importURL = importURLs[i];
@@ -1636,8 +1764,8 @@ var NavigationStateManager = class {
1636
1764
  }
1637
1765
  }
1638
1766
  __vormaClientGlobal.set("clientModuleMap", clientModuleMap);
1639
- if (result.json.cssBundles && result.json.cssBundles.length > 0) {
1640
- AssetManager.applyCSS(result.json.cssBundles);
1767
+ if (json.cssBundles && json.cssBundles.length > 0) {
1768
+ AssetManager.applyCSS(json.cssBundles);
1641
1769
  }
1642
1770
  }
1643
1771
  if (entry.type === "revalidation") {
@@ -1648,19 +1776,19 @@ var NavigationStateManager = class {
1648
1776
  }
1649
1777
  }
1650
1778
  this.transitionPhase(entry.targetUrl, "waiting");
1651
- if (!this._navigations.has(entry.targetUrl)) {
1779
+ if (!this.findNavigationEntry(entry.targetUrl)) {
1652
1780
  return;
1653
1781
  }
1654
1782
  const oldID = __vormaClientGlobal.get("buildID");
1655
- const newID = getBuildIDFromResponse(result.response);
1783
+ const newID = getBuildIDFromResponse(response);
1656
1784
  if (newID && newID !== oldID) {
1657
1785
  dispatchBuildIDEvent({ newID, oldID });
1658
1786
  }
1659
- const clientLoadersResult = await result.waitFnPromise;
1787
+ const clientLoadersResult = await waitFnPromise;
1660
1788
  setClientLoadersState(clientLoadersResult);
1661
- if (result.cssBundlePromises.length > 0) {
1789
+ if (cssBundlePromises.length > 0) {
1662
1790
  try {
1663
- await Promise.all(result.cssBundlePromises);
1791
+ await Promise.all(cssBundlePromises);
1664
1792
  } catch (error) {
1665
1793
  logError("Error preloading CSS bundles:", error);
1666
1794
  }
@@ -1675,12 +1803,12 @@ var NavigationStateManager = class {
1675
1803
  this.transitionPhase(entry.targetUrl, "rendering");
1676
1804
  try {
1677
1805
  await __reRenderApp({
1678
- json: result.json,
1806
+ json,
1679
1807
  navigationType: entry.type,
1680
1808
  runHistoryOptions: entry.intent === "navigate" ? {
1681
1809
  href: entry.targetUrl,
1682
- scrollStateToRestore: result.props.scrollStateToRestore,
1683
- replace: entry.replace || result.props.replace,
1810
+ scrollStateToRestore: props.scrollStateToRestore,
1811
+ replace: entry.replace || props.replace,
1684
1812
  scrollToTop: entry.scrollToTop,
1685
1813
  state: entry.state
1686
1814
  } : void 0,
@@ -1703,7 +1831,7 @@ var NavigationStateManager = class {
1703
1831
  }
1704
1832
  async submit(url, requestInit, options) {
1705
1833
  const abortController = new AbortController();
1706
- const submissionKey = options?.dedupeKey ? `submission:${options.dedupeKey}` : Symbol("submission");
1834
+ const submissionKey = options?.dedupeKey ? `submission:${options.dedupeKey}` : /* @__PURE__ */ Symbol("submission");
1707
1835
  if (typeof submissionKey === "string") {
1708
1836
  const existing = this._submissions.get(submissionKey);
1709
1837
  if (existing) {
@@ -1775,59 +1903,93 @@ var NavigationStateManager = class {
1775
1903
  this.scheduleStatusUpdate();
1776
1904
  }
1777
1905
  }
1778
- setNavigation(key, entry) {
1779
- this._navigations.set(key, entry);
1780
- this.scheduleStatusUpdate();
1906
+ findNavigationEntry(targetUrl) {
1907
+ if (this._activeNavigation?.targetUrl === targetUrl) {
1908
+ return this._activeNavigation;
1909
+ }
1910
+ const prefetch = this._prefetchCache.get(targetUrl);
1911
+ if (prefetch) {
1912
+ return prefetch;
1913
+ }
1914
+ if (this._pendingRevalidation?.targetUrl === targetUrl) {
1915
+ return this._pendingRevalidation;
1916
+ }
1917
+ return void 0;
1781
1918
  }
1782
1919
  deleteNavigation(key) {
1783
- const result = this._navigations.delete(key);
1784
- if (result) {
1920
+ if (this._activeNavigation?.targetUrl === key) {
1921
+ this._activeNavigation = null;
1785
1922
  this.scheduleStatusUpdate();
1923
+ return true;
1924
+ }
1925
+ if (this._prefetchCache.has(key)) {
1926
+ this._prefetchCache.delete(key);
1927
+ return true;
1786
1928
  }
1787
- return result;
1929
+ if (this._pendingRevalidation?.targetUrl === key) {
1930
+ this._pendingRevalidation = null;
1931
+ this.scheduleStatusUpdate();
1932
+ return true;
1933
+ }
1934
+ return false;
1788
1935
  }
1789
1936
  removeNavigation(key) {
1790
- this.deleteNavigation(key);
1937
+ const entry = this.findNavigationEntry(key);
1938
+ if (entry) {
1939
+ entry.control.abortController?.abort();
1940
+ this.deleteNavigation(key);
1941
+ }
1791
1942
  }
1792
1943
  getNavigation(key) {
1793
- return this._navigations.get(key);
1944
+ return this.findNavigationEntry(key);
1794
1945
  }
1795
1946
  hasNavigation(key) {
1796
- return this._navigations.has(key);
1947
+ return this.findNavigationEntry(key) !== void 0;
1797
1948
  }
1798
1949
  getNavigationsSize() {
1799
- return this._navigations.size;
1950
+ let size = 0;
1951
+ if (this._activeNavigation) size++;
1952
+ size += this._prefetchCache.size;
1953
+ if (this._pendingRevalidation) size++;
1954
+ return size;
1800
1955
  }
1801
1956
  getNavigations() {
1802
- return this._navigations;
1803
- }
1804
- abortAllNavigationsExcept(excludeHref) {
1805
- for (const [href, nav] of this._navigations.entries()) {
1806
- if (href !== excludeHref) {
1807
- nav.control.abortController?.abort();
1808
- this.deleteNavigation(href);
1809
- }
1957
+ const map = /* @__PURE__ */ new Map();
1958
+ if (this._activeNavigation) {
1959
+ map.set(this._activeNavigation.targetUrl, this._activeNavigation);
1960
+ }
1961
+ for (const [key, entry] of this._prefetchCache) {
1962
+ map.set(key, entry);
1810
1963
  }
1964
+ if (this._pendingRevalidation) {
1965
+ map.set(
1966
+ this._pendingRevalidation.targetUrl,
1967
+ this._pendingRevalidation
1968
+ );
1969
+ }
1970
+ return map;
1811
1971
  }
1812
1972
  getStatus() {
1813
- const navigations = Array.from(this._navigations.values());
1814
- const submissions = Array.from(this._submissions.values());
1815
- const isNavigating = navigations.some(
1816
- (nav) => nav.intent === "navigate" && nav.phase !== "complete"
1817
- );
1818
- const isRevalidating = navigations.some(
1819
- (nav) => nav.type === "revalidation" && nav.phase !== "complete"
1820
- );
1821
- const isSubmitting = submissions.some(
1973
+ const isNavigating = this._activeNavigation !== null && this._activeNavigation.intent === "navigate" && this._activeNavigation.phase !== "complete";
1974
+ const isRevalidating = this._pendingRevalidation !== null && this._pendingRevalidation.phase !== "complete";
1975
+ const isSubmitting = Array.from(this._submissions.values()).some(
1822
1976
  (x) => !x.skipGlobalLoadingIndicator
1823
1977
  );
1824
1978
  return { isNavigating, isSubmitting, isRevalidating };
1825
1979
  }
1826
1980
  clearAll() {
1827
- for (const nav of this._navigations.values()) {
1828
- nav.control.abortController?.abort();
1981
+ if (this._activeNavigation) {
1982
+ this._activeNavigation.control.abortController?.abort();
1983
+ this._activeNavigation = null;
1984
+ }
1985
+ for (const prefetch of this._prefetchCache.values()) {
1986
+ prefetch.control.abortController?.abort();
1987
+ }
1988
+ this._prefetchCache.clear();
1989
+ if (this._pendingRevalidation) {
1990
+ this._pendingRevalidation.control.abortController?.abort();
1991
+ this._pendingRevalidation = null;
1829
1992
  }
1830
- this._navigations.clear();
1831
1993
  for (const sub of this._submissions.values()) {
1832
1994
  sub.control.abortController?.abort();
1833
1995
  }
@@ -2228,22 +2390,42 @@ function __makeLinkOnClickFn(callbacks) {
2228
2390
  state: callbacks.state
2229
2391
  });
2230
2392
  if (!control.promise) return;
2231
- const res = await control.promise;
2232
- if (!res) {
2233
- const targetUrl2 = new URL(anchor.href, window.location.href).href;
2234
- navigationStateManager.removeNavigation(targetUrl2);
2235
- return;
2236
- }
2237
- await callbacks.beforeRender?.(e);
2393
+ const outcome = await control.promise;
2238
2394
  const targetUrl = new URL(anchor.href, window.location.href).href;
2239
- const entry = navigationStateManager.getNavigation(targetUrl);
2240
- if (entry) {
2241
- await navigationStateManager["processNavigationResult"](
2242
- res,
2243
- entry
2244
- );
2395
+ switch (outcome.type) {
2396
+ case "aborted":
2397
+ navigationStateManager.removeNavigation(targetUrl);
2398
+ return;
2399
+ case "redirect": {
2400
+ await callbacks.beforeRender?.(e);
2401
+ navigationStateManager.removeNavigation(targetUrl);
2402
+ await effectuateRedirectDataResult(
2403
+ outcome.redirectData,
2404
+ outcome.props.redirectCount || 0,
2405
+ outcome.props
2406
+ );
2407
+ await callbacks.afterRender?.(e);
2408
+ return;
2409
+ }
2410
+ case "success": {
2411
+ await callbacks.beforeRender?.(e);
2412
+ const entry = navigationStateManager.getNavigation(targetUrl);
2413
+ if (entry) {
2414
+ await navigationStateManager.processSuccessfulNavigation(
2415
+ outcome,
2416
+ entry
2417
+ );
2418
+ }
2419
+ await callbacks.afterRender?.(e);
2420
+ return;
2421
+ }
2422
+ default: {
2423
+ const _exhaustive = outcome;
2424
+ throw new Error(
2425
+ `Unexpected outcome type: ${_exhaustive.type}`
2426
+ );
2427
+ }
2245
2428
  }
2246
- await callbacks.afterRender?.(e);
2247
2429
  }
2248
2430
  };
2249
2431
  }