@buzola/router 0.0.18 → 0.0.19

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 (47) hide show
  1. package/dist/define/create-page.js +1 -1
  2. package/dist/engine/loader-cache.d.ts +1 -1
  3. package/dist/engine/loader-cache.js +1 -1
  4. package/dist/engine/loader-state.d.ts +5 -5
  5. package/dist/engine/loader-state.d.ts.map +1 -1
  6. package/dist/engine/loader-state.js +8 -8
  7. package/dist/engine/loader-state.js.map +1 -1
  8. package/dist/engine/matcher.js +4 -4
  9. package/dist/engine/matcher.js.map +1 -1
  10. package/dist/engine/navigation-adapter.js +2 -2
  11. package/dist/engine/navigation-adapter.js.map +1 -1
  12. package/dist/engine/route-tree.js +2 -2
  13. package/dist/engine/route-tree.js.map +1 -1
  14. package/dist/engine/router.d.ts +6 -6
  15. package/dist/engine/router.js +10 -10
  16. package/dist/engine/router.js.map +1 -1
  17. package/dist/engine/types.d.ts +8 -8
  18. package/dist/engine/types.d.ts.map +1 -1
  19. package/dist/engine/utils.d.ts +3 -3
  20. package/dist/engine/utils.js +3 -3
  21. package/dist/react/hooks.d.ts +2 -2
  22. package/dist/react/hooks.js +1 -1
  23. package/dist/react/link.d.ts +4 -4
  24. package/dist/react/link.js +1 -1
  25. package/dist/react/outlet.js +2 -2
  26. package/dist/react/outlet.js.map +1 -1
  27. package/package.json +1 -1
  28. package/src/__tests__/create-page.test.ts +1 -1
  29. package/src/__tests__/loader-cache.test.ts +1 -1
  30. package/src/__tests__/loader-state.test.ts +12 -12
  31. package/src/__tests__/navigation-adapter.test.ts +2 -2
  32. package/src/__tests__/react.test.tsx +4 -4
  33. package/src/__tests__/router.test.ts +2 -2
  34. package/src/__tests__/setup-dom.ts +1 -1
  35. package/src/__tests__/type-safety.test.tsx +22 -22
  36. package/src/define/create-page.tsx +1 -1
  37. package/src/engine/loader-cache.ts +1 -1
  38. package/src/engine/loader-state.ts +9 -9
  39. package/src/engine/matcher.ts +4 -4
  40. package/src/engine/navigation-adapter.ts +2 -2
  41. package/src/engine/route-tree.ts +3 -3
  42. package/src/engine/router.ts +11 -11
  43. package/src/engine/types.ts +8 -8
  44. package/src/engine/utils.ts +3 -3
  45. package/src/react/hooks.ts +2 -2
  46. package/src/react/link.tsx +5 -5
  47. package/src/react/outlet.tsx +2 -2
@@ -22,12 +22,12 @@ export function extractParamNames(pattern) {
22
22
  return names;
23
23
  }
24
24
  /**
25
- * Whether two URLs address the same page — i.e. only search/hash differ.
25
+ * Whether two URLs address the same page, i.e. only search/hash differ.
26
26
  * Same-path navigations (filters, sorting, pagination, search) keep the page
27
27
  * on screen and keep its loader data as stale; path changes do not.
28
28
  *
29
- * Note this ignores the hash. Callers that care about fragment changes — focus
30
- * handling, for one — have to compare `hash` themselves.
29
+ * Note this ignores the hash. Callers that care about fragment changes (focus
30
+ * handling, for one) have to compare `hash` themselves.
31
31
  */
32
32
  export function isSamePath(a, b) {
33
33
  const urlA = typeof a === 'string' ? new URL(a) : a;
@@ -7,7 +7,7 @@ export declare function useRouter(): Router;
7
7
  /**
8
8
  * Type-safe navigation function.
9
9
  * Uses page IDs instead of URL patterns.
10
- * Persistent params are optional — resolved from current URL or fallback.
10
+ * Persistent params are optional: resolved from current URL or fallback.
11
11
  */
12
12
  type NavigateFn = <P extends RegisteredPage>(to: P, ...args: [keyof EffectivePageParams<P>] extends [never] ? [options?: NavigateOptions] : {} extends EffectivePageParams<P> ? [options?: NavigateOptions & {
13
13
  params?: EffectivePageParams<P>;
@@ -16,7 +16,7 @@ type NavigateFn = <P extends RegisteredPage>(to: P, ...args: [keyof EffectivePag
16
16
  }]) => void;
17
17
  /**
18
18
  * Hook to get a type-safe navigate function.
19
- * Navigates using page IDs — the router looks up the route pattern from the page registry.
19
+ * Navigates using page IDs: the router looks up the route pattern from the page registry.
20
20
  */
21
21
  export declare function useNavigate(): NavigateFn;
22
22
  /**
@@ -23,7 +23,7 @@ function useRouteContext() {
23
23
  }
24
24
  /**
25
25
  * Hook to get a type-safe navigate function.
26
- * Navigates using page IDs — the router looks up the route pattern from the page registry.
26
+ * Navigates using page IDs: the router looks up the route pattern from the page registry.
27
27
  */
28
28
  export function useNavigate() {
29
29
  const router = useRouter();
@@ -25,9 +25,9 @@ type LinkPropsBase = Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href'>
25
25
  asChild?: boolean;
26
26
  /**
27
27
  * Prefetch strategy for lazy-loaded route code.
28
- * - `'intent'` — preload on mouseEnter/focus (default)
29
- * - `'render'` — preload as soon as the Link renders
30
- * - `'none'` — disable prefetching
28
+ * - `'intent'`: preload on mouseEnter/focus (default)
29
+ * - `'render'`: preload as soon as the Link renders
30
+ * - `'none'`: disable prefetching
31
31
  */
32
32
  prefetch?: 'intent' | 'render' | 'none';
33
33
  };
@@ -43,7 +43,7 @@ export type LinkProps<P extends RegisteredPage = RegisteredPage> = [keyof Effect
43
43
  };
44
44
  /**
45
45
  * Type-safe link component.
46
- * Uses page IDs for navigation — the router looks up the route pattern from the page registry.
46
+ * Uses page IDs for navigation: the router looks up the route pattern from the page registry.
47
47
  * For simple navigations, renders a native <a> and lets the Navigation API handle it.
48
48
  * Uses programmatic navigation when state, replace, resetScroll, or viewTransition is needed.
49
49
  *
@@ -3,7 +3,7 @@ import React, { useCallback, useEffect, useRef } from 'react';
3
3
  import { useRouter, useRouterState } from './hooks.js';
4
4
  /**
5
5
  * Type-safe link component.
6
- * Uses page IDs for navigation — the router looks up the route pattern from the page registry.
6
+ * Uses page IDs for navigation: the router looks up the route pattern from the page registry.
7
7
  * For simple navigations, renders a native <a> and lets the Navigation API handle it.
8
8
  * Uses programmatic navigation when state, replace, resetScroll, or viewTransition is needed.
9
9
  *
@@ -8,7 +8,7 @@ function DefaultNotFound() {
8
8
  style: { fontFamily: 'system-ui, sans-serif', padding: '2rem', maxWidth: '600px', margin: '0 auto' },
9
9
  }, isDev
10
10
  ? [
11
- React.createElement('h1', { key: 'h' }, '404 — Page Not Found'),
11
+ React.createElement('h1', { key: 'h' }, '404: Page Not Found'),
12
12
  React.createElement('p', { key: 'p1' }, `No route matches "${typeof window !== 'undefined' ? window.location.pathname : ''}".`),
13
13
  React.createElement('p', {
14
14
  key: 'p2',
@@ -56,7 +56,7 @@ export function Outlet({ fallback, errorFallback, notFound }) {
56
56
  };
57
57
  }, [state, depth, effectiveDepth, matches, match, routeContext.params]);
58
58
  if (!match) {
59
- // At root depth with no matches — show 404
59
+ // At root depth with no matches: show 404
60
60
  if (depth === 0 && matches.length === 0) {
61
61
  if (notFound === null)
62
62
  return null;
@@ -1 +1 @@
1
- {"version":3,"file":"outlet.js","sourceRoot":"","sources":["../../src/react/outlet.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"outlet.js","sourceRoot":"","sources":["../../src/react/outlet.tsx"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buzola/router",
3
- "version": "0.0.18",
3
+ "version": "0.0.19",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -610,7 +610,7 @@ describe('redirect()', () => {
610
610
  })
611
611
  .render(() => null)
612
612
 
613
- expect(typeof receivedRedirect).toBe('undefined') // not called yet — component not rendered
613
+ expect(typeof receivedRedirect).toBe('undefined') // not called yet: component not rendered
614
614
  // Verify the type is correct by checking the function signature
615
615
  expect(page.__buzolaPage).toBe(true)
616
616
  })
@@ -91,7 +91,7 @@ describe('LoaderCache', () => {
91
91
  cache.set('a', 1)
92
92
  cache.set('b', 2)
93
93
 
94
- // Update 'a' — should move to most recent
94
+ // Update 'a': should move to most recent
95
95
  cache.set('a', 10)
96
96
 
97
97
  // Now 'b' is the oldest
@@ -74,7 +74,7 @@ describe('PageLoaderState', () => {
74
74
  })
75
75
 
76
76
  describe('invalidation (cache key change)', () => {
77
- it('with stale data — returns stale data with isLoading and starts background fetch', () => {
77
+ it('with stale data: returns stale data with isLoading and starts background fetch', () => {
78
78
  const state = createState()
79
79
  resolveCtx(state, { cacheKey: 'k:0' })
80
80
  state.commitResult('stale-data')
@@ -93,12 +93,12 @@ describe('PageLoaderState', () => {
93
93
  expect(bgLoad).toBeDefined()
94
94
  })
95
95
 
96
- it('without stale data — returns suspend instruction', () => {
96
+ it('without stale data: returns suspend instruction', () => {
97
97
  const state = createState()
98
98
  const promise = Promise.resolve('data')
99
99
  const instruction = resolveCtx(state, { cacheKey: 'k:0', load: () => promise })
100
100
 
101
- // No commitResult called — no stale data
101
+ // No commitResult called: no stale data
102
102
  const second = resolveCtx(state, { cacheKey: 'k:1', load: () => Promise.resolve('new') })
103
103
  expect(second.action).toBe('suspend')
104
104
  })
@@ -149,7 +149,7 @@ describe('PageLoaderState', () => {
149
149
  onBackgroundSettled: settled1,
150
150
  })
151
151
 
152
- // Immediately invalidate again — supersedes the first background fetch
152
+ // Immediately invalidate again: supersedes the first background fetch
153
153
  state.commitBackgroundResult('stale') // commit so hasResolved stays true
154
154
  const settled2 = mock(() => {})
155
155
  resolveCtx(state, {
@@ -200,7 +200,7 @@ describe('PageLoaderState', () => {
200
200
  resolveCtx(state, { url: 'http://localhost/a', cacheKey: 'a:0', loaderCache: cache })
201
201
  state.commitResult('data-a')
202
202
 
203
- // Navigate to B — should save A's data to cache
203
+ // Navigate to B: should save A's data to cache
204
204
  resolveCtx(state, { url: 'http://localhost/b', cacheKey: 'b:0', loaderCache: cache })
205
205
 
206
206
  expect(cache.size).toBe(1)
@@ -218,7 +218,7 @@ describe('PageLoaderState', () => {
218
218
  resolveCtx(state, { url: 'http://localhost/b', cacheKey: 'b:0', loaderCache: cache })
219
219
  state.commitResult('data-b')
220
220
 
221
- // Navigate back to A — should restore from cache
221
+ // Navigate back to A: should restore from cache
222
222
  const instruction = resolveCtx(state, {
223
223
  url: 'http://localhost/a',
224
224
  cacheKey: 'a:0',
@@ -229,11 +229,11 @@ describe('PageLoaderState', () => {
229
229
  expect(instruction.action).toBe('render')
230
230
  if (instruction.action === 'render') {
231
231
  expect(instruction.data).toBe('data-a')
232
- expect(instruction.isLoading).toBe(true) // SWR — stale data + background reload
232
+ expect(instruction.isLoading).toBe(true) // SWR: stale data + background reload
233
233
  }
234
234
  })
235
235
 
236
- it('keeps data as stale when only the query changes — no suspend', () => {
236
+ it('keeps data as stale when only the query changes: no suspend', () => {
237
237
  const cache = new LoaderCache(10)
238
238
  const state = createState()
239
239
 
@@ -301,7 +301,7 @@ describe('PageLoaderState', () => {
301
301
  // Load A without committing (no stale data to cache)
302
302
  resolveCtx(state, { url: 'http://localhost/a', cacheKey: 'a:0', loaderCache: cache })
303
303
 
304
- // Navigate to B — no cache for B
304
+ // Navigate to B: no cache for B
305
305
  const instruction = resolveCtx(state, {
306
306
  url: 'http://localhost/b',
307
307
  cacheKey: 'b:0',
@@ -317,7 +317,7 @@ describe('PageLoaderState', () => {
317
317
  const cache = new LoaderCache(10)
318
318
  const state1 = createState()
319
319
 
320
- // First mount — load and dispose
320
+ // First mount: load and dispose
321
321
  resolveCtx(state1, { url: 'http://localhost/a', cacheKey: 'a:0', loaderCache: cache })
322
322
  state1.commitResult('cached-data')
323
323
  state1.dispose(cache) // saves to cache
@@ -386,13 +386,13 @@ describe('PageLoaderState', () => {
386
386
  })
387
387
 
388
388
  describe('no loader cache', () => {
389
- it('works without loaderCache — no stale caching', () => {
389
+ it('works without loaderCache: no stale caching', () => {
390
390
  const state = createState()
391
391
 
392
392
  resolveCtx(state, { url: 'http://localhost/a', cacheKey: 'a:0' })
393
393
  state.commitResult('data-a')
394
394
 
395
- // URL change without cache — no restore
395
+ // URL change without cache: no restore
396
396
  const instruction = resolveCtx(state, { url: 'http://localhost/b', cacheKey: 'b:0' })
397
397
  expect(instruction.action).toBe('suspend')
398
398
  })
@@ -39,7 +39,7 @@ describe('createBrowserNavigationAdapter', () => {
39
39
  /* The case actually seen in the wild: the getter returns the empty string on a
40
40
  document that is not fully active, which is what Safari handed us. `new
41
41
  URL('')` throws just as `new URL(null)` does, and it throws out of the
42
- `Router` constructor during render — a blank page, not a degraded route. An
42
+ `Router` constructor during render: a blank page, not a degraded route. An
43
43
  empty string is falsy but not nullish, so this is the case `??` would miss. */
44
44
  it('falls back to the document URL when the entry reports an empty URL', () => {
45
45
  const adapter = withNavigation({ url: '' })
@@ -50,7 +50,7 @@ describe('createBrowserNavigationAdapter', () => {
50
50
  /* `url` is also typed nullable: the getter returns null for entries whose
51
51
  document is not the current one and was fetched under a `no-referrer` or
52
52
  `origin` referrer policy. That branch needs `sameDocument` to be false, which
53
- `currentEntry` never is, so it should not be reachable through this call —
53
+ `currentEntry` never is, so it should not be reachable through this call,
54
54
  but the type permits it and the fallback costs nothing. */
55
55
  it('falls back to the document URL when the entry reports no URL', () => {
56
56
  const adapter = withNavigation({ url: null })
@@ -563,7 +563,7 @@ describe('Page loaders', () => {
563
563
  })
564
564
  await flushNav()
565
565
 
566
- // No Suspense fallback — the stale render stays up until the reload lands
566
+ // No Suspense fallback: the stale render stays up until the reload lands
567
567
  expect(container.textContent).toBe('loads:1')
568
568
 
569
569
  await act(async () => {
@@ -672,7 +672,7 @@ describe('Outlet', () => {
672
672
  {
673
673
  path: '/admin',
674
674
  isLayout: true,
675
- // No component — should be skipped
675
+ // No component: should be skipped
676
676
  children: [
677
677
  { path: '/dashboard', component: () => <div>Dashboard</div> },
678
678
  ],
@@ -952,7 +952,7 @@ describe('useBlocker', () => {
952
952
 
953
953
  expect(getByTestId('state').textContent).toBe('idle')
954
954
 
955
- // Try to navigate — should be blocked
955
+ // Try to navigate: should be blocked
956
956
  router.navigate('/about')
957
957
  await flushNav()
958
958
 
@@ -1123,7 +1123,7 @@ describe('ErrorBoundary', () => {
1123
1123
 
1124
1124
  expect(container.textContent).toContain('Route error: boom')
1125
1125
 
1126
- // Change resetKey and stop throwing — should reset
1126
+ // Change resetKey and stop throwing: should reset
1127
1127
  rerender(
1128
1128
  <ErrorBoundary resetKey="b">
1129
1129
  <MaybeThrow shouldThrow={false} />
@@ -307,7 +307,7 @@ describe('Router same-URL navigation', () => {
307
307
  expect(count()).toBe(1)
308
308
  })
309
309
 
310
- it('does not invalidate when the URL changes — the href-keyed loader reloads on its own', () => {
310
+ it('does not invalidate when the URL changes: the href-keyed loader reloads on its own', () => {
311
311
  const { router, count } = createCountingRouter('http://localhost/')
312
312
 
313
313
  router.navigate('/list')
@@ -408,7 +408,7 @@ describe('Router focus reset', () => {
408
408
  try {
409
409
  const { router, captured } = createCapturingRouter('http://localhost/list')
410
410
 
411
- // No route matches — the router bails out before intercepting
411
+ // No route matches: the router bails out before intercepting
412
412
  router.navigate('/unmatched', { focusReset: 'manual' })
413
413
  expect(captured).toHaveLength(0)
414
414
 
@@ -11,7 +11,7 @@ for (const key of Object.getOwnPropertyNames(window)) {
11
11
  configurable: true,
12
12
  })
13
13
  } catch {
14
- // Some properties can't be set — skip them
14
+ // Some properties can't be set: skip them
15
15
  }
16
16
  }
17
17
 
@@ -29,7 +29,7 @@ describe('createPage route type safety', () => {
29
29
  })
30
30
 
31
31
  it('rejects dynamic route without params', () => {
32
- // @ts-expect-error — :id in route but no .params() defined
32
+ // @ts-expect-error: :id in route but no .params() defined
33
33
  const _page = createPage().route('/users/:id').render(() => null)
34
34
  expect(true).toBe(true)
35
35
  })
@@ -45,7 +45,7 @@ describe('createPage route type safety', () => {
45
45
  it('rejects dynamic route when param is not in schema', () => {
46
46
  const _page = createPage()
47
47
  .params(mockSchema<{ userId: string }>())
48
- // @ts-expect-error — :id is not a key of { userId: string }
48
+ // @ts-expect-error: :id is not a key of { userId: string }
49
49
  .route('/users/:id')
50
50
  .render(({ params }) => null)
51
51
  expect(true).toBe(true)
@@ -62,7 +62,7 @@ describe('createPage route type safety', () => {
62
62
  it('rejects when one route param is missing from schema', () => {
63
63
  const _page = createPage()
64
64
  .params(mockSchema<{ userId: string }>())
65
- // @ts-expect-error — :postId is not in { userId: string }
65
+ // @ts-expect-error: :postId is not in { userId: string }
66
66
  .route('/users/:userId/posts/:postId')
67
67
  .render(({ params }) => null)
68
68
  expect(true).toBe(true)
@@ -97,14 +97,14 @@ describe('createPage catch type safety', () => {
97
97
 
98
98
  it('no .catch() after .catch()', () => {
99
99
  const withCatch = createPage().catch(({ error }) => <div>{error.message}</div>)
100
- // @ts-expect-error — .catch() should not be available after .catch()
100
+ // @ts-expect-error: .catch() should not be available after .catch()
101
101
  withCatch.catch
102
102
  expect(true).toBe(true)
103
103
  })
104
104
 
105
105
  it('no .loader() after .catch()', () => {
106
106
  const withCatch = createPage().catch(({ error }) => <div>{error.message}</div>)
107
- // @ts-expect-error — .loader() should not be available after .catch()
107
+ // @ts-expect-error: .loader() should not be available after .catch()
108
108
  withCatch.loader
109
109
  expect(true).toBe(true)
110
110
  })
@@ -125,7 +125,7 @@ describe('createPage catch type safety', () => {
125
125
  it('.catch() handler has empty params when no schema defined', () => {
126
126
  const _page = createPage()
127
127
  .catch(({ params }) => {
128
- // @ts-expect-error — no params defined, accessing unknown key
128
+ // @ts-expect-error: no params defined, accessing unknown key
129
129
  params.anything
130
130
  return null
131
131
  })
@@ -144,7 +144,7 @@ describe('LinkProps type safety', () => {
144
144
  })
145
145
 
146
146
  it('static page rejects params', () => {
147
- // @ts-expect-error — static page should not accept params
147
+ // @ts-expect-error: static page should not accept params
148
148
  const _a: LinkProps<'about'> = { to: 'about', params: { foo: 'bar' } }
149
149
  expect(true).toBe(true)
150
150
  })
@@ -152,13 +152,13 @@ describe('LinkProps type safety', () => {
152
152
  it('dynamic page requires params', () => {
153
153
  const _a: LinkProps<'users/detail'> = { to: 'users/detail', params: { userId: '1' } }
154
154
 
155
- // @ts-expect-error — missing required params
155
+ // @ts-expect-error: missing required params
156
156
  const _b: LinkProps<'users/detail'> = { to: 'users/detail' }
157
157
  expect(true).toBe(true)
158
158
  })
159
159
 
160
160
  it('dynamic page requires correct param names', () => {
161
- // @ts-expect-error — wrong param name
161
+ // @ts-expect-error: wrong param name
162
162
  const _a: LinkProps<'users/detail'> = { to: 'users/detail', params: { id: '1' } }
163
163
  expect(true).toBe(true)
164
164
  })
@@ -171,14 +171,14 @@ describe('LinkProps type safety', () => {
171
171
 
172
172
  const _b: LinkProps<'users/posts'> = {
173
173
  to: 'users/posts',
174
- // @ts-expect-error — missing postId
174
+ // @ts-expect-error: missing postId
175
175
  params: { userId: '1' },
176
176
  }
177
177
  expect(true).toBe(true)
178
178
  })
179
179
 
180
180
  it('rejects unregistered pages', () => {
181
- // @ts-expect-error — page not in BuzolaPageMap
181
+ // @ts-expect-error: page not in BuzolaPageMap
182
182
  const _a: LinkProps<'unknown'> = { to: 'unknown' }
183
183
  expect(true).toBe(true)
184
184
  })
@@ -196,7 +196,7 @@ describe('Link JSX generic inference', () => {
196
196
  })
197
197
 
198
198
  it('rejects params on static page', () => {
199
- // @ts-expect-error — static page should not accept params
199
+ // @ts-expect-error: static page should not accept params
200
200
  const _el = <Link to="about" params={{ foo: 'bar' }}>About</Link>
201
201
  expect(true).toBe(true)
202
202
  })
@@ -204,13 +204,13 @@ describe('Link JSX generic inference', () => {
204
204
  it('requires params on dynamic page', () => {
205
205
  const _el = <Link to="users/detail" params={{ userId: '1' }}>User</Link>
206
206
 
207
- // @ts-expect-error — missing required params
207
+ // @ts-expect-error: missing required params
208
208
  const _el2 = <Link to="users/detail">User</Link>
209
209
  expect(true).toBe(true)
210
210
  })
211
211
 
212
212
  it('rejects wrong param names', () => {
213
- // @ts-expect-error — wrong param name
213
+ // @ts-expect-error: wrong param name
214
214
  const _el = <Link to="users/detail" params={{ id: '1' }}>User</Link>
215
215
  expect(true).toBe(true)
216
216
  })
@@ -218,13 +218,13 @@ describe('Link JSX generic inference', () => {
218
218
  it('requires all params for multi-param page', () => {
219
219
  const _ok = <Link to="users/posts" params={{ userId: '1', postId: '2' }}>Post</Link>
220
220
 
221
- // @ts-expect-error — missing postId
221
+ // @ts-expect-error: missing postId
222
222
  const _bad = <Link to="users/posts" params={{ userId: '1' }}>Post</Link>
223
223
  expect(true).toBe(true)
224
224
  })
225
225
 
226
226
  it('rejects unregistered pages', () => {
227
- // @ts-expect-error — page not in BuzolaPageMap
227
+ // @ts-expect-error: page not in BuzolaPageMap
228
228
  const _el = <Link to="nope">Nope</Link>
229
229
  expect(true).toBe(true)
230
230
  })
@@ -258,7 +258,7 @@ describe('NavigateFn type safety', () => {
258
258
  })
259
259
 
260
260
  it('static page rejects params', () => {
261
- // @ts-expect-error — static page should not accept params
261
+ // @ts-expect-error: static page should not accept params
262
262
  noop('home', { params: { foo: 'bar' } })
263
263
  expect(true).toBe(true)
264
264
  })
@@ -266,19 +266,19 @@ describe('NavigateFn type safety', () => {
266
266
  it('dynamic page requires options with params', () => {
267
267
  noop('users/detail', { params: { userId: '1' } })
268
268
 
269
- // @ts-expect-error — missing required options with params
269
+ // @ts-expect-error: missing required options with params
270
270
  noop('users/detail')
271
271
  expect(true).toBe(true)
272
272
  })
273
273
 
274
274
  it('dynamic page requires correct param names', () => {
275
- // @ts-expect-error — wrong param name (id instead of userId)
275
+ // @ts-expect-error: wrong param name (id instead of userId)
276
276
  noop('users/detail', { params: { id: '1' } })
277
277
  expect(true).toBe(true)
278
278
  })
279
279
 
280
280
  it('dynamic page rejects params passed directly as options (without params wrapper)', () => {
281
- // @ts-expect-error — { userId: '1' } is not valid, must be { params: { userId: '1' } }
281
+ // @ts-expect-error: { userId: '1' } is not valid, must be { params: { userId: '1' } }
282
282
  noop('users/detail', { userId: '1' })
283
283
  expect(true).toBe(true)
284
284
  })
@@ -286,13 +286,13 @@ describe('NavigateFn type safety', () => {
286
286
  it('multi-param page requires all params', () => {
287
287
  noop('users/posts', { params: { userId: '1', postId: '2' } })
288
288
 
289
- // @ts-expect-error — missing postId
289
+ // @ts-expect-error: missing postId
290
290
  noop('users/posts', { params: { userId: '1' } })
291
291
  expect(true).toBe(true)
292
292
  })
293
293
 
294
294
  it('rejects unregistered pages', () => {
295
- // @ts-expect-error — page not in BuzolaPageMap
295
+ // @ts-expect-error: page not in BuzolaPageMap
296
296
  noop('unknown')
297
297
  expect(true).toBe(true)
298
298
  })
@@ -156,7 +156,7 @@ function combineLoaders(loaders: LoaderFn[]): LoaderFn | undefined {
156
156
  }
157
157
 
158
158
  /**
159
- * Follow a loader redirect, unless it points at the URL the page is already on —
159
+ * Follow a loader redirect, unless it points at the URL the page is already on:
160
160
  * that navigation re-runs the loader, which returns the same redirect, forever.
161
161
  */
162
162
  function followRedirect(router: Router | null, target: BuzolaRedirect): void {
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Global LRU cache for page loader data.
3
3
  * Shared across all page components to limit total memory usage.
4
- * Uses Map insertion order for LRU eviction — oldest entries are evicted first.
4
+ * Uses Map insertion order for LRU eviction: oldest entries are evicted first.
5
5
  */
6
6
  export class LoaderCache {
7
7
  private cache = new Map<string, unknown>()
@@ -8,7 +8,7 @@ export type LoaderInstruction =
8
8
  | { action: 'suspend'; promise: Promise<unknown> }
9
9
 
10
10
  export interface LoaderResolveContext {
11
- /** Current URL href — used for stale cache keying */
11
+ /** Current URL href: used for stale cache keying */
12
12
  currentUrlHref: string
13
13
  /** Cache key that changes on URL change or invalidation */
14
14
  cacheKey: string
@@ -31,7 +31,7 @@ let nextId = 0
31
31
  /**
32
32
  * Manages loader data lifecycle outside of React's render cycle.
33
33
  *
34
- * This state survives React Suspense retries — when use() suspends, React
34
+ * This state survives React Suspense retries. When use() suspends, React
35
35
  * discards uncommitted hook state (useRef, useState, useMemo). Closure/instance
36
36
  * state is preserved, preventing infinite suspend loops from promise recreation.
37
37
  *
@@ -43,7 +43,7 @@ export class PageLoaderState {
43
43
  private activeCacheKey: string | null = null
44
44
  private activePromise: Promise<unknown> | null = null
45
45
  private activeResolvedData: unknown
46
- /** URL the currently held data was loaded for — data is cached under this href, not the active one. */
46
+ /** URL the currently held data was loaded for: data is cached under this href, not the active one. */
47
47
  private resolvedUrlHref: string | null = null
48
48
  private hasResolved = false
49
49
  private pendingBackgroundKey: string | null = null
@@ -62,7 +62,7 @@ export class PageLoaderState {
62
62
 
63
63
  this.handleUrlChange(currentUrlHref, loaderCache)
64
64
 
65
- // ─── Same cache key — return current state ─────
65
+ // ─── Same cache key: return current state ─────
66
66
  if (this.activeCacheKey === cacheKey) {
67
67
  if (this.pendingBackgroundKey === cacheKey) {
68
68
  return { action: 'render', data: this.activeResolvedData, isLoading: true }
@@ -110,14 +110,14 @@ export class PageLoaderState {
110
110
  this.hasResolved = true
111
111
  }
112
112
 
113
- /** Commit background SWR success — update data and clear pending flag */
113
+ /** Commit background SWR success: update data and clear pending flag */
114
114
  commitBackgroundResult(data: unknown): void {
115
115
  this.activeResolvedData = data
116
116
  this.resolvedUrlHref = this.activeUrlHref
117
117
  this.pendingBackgroundKey = null
118
118
  }
119
119
 
120
- /** Mark background load as failed — next render will suspend with rejected promise */
120
+ /** Mark background load as failed: next render will suspend with rejected promise */
121
121
  commitBackgroundError(): void {
122
122
  this.hasResolved = false
123
123
  this.pendingBackgroundKey = null
@@ -141,7 +141,7 @@ export class PageLoaderState {
141
141
 
142
142
  private handleUrlChange(currentUrlHref: string, loaderCache: LoaderCache | undefined): void {
143
143
  if (this.activeUrlHref !== null && this.activeUrlHref !== currentUrlHref) {
144
- // URL changed — save the held data to stale cache under the href it was loaded for
144
+ // URL changed: save the held data to stale cache under the href it was loaded for
145
145
  if (loaderCache && this.hasResolved && this.activeResolvedData !== undefined && this.resolvedUrlHref) {
146
146
  this.saveToStaleCache(loaderCache, this.resolvedUrlHref, this.activeResolvedData)
147
147
  }
@@ -152,7 +152,7 @@ export class PageLoaderState {
152
152
  this.resolvedUrlHref = currentUrlHref
153
153
  this.hasResolved = true
154
154
  } else if (!isSamePath(this.activeUrlHref, currentUrlHref)) {
155
- // Different path — the held data describes another page, drop it and suspend
155
+ // Different path: the held data describes another page, so drop it and suspend
156
156
  this.hasResolved = false
157
157
  this.activeResolvedData = undefined
158
158
  this.resolvedUrlHref = null
@@ -161,7 +161,7 @@ export class PageLoaderState {
161
161
  // keep the data as stale so resolve() serves it while the new query loads.
162
162
  this.pendingBackgroundKey = null
163
163
  } else if (this.activeUrlHref === null && loaderCache) {
164
- // Remount — try to restore from stale cache
164
+ // Remount: try to restore from stale cache
165
165
  const cached = this.restoreFromStaleCache(loaderCache, currentUrlHref)
166
166
  if (cached.hit) {
167
167
  this.activeResolvedData = cached.value
@@ -20,7 +20,7 @@ function matchTrie(
20
20
  values: string[],
21
21
  pathname: string,
22
22
  ): RouteMatch[] | null {
23
- // All segments consumed — check if this trie node has a terminal route
23
+ // All segments consumed: check if this trie node has a terminal route
24
24
  if (index === segments.length) {
25
25
  if (node.route) {
26
26
  return buildMatchChain(node.route, values, pathname)
@@ -30,20 +30,20 @@ function matchTrie(
30
30
 
31
31
  const segment = segments[index]
32
32
 
33
- // 1. Try static child first — O(1) Map lookup, always most specific
33
+ // 1. Try static child first: O(1) Map lookup, always most specific
34
34
  const staticChild = node.staticChildren.get(segment)
35
35
  if (staticChild) {
36
36
  const result = matchTrie(staticChild, segments, index + 1, values, pathname)
37
37
  if (result) return result
38
38
  }
39
39
 
40
- // 2. Try dynamic child — matches any single segment
40
+ // 2. Try dynamic child: matches any single segment
41
41
  if (node.dynamicChild) {
42
42
  const result = matchTrie(node.dynamicChild, segments, index + 1, [...values, decodeURIComponent(segment)], pathname)
43
43
  if (result) return result
44
44
  }
45
45
 
46
- // 3. Try catch-all child — consumes all remaining segments
46
+ // 3. Try catch-all child: consumes all remaining segments
47
47
  if (node.catchAllChild) {
48
48
  const remaining = segments.slice(index).map(s => decodeURIComponent(s)).join('/')
49
49
  return buildMatchChain(node.catchAllChild, [...values, remaining], pathname)