toiljs 0.0.14 → 0.0.16

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 (225) hide show
  1. package/.babelrc +13 -13
  2. package/.gitattributes +2 -2
  3. package/.github/ISSUE_TEMPLATE/bug_report.md +38 -38
  4. package/.github/ISSUE_TEMPLATE/bug_report.yml +90 -90
  5. package/.github/ISSUE_TEMPLATE/config.yml +8 -8
  6. package/.github/ISSUE_TEMPLATE/feature_request.md +20 -20
  7. package/.github/PULL_REQUEST_TEMPLATE.md +43 -43
  8. package/.github/changelog-config.json +45 -45
  9. package/.github/dependabot.yml +27 -27
  10. package/.github/workflows/ci.yml +191 -191
  11. package/.prettierrc.json +11 -11
  12. package/.vscode/settings.json +9 -9
  13. package/CHANGELOG.md +5 -5
  14. package/LICENSE +187 -187
  15. package/README.md +339 -315
  16. package/as-pect.asconfig.json +34 -34
  17. package/as-pect.config.js +65 -65
  18. package/assets/logo.svg +36 -36
  19. package/build/backend/.tsbuildinfo +1 -1
  20. package/build/cli/.tsbuildinfo +1 -1
  21. package/build/cli/index.js +2926 -191
  22. package/build/client/.tsbuildinfo +1 -1
  23. package/build/client/dev/devtools.d.ts +6 -0
  24. package/build/client/dev/devtools.js +442 -0
  25. package/build/client/dev/error-overlay.d.ts +9 -0
  26. package/build/client/dev/error-overlay.js +19 -4
  27. package/build/client/head/metadata.d.ts +3 -1
  28. package/build/client/head/metadata.js +8 -0
  29. package/build/client/index.d.ts +4 -4
  30. package/build/client/index.js +2 -2
  31. package/build/client/navigation/navigation.d.ts +2 -0
  32. package/build/client/navigation/navigation.js +9 -1
  33. package/build/client/navigation/prefetch.d.ts +1 -0
  34. package/build/client/navigation/prefetch.js +35 -0
  35. package/build/client/routing/Router.js +1 -1
  36. package/build/client/routing/hooks.js +6 -2
  37. package/build/client/routing/loader.d.ts +25 -0
  38. package/build/client/routing/loader.js +53 -7
  39. package/build/client/routing/mount.js +4 -3
  40. package/build/compiler/.tsbuildinfo +1 -1
  41. package/build/compiler/config.d.ts +18 -0
  42. package/build/compiler/config.js +8 -0
  43. package/build/compiler/docs.js +16 -16
  44. package/build/compiler/generate.js +3 -0
  45. package/build/compiler/index.d.ts +2 -2
  46. package/build/compiler/index.js +3 -1
  47. package/build/compiler/plugin.js +156 -0
  48. package/build/compiler/prerender.d.ts +1 -0
  49. package/build/compiler/prerender.js +2 -1
  50. package/build/compiler/seo.d.ts +2 -2
  51. package/build/compiler/seo.js +8 -6
  52. package/build/compiler/ssg.d.ts +5 -0
  53. package/build/compiler/ssg.js +121 -0
  54. package/build/io/.tsbuildinfo +1 -1
  55. package/build/logger/.tsbuildinfo +1 -1
  56. package/build/shared/.tsbuildinfo +1 -1
  57. package/eslint.config.js +48 -48
  58. package/examples/basic/client/404.tsx +11 -11
  59. package/examples/basic/client/components/.gitkeep +1 -1
  60. package/examples/basic/client/global-error.tsx +13 -13
  61. package/examples/basic/client/layout.tsx +25 -25
  62. package/examples/basic/client/public/images/.gitkeep +1 -1
  63. package/examples/basic/client/public/images/logo.svg +36 -36
  64. package/examples/basic/client/public/robots.txt +2 -2
  65. package/examples/basic/client/routes/docs/[...slug].tsx +12 -12
  66. package/examples/basic/client/routes/features/error/error.tsx +16 -16
  67. package/examples/basic/client/routes/features/template/b.tsx +14 -14
  68. package/examples/basic/client/routes/files/[[...slug]].tsx +21 -21
  69. package/examples/basic/client/routes/gallery/layout.tsx +13 -13
  70. package/examples/basic/client/routes/io.tsx +24 -24
  71. package/examples/basic/client/routes/loader-demo/loading.tsx +13 -13
  72. package/examples/basic/client/routes/search.tsx +61 -61
  73. package/examples/basic/client/toil.tsx +5 -5
  74. package/package.json +155 -147
  75. package/presets/eslint.js +88 -88
  76. package/presets/no-uint8array-tostring.js +200 -200
  77. package/presets/prettier.json +18 -18
  78. package/presets/tsconfig.json +37 -37
  79. package/src/backend/index.ts +160 -160
  80. package/src/cli/proc.ts +50 -50
  81. package/src/cli/updates.ts +69 -69
  82. package/src/cli/validate.ts +31 -31
  83. package/src/client/channel/channel.ts +146 -146
  84. package/src/client/components/Form.tsx +65 -65
  85. package/src/client/components/Script.tsx +113 -113
  86. package/src/client/components/Slot.tsx +21 -21
  87. package/src/client/dev/devtools.tsx +973 -0
  88. package/src/client/dev/error-overlay.tsx +30 -4
  89. package/src/client/head/head.ts +167 -167
  90. package/src/client/head/metadata.ts +19 -1
  91. package/src/client/index.ts +19 -9
  92. package/src/client/navigation/NavLink.tsx +86 -86
  93. package/src/client/navigation/navigation.ts +25 -5
  94. package/src/client/navigation/prefetch.ts +169 -130
  95. package/src/client/navigation/scroll.ts +53 -53
  96. package/src/client/routing/Router.tsx +8 -2
  97. package/src/client/routing/action.ts +122 -122
  98. package/src/client/routing/error-boundary.tsx +43 -43
  99. package/src/client/routing/hooks.ts +21 -6
  100. package/src/client/routing/loader.ts +325 -225
  101. package/src/client/routing/match.ts +47 -47
  102. package/src/client/routing/mount.tsx +54 -52
  103. package/src/client/routing/params-context.ts +10 -10
  104. package/src/client/routing/slot-context.ts +7 -7
  105. package/src/client/search/search.ts +189 -189
  106. package/src/client/search/use-page-search.ts +73 -73
  107. package/src/client/types.ts +73 -73
  108. package/src/compiler/config.ts +47 -1
  109. package/src/compiler/docs.ts +228 -228
  110. package/src/compiler/generate.ts +394 -391
  111. package/src/compiler/index.ts +64 -54
  112. package/src/compiler/pages.ts +70 -70
  113. package/src/compiler/plugin.ts +170 -2
  114. package/src/compiler/prerender.ts +5 -1
  115. package/src/compiler/seo.ts +23 -7
  116. package/src/compiler/ssg.ts +162 -0
  117. package/src/io/BinaryReader.ts +340 -340
  118. package/src/io/BinaryWriter.ts +385 -385
  119. package/src/io/FastMap.ts +127 -127
  120. package/src/io/index.ts +11 -11
  121. package/src/io/lengths.ts +14 -14
  122. package/src/io/types.ts +18 -18
  123. package/src/logger/index.ts +22 -22
  124. package/src/server/index.ts +10 -10
  125. package/src/server/main.ts +13 -13
  126. package/src/server/tsconfig.json +4 -4
  127. package/src/shared/index.ts +10 -10
  128. package/std/client/index.d.ts +15 -15
  129. package/std/client/package.json +3 -3
  130. package/test/assembly/example.spec.ts +7 -7
  131. package/test/channel.test.ts +21 -21
  132. package/test/dom/Link.test.tsx +47 -47
  133. package/test/dom/NavLink.test.tsx +37 -37
  134. package/test/dom/error-overlay.test.tsx +44 -44
  135. package/test/dom/loader.test.tsx +121 -121
  136. package/test/dom/navigation.test.ts +59 -59
  137. package/test/dom/revalidate.test.tsx +38 -38
  138. package/test/dom/route-head.test.tsx +78 -78
  139. package/test/dom/router-loading.test.tsx +44 -44
  140. package/test/dom/scroll.test.ts +56 -56
  141. package/test/dom/use-metadata.test.tsx +58 -0
  142. package/test/io.test.ts +93 -93
  143. package/test/navlink.test.ts +28 -28
  144. package/test/placeholder.test.ts +9 -9
  145. package/test/routes.test.ts +76 -76
  146. package/test/seo.test.ts +175 -164
  147. package/test/slot-layouts.test.ts +69 -69
  148. package/test/ssg.test.ts +36 -0
  149. package/test/update.test.ts +44 -44
  150. package/test/validate.test.ts +42 -42
  151. package/toil-routes.d.ts +7 -0
  152. package/toilconfig.json +30 -30
  153. package/tsconfig.backend.json +13 -13
  154. package/tsconfig.base.json +35 -35
  155. package/tsconfig.cli.json +13 -13
  156. package/tsconfig.client.json +14 -14
  157. package/tsconfig.compiler.json +13 -13
  158. package/tsconfig.io.json +12 -12
  159. package/tsconfig.json +22 -22
  160. package/tsconfig.logger.json +12 -12
  161. package/tsconfig.server.json +10 -10
  162. package/tsconfig.shared.json +12 -12
  163. package/vitest.config.ts +26 -26
  164. package/.idea/codeStyles/Project.xml +0 -54
  165. package/.idea/codeStyles/codeStyleConfig.xml +0 -5
  166. package/.idea/inspectionProfiles/Project_Default.xml +0 -6
  167. package/.idea/modules.xml +0 -8
  168. package/.idea/prettier.xml +0 -7
  169. package/.idea/toiljs.iml +0 -8
  170. package/.idea/vcs.xml +0 -6
  171. package/.toil/entry.tsx +0 -9
  172. package/.toil/index.html +0 -12
  173. package/.toil/routes.ts +0 -9
  174. package/build/cli/configure.d.ts +0 -16
  175. package/build/cli/configure.js +0 -272
  176. package/build/cli/create.d.ts +0 -16
  177. package/build/cli/create.js +0 -420
  178. package/build/cli/diagnostics.d.ts +0 -55
  179. package/build/cli/diagnostics.js +0 -333
  180. package/build/cli/doctor.d.ts +0 -6
  181. package/build/cli/doctor.js +0 -249
  182. package/build/cli/features.d.ts +0 -25
  183. package/build/cli/features.js +0 -107
  184. package/build/cli/index.d.ts +0 -2
  185. package/build/cli/proc.d.ts +0 -6
  186. package/build/cli/proc.js +0 -31
  187. package/build/cli/ui.d.ts +0 -9
  188. package/build/cli/ui.js +0 -75
  189. package/build/cli/update.d.ts +0 -7
  190. package/build/cli/update.js +0 -117
  191. package/build/cli/updates.d.ts +0 -10
  192. package/build/cli/updates.js +0 -45
  193. package/build/cli/validate.d.ts +0 -4
  194. package/build/cli/validate.js +0 -19
  195. package/build/client/Link.d.ts +0 -8
  196. package/build/client/Link.js +0 -44
  197. package/build/client/NavLink.d.ts +0 -14
  198. package/build/client/NavLink.js +0 -37
  199. package/build/client/Router.d.ts +0 -7
  200. package/build/client/Router.js +0 -55
  201. package/build/client/channel.d.ts +0 -23
  202. package/build/client/channel.js +0 -94
  203. package/build/client/error-boundary.d.ts +0 -16
  204. package/build/client/error-boundary.js +0 -19
  205. package/build/client/head.d.ts +0 -26
  206. package/build/client/head.js +0 -87
  207. package/build/client/hooks.d.ts +0 -17
  208. package/build/client/hooks.js +0 -48
  209. package/build/client/lazy.d.ts +0 -16
  210. package/build/client/lazy.js +0 -53
  211. package/build/client/match.d.ts +0 -2
  212. package/build/client/match.js +0 -32
  213. package/build/client/mount.d.ts +0 -2
  214. package/build/client/mount.js +0 -13
  215. package/build/client/navigation.d.ts +0 -13
  216. package/build/client/navigation.js +0 -97
  217. package/build/client/params-context.d.ts +0 -2
  218. package/build/client/params-context.js +0 -2
  219. package/build/client/prefetch.d.ts +0 -11
  220. package/build/client/prefetch.js +0 -100
  221. package/build/client/runtime.d.ts +0 -31
  222. package/build/client/runtime.js +0 -112
  223. package/build/client/scroll.d.ts +0 -8
  224. package/build/client/scroll.js +0 -36
  225. package/toil-env.d.ts +0 -16
@@ -2,12 +2,17 @@ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Component, useSyncExternalStore, } from 'react';
3
3
  let current = null;
4
4
  const listeners = new Set();
5
+ let errorLog = [];
6
+ const MAX_LOG = 50;
5
7
  function emit() {
6
8
  for (const listener of listeners)
7
9
  listener();
8
10
  }
9
11
  function setDevError(next) {
10
12
  current = next;
13
+ if (next) {
14
+ errorLog = [...errorLog, next].slice(-MAX_LOG);
15
+ }
11
16
  emit();
12
17
  }
13
18
  function subscribe(listener) {
@@ -16,6 +21,10 @@ function subscribe(listener) {
16
21
  listeners.delete(listener);
17
22
  };
18
23
  }
24
+ export function getErrorLog() {
25
+ return errorLog;
26
+ }
27
+ export const subscribeErrors = subscribe;
19
28
  export function isDevMode() {
20
29
  try {
21
30
  return Boolean(import.meta.env?.DEV);
@@ -30,13 +39,14 @@ export function initDevErrorOverlay() {
30
39
  return;
31
40
  windowBound = true;
32
41
  window.addEventListener('error', (event) => {
33
- if (event.error instanceof Error)
34
- setDevError({ error: event.error, source: 'window' });
42
+ if (event.error instanceof Error) {
43
+ setDevError({ error: event.error, source: 'window', time: Date.now() });
44
+ }
35
45
  });
36
46
  window.addEventListener('unhandledrejection', (event) => {
37
47
  const reason = event.reason;
38
48
  const error = reason instanceof Error ? reason : new Error(String(reason));
39
- setDevError({ error, source: 'unhandledrejection' });
49
+ setDevError({ error, source: 'unhandledrejection', time: Date.now() });
40
50
  });
41
51
  }
42
52
  export class DevErrorBoundary extends Component {
@@ -46,7 +56,12 @@ export class DevErrorBoundary extends Component {
46
56
  return { crashed: true };
47
57
  }
48
58
  componentDidCatch(error, info) {
49
- setDevError({ error, componentStack: info.componentStack ?? undefined, source: 'render' });
59
+ setDevError({
60
+ error,
61
+ componentStack: info.componentStack ?? undefined,
62
+ source: 'render',
63
+ time: Date.now(),
64
+ });
50
65
  }
51
66
  componentDidMount() {
52
67
  this.unsubscribe = subscribe(() => {
@@ -1,4 +1,4 @@
1
- import type { HeadSpec, LinkTag, MetaTag } from './head.js';
1
+ import { type HeadSpec, type LinkTag, type MetaTag } from './head.js';
2
2
  import type { RouteParams } from '../routing/match.js';
3
3
  export interface OpenGraph {
4
4
  readonly title?: string;
@@ -27,3 +27,5 @@ export interface GenerateMetadataArgs<T = unknown> {
27
27
  }
28
28
  export type GenerateMetadata<T = unknown> = (args: GenerateMetadataArgs<T>) => Metadata | Promise<Metadata>;
29
29
  export declare function resolveMetadata(metadata: Metadata): HeadSpec;
30
+ export declare function useMetadata(metadata: Metadata): void;
31
+ export declare function Metadata(props: Metadata): null;
@@ -1,3 +1,4 @@
1
+ import { useHead } from './head.js';
1
2
  export function resolveMetadata(metadata) {
2
3
  const meta = [];
3
4
  if (metadata.description !== undefined) {
@@ -38,3 +39,10 @@ export function resolveMetadata(metadata) {
38
39
  link.push(...metadata.link);
39
40
  return { title: metadata.title, titleTemplate: metadata.titleTemplate, meta, link };
40
41
  }
42
+ export function useMetadata(metadata) {
43
+ useHead(resolveMetadata(metadata));
44
+ }
45
+ export function Metadata(props) {
46
+ useMetadata(props);
47
+ return null;
48
+ }
@@ -4,12 +4,12 @@ export { Link } from './navigation/Link.js';
4
4
  export type { LinkProps } from './navigation/Link.js';
5
5
  export { NavLink, matchActive } from './navigation/NavLink.js';
6
6
  export type { NavLinkProps, NavLinkState } from './navigation/NavLink.js';
7
- export { navigate, back, forward, refresh, setViewTransitions } from './navigation/navigation.js';
7
+ export { navigate, back, forward, refresh, setViewTransitions, setTransitions, href, } from './navigation/navigation.js';
8
8
  export type { NavigateOptions } from './navigation/navigation.js';
9
9
  export { useParams, useNavigate, useLocation, usePathname, useSearchParams, useRouter, useNavigationPending, } from './routing/hooks.js';
10
10
  export type { RouterInstance } from './routing/hooks.js';
11
11
  export { useLoaderData, revalidate, invalidateLoaderData } from './routing/loader.js';
12
- export type { LoaderArgs, LoaderFunction, LoaderData, Revalidate } from './routing/loader.js';
12
+ export type { LoaderArgs, LoaderFunction, LoaderData, Revalidate, StaticParams, GenerateStaticParams, } from './routing/loader.js';
13
13
  export { useAction } from './routing/action.js';
14
14
  export type { UseActionOptions, ActionState, ActionHandle, RevalidateTarget, } from './routing/action.js';
15
15
  export { prefetch } from './navigation/prefetch.js';
@@ -20,8 +20,8 @@ export { connectChannel, useChannel, resolveChannelUrl } from './channel/channel
20
20
  export type { Channel, ChannelOptions, ChannelHook, ChannelData } from './channel/channel.js';
21
21
  export { useHead, useTitle, Head, mergeHead } from './head/head.js';
22
22
  export type { HeadSpec, MetaTag, LinkTag, ResolvedHead } from './head/head.js';
23
- export { resolveMetadata } from './head/metadata.js';
24
- export type { Metadata, GenerateMetadata, GenerateMetadataArgs, OpenGraph, } from './head/metadata.js';
23
+ export { resolveMetadata, useMetadata, Metadata } from './head/metadata.js';
24
+ export type { GenerateMetadata, GenerateMetadataArgs, OpenGraph } from './head/metadata.js';
25
25
  export { searchPages, registerPages, getPages, pagePath } from './search/search.js';
26
26
  export type { PageMeta, PageSearchResult, PageSearchOptions, SearchField, SearchHints, } from './search/search.js';
27
27
  export { usePageSearch } from './search/use-page-search.js';
@@ -2,7 +2,7 @@ export { mount } from './routing/mount.js';
2
2
  export { Router } from './routing/Router.js';
3
3
  export { Link } from './navigation/Link.js';
4
4
  export { NavLink, matchActive } from './navigation/NavLink.js';
5
- export { navigate, back, forward, refresh, setViewTransitions } from './navigation/navigation.js';
5
+ export { navigate, back, forward, refresh, setViewTransitions, setTransitions, href, } from './navigation/navigation.js';
6
6
  export { useParams, useNavigate, useLocation, usePathname, useSearchParams, useRouter, useNavigationPending, } from './routing/hooks.js';
7
7
  export { useLoaderData, revalidate, invalidateLoaderData } from './routing/loader.js';
8
8
  export { useAction } from './routing/action.js';
@@ -10,7 +10,7 @@ export { prefetch } from './navigation/prefetch.js';
10
10
  export { matchRoute } from './routing/match.js';
11
11
  export { connectChannel, useChannel, resolveChannelUrl } from './channel/channel.js';
12
12
  export { useHead, useTitle, Head, mergeHead } from './head/head.js';
13
- export { resolveMetadata } from './head/metadata.js';
13
+ export { resolveMetadata, useMetadata, Metadata } from './head/metadata.js';
14
14
  export { searchPages, registerPages, getPages, pagePath } from './search/search.js';
15
15
  export { usePageSearch } from './search/use-page-search.js';
16
16
  export { Image } from './components/Image.js';
@@ -1,5 +1,7 @@
1
1
  import type { Href } from '../types.js';
2
+ export declare const href: (path: string) => Href;
2
3
  export declare function setViewTransitions(enabled: boolean): void;
4
+ export declare function setTransitions(enabled: boolean): void;
3
5
  export declare function isSoftNavigation(): boolean;
4
6
  export declare function previousPathname(): string;
5
7
  export declare function settleNavigation(): void;
@@ -1,12 +1,17 @@
1
1
  import { startTransition } from 'react';
2
2
  import { flushSync } from 'react-dom';
3
3
  import { enableManualScrollRestoration, planScroll, rememberScroll } from './scroll.js';
4
+ export const href = (path) => path;
4
5
  const listeners = new Set();
5
6
  let popstateBound = false;
6
7
  let viewTransitions = false;
7
8
  export function setViewTransitions(enabled) {
8
9
  viewTransitions = enabled;
9
10
  }
11
+ let navTransitions = false;
12
+ export function setTransitions(enabled) {
13
+ navTransitions = enabled;
14
+ }
10
15
  function shouldViewTransition() {
11
16
  if (!viewTransitions || typeof document === 'undefined' || typeof window === 'undefined') {
12
17
  return false;
@@ -31,9 +36,12 @@ function notify() {
31
36
  flushSync(runListeners);
32
37
  });
33
38
  }
34
- else {
39
+ else if (navTransitions) {
35
40
  startTransition(runListeners);
36
41
  }
42
+ else {
43
+ runListeners();
44
+ }
37
45
  }
38
46
  let softNav = false;
39
47
  let currentPath = typeof window === 'undefined' ? '/' : window.location.pathname;
@@ -8,4 +8,5 @@ declare global {
8
8
  }
9
9
  }
10
10
  export declare function prefetch(href: string): void;
11
+ export declare function prefetchData(href: string): void;
11
12
  export declare function startPrefetcher(routes: RouteDef[]): void;
@@ -1,6 +1,8 @@
1
+ import { prefetchRouteData } from '../routing/loader.js';
1
2
  import { matchRoute } from '../routing/match.js';
2
3
  let routeTable = [];
3
4
  const warmed = new WeakSet();
5
+ const dataWarmed = new Set();
4
6
  let io = null;
5
7
  let mo = null;
6
8
  function routeForHref(href) {
@@ -32,6 +34,29 @@ export function prefetch(href) {
32
34
  if (route)
33
35
  warm(route);
34
36
  }
37
+ export function prefetchData(href) {
38
+ let url;
39
+ try {
40
+ url = new URL(href, window.location.href);
41
+ }
42
+ catch {
43
+ return;
44
+ }
45
+ if (url.origin !== window.location.origin)
46
+ return;
47
+ const key = url.pathname + url.search;
48
+ if (dataWarmed.has(key))
49
+ return;
50
+ for (const route of routeTable) {
51
+ const params = matchRoute(route.pattern, url.pathname);
52
+ if (params) {
53
+ dataWarmed.add(key);
54
+ warm(route);
55
+ prefetchRouteData(route, params, url.pathname, url.search);
56
+ return;
57
+ }
58
+ }
59
+ }
35
60
  function isPrefetchable(a) {
36
61
  if (a.target && a.target !== '_self')
37
62
  return false;
@@ -89,6 +114,16 @@ export function startPrefetcher(routes) {
89
114
  }
90
115
  }
91
116
  });
117
+ const onIntent = (event) => {
118
+ const target = event.target;
119
+ if (!(target instanceof Element))
120
+ return;
121
+ const a = target.closest('a[href]');
122
+ if (a instanceof HTMLAnchorElement && isPrefetchable(a) && a.href)
123
+ prefetchData(a.href);
124
+ };
125
+ document.addEventListener('pointerover', onIntent, { passive: true });
126
+ document.addEventListener('focusin', onIntent);
92
127
  const begin = () => {
93
128
  scan(document);
94
129
  mo?.observe(document.body, { childList: true, subtree: true });
@@ -21,7 +21,7 @@ function renderMatched(matched, params, pathname, epoch, keyPrefix) {
21
21
  const fallback = matched.loading
22
22
  ? createElement(Suspense, { fallback: null }, createElement(loadingComponent(matched.loading)))
23
23
  : null;
24
- let content = (_jsx(Suspense, { fallback: fallback, children: _jsx(RoutePage, { route: matched, params: params, dataKey: dataKey, epoch: epoch }) }, matched.loading ? `${dataKey}:${String(epoch)}` : undefined));
24
+ let content = (_jsx(Suspense, { fallback: fallback, children: _jsx(RoutePage, { route: matched, params: params, dataKey: dataKey, epoch: epoch }, dataKey) }, matched.loading ? `${dataKey}:${String(epoch)}` : undefined));
25
25
  const templates = matched.templates ?? [];
26
26
  for (let i = templates.length - 1; i >= 0; i--) {
27
27
  const Template = nestedLayout(templates[i]);
@@ -1,4 +1,4 @@
1
- import { useContext, useEffect, useMemo, useReducer, useSyncExternalStore } from 'react';
1
+ import { startTransition, useContext, useEffect, useMemo, useReducer, useSyncExternalStore, } from 'react';
2
2
  import { back, forward, isNavigationPending, navigate, refresh, subscribeLocation, subscribePending, } from '../navigation/navigation.js';
3
3
  import { clearLoaderData, revalidate as revalidateData } from './loader.js';
4
4
  import { ParamsContext } from './params-context.js';
@@ -32,7 +32,11 @@ export function useRouter() {
32
32
  }
33
33
  function useLocationSubscription() {
34
34
  const [, forceUpdate] = useReducer((n) => n + 1, 0);
35
- useEffect(() => subscribeLocation(forceUpdate), []);
35
+ useEffect(() => subscribeLocation(() => {
36
+ startTransition(() => {
37
+ forceUpdate();
38
+ });
39
+ }), []);
36
40
  }
37
41
  export function useLocation() {
38
42
  useLocationSubscription();
@@ -7,6 +7,8 @@ export interface LoaderArgs {
7
7
  readonly searchParams: URLSearchParams;
8
8
  }
9
9
  export type LoaderFunction<T = unknown> = (args: LoaderArgs) => T | Promise<T>;
10
+ export type StaticParams = Record<string, string | string[]>;
11
+ export type GenerateStaticParams = () => StaticParams[] | Promise<StaticParams[]>;
10
12
  export type Revalidate = number | false;
11
13
  export type LoaderData<T> = T extends (...args: never[]) => infer R ? Awaited<R> : T;
12
14
  interface RouteData {
@@ -14,7 +16,30 @@ interface RouteData {
14
16
  data: unknown;
15
17
  head?: HeadSpec;
16
18
  }
19
+ interface Entry {
20
+ status: 'pending' | 'done' | 'error';
21
+ promise: Promise<void>;
22
+ value?: RouteData;
23
+ error?: unknown;
24
+ loadedAt: number;
25
+ revalidate: Revalidate;
26
+ epoch: number;
27
+ hasLoader: boolean;
28
+ prefetched: boolean;
29
+ }
30
+ export declare function subscribeLoaderCache(listener: () => void): () => void;
31
+ export interface LoaderCacheSnapshot {
32
+ readonly key: string;
33
+ readonly status: Entry['status'];
34
+ readonly hasLoader: boolean;
35
+ readonly revalidate: Revalidate;
36
+ readonly loadedAt: number;
37
+ readonly epoch: number;
38
+ readonly data: unknown;
39
+ }
40
+ export declare function inspectLoaderCache(): LoaderCacheSnapshot[];
17
41
  export declare function loaderKey(pathname: string, search: string): string;
42
+ export declare function prefetchRouteData(route: RouteDef, params: RouteParams, pathname: string, search: string): void;
18
43
  export declare function readRouteData(route: RouteDef, params: RouteParams, key: string, epoch: number): RouteData;
19
44
  export declare function clearLoaderData(): void;
20
45
  export declare function invalidateLoaderData(href?: string): void;
@@ -1,14 +1,45 @@
1
1
  import { createContext, useContext } from 'react';
2
2
  import { resolveMetadata } from '../head/metadata.js';
3
- import { refresh as rerender } from '../navigation/navigation.js';
3
+ import { navigationEpoch, refresh as rerender } from '../navigation/navigation.js';
4
4
  const cache = new Map();
5
5
  const MAX_ENTRIES = 32;
6
+ const cacheListeners = new Set();
7
+ let cacheSnapshot = [];
8
+ function emitCache() {
9
+ cacheSnapshot = [...cache.entries()].map(([key, e]) => ({
10
+ key,
11
+ status: e.status,
12
+ hasLoader: e.hasLoader,
13
+ revalidate: e.revalidate,
14
+ loadedAt: e.loadedAt,
15
+ epoch: e.epoch,
16
+ data: e.value?.data,
17
+ }));
18
+ for (const l of cacheListeners)
19
+ l();
20
+ }
21
+ export function subscribeLoaderCache(listener) {
22
+ cacheListeners.add(listener);
23
+ return () => {
24
+ cacheListeners.delete(listener);
25
+ };
26
+ }
27
+ export function inspectLoaderCache() {
28
+ return cacheSnapshot;
29
+ }
6
30
  export function loaderKey(pathname, search) {
7
31
  return `${pathname}${search}`;
8
32
  }
9
- async function loadRoute(route, params) {
33
+ export function prefetchRouteData(route, params, pathname, search) {
34
+ const key = loaderKey(pathname, search);
35
+ const existing = cache.get(key);
36
+ if (existing && existing.status !== 'error')
37
+ return;
38
+ startFetch(route, params, key, navigationEpoch(), search, true);
39
+ }
40
+ async function loadRoute(route, params, search) {
10
41
  const mod = await route.load();
11
- const searchParams = new URLSearchParams(typeof window === 'undefined' ? '' : window.location.search);
42
+ const searchParams = new URLSearchParams(search);
12
43
  const data = mod.loader ? await mod.loader({ params, searchParams }) : undefined;
13
44
  let head;
14
45
  if (mod.generateMetadata) {
@@ -30,11 +61,14 @@ function isStale(entry, epoch) {
30
61
  return false;
31
62
  if (entry.revalidate === false)
32
63
  return false;
33
- if (entry.revalidate === 0)
64
+ if (entry.revalidate === 0) {
65
+ if (entry.prefetched)
66
+ return false;
34
67
  return entry.epoch !== epoch;
68
+ }
35
69
  return Date.now() - entry.loadedAt >= entry.revalidate * 1000;
36
70
  }
37
- function startFetch(route, params, key, epoch) {
71
+ function startFetch(route, params, key, epoch, search, prefetched = false) {
38
72
  const created = {
39
73
  status: 'pending',
40
74
  promise: Promise.resolve(),
@@ -42,17 +76,20 @@ function startFetch(route, params, key, epoch) {
42
76
  revalidate: 0,
43
77
  epoch,
44
78
  hasLoader: false,
79
+ prefetched,
45
80
  };
46
- created.promise = loadRoute(route, params).then((result) => {
81
+ created.promise = loadRoute(route, params, search).then((result) => {
47
82
  created.value = result.data;
48
83
  created.revalidate = result.revalidate;
49
84
  created.hasLoader = result.hasLoader;
50
85
  created.loadedAt = Date.now();
51
86
  created.status = 'done';
87
+ emitCache();
52
88
  }, (error) => {
53
89
  created.error = error;
54
90
  created.loadedAt = Date.now();
55
91
  created.status = 'error';
92
+ emitCache();
56
93
  });
57
94
  cache.set(key, created);
58
95
  while (cache.size > MAX_ENTRIES) {
@@ -61,24 +98,31 @@ function startFetch(route, params, key, epoch) {
61
98
  break;
62
99
  cache.delete(oldest);
63
100
  }
101
+ emitCache();
64
102
  return created;
65
103
  }
66
104
  export function readRouteData(route, params, key, epoch) {
105
+ const search = typeof window === 'undefined' ? '' : window.location.search;
67
106
  let entry = cache.get(key);
68
107
  if (entry && entry.status !== 'pending' && isStale(entry, epoch)) {
69
108
  entry = undefined;
70
109
  }
71
- entry ??= startFetch(route, params, key, epoch);
110
+ entry ??= startFetch(route, params, key, epoch, search);
72
111
  if (entry.status === 'pending')
73
112
  throw entry.promise;
74
113
  if (entry.status === 'error')
75
114
  throw entry.error;
76
115
  if (!entry.value)
77
116
  throw entry.promise;
117
+ if (entry.prefetched) {
118
+ entry.prefetched = false;
119
+ entry.epoch = epoch;
120
+ }
78
121
  return entry.value;
79
122
  }
80
123
  export function clearLoaderData() {
81
124
  cache.clear();
125
+ emitCache();
82
126
  }
83
127
  function keyForHref(href) {
84
128
  if (typeof window === 'undefined')
@@ -94,11 +138,13 @@ function keyForHref(href) {
94
138
  export function invalidateLoaderData(href) {
95
139
  if (href === undefined) {
96
140
  cache.clear();
141
+ emitCache();
97
142
  return;
98
143
  }
99
144
  const key = keyForHref(href);
100
145
  if (key !== undefined)
101
146
  cache.delete(key);
147
+ emitCache();
102
148
  }
103
149
  export function revalidate(href) {
104
150
  invalidateLoaderData(href);
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { createRoot } from 'react-dom/client';
3
- import { DevErrorBoundary, DevErrorOverlay, initDevErrorOverlay, isDevMode, } from '../dev/error-overlay.js';
3
+ import { DevToolbar } from '../dev/devtools.js';
4
+ import { DevErrorBoundary, DevErrorOverlay, initDevErrorOverlay, } from '../dev/error-overlay.js';
4
5
  import { initNavigation } from '../navigation/navigation.js';
5
6
  import { startPrefetcher } from '../navigation/prefetch.js';
6
7
  import { Router } from './Router.js';
@@ -10,9 +11,9 @@ export function mount(routes, layout = null, notFound = null, globalError = null
10
11
  throw new Error('toil: #root element not found');
11
12
  initNavigation();
12
13
  const app = (_jsx(Router, { routes: routes, layout: layout, notFound: notFound, globalError: globalError, slots: slots }));
13
- if (isDevMode()) {
14
+ if (import.meta.env.DEV) {
14
15
  initDevErrorOverlay();
15
- createRoot(el).render(_jsxs(_Fragment, { children: [_jsx(DevErrorBoundary, { children: app }), _jsx(DevErrorOverlay, {})] }));
16
+ createRoot(el).render(_jsxs(_Fragment, { children: [_jsx(DevErrorBoundary, { children: app }), _jsx(DevErrorOverlay, {}), _jsx(DevToolbar, { routes: routes, slots: slots })] }));
16
17
  }
17
18
  else {
18
19
  createRoot(el).render(app);