@tanstack/router-core 0.0.1-beta.16 → 0.0.1-beta.160

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 (64) hide show
  1. package/LICENSE +21 -0
  2. package/build/cjs/fileRoute.js +29 -0
  3. package/build/cjs/fileRoute.js.map +1 -0
  4. package/build/cjs/history.js +226 -0
  5. package/build/cjs/history.js.map +1 -0
  6. package/build/cjs/index.js +78 -0
  7. package/build/cjs/{packages/router-core/src/index.js.map → index.js.map} +1 -1
  8. package/build/cjs/{packages/router-core/src/path.js → path.js} +45 -56
  9. package/build/cjs/path.js.map +1 -0
  10. package/build/cjs/{packages/router-core/src/qss.js → qss.js} +10 -16
  11. package/build/cjs/qss.js.map +1 -0
  12. package/build/cjs/route.js +101 -0
  13. package/build/cjs/route.js.map +1 -0
  14. package/build/cjs/router.js +1128 -0
  15. package/build/cjs/router.js.map +1 -0
  16. package/build/cjs/{packages/router-core/src/searchParams.js → searchParams.js} +34 -19
  17. package/build/cjs/searchParams.js.map +1 -0
  18. package/build/cjs/{packages/router-core/src/utils.js → utils.js} +54 -64
  19. package/build/cjs/utils.js.map +1 -0
  20. package/build/esm/index.js +1431 -2089
  21. package/build/esm/index.js.map +1 -1
  22. package/build/stats-html.html +59 -49
  23. package/build/stats-react.json +203 -234
  24. package/build/types/index.d.ts +573 -431
  25. package/build/umd/index.development.js +1667 -2221
  26. package/build/umd/index.development.js.map +1 -1
  27. package/build/umd/index.production.js +13 -2
  28. package/build/umd/index.production.js.map +1 -1
  29. package/package.json +11 -7
  30. package/src/fileRoute.ts +162 -0
  31. package/src/history.ts +292 -0
  32. package/src/index.ts +3 -10
  33. package/src/link.ts +121 -118
  34. package/src/path.ts +37 -17
  35. package/src/qss.ts +1 -2
  36. package/src/route.ts +874 -218
  37. package/src/routeInfo.ts +47 -211
  38. package/src/router.ts +1503 -1023
  39. package/src/searchParams.ts +33 -9
  40. package/src/utils.ts +80 -49
  41. package/build/cjs/_virtual/_rollupPluginBabelHelpers.js +0 -33
  42. package/build/cjs/_virtual/_rollupPluginBabelHelpers.js.map +0 -1
  43. package/build/cjs/node_modules/@babel/runtime/helpers/esm/extends.js +0 -33
  44. package/build/cjs/node_modules/@babel/runtime/helpers/esm/extends.js.map +0 -1
  45. package/build/cjs/node_modules/history/index.js +0 -815
  46. package/build/cjs/node_modules/history/index.js.map +0 -1
  47. package/build/cjs/node_modules/tiny-invariant/dist/esm/tiny-invariant.js +0 -30
  48. package/build/cjs/node_modules/tiny-invariant/dist/esm/tiny-invariant.js.map +0 -1
  49. package/build/cjs/packages/router-core/src/index.js +0 -58
  50. package/build/cjs/packages/router-core/src/path.js.map +0 -1
  51. package/build/cjs/packages/router-core/src/qss.js.map +0 -1
  52. package/build/cjs/packages/router-core/src/route.js +0 -147
  53. package/build/cjs/packages/router-core/src/route.js.map +0 -1
  54. package/build/cjs/packages/router-core/src/routeConfig.js +0 -69
  55. package/build/cjs/packages/router-core/src/routeConfig.js.map +0 -1
  56. package/build/cjs/packages/router-core/src/routeMatch.js +0 -231
  57. package/build/cjs/packages/router-core/src/routeMatch.js.map +0 -1
  58. package/build/cjs/packages/router-core/src/router.js +0 -833
  59. package/build/cjs/packages/router-core/src/router.js.map +0 -1
  60. package/build/cjs/packages/router-core/src/searchParams.js.map +0 -1
  61. package/build/cjs/packages/router-core/src/utils.js.map +0 -1
  62. package/src/frameworks.ts +0 -11
  63. package/src/routeConfig.ts +0 -514
  64. package/src/routeMatch.ts +0 -319
@@ -0,0 +1,1128 @@
1
+ /**
2
+ * @tanstack/router-core/src/index.ts
3
+ *
4
+ * Copyright (c) TanStack
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE.md file in the root directory of this source tree.
8
+ *
9
+ * @license MIT
10
+ */
11
+ 'use strict';
12
+
13
+ Object.defineProperty(exports, '__esModule', { value: true });
14
+
15
+ var reactStore = require('@tanstack/react-store');
16
+ var invariant = require('tiny-invariant');
17
+ var path = require('./path.js');
18
+ var searchParams = require('./searchParams.js');
19
+ var utils = require('./utils.js');
20
+ var history = require('./history.js');
21
+
22
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
23
+
24
+ var invariant__default = /*#__PURE__*/_interopDefaultLegacy(invariant);
25
+
26
+ //
27
+
28
+ const componentTypes = ['component', 'errorComponent', 'pendingComponent'];
29
+ class Router {
30
+ #unsubHistory;
31
+ constructor(options) {
32
+ this.options = {
33
+ defaultPreloadDelay: 50,
34
+ context: undefined,
35
+ ...options,
36
+ stringifySearch: options?.stringifySearch ?? searchParams.defaultStringifySearch,
37
+ parseSearch: options?.parseSearch ?? searchParams.defaultParseSearch
38
+ // fetchServerDataFn: options?.fetchServerDataFn ?? defaultFetchServerDataFn,
39
+ };
40
+
41
+ this.__store = new reactStore.Store(getInitialRouterState(), {
42
+ onUpdate: () => {
43
+ const prev = this.state;
44
+ const next = this.__store.state;
45
+ console.log(Object.values(next.matchesById).find(d => d.status === 'error'));
46
+ const matchesByIdChanged = prev.matchesById !== next.matchesById;
47
+ let matchesChanged;
48
+ let pendingMatchesChanged;
49
+ if (!matchesByIdChanged) {
50
+ matchesChanged = prev.matchIds.length !== next.matchIds.length || prev.matchIds.some((d, i) => d !== next.matchIds[i]);
51
+ pendingMatchesChanged = prev.pendingMatchIds.length !== next.pendingMatchIds.length || prev.pendingMatchIds.some((d, i) => d !== next.pendingMatchIds[i]);
52
+ }
53
+ if (matchesByIdChanged || matchesChanged) {
54
+ next.matches = next.matchIds.map(id => {
55
+ return next.matchesById[id];
56
+ });
57
+ }
58
+ if (matchesByIdChanged || pendingMatchesChanged) {
59
+ next.pendingMatches = next.pendingMatchIds.map(id => {
60
+ return next.matchesById[id];
61
+ });
62
+ }
63
+ next.isFetching = [...next.matches, ...next.pendingMatches].some(d => d.isFetching);
64
+ this.state = next;
65
+ },
66
+ defaultPriority: 'low'
67
+ });
68
+ this.state = this.__store.state;
69
+ this.update(options);
70
+ const next = this.buildNext({
71
+ hash: true,
72
+ fromCurrent: true,
73
+ search: true,
74
+ state: true
75
+ });
76
+ if (this.state.location.href !== next.href) {
77
+ this.#commitLocation({
78
+ ...next,
79
+ replace: true
80
+ });
81
+ }
82
+ }
83
+ reset = () => {
84
+ this.__store.setState(s => Object.assign(s, getInitialRouterState()));
85
+ };
86
+ mount = () => {
87
+ // If the router matches are empty, start loading the matches
88
+ // if (!this.state.matches.length) {
89
+ this.safeLoad();
90
+ // }
91
+ };
92
+
93
+ update = opts => {
94
+ this.options = {
95
+ ...this.options,
96
+ ...opts,
97
+ context: {
98
+ ...this.options.context,
99
+ ...opts?.context
100
+ }
101
+ };
102
+ if (!this.history || this.options.history && this.options.history !== this.history) {
103
+ if (this.#unsubHistory) {
104
+ this.#unsubHistory();
105
+ }
106
+ this.history = this.options.history ?? (isServer ? history.createMemoryHistory() : history.createBrowserHistory());
107
+ const parsedLocation = this.#parseLocation();
108
+ this.__store.setState(s => ({
109
+ ...s,
110
+ resolvedLocation: parsedLocation,
111
+ location: parsedLocation
112
+ }));
113
+ this.#unsubHistory = this.history.listen(() => {
114
+ this.safeLoad({
115
+ next: this.#parseLocation(this.state.location)
116
+ });
117
+ });
118
+ }
119
+ const {
120
+ basepath,
121
+ routeTree
122
+ } = this.options;
123
+ this.basepath = `/${path.trimPath(basepath ?? '') ?? ''}`;
124
+ if (routeTree && routeTree !== this.routeTree) {
125
+ this.#buildRouteTree(routeTree);
126
+ }
127
+ return this;
128
+ };
129
+ buildNext = opts => {
130
+ const next = this.#buildLocation(opts);
131
+ const __matches = this.matchRoutes(next.pathname, next.search);
132
+ return this.#buildLocation({
133
+ ...opts,
134
+ __matches
135
+ });
136
+ };
137
+ cancelMatches = () => {
138
+ this.state.matches.forEach(match => {
139
+ this.cancelMatch(match.id);
140
+ });
141
+ };
142
+ cancelMatch = id => {
143
+ this.getRouteMatch(id)?.abortController?.abort();
144
+ };
145
+ safeLoad = async opts => {
146
+ try {
147
+ return this.load(opts);
148
+ } catch (err) {
149
+ // Don't do anything
150
+ }
151
+ };
152
+ latestLoadPromise = Promise.resolve();
153
+ load = async opts => {
154
+ const promise = new Promise(async (resolve, reject) => {
155
+ let latestPromise;
156
+ const checkLatest = () => {
157
+ return this.latestLoadPromise !== promise ? this.latestLoadPromise : undefined;
158
+ };
159
+
160
+ // Cancel any pending matches
161
+ // this.cancelMatches()
162
+
163
+ let pendingMatches;
164
+ this.__store.batch(() => {
165
+ if (opts?.next) {
166
+ // Ingest the new location
167
+ this.__store.setState(s => ({
168
+ ...s,
169
+ location: opts.next
170
+ }));
171
+ }
172
+
173
+ // Match the routes
174
+ pendingMatches = this.matchRoutes(this.state.location.pathname, this.state.location.search, {
175
+ throwOnError: opts?.throwOnError,
176
+ debug: true
177
+ });
178
+ this.__store.setState(s => ({
179
+ ...s,
180
+ status: 'pending',
181
+ pendingMatchIds: pendingMatches.map(d => d.id),
182
+ matchesById: this.#mergeMatches(s.matchesById, pendingMatches)
183
+ }));
184
+ });
185
+ try {
186
+ // Load the matches
187
+ try {
188
+ await this.loadMatches(pendingMatches);
189
+ } catch (err) {
190
+ // swallow this error, since we'll display the
191
+ // errors on the route components
192
+ }
193
+
194
+ // Only apply the latest transition
195
+ if (latestPromise = checkLatest()) {
196
+ return latestPromise;
197
+ }
198
+ const prevLocation = this.state.resolvedLocation;
199
+ this.__store.setState(s => ({
200
+ ...s,
201
+ status: 'idle',
202
+ resolvedLocation: s.location,
203
+ matchIds: s.pendingMatchIds,
204
+ pendingMatchIds: []
205
+ }));
206
+ if (prevLocation.href !== this.state.location.href) {
207
+ this.options.onRouteChange?.();
208
+ }
209
+ resolve();
210
+ } catch (err) {
211
+ // Only apply the latest transition
212
+ if (latestPromise = checkLatest()) {
213
+ return latestPromise;
214
+ }
215
+ reject(err);
216
+ }
217
+ });
218
+ this.latestLoadPromise = promise;
219
+ return this.latestLoadPromise;
220
+ };
221
+ #mergeMatches = (prevMatchesById, nextMatches) => {
222
+ const nextMatchesById = {
223
+ ...prevMatchesById
224
+ };
225
+ let hadNew = false;
226
+ nextMatches.forEach(match => {
227
+ if (!nextMatchesById[match.id]) {
228
+ hadNew = true;
229
+ nextMatchesById[match.id] = match;
230
+ }
231
+ });
232
+ if (!hadNew) {
233
+ return prevMatchesById;
234
+ }
235
+ return nextMatchesById;
236
+ };
237
+ getRoute = id => {
238
+ const route = this.routesById[id];
239
+ invariant__default["default"](route, `Route with id "${id}" not found`);
240
+ return route;
241
+ };
242
+ preloadRoute = async (navigateOpts = this.state.location) => {
243
+ const next = this.buildNext(navigateOpts);
244
+ const matches = this.matchRoutes(next.pathname, next.search, {
245
+ throwOnError: true
246
+ });
247
+ this.__store.setState(s => {
248
+ return {
249
+ ...s,
250
+ matchesById: this.#mergeMatches(s.matchesById, matches)
251
+ };
252
+ });
253
+ await this.loadMatches(matches, {
254
+ preload: true,
255
+ maxAge: navigateOpts.maxAge
256
+ });
257
+ return matches;
258
+ };
259
+ cleanMatches = () => {
260
+ const now = Date.now();
261
+ const outdatedMatchIds = Object.values(this.state.matchesById).filter(match => {
262
+ const route = this.getRoute(match.routeId);
263
+ return !this.state.matchIds.includes(match.id) && !this.state.pendingMatchIds.includes(match.id) && match.preloadInvalidAt < now && (route.options.gcMaxAge ? match.updatedAt + route.options.gcMaxAge < now : true);
264
+ }).map(d => d.id);
265
+ if (outdatedMatchIds.length) {
266
+ this.__store.setState(s => {
267
+ const matchesById = {
268
+ ...s.matchesById
269
+ };
270
+ outdatedMatchIds.forEach(id => {
271
+ delete matchesById[id];
272
+ });
273
+ return {
274
+ ...s,
275
+ matchesById
276
+ };
277
+ });
278
+ }
279
+ };
280
+ matchRoutes = (pathname, locationSearch, opts) => {
281
+ let routeParams = {};
282
+ let foundRoute = this.flatRoutes.find(route => {
283
+ const matchedParams = path.matchPathname(this.basepath, path.trimPathRight(pathname), {
284
+ to: route.fullPath,
285
+ caseSensitive: route.options.caseSensitive ?? this.options.caseSensitive
286
+ });
287
+ if (matchedParams) {
288
+ routeParams = matchedParams;
289
+ return true;
290
+ }
291
+ return false;
292
+ });
293
+ let routeCursor = foundRoute || this.routesById['__root__'];
294
+ let matchedRoutes = [routeCursor];
295
+ // let includingLayouts = true
296
+ while (routeCursor?.parentRoute) {
297
+ routeCursor = routeCursor.parentRoute;
298
+ if (routeCursor) matchedRoutes.unshift(routeCursor);
299
+ }
300
+
301
+ // Existing matches are matches that are already loaded along with
302
+ // pending matches that are still loading
303
+
304
+ const parseErrors = matchedRoutes.map(route => {
305
+ let parsedParamsError;
306
+ if (route.options.parseParams) {
307
+ try {
308
+ const parsedParams = route.options.parseParams(routeParams);
309
+ // Add the parsed params to the accumulated params bag
310
+ Object.assign(routeParams, parsedParams);
311
+ } catch (err) {
312
+ parsedParamsError = new PathParamError(err.message, {
313
+ cause: err
314
+ });
315
+ if (opts?.throwOnError) {
316
+ throw parsedParamsError;
317
+ }
318
+ return parsedParamsError;
319
+ }
320
+ }
321
+ return;
322
+ });
323
+ const matches = matchedRoutes.map((route, index) => {
324
+ const interpolatedPath = path.interpolatePath(route.path, routeParams);
325
+ const key = route.options.key ? route.options.key({
326
+ params: routeParams,
327
+ search: locationSearch
328
+ }) ?? '' : '';
329
+ const stringifiedKey = key ? JSON.stringify(key) : '';
330
+ const matchId = path.interpolatePath(route.id, routeParams, true) + stringifiedKey;
331
+
332
+ // Waste not, want not. If we already have a match for this route,
333
+ // reuse it. This is important for layout routes, which might stick
334
+ // around between navigation actions that only change leaf routes.
335
+ const existingMatch = this.getRouteMatch(matchId);
336
+ if (existingMatch) {
337
+ return {
338
+ ...existingMatch
339
+ };
340
+ }
341
+
342
+ // Create a fresh route match
343
+ const hasLoaders = !!(route.options.loader || componentTypes.some(d => route.options[d]?.preload));
344
+ const routeMatch = {
345
+ id: matchId,
346
+ key: stringifiedKey,
347
+ routeId: route.id,
348
+ params: routeParams,
349
+ pathname: path.joinPaths([this.basepath, interpolatedPath]),
350
+ updatedAt: Date.now(),
351
+ invalidAt: Infinity,
352
+ preloadInvalidAt: Infinity,
353
+ routeSearch: {},
354
+ search: {},
355
+ status: hasLoaders ? 'pending' : 'success',
356
+ isFetching: false,
357
+ invalid: false,
358
+ error: undefined,
359
+ paramsError: parseErrors[index],
360
+ searchError: undefined,
361
+ loaderData: undefined,
362
+ loadPromise: Promise.resolve(),
363
+ routeContext: undefined,
364
+ context: undefined,
365
+ abortController: new AbortController(),
366
+ fetchedAt: 0
367
+ };
368
+ return routeMatch;
369
+ });
370
+
371
+ // Take each match and resolve its search params and context
372
+ // This has to happen after the matches are created or found
373
+ // so that we can use the parent match's search params and context
374
+ matches.forEach((match, i) => {
375
+ const parentMatch = matches[i - 1];
376
+ const route = this.getRoute(match.routeId);
377
+ const searchInfo = (() => {
378
+ // Validate the search params and stabilize them
379
+ const parentSearchInfo = {
380
+ search: parentMatch?.search ?? locationSearch,
381
+ routeSearch: parentMatch?.routeSearch ?? locationSearch
382
+ };
383
+ try {
384
+ const validator = typeof route.options.validateSearch === 'object' ? route.options.validateSearch.parse : route.options.validateSearch;
385
+ const routeSearch = validator?.(parentSearchInfo.search) ?? {};
386
+ const search = {
387
+ ...parentSearchInfo.search,
388
+ ...routeSearch
389
+ };
390
+ return {
391
+ routeSearch: utils.replaceEqualDeep(match.routeSearch, routeSearch),
392
+ search: utils.replaceEqualDeep(match.search, search)
393
+ };
394
+ } catch (err) {
395
+ match.searchError = new SearchParamError(err.message, {
396
+ cause: err
397
+ });
398
+ if (opts?.throwOnError) {
399
+ throw match.searchError;
400
+ }
401
+ return parentSearchInfo;
402
+ }
403
+ })();
404
+ Object.assign(match, {
405
+ ...searchInfo
406
+ });
407
+ const contextInfo = (() => {
408
+ try {
409
+ const routeContext = route.options.getContext?.({
410
+ parentContext: parentMatch?.routeContext ?? {},
411
+ context: parentMatch?.context ?? this?.options.context ?? {},
412
+ params: match.params,
413
+ search: match.search
414
+ }) || {};
415
+ const context = {
416
+ ...(parentMatch?.context ?? this?.options.context),
417
+ ...routeContext
418
+ };
419
+ return {
420
+ context,
421
+ routeContext
422
+ };
423
+ } catch (err) {
424
+ route.options.onError?.(err);
425
+ throw err;
426
+ }
427
+ })();
428
+ Object.assign(match, {
429
+ ...contextInfo
430
+ });
431
+ });
432
+ return matches;
433
+ };
434
+ loadMatches = async (resolvedMatches, opts) => {
435
+ this.cleanMatches();
436
+ if (!opts?.preload) {
437
+ resolvedMatches.forEach(match => {
438
+ // Update each match with its latest route data
439
+ this.setRouteMatch(match.id, s => ({
440
+ ...s,
441
+ routeSearch: match.routeSearch,
442
+ search: match.search,
443
+ routeContext: match.routeContext,
444
+ context: match.context,
445
+ error: match.error,
446
+ paramsError: match.paramsError,
447
+ searchError: match.searchError,
448
+ params: match.params
449
+ }));
450
+ });
451
+ }
452
+ let firstBadMatchIndex;
453
+
454
+ // Check each match middleware to see if the route can be accessed
455
+ try {
456
+ for (const [index, match] of resolvedMatches.entries()) {
457
+ const route = this.getRoute(match.routeId);
458
+ const handleError = (err, handler) => {
459
+ firstBadMatchIndex = firstBadMatchIndex ?? index;
460
+ handler = handler || route.options.onError;
461
+ if (isRedirect(err)) {
462
+ throw err;
463
+ }
464
+ try {
465
+ handler?.(err);
466
+ } catch (errorHandlerErr) {
467
+ err = errorHandlerErr;
468
+ if (isRedirect(errorHandlerErr)) {
469
+ throw errorHandlerErr;
470
+ }
471
+ }
472
+ console.log('set error');
473
+ this.setRouteMatch(match.id, s => ({
474
+ ...s,
475
+ error: err,
476
+ status: 'error',
477
+ updatedAt: Date.now()
478
+ }));
479
+ };
480
+ if (match.paramsError) {
481
+ handleError(match.paramsError, route.options.onParseParamsError);
482
+ }
483
+ if (match.searchError) {
484
+ handleError(match.searchError, route.options.onValidateSearchError);
485
+ }
486
+ let didError = false;
487
+ try {
488
+ await route.options.beforeLoad?.({
489
+ ...match,
490
+ preload: !!opts?.preload
491
+ });
492
+ } catch (err) {
493
+ handleError(err, route.options.onBeforeLoadError);
494
+ didError = true;
495
+ }
496
+
497
+ // If we errored, do not run the next matches' middleware
498
+ if (didError) {
499
+ break;
500
+ }
501
+ }
502
+ } catch (err) {
503
+ if (!opts?.preload) {
504
+ this.navigate(err);
505
+ }
506
+ throw err;
507
+ }
508
+ const validResolvedMatches = resolvedMatches.slice(0, firstBadMatchIndex);
509
+ const matchPromises = [];
510
+ validResolvedMatches.forEach((match, index) => {
511
+ matchPromises.push((async () => {
512
+ const parentMatchPromise = matchPromises[index - 1];
513
+ const route = this.getRoute(match.routeId);
514
+ if (match.isFetching || match.status === 'success' && !this.getIsInvalid({
515
+ matchId: match.id,
516
+ preload: opts?.preload
517
+ })) {
518
+ return this.getRouteMatch(match.id)?.loadPromise;
519
+ }
520
+ const fetchedAt = Date.now();
521
+ const checkLatest = () => {
522
+ const latest = this.getRouteMatch(match.id);
523
+ return latest && latest.fetchedAt !== fetchedAt ? latest.loadPromise : undefined;
524
+ };
525
+ const load = async () => {
526
+ let latestPromise;
527
+ const handleError = err => {
528
+ if (isRedirect(err)) {
529
+ if (!opts?.preload) {
530
+ this.navigate(err);
531
+ }
532
+ return true;
533
+ }
534
+ return false;
535
+ };
536
+ try {
537
+ const componentsPromise = Promise.all(componentTypes.map(async type => {
538
+ const component = route.options[type];
539
+ if (component?.preload) {
540
+ await component.preload();
541
+ }
542
+ }));
543
+ const loaderPromise = route.options.loader?.({
544
+ ...match,
545
+ preload: !!opts?.preload,
546
+ parentMatchPromise
547
+ });
548
+ const [_, loader] = await Promise.all([componentsPromise, loaderPromise]);
549
+ if (latestPromise = checkLatest()) return await latestPromise;
550
+ this.setRouteMatchData(match.id, () => loader, opts);
551
+ } catch (loaderError) {
552
+ if (latestPromise = checkLatest()) return await latestPromise;
553
+ handleError(loaderError);
554
+ let error = loaderError;
555
+ try {
556
+ if (route.options.onLoadError) {
557
+ route.options.onLoadError?.(loaderError);
558
+ } else {
559
+ route.options.onError?.(loaderError);
560
+ }
561
+ } catch (errorHandlerErr) {
562
+ error = errorHandlerErr;
563
+ handleError(error);
564
+ }
565
+ console.log('set error');
566
+ this.setRouteMatch(match.id, s => ({
567
+ ...s,
568
+ error: error,
569
+ status: 'error',
570
+ isFetching: false,
571
+ updatedAt: Date.now()
572
+ }));
573
+ console.log(this.getRouteMatch(match.id)?.status);
574
+ }
575
+ };
576
+ const loadPromise = load();
577
+ this.setRouteMatch(match.id, s => ({
578
+ ...s,
579
+ status: s.status !== 'success' ? 'pending' : s.status,
580
+ isFetching: true,
581
+ loadPromise,
582
+ fetchedAt,
583
+ invalid: false
584
+ }));
585
+ await loadPromise;
586
+ })());
587
+ });
588
+ await Promise.all(matchPromises);
589
+ };
590
+ reload = () => {
591
+ return this.navigate({
592
+ fromCurrent: true,
593
+ replace: true,
594
+ search: true
595
+ });
596
+ };
597
+ resolvePath = (from, path$1) => {
598
+ return path.resolvePath(this.basepath, from, path.cleanPath(path$1));
599
+ };
600
+ navigate = async ({
601
+ from,
602
+ to = '',
603
+ search,
604
+ hash,
605
+ replace,
606
+ params
607
+ }) => {
608
+ // If this link simply reloads the current route,
609
+ // make sure it has a new key so it will trigger a data refresh
610
+
611
+ // If this `to` is a valid external URL, return
612
+ // null for LinkUtils
613
+ const toString = String(to);
614
+ const fromString = typeof from === 'undefined' ? from : String(from);
615
+ let isExternal;
616
+ try {
617
+ new URL(`${toString}`);
618
+ isExternal = true;
619
+ } catch (e) {}
620
+ invariant__default["default"](!isExternal, 'Attempting to navigate to external url with this.navigate!');
621
+ return this.#commitLocation({
622
+ from: fromString,
623
+ to: toString,
624
+ search,
625
+ hash,
626
+ replace,
627
+ params
628
+ });
629
+ };
630
+ matchRoute = (location, opts) => {
631
+ location = {
632
+ ...location,
633
+ to: location.to ? this.resolvePath(location.from ?? '', location.to) : undefined
634
+ };
635
+ const next = this.buildNext(location);
636
+ if (opts?.pending && this.state.status !== 'pending') {
637
+ return false;
638
+ }
639
+ const baseLocation = opts?.pending ? this.state.location : this.state.resolvedLocation;
640
+ if (!baseLocation) {
641
+ return false;
642
+ }
643
+ const match = path.matchPathname(this.basepath, baseLocation.pathname, {
644
+ ...opts,
645
+ to: next.pathname
646
+ });
647
+ if (!match) {
648
+ return false;
649
+ }
650
+ if (opts?.includeSearch ?? true) {
651
+ return utils.partialDeepEqual(baseLocation.search, next.search) ? match : false;
652
+ }
653
+ return match;
654
+ };
655
+ buildLink = ({
656
+ from,
657
+ to = '.',
658
+ search,
659
+ params,
660
+ hash,
661
+ target,
662
+ replace,
663
+ activeOptions,
664
+ preload,
665
+ preloadDelay: userPreloadDelay,
666
+ disabled,
667
+ state
668
+ }) => {
669
+ // If this link simply reloads the current route,
670
+ // make sure it has a new key so it will trigger a data refresh
671
+
672
+ // If this `to` is a valid external URL, return
673
+ // null for LinkUtils
674
+
675
+ try {
676
+ new URL(`${to}`);
677
+ return {
678
+ type: 'external',
679
+ href: to
680
+ };
681
+ } catch (e) {}
682
+ const nextOpts = {
683
+ from,
684
+ to,
685
+ search,
686
+ params,
687
+ hash,
688
+ replace,
689
+ state
690
+ };
691
+ const next = this.buildNext(nextOpts);
692
+ preload = preload ?? this.options.defaultPreload;
693
+ const preloadDelay = userPreloadDelay ?? this.options.defaultPreloadDelay ?? 0;
694
+
695
+ // Compare path/hash for matches
696
+ const currentPathSplit = this.state.location.pathname.split('/');
697
+ const nextPathSplit = next.pathname.split('/');
698
+ const pathIsFuzzyEqual = nextPathSplit.every((d, i) => d === currentPathSplit[i]);
699
+ // Combine the matches based on user options
700
+ const pathTest = activeOptions?.exact ? this.state.location.pathname === next.pathname : pathIsFuzzyEqual;
701
+ const hashTest = activeOptions?.includeHash ? this.state.location.hash === next.hash : true;
702
+ const searchTest = activeOptions?.includeSearch ?? true ? utils.partialDeepEqual(this.state.location.search, next.search) : true;
703
+
704
+ // The final "active" test
705
+ const isActive = pathTest && hashTest && searchTest;
706
+
707
+ // The click handler
708
+ const handleClick = e => {
709
+ if (!disabled && !isCtrlEvent(e) && !e.defaultPrevented && (!target || target === '_self') && e.button === 0) {
710
+ e.preventDefault();
711
+
712
+ // All is well? Navigate!
713
+ this.#commitLocation(nextOpts);
714
+ }
715
+ };
716
+
717
+ // The click handler
718
+ const handleFocus = e => {
719
+ if (preload) {
720
+ this.preloadRoute(nextOpts).catch(err => {
721
+ console.warn(err);
722
+ console.warn('Error preloading route! ☝️');
723
+ });
724
+ }
725
+ };
726
+ const handleTouchStart = e => {
727
+ this.preloadRoute(nextOpts).catch(err => {
728
+ console.warn(err);
729
+ console.warn('Error preloading route! ☝️');
730
+ });
731
+ };
732
+ const handleEnter = e => {
733
+ const target = e.target || {};
734
+ if (preload) {
735
+ if (target.preloadTimeout) {
736
+ return;
737
+ }
738
+ target.preloadTimeout = setTimeout(() => {
739
+ target.preloadTimeout = null;
740
+ this.preloadRoute(nextOpts).catch(err => {
741
+ console.warn(err);
742
+ console.warn('Error preloading route! ☝️');
743
+ });
744
+ }, preloadDelay);
745
+ }
746
+ };
747
+ const handleLeave = e => {
748
+ const target = e.target || {};
749
+ if (target.preloadTimeout) {
750
+ clearTimeout(target.preloadTimeout);
751
+ target.preloadTimeout = null;
752
+ }
753
+ };
754
+ return {
755
+ type: 'internal',
756
+ next,
757
+ handleFocus,
758
+ handleClick,
759
+ handleEnter,
760
+ handleLeave,
761
+ handleTouchStart,
762
+ isActive,
763
+ disabled
764
+ };
765
+ };
766
+ dehydrate = () => {
767
+ return {
768
+ state: utils.pick(this.state, ['location', 'status', 'lastUpdated'])
769
+ };
770
+ };
771
+ hydrate = async __do_not_use_server_ctx => {
772
+ let _ctx = __do_not_use_server_ctx;
773
+ // Client hydrates from window
774
+ if (typeof document !== 'undefined') {
775
+ _ctx = window.__TSR_DEHYDRATED__;
776
+ }
777
+ invariant__default["default"](_ctx, 'Expected to find a __TSR_DEHYDRATED__ property on window... but we did not. Did you forget to render <DehydrateRouter /> in your app?');
778
+ const ctx = _ctx;
779
+ this.dehydratedData = ctx.payload;
780
+ this.options.hydrate?.(ctx.payload);
781
+ const routerState = ctx.router.state;
782
+ this.__store.setState(s => {
783
+ return {
784
+ ...s,
785
+ ...routerState,
786
+ resolvedLocation: routerState.location
787
+ };
788
+ });
789
+ await this.load();
790
+ return;
791
+ };
792
+ injectedHtml = [];
793
+ injectHtml = async html => {
794
+ this.injectedHtml.push(html);
795
+ };
796
+ dehydrateData = (key, getData) => {
797
+ if (typeof document === 'undefined') {
798
+ const strKey = typeof key === 'string' ? key : JSON.stringify(key);
799
+ this.injectHtml(async () => {
800
+ const id = `__TSR_DEHYDRATED__${strKey}`;
801
+ const data = typeof getData === 'function' ? await getData() : getData;
802
+ return `<script id='${id}' suppressHydrationWarning>window["__TSR_DEHYDRATED__${escapeJSON(strKey)}"] = ${JSON.stringify(data)}
803
+ ;(() => {
804
+ var el = document.getElementById('${id}')
805
+ el.parentElement.removeChild(el)
806
+ })()
807
+ </script>`;
808
+ });
809
+ return () => this.hydrateData(key);
810
+ }
811
+ return () => undefined;
812
+ };
813
+ hydrateData = key => {
814
+ if (typeof document !== 'undefined') {
815
+ const strKey = typeof key === 'string' ? key : JSON.stringify(key);
816
+ return window[`__TSR_DEHYDRATED__${strKey}`];
817
+ }
818
+ return undefined;
819
+ };
820
+
821
+ // resolveMatchPromise = (matchId: string, key: string, value: any) => {
822
+ // this.state.matches
823
+ // .find((d) => d.id === matchId)
824
+ // ?.__promisesByKey[key]?.resolve(value)
825
+ // }
826
+
827
+ #buildRouteTree = routeTree => {
828
+ this.routeTree = routeTree;
829
+ this.routesById = {};
830
+ this.routesByPath = {};
831
+ this.flatRoutes = [];
832
+ const recurseRoutes = routes => {
833
+ routes.forEach((route, i) => {
834
+ route.init({
835
+ originalIndex: i,
836
+ router: this
837
+ });
838
+ const existingRoute = this.routesById[route.id];
839
+ invariant__default["default"](!existingRoute, `Duplicate routes found with id: ${String(route.id)}`);
840
+ this.routesById[route.id] = route;
841
+ if (!route.isRoot && route.path) {
842
+ const trimmedFullPath = path.trimPathRight(route.fullPath);
843
+ if (!this.routesByPath[trimmedFullPath] || route.fullPath.endsWith('/')) {
844
+ this.routesByPath[trimmedFullPath] = route;
845
+ }
846
+ }
847
+ const children = route.children;
848
+ if (children?.length) {
849
+ recurseRoutes(children);
850
+ }
851
+ });
852
+ };
853
+ recurseRoutes([routeTree]);
854
+ this.flatRoutes = Object.values(this.routesByPath).map((d, i) => {
855
+ const trimmed = path.trimPath(d.fullPath);
856
+ const parsed = path.parsePathname(trimmed);
857
+ while (parsed.length > 1 && parsed[0]?.value === '/') {
858
+ parsed.shift();
859
+ }
860
+ const score = parsed.map(d => {
861
+ if (d.type === 'param') {
862
+ return 0.5;
863
+ }
864
+ if (d.type === 'wildcard') {
865
+ return 0.25;
866
+ }
867
+ return 1;
868
+ });
869
+ return {
870
+ child: d,
871
+ trimmed,
872
+ parsed,
873
+ index: i,
874
+ score
875
+ };
876
+ }).sort((a, b) => {
877
+ let isIndex = a.trimmed === '/' ? 1 : b.trimmed === '/' ? -1 : 0;
878
+ if (isIndex !== 0) return isIndex;
879
+ const length = Math.min(a.score.length, b.score.length);
880
+
881
+ // Sort by length of score
882
+ if (a.score.length !== b.score.length) {
883
+ return b.score.length - a.score.length;
884
+ }
885
+
886
+ // Sort by min available score
887
+ for (let i = 0; i < length; i++) {
888
+ if (a.score[i] !== b.score[i]) {
889
+ return b.score[i] - a.score[i];
890
+ }
891
+ }
892
+
893
+ // Sort by min available parsed value
894
+ for (let i = 0; i < length; i++) {
895
+ if (a.parsed[i].value !== b.parsed[i].value) {
896
+ return a.parsed[i].value > b.parsed[i].value ? 1 : -1;
897
+ }
898
+ }
899
+
900
+ // Sort by length of trimmed full path
901
+ if (a.trimmed !== b.trimmed) {
902
+ return a.trimmed > b.trimmed ? 1 : -1;
903
+ }
904
+
905
+ // Sort by original index
906
+ return a.index - b.index;
907
+ }).map((d, i) => {
908
+ d.child.rank = i;
909
+ return d.child;
910
+ });
911
+ };
912
+ #parseLocation = previousLocation => {
913
+ let {
914
+ pathname,
915
+ search,
916
+ hash,
917
+ state
918
+ } = this.history.location;
919
+ const parsedSearch = this.options.parseSearch(search);
920
+ return {
921
+ pathname: pathname,
922
+ searchStr: search,
923
+ search: utils.replaceEqualDeep(previousLocation?.search, parsedSearch),
924
+ hash: hash.split('#').reverse()[0] ?? '',
925
+ href: `${pathname}${search}${hash}`,
926
+ state: state,
927
+ key: state?.key || '__init__'
928
+ };
929
+ };
930
+ #buildLocation = (dest = {}) => {
931
+ dest.fromCurrent = dest.fromCurrent ?? dest.to === '';
932
+ const fromPathname = dest.fromCurrent ? this.state.location.pathname : dest.from ?? this.state.location.pathname;
933
+ let pathname = path.resolvePath(this.basepath ?? '/', fromPathname, `${dest.to ?? ''}`);
934
+ const fromMatches = this.matchRoutes(this.state.location.pathname, this.state.location.search);
935
+ const prevParams = {
936
+ ...utils.last(fromMatches)?.params
937
+ };
938
+ let nextParams = (dest.params ?? true) === true ? prevParams : utils.functionalUpdate(dest.params, prevParams);
939
+ if (nextParams) {
940
+ dest.__matches?.map(d => this.getRoute(d.routeId).options.stringifyParams).filter(Boolean).forEach(fn => {
941
+ nextParams = {
942
+ ...nextParams,
943
+ ...fn(nextParams)
944
+ };
945
+ });
946
+ }
947
+ pathname = path.interpolatePath(pathname, nextParams ?? {});
948
+ const preSearchFilters = dest.__matches?.map(match => this.getRoute(match.routeId).options.preSearchFilters ?? []).flat().filter(Boolean) ?? [];
949
+ const postSearchFilters = dest.__matches?.map(match => this.getRoute(match.routeId).options.postSearchFilters ?? []).flat().filter(Boolean) ?? [];
950
+
951
+ // Pre filters first
952
+ const preFilteredSearch = preSearchFilters?.length ? preSearchFilters?.reduce((prev, next) => next(prev), this.state.location.search) : this.state.location.search;
953
+
954
+ // Then the link/navigate function
955
+ const destSearch = dest.search === true ? preFilteredSearch // Preserve resolvedFrom true
956
+ : dest.search ? utils.functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
957
+ : preSearchFilters?.length ? preFilteredSearch // Preserve resolvedFrom filters
958
+ : {};
959
+
960
+ // Then post filters
961
+ const postFilteredSearch = postSearchFilters?.length ? postSearchFilters.reduce((prev, next) => next(prev), destSearch) : destSearch;
962
+ const search = utils.replaceEqualDeep(this.state.location.search, postFilteredSearch);
963
+ const searchStr = this.options.stringifySearch(search);
964
+ const hash = dest.hash === true ? this.state.location.hash : utils.functionalUpdate(dest.hash, this.state.location.hash);
965
+ const hashStr = hash ? `#${hash}` : '';
966
+ const nextState = dest.state === true ? this.state.location.state : utils.functionalUpdate(dest.state, this.state.location.state);
967
+ return {
968
+ pathname,
969
+ search,
970
+ searchStr,
971
+ state: nextState,
972
+ hash,
973
+ href: this.history.createHref(`${pathname}${searchStr}${hashStr}`),
974
+ key: dest.key
975
+ };
976
+ };
977
+ #commitLocation = async location => {
978
+ const next = this.buildNext(location);
979
+ const id = '' + Date.now() + Math.random();
980
+ if (this.navigateTimeout) clearTimeout(this.navigateTimeout);
981
+ let nextAction = 'replace';
982
+ if (!location.replace) {
983
+ nextAction = 'push';
984
+ }
985
+ const isSameUrl = this.state.location.href === next.href;
986
+ if (isSameUrl && !next.key) {
987
+ nextAction = 'replace';
988
+ }
989
+ const href = `${next.pathname}${next.searchStr}${next.hash ? `#${next.hash}` : ''}`;
990
+ this.history[nextAction === 'push' ? 'push' : 'replace'](href, {
991
+ id,
992
+ ...next.state
993
+ });
994
+ return this.latestLoadPromise;
995
+ };
996
+ getRouteMatch = id => {
997
+ return this.state.matchesById[id];
998
+ };
999
+ setRouteMatch = (id, updater) => {
1000
+ this.__store.setState(prev => {
1001
+ if (!prev.matchesById[id]) {
1002
+ console.warn(`No match found with id: ${id}`);
1003
+ }
1004
+ return {
1005
+ ...prev,
1006
+ matchesById: {
1007
+ ...prev.matchesById,
1008
+ [id]: updater(prev.matchesById[id])
1009
+ }
1010
+ };
1011
+ });
1012
+ };
1013
+ setRouteMatchData = (id, updater, opts) => {
1014
+ const match = this.getRouteMatch(id);
1015
+ if (!match) return;
1016
+ const route = this.getRoute(match.routeId);
1017
+ const updatedAt = opts?.updatedAt ?? Date.now();
1018
+ const preloadInvalidAt = updatedAt + (opts?.maxAge ?? route.options.preloadMaxAge ?? this.options.defaultPreloadMaxAge ?? 5000);
1019
+ const invalidAt = updatedAt + (opts?.maxAge ?? route.options.maxAge ?? this.options.defaultMaxAge ?? Infinity);
1020
+ console.log('set success');
1021
+ this.setRouteMatch(id, s => ({
1022
+ ...s,
1023
+ error: undefined,
1024
+ status: 'success',
1025
+ isFetching: false,
1026
+ updatedAt: Date.now(),
1027
+ loaderData: utils.functionalUpdate(updater, s.loaderData),
1028
+ preloadInvalidAt,
1029
+ invalidAt
1030
+ }));
1031
+ if (this.state.matches.find(d => d.id === id)) ;
1032
+ };
1033
+ invalidate = async opts => {
1034
+ if (opts?.matchId) {
1035
+ this.setRouteMatch(opts.matchId, s => ({
1036
+ ...s,
1037
+ invalid: true
1038
+ }));
1039
+ const matchIndex = this.state.matches.findIndex(d => d.id === opts.matchId);
1040
+ const childMatch = this.state.matches[matchIndex + 1];
1041
+ if (childMatch) {
1042
+ return this.invalidate({
1043
+ matchId: childMatch.id,
1044
+ reload: false
1045
+ });
1046
+ }
1047
+ } else {
1048
+ this.__store.batch(() => {
1049
+ Object.values(this.state.matchesById).forEach(match => {
1050
+ this.setRouteMatch(match.id, s => ({
1051
+ ...s,
1052
+ invalid: true
1053
+ }));
1054
+ });
1055
+ });
1056
+ }
1057
+ if (opts?.reload ?? true) {
1058
+ return this.reload();
1059
+ }
1060
+ };
1061
+ getIsInvalid = opts => {
1062
+ if (!opts?.matchId) {
1063
+ return !!this.state.matches.find(d => this.getIsInvalid({
1064
+ matchId: d.id,
1065
+ preload: opts?.preload
1066
+ }));
1067
+ }
1068
+ const match = this.getRouteMatch(opts?.matchId);
1069
+ if (!match) {
1070
+ return false;
1071
+ }
1072
+ const now = Date.now();
1073
+ return match.invalid || (opts?.preload ? match.preloadInvalidAt : match.invalidAt) < now;
1074
+ };
1075
+ }
1076
+
1077
+ // Detect if we're in the DOM
1078
+ const isServer = typeof window === 'undefined' || !window.document.createElement;
1079
+ function getInitialRouterState() {
1080
+ return {
1081
+ status: 'idle',
1082
+ isFetching: false,
1083
+ resolvedLocation: null,
1084
+ location: null,
1085
+ matchesById: {},
1086
+ matchIds: [],
1087
+ pendingMatchIds: [],
1088
+ matches: [],
1089
+ pendingMatches: [],
1090
+ lastUpdated: Date.now()
1091
+ };
1092
+ }
1093
+ function isCtrlEvent(e) {
1094
+ return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey);
1095
+ }
1096
+ function redirect(opts) {
1097
+ opts.isRedirect = true;
1098
+ return opts;
1099
+ }
1100
+ function isRedirect(obj) {
1101
+ return !!obj?.isRedirect;
1102
+ }
1103
+ class SearchParamError extends Error {}
1104
+ class PathParamError extends Error {}
1105
+ function escapeJSON(jsonString) {
1106
+ return jsonString.replace(/\\/g, '\\\\') // Escape backslashes
1107
+ .replace(/'/g, "\\'") // Escape single quotes
1108
+ .replace(/"/g, '\\"'); // Escape double quotes
1109
+ }
1110
+
1111
+ // A function that takes an import() argument which is a function and returns a new function that will
1112
+ // proxy arguments from the caller to the imported function, retaining all type
1113
+ // information along the way
1114
+ function lazyFn(fn, key) {
1115
+ return async (...args) => {
1116
+ const imported = await fn();
1117
+ return imported[key || 'default'](...args);
1118
+ };
1119
+ }
1120
+
1121
+ exports.PathParamError = PathParamError;
1122
+ exports.Router = Router;
1123
+ exports.SearchParamError = SearchParamError;
1124
+ exports.componentTypes = componentTypes;
1125
+ exports.isRedirect = isRedirect;
1126
+ exports.lazyFn = lazyFn;
1127
+ exports.redirect = redirect;
1128
+ //# sourceMappingURL=router.js.map