@benjavicente/router-core 1.168.9 → 1.169.2

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 (169) hide show
  1. package/dist/cjs/Matches.cjs +1 -1
  2. package/dist/cjs/Matches.cjs.map +1 -1
  3. package/dist/cjs/config.cjs +1 -1
  4. package/dist/cjs/config.cjs.map +1 -1
  5. package/dist/cjs/defer.cjs +1 -1
  6. package/dist/cjs/defer.cjs.map +1 -1
  7. package/dist/cjs/hash-scroll.cjs +1 -1
  8. package/dist/cjs/hash-scroll.cjs.map +1 -1
  9. package/dist/cjs/index.cjs +4 -0
  10. package/dist/cjs/index.d.cts +3 -3
  11. package/dist/cjs/isServer/client.cjs +1 -1
  12. package/dist/cjs/isServer/client.cjs.map +1 -1
  13. package/dist/cjs/isServer/development.cjs +1 -1
  14. package/dist/cjs/isServer/development.cjs.map +1 -1
  15. package/dist/cjs/isServer/server.cjs +1 -1
  16. package/dist/cjs/isServer/server.cjs.map +1 -1
  17. package/dist/cjs/link.cjs +1 -1
  18. package/dist/cjs/link.cjs.map +1 -1
  19. package/dist/cjs/load-matches.cjs +26 -26
  20. package/dist/cjs/load-matches.cjs.map +1 -1
  21. package/dist/cjs/manifest.cjs +31 -0
  22. package/dist/cjs/manifest.cjs.map +1 -1
  23. package/dist/cjs/manifest.d.cts +8 -0
  24. package/dist/cjs/new-process-route-tree.cjs +55 -67
  25. package/dist/cjs/new-process-route-tree.cjs.map +1 -1
  26. package/dist/cjs/new-process-route-tree.d.cts +4 -26
  27. package/dist/cjs/not-found.cjs +1 -1
  28. package/dist/cjs/not-found.cjs.map +1 -1
  29. package/dist/cjs/path.cjs +1 -22
  30. package/dist/cjs/path.cjs.map +1 -1
  31. package/dist/cjs/root.cjs +1 -1
  32. package/dist/cjs/root.cjs.map +1 -1
  33. package/dist/cjs/route.cjs.map +1 -1
  34. package/dist/cjs/route.d.cts +6 -34
  35. package/dist/cjs/router.cjs +94 -80
  36. package/dist/cjs/router.cjs.map +1 -1
  37. package/dist/cjs/router.d.cts +6 -5
  38. package/dist/cjs/scroll-restoration-script/server.cjs +1 -1
  39. package/dist/cjs/scroll-restoration-script/server.cjs.map +1 -1
  40. package/dist/cjs/scroll-restoration.cjs +7 -7
  41. package/dist/cjs/scroll-restoration.cjs.map +1 -1
  42. package/dist/cjs/searchParams.cjs +2 -2
  43. package/dist/cjs/searchParams.cjs.map +1 -1
  44. package/dist/cjs/ssr/constants.cjs +2 -2
  45. package/dist/cjs/ssr/constants.cjs.map +1 -1
  46. package/dist/cjs/ssr/createRequestHandler.cjs +2 -2
  47. package/dist/cjs/ssr/createRequestHandler.cjs.map +1 -1
  48. package/dist/cjs/ssr/serializer/RawStream.cjs +50 -41
  49. package/dist/cjs/ssr/serializer/RawStream.cjs.map +1 -1
  50. package/dist/cjs/ssr/serializer/RawStream.d.cts +12 -4
  51. package/dist/cjs/ssr/serializer/ShallowErrorPlugin.cjs +1 -1
  52. package/dist/cjs/ssr/serializer/ShallowErrorPlugin.cjs.map +1 -1
  53. package/dist/cjs/ssr/serializer/ShallowErrorPlugin.d.cts +2 -2
  54. package/dist/cjs/ssr/serializer/seroval-plugins.cjs +1 -1
  55. package/dist/cjs/ssr/serializer/seroval-plugins.cjs.map +1 -1
  56. package/dist/cjs/ssr/serializer/seroval-plugins.d.cts +2 -1
  57. package/dist/cjs/ssr/serializer/transformer.cjs +16 -14
  58. package/dist/cjs/ssr/serializer/transformer.cjs.map +1 -1
  59. package/dist/cjs/ssr/serializer/transformer.d.cts +24 -23
  60. package/dist/cjs/ssr/ssr-client.cjs +9 -9
  61. package/dist/cjs/ssr/ssr-client.cjs.map +1 -1
  62. package/dist/cjs/ssr/ssr-server.cjs +110 -22
  63. package/dist/cjs/ssr/ssr-server.cjs.map +1 -1
  64. package/dist/cjs/ssr/ssr-server.d.cts +4 -2
  65. package/dist/cjs/ssr/transformStreamWithRouter.cjs +10 -7
  66. package/dist/cjs/ssr/transformStreamWithRouter.cjs.map +1 -1
  67. package/dist/cjs/stores.cjs +57 -57
  68. package/dist/cjs/stores.cjs.map +1 -1
  69. package/dist/cjs/stores.d.cts +16 -16
  70. package/dist/cjs/utils.cjs +12 -7
  71. package/dist/cjs/utils.cjs.map +1 -1
  72. package/dist/cjs/utils.d.cts +1 -0
  73. package/dist/esm/Matches.js +1 -1
  74. package/dist/esm/Matches.js.map +1 -1
  75. package/dist/esm/config.js +1 -1
  76. package/dist/esm/config.js.map +1 -1
  77. package/dist/esm/defer.js +1 -1
  78. package/dist/esm/defer.js.map +1 -1
  79. package/dist/esm/hash-scroll.js +1 -1
  80. package/dist/esm/hash-scroll.js.map +1 -1
  81. package/dist/esm/index.d.ts +3 -3
  82. package/dist/esm/index.js +3 -3
  83. package/dist/esm/isServer/client.js +1 -1
  84. package/dist/esm/isServer/client.js.map +1 -1
  85. package/dist/esm/isServer/development.js +1 -1
  86. package/dist/esm/isServer/development.js.map +1 -1
  87. package/dist/esm/isServer/server.js +1 -1
  88. package/dist/esm/isServer/server.js.map +1 -1
  89. package/dist/esm/link.js +1 -1
  90. package/dist/esm/link.js.map +1 -1
  91. package/dist/esm/load-matches.js +26 -26
  92. package/dist/esm/load-matches.js.map +1 -1
  93. package/dist/esm/manifest.d.ts +8 -0
  94. package/dist/esm/manifest.js +28 -1
  95. package/dist/esm/manifest.js.map +1 -1
  96. package/dist/esm/new-process-route-tree.d.ts +4 -26
  97. package/dist/esm/new-process-route-tree.js +55 -68
  98. package/dist/esm/new-process-route-tree.js.map +1 -1
  99. package/dist/esm/not-found.js +1 -1
  100. package/dist/esm/not-found.js.map +1 -1
  101. package/dist/esm/path.js +1 -22
  102. package/dist/esm/path.js.map +1 -1
  103. package/dist/esm/root.js +1 -1
  104. package/dist/esm/root.js.map +1 -1
  105. package/dist/esm/route.d.ts +6 -34
  106. package/dist/esm/route.js.map +1 -1
  107. package/dist/esm/router.d.ts +6 -5
  108. package/dist/esm/router.js +96 -82
  109. package/dist/esm/router.js.map +1 -1
  110. package/dist/esm/scroll-restoration-script/server.js +1 -1
  111. package/dist/esm/scroll-restoration-script/server.js.map +1 -1
  112. package/dist/esm/scroll-restoration.js +7 -7
  113. package/dist/esm/scroll-restoration.js.map +1 -1
  114. package/dist/esm/searchParams.js +2 -2
  115. package/dist/esm/searchParams.js.map +1 -1
  116. package/dist/esm/ssr/constants.js +2 -2
  117. package/dist/esm/ssr/constants.js.map +1 -1
  118. package/dist/esm/ssr/createRequestHandler.js +2 -2
  119. package/dist/esm/ssr/createRequestHandler.js.map +1 -1
  120. package/dist/esm/ssr/serializer/RawStream.d.ts +12 -4
  121. package/dist/esm/ssr/serializer/RawStream.js +50 -41
  122. package/dist/esm/ssr/serializer/RawStream.js.map +1 -1
  123. package/dist/esm/ssr/serializer/ShallowErrorPlugin.d.ts +2 -2
  124. package/dist/esm/ssr/serializer/ShallowErrorPlugin.js +1 -1
  125. package/dist/esm/ssr/serializer/ShallowErrorPlugin.js.map +1 -1
  126. package/dist/esm/ssr/serializer/seroval-plugins.d.ts +2 -1
  127. package/dist/esm/ssr/serializer/seroval-plugins.js +1 -1
  128. package/dist/esm/ssr/serializer/seroval-plugins.js.map +1 -1
  129. package/dist/esm/ssr/serializer/transformer.d.ts +24 -23
  130. package/dist/esm/ssr/serializer/transformer.js +16 -14
  131. package/dist/esm/ssr/serializer/transformer.js.map +1 -1
  132. package/dist/esm/ssr/ssr-client.js +9 -9
  133. package/dist/esm/ssr/ssr-client.js.map +1 -1
  134. package/dist/esm/ssr/ssr-server.d.ts +4 -2
  135. package/dist/esm/ssr/ssr-server.js +110 -22
  136. package/dist/esm/ssr/ssr-server.js.map +1 -1
  137. package/dist/esm/ssr/transformStreamWithRouter.js +10 -7
  138. package/dist/esm/ssr/transformStreamWithRouter.js.map +1 -1
  139. package/dist/esm/stores.d.ts +16 -16
  140. package/dist/esm/stores.js +58 -58
  141. package/dist/esm/stores.js.map +1 -1
  142. package/dist/esm/utils.d.ts +1 -0
  143. package/dist/esm/utils.js +12 -8
  144. package/dist/esm/utils.js.map +1 -1
  145. package/package.json +4 -8
  146. package/skills/router-core/auth-and-guards/SKILL.md +41 -0
  147. package/skills/router-core/ssr/SKILL.md +59 -5
  148. package/skills/router-core/type-safety/SKILL.md +37 -42
  149. package/src/hash-scroll.ts +1 -1
  150. package/src/index.ts +9 -2
  151. package/src/load-matches.ts +14 -15
  152. package/src/manifest.ts +46 -0
  153. package/src/new-process-route-tree.ts +93 -106
  154. package/src/not-found.ts +1 -1
  155. package/src/path.ts +1 -27
  156. package/src/route.ts +10 -36
  157. package/src/router.ts +151 -140
  158. package/src/scroll-restoration.ts +1 -1
  159. package/src/ssr/createRequestHandler.ts +4 -5
  160. package/src/ssr/serializer/RawStream.ts +65 -56
  161. package/src/ssr/serializer/ShallowErrorPlugin.ts +2 -2
  162. package/src/ssr/serializer/seroval-plugins.ts +2 -1
  163. package/src/ssr/serializer/transformer.ts +71 -76
  164. package/src/ssr/ssr-client.ts +8 -12
  165. package/src/ssr/ssr-server.ts +170 -14
  166. package/src/ssr/transformStreamWithRouter.ts +3 -0
  167. package/src/stores.ts +86 -86
  168. package/src/utils.ts +7 -0
  169. package/bin/intent.js +0 -25
@@ -1,7 +1,14 @@
1
1
  import { crossSerializeStream, getCrossReferenceHeader } from 'seroval'
2
2
  import { invariant } from '../invariant'
3
+ import {
4
+ createInlineCssPlaceholderAsset,
5
+ createInlineCssStyleAsset,
6
+ getStylesheetHref,
7
+ isInlinableStylesheet,
8
+ } from '../manifest'
3
9
  import { decodePath } from '../utils'
4
10
  import { createLRUCache } from '../lru-cache'
11
+ import { rootRouteId } from '../root'
5
12
  import minifiedTsrBootStrapScript from './tsrScript?script-string'
6
13
  import { GLOBAL_TSR, TSR_SCRIPT_BARRIER_ID } from './constants'
7
14
  import { dehydrateSsrMatchId } from './ssr-match-id'
@@ -156,9 +163,11 @@ const isProd = process.env.NODE_ENV === 'production'
156
163
  type FilteredRoutes = Manifest['routes']
157
164
 
158
165
  type ManifestLRU = LRUCache<string, FilteredRoutes>
166
+ type InlineCssLRU = LRUCache<string, string>
159
167
 
160
168
  const MANIFEST_CACHE_SIZE = 100
161
169
  const manifestCaches = new WeakMap<Manifest, ManifestLRU>()
170
+ const inlineCssCaches = new WeakMap<Manifest, InlineCssLRU>()
162
171
 
163
172
  function getManifestCache(manifest: Manifest): ManifestLRU {
164
173
  const cache = manifestCaches.get(manifest)
@@ -168,15 +177,143 @@ function getManifestCache(manifest: Manifest): ManifestLRU {
168
177
  return newCache
169
178
  }
170
179
 
180
+ function getInlineCssCache(manifest: Manifest): InlineCssLRU {
181
+ const cache = inlineCssCaches.get(manifest)
182
+ if (cache) return cache
183
+ const newCache = createLRUCache<string, string>(MANIFEST_CACHE_SIZE)
184
+ inlineCssCaches.set(manifest, newCache)
185
+ return newCache
186
+ }
187
+
188
+ function getInlineCssHrefsForMatches(
189
+ manifest: Manifest | undefined,
190
+ matches: Array<AnyRouteMatch>,
191
+ ) {
192
+ const styles = manifest?.inlineCss?.styles
193
+ if (!styles) return []
194
+
195
+ const seen = new Set<string>()
196
+ const hrefs: Array<string> = []
197
+
198
+ for (const match of matches) {
199
+ const assets = manifest?.routes[match.routeId]?.assets ?? []
200
+ for (const asset of assets) {
201
+ const href = getStylesheetHref(asset)
202
+ if (!href || seen.has(href) || styles[href] === undefined) {
203
+ continue
204
+ }
205
+ seen.add(href)
206
+ hrefs.push(href)
207
+ }
208
+ }
209
+
210
+ return hrefs
211
+ }
212
+
213
+ function getInlineCssForHrefs(manifest: Manifest, hrefs: Array<string>) {
214
+ const styles = manifest.inlineCss?.styles
215
+ if (!styles || hrefs.length === 0) return undefined
216
+
217
+ const cacheKey = hrefs.join('\0')
218
+ if (isProd) {
219
+ const cached = getInlineCssCache(manifest).get(cacheKey)
220
+ if (cached !== undefined) return cached
221
+ }
222
+
223
+ const css = hrefs.map((href) => styles[href]!).join('')
224
+
225
+ if (isProd) {
226
+ getInlineCssCache(manifest).set(cacheKey, css)
227
+ }
228
+
229
+ return css
230
+ }
231
+
232
+ function getInlineCssAssetForMatches(
233
+ manifest: Manifest | undefined,
234
+ matches: Array<AnyRouteMatch>,
235
+ ) {
236
+ if (!manifest?.inlineCss) return undefined
237
+
238
+ const hrefs = getInlineCssHrefsForMatches(manifest, matches)
239
+ const css = getInlineCssForHrefs(manifest, hrefs)
240
+
241
+ return css === undefined ? undefined : createInlineCssStyleAsset(css)
242
+ }
243
+
244
+ function stripInlinedStylesheetAssets(
245
+ manifest: Manifest,
246
+ routes: FilteredRoutes,
247
+ matches: Array<AnyRouteMatch>,
248
+ ): FilteredRoutes {
249
+ if (!manifest.inlineCss) {
250
+ return routes
251
+ }
252
+
253
+ const nextRoutes: FilteredRoutes = {}
254
+
255
+ for (const [routeId, route] of Object.entries(routes)) {
256
+ const assets = route.assets?.filter(
257
+ (asset) => !isInlinableStylesheet(manifest, asset),
258
+ )
259
+
260
+ const nextRoute = { ...route }
261
+ if (assets) {
262
+ if (assets.length > 0) {
263
+ nextRoute.assets = assets
264
+ } else {
265
+ delete nextRoute.assets
266
+ }
267
+ }
268
+ nextRoutes[routeId] = nextRoute
269
+ }
270
+
271
+ if (getInlineCssAssetForMatches(manifest, matches)) {
272
+ const rootRoute = nextRoutes[rootRouteId] ?? {}
273
+ nextRoutes[rootRouteId] = {
274
+ ...rootRoute,
275
+ assets: [createInlineCssPlaceholderAsset(), ...(rootRoute.assets ?? [])],
276
+ }
277
+ }
278
+
279
+ return nextRoutes
280
+ }
281
+
171
282
  export function attachRouterServerSsrUtils({
172
283
  router,
173
284
  manifest,
285
+ getRequestAssets,
286
+ includeUnmatchedRouteAssets = true,
174
287
  }: {
175
288
  router: AnyRouter
176
289
  manifest: Manifest | undefined
290
+ getRequestAssets?: () => Array<RouterManagedTag> | undefined
291
+ includeUnmatchedRouteAssets?: boolean
177
292
  }) {
178
293
  router.ssr = {
179
- manifest,
294
+ get manifest() {
295
+ const requestAssets = getRequestAssets?.()
296
+ const inlineCssAsset = getInlineCssAssetForMatches(
297
+ manifest,
298
+ router.stores.matches.get(),
299
+ )
300
+ if (!requestAssets?.length && !inlineCssAsset) return manifest
301
+ // Merge request-scoped assets into root route without mutating cached manifest
302
+ return {
303
+ ...manifest,
304
+ routes: {
305
+ ...manifest?.routes,
306
+ [rootRouteId]: {
307
+ ...manifest?.routes?.[rootRouteId],
308
+ assets: [
309
+ ...(requestAssets ?? []),
310
+ ...(inlineCssAsset ? [inlineCssAsset] : []),
311
+ ...(manifest?.routes?.[rootRouteId]?.assets ?? []),
312
+ ],
313
+ },
314
+ },
315
+ }
316
+ },
180
317
  }
181
318
  let _dehydrated = false
182
319
  let _serializationFinished = false
@@ -200,7 +337,7 @@ export function attachRouterServerSsrUtils({
200
337
  const html = `<script${router.options.ssr?.nonce ? ` nonce='${router.options.ssr.nonce}'` : ''}>${script}</script>`
201
338
  router.serverSsr!.injectHtml(html)
202
339
  },
203
- dehydrate: async () => {
340
+ dehydrate: async (opts?: { requestAssets?: Array<RouterManagedTag> }) => {
204
341
  if (_dehydrated) {
205
342
  if (process.env.NODE_ENV !== 'production') {
206
343
  throw new Error('Invariant failed: router is already dehydrated!')
@@ -208,7 +345,7 @@ export function attachRouterServerSsrUtils({
208
345
 
209
346
  invariant()
210
347
  }
211
- let matchesToDehydrate = router.stores.activeMatchesSnapshot.state
348
+ let matchesToDehydrate = router.stores.matches.get()
212
349
  if (router.isShell()) {
213
350
  // In SPA mode we only want to dehydrate the root match
214
351
  matchesToDehydrate = matchesToDehydrate.slice(0, 1)
@@ -216,12 +353,14 @@ export function attachRouterServerSsrUtils({
216
353
  const matches = matchesToDehydrate.map(dehydrateMatch)
217
354
 
218
355
  let manifestToDehydrate: Manifest | undefined = undefined
219
- // For currently matched routes, send full manifest (preloads + assets)
220
- // For all other routes, only send assets (no preloads as they are handled via dynamic imports)
356
+ // For currently matched routes, send full manifest (preloads + assets).
357
+ // For unmatched routes, include assets only when includeUnmatchedRouteAssets
358
+ // is true; otherwise omit them entirely. Preloads for unmatched routes are
359
+ // still excluded because they are handled via dynamic imports.
221
360
  if (manifest) {
222
361
  // Prod-only caching; in dev manifests may be replaced/updated (HMR)
223
362
  const currentRouteIdsList = matchesToDehydrate.map((m) => m.routeId)
224
- const manifestCacheKey = currentRouteIdsList.join('\0')
363
+ const manifestCacheKey = `${currentRouteIdsList.join('\0')}\0includeUnmatchedRouteAssets=${includeUnmatchedRouteAssets}`
225
364
 
226
365
  let filteredRoutes: FilteredRoutes | undefined
227
366
 
@@ -238,6 +377,7 @@ export function attachRouterServerSsrUtils({
238
377
  if (currentRouteIds.has(routeId)) {
239
378
  nextFilteredRoutes[routeId] = routeManifest
240
379
  } else if (
380
+ includeUnmatchedRouteAssets &&
241
381
  routeManifest.assets &&
242
382
  routeManifest.assets.length > 0
243
383
  ) {
@@ -247,15 +387,28 @@ export function attachRouterServerSsrUtils({
247
387
  }
248
388
  }
249
389
 
390
+ filteredRoutes = stripInlinedStylesheetAssets(
391
+ manifest,
392
+ nextFilteredRoutes,
393
+ matchesToDehydrate,
394
+ )
395
+
250
396
  if (isProd) {
251
- getManifestCache(manifest).set(manifestCacheKey, nextFilteredRoutes)
397
+ getManifestCache(manifest).set(manifestCacheKey, filteredRoutes)
252
398
  }
253
-
254
- filteredRoutes = nextFilteredRoutes
255
399
  }
256
400
 
257
401
  manifestToDehydrate = {
258
- routes: filteredRoutes,
402
+ routes: { ...filteredRoutes },
403
+ }
404
+
405
+ // Merge request-scoped assets into root route (without mutating cached manifest)
406
+ if (opts?.requestAssets?.length) {
407
+ const existingRoot = manifestToDehydrate.routes[rootRouteId]
408
+ manifestToDehydrate.routes[rootRouteId] = {
409
+ ...existingRoot,
410
+ assets: [...opts.requestAssets, ...(existingRoot?.assets ?? [])],
411
+ }
259
412
  }
260
413
  }
261
414
  const dehydratedRouter: DehydratedRouter = {
@@ -305,6 +458,13 @@ export function attachRouterServerSsrUtils({
305
458
  }
306
459
  scriptBuffer.enqueue(serialized)
307
460
  },
461
+ onError: (err: unknown) => {
462
+ console.error('Serialization error:', err)
463
+ if (err && (err as any).stack) {
464
+ console.error((err as any).stack)
465
+ }
466
+ signalSerializationComplete()
467
+ },
308
468
  scopeId: SCOPE_ID,
309
469
  onDone: () => {
310
470
  scriptBuffer.enqueue(GLOBAL_TSR + '.e()')
@@ -313,10 +473,6 @@ export function attachRouterServerSsrUtils({
313
473
  scriptBuffer.flush()
314
474
  signalSerializationComplete()
315
475
  },
316
- onError: (err) => {
317
- console.error('Serialization error:', err)
318
- signalSerializationComplete()
319
- },
320
476
  })
321
477
  },
322
478
  isDehydrated() {
@@ -366,6 +366,9 @@ export function transformStreamWithRouter(
366
366
  if (isAppRendering || leftover || pendingClosingTags) {
367
367
  appendRouterHtml(html)
368
368
  } else {
369
+ // App is done rendering - flush any pending buffer first to maintain order,
370
+ // then write the new HTML directly
371
+ flushPendingRouterHtml()
369
372
  safeEnqueue(html)
370
373
  }
371
374
  })
package/src/stores.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createLRUCache } from './lru-cache'
2
- import { arraysEqual } from './utils'
2
+ import { arraysEqual, functionalUpdate } from './utils'
3
3
 
4
4
  import type { AnyRoute } from './route'
5
5
  import type { RouterState } from './router'
@@ -9,13 +9,13 @@ import type { AnyRedirect } from './redirect'
9
9
  import type { AnyRouteMatch } from './Matches'
10
10
 
11
11
  export interface RouterReadableStore<TValue> {
12
- readonly state: TValue
12
+ get: () => TValue
13
13
  }
14
14
 
15
15
  export interface RouterWritableStore<
16
16
  TValue,
17
17
  > extends RouterReadableStore<TValue> {
18
- setState: (updater: (prev: TValue) => TValue) => void
18
+ set: ((updater: (prev: TValue) => TValue) => void) & ((value: TValue) => void)
19
19
  }
20
20
 
21
21
  export type RouterBatchFn = (fn: () => void) => void
@@ -49,11 +49,11 @@ export function createNonReactiveMutableStore<TValue>(
49
49
  let value = initialValue
50
50
 
51
51
  return {
52
- get state() {
52
+ get() {
53
53
  return value
54
54
  },
55
- setState(updater: (prev: TValue) => TValue) {
56
- value = updater(value)
55
+ set(nextOrUpdater: TValue | ((prev: TValue) => TValue)) {
56
+ value = functionalUpdate(nextOrUpdater, value)
57
57
  },
58
58
  }
59
59
  }
@@ -63,7 +63,7 @@ export function createNonReactiveReadonlyStore<TValue>(
63
63
  read: () => TValue,
64
64
  ): RouterReadableStore<TValue> {
65
65
  return {
66
- get state() {
66
+ get() {
67
67
  return read()
68
68
  },
69
69
  }
@@ -81,24 +81,24 @@ export interface RouterStores<in out TRouteTree extends AnyRoute> {
81
81
  statusCode: RouterWritableStore<number>
82
82
  redirect: RouterWritableStore<AnyRedirect | undefined>
83
83
  matchesId: RouterWritableStore<Array<string>>
84
- pendingMatchesId: RouterWritableStore<Array<string>>
84
+ pendingIds: RouterWritableStore<Array<string>>
85
85
  /** @internal */
86
- cachedMatchesId: RouterWritableStore<Array<string>>
87
- activeMatchesSnapshot: ReadableStore<Array<AnyRouteMatch>>
88
- pendingMatchesSnapshot: ReadableStore<Array<AnyRouteMatch>>
89
- cachedMatchesSnapshot: ReadableStore<Array<AnyRouteMatch>>
90
- firstMatchId: ReadableStore<string | undefined>
91
- hasPendingMatches: ReadableStore<boolean>
92
- matchRouteReactivity: ReadableStore<{
86
+ cachedIds: RouterWritableStore<Array<string>>
87
+ matches: ReadableStore<Array<AnyRouteMatch>>
88
+ pendingMatches: ReadableStore<Array<AnyRouteMatch>>
89
+ cachedMatches: ReadableStore<Array<AnyRouteMatch>>
90
+ firstId: ReadableStore<string | undefined>
91
+ hasPending: ReadableStore<boolean>
92
+ matchRouteDeps: ReadableStore<{
93
93
  locationHref: string
94
94
  resolvedLocationHref: string | undefined
95
95
  status: RouterState<TRouteTree>['status']
96
96
  }>
97
97
  __store: RouterReadableStore<RouterState<TRouteTree>>
98
98
 
99
- activeMatchStoresById: Map<string, MatchStore>
100
- pendingMatchStoresById: Map<string, MatchStore>
101
- cachedMatchStoresById: Map<string, MatchStore>
99
+ matchStores: Map<string, MatchStore>
100
+ pendingMatchStores: Map<string, MatchStore>
101
+ cachedMatchStores: Map<string, MatchStore>
102
102
 
103
103
  /**
104
104
  * Get a computed store that resolves a routeId to its current match state.
@@ -106,13 +106,13 @@ export interface RouterStores<in out TRouteTree extends AnyRoute> {
106
106
  * The computed depends on matchesId + the individual match store, so
107
107
  * subscribers are only notified when the resolved match state changes.
108
108
  */
109
- getMatchStoreByRouteId: (
109
+ getRouteMatchStore: (
110
110
  routeId: string,
111
111
  ) => RouterReadableStore<AnyRouteMatch | undefined>
112
112
 
113
- setActiveMatches: (nextMatches: Array<AnyRouteMatch>) => void
114
- setPendingMatches: (nextMatches: Array<AnyRouteMatch>) => void
115
- setCachedMatches: (nextMatches: Array<AnyRouteMatch>) => void
113
+ setMatches: (nextMatches: Array<AnyRouteMatch>) => void
114
+ setPending: (nextMatches: Array<AnyRouteMatch>) => void
115
+ setCached: (nextMatches: Array<AnyRouteMatch>) => void
116
116
  }
117
117
 
118
118
  export function createRouterStores<TRouteTree extends AnyRoute>(
@@ -122,9 +122,9 @@ export function createRouterStores<TRouteTree extends AnyRoute>(
122
122
  const { createMutableStore, createReadonlyStore, batch, init } = config
123
123
 
124
124
  // non reactive utilities
125
- const activeMatchStoresById = new Map<string, MatchStore>()
126
- const pendingMatchStoresById = new Map<string, MatchStore>()
127
- const cachedMatchStoresById = new Map<string, MatchStore>()
125
+ const matchStores = new Map<string, MatchStore>()
126
+ const pendingMatchStores = new Map<string, MatchStore>()
127
+ const cachedMatchStores = new Map<string, MatchStore>()
128
128
 
129
129
  // atoms
130
130
  const status = createMutableStore(initialState.status)
@@ -136,43 +136,43 @@ export function createRouterStores<TRouteTree extends AnyRoute>(
136
136
  const statusCode = createMutableStore(initialState.statusCode)
137
137
  const redirect = createMutableStore(initialState.redirect)
138
138
  const matchesId = createMutableStore<Array<string>>([])
139
- const pendingMatchesId = createMutableStore<Array<string>>([])
140
- const cachedMatchesId = createMutableStore<Array<string>>([])
139
+ const pendingIds = createMutableStore<Array<string>>([])
140
+ const cachedIds = createMutableStore<Array<string>>([])
141
141
 
142
142
  // 1st order derived stores
143
- const activeMatchesSnapshot = createReadonlyStore(() =>
144
- readPoolMatches(activeMatchStoresById, matchesId.state),
143
+ const matches = createReadonlyStore(() =>
144
+ readPoolMatches(matchStores, matchesId.get()),
145
145
  )
146
- const pendingMatchesSnapshot = createReadonlyStore(() =>
147
- readPoolMatches(pendingMatchStoresById, pendingMatchesId.state),
146
+ const pendingMatches = createReadonlyStore(() =>
147
+ readPoolMatches(pendingMatchStores, pendingIds.get()),
148
148
  )
149
- const cachedMatchesSnapshot = createReadonlyStore(() =>
150
- readPoolMatches(cachedMatchStoresById, cachedMatchesId.state),
149
+ const cachedMatches = createReadonlyStore(() =>
150
+ readPoolMatches(cachedMatchStores, cachedIds.get()),
151
151
  )
152
- const firstMatchId = createReadonlyStore(() => matchesId.state[0])
153
- const hasPendingMatches = createReadonlyStore(() =>
154
- matchesId.state.some((matchId) => {
155
- const store = activeMatchStoresById.get(matchId)
156
- return store?.state.status === 'pending'
152
+ const firstId = createReadonlyStore(() => matchesId.get()[0])
153
+ const hasPending = createReadonlyStore(() =>
154
+ matchesId.get().some((matchId) => {
155
+ const store = matchStores.get(matchId)
156
+ return store?.get().status === 'pending'
157
157
  }),
158
158
  )
159
- const matchRouteReactivity = createReadonlyStore(() => ({
160
- locationHref: location.state.href,
161
- resolvedLocationHref: resolvedLocation.state?.href,
162
- status: status.state,
159
+ const matchRouteDeps = createReadonlyStore(() => ({
160
+ locationHref: location.get().href,
161
+ resolvedLocationHref: resolvedLocation.get()?.href,
162
+ status: status.get(),
163
163
  }))
164
164
 
165
165
  // compatibility "big" state store
166
166
  const __store = createReadonlyStore(() => ({
167
- status: status.state,
168
- loadedAt: loadedAt.state,
169
- isLoading: isLoading.state,
170
- isTransitioning: isTransitioning.state,
171
- matches: activeMatchesSnapshot.state,
172
- location: location.state,
173
- resolvedLocation: resolvedLocation.state,
174
- statusCode: statusCode.state,
175
- redirect: redirect.state,
167
+ status: status.get(),
168
+ loadedAt: loadedAt.get(),
169
+ isLoading: isLoading.get(),
170
+ isTransitioning: isTransitioning.get(),
171
+ matches: matches.get(),
172
+ location: location.get(),
173
+ resolvedLocation: resolvedLocation.get(),
174
+ statusCode: statusCode.get(),
175
+ redirect: redirect.get(),
176
176
  }))
177
177
 
178
178
  // Per-routeId computed store cache.
@@ -188,21 +188,21 @@ export function createRouterStores<TRouteTree extends AnyRoute>(
188
188
  RouterReadableStore<AnyRouteMatch | undefined>
189
189
  >(64)
190
190
 
191
- function getMatchStoreByRouteId(
191
+ function getRouteMatchStore(
192
192
  routeId: string,
193
193
  ): RouterReadableStore<AnyRouteMatch | undefined> {
194
194
  let cached = matchStoreByRouteIdCache.get(routeId)
195
195
  if (!cached) {
196
196
  cached = createReadonlyStore(() => {
197
- // Reading matchesId.state tracks it as a dependency.
197
+ // Reading matchesId.get() tracks it as a dependency.
198
198
  // When matchesId changes (navigation), this computed re-evaluates.
199
- const ids = matchesId.state
199
+ const ids = matchesId.get()
200
200
  for (const id of ids) {
201
- const matchStore = activeMatchStoresById.get(id)
201
+ const matchStore = matchStores.get(id)
202
202
  if (matchStore && matchStore.routeId === routeId) {
203
- // Reading matchStore.state tracks it as a dependency.
203
+ // Reading matchStore.get() tracks it as a dependency.
204
204
  // When the match store's state changes, this re-evaluates.
205
- return matchStore.state
205
+ return matchStore.get()
206
206
  }
207
207
  }
208
208
  return undefined
@@ -223,64 +223,64 @@ export function createRouterStores<TRouteTree extends AnyRoute>(
223
223
  statusCode,
224
224
  redirect,
225
225
  matchesId,
226
- pendingMatchesId,
227
- cachedMatchesId,
226
+ pendingIds,
227
+ cachedIds,
228
228
 
229
229
  // derived
230
- activeMatchesSnapshot,
231
- pendingMatchesSnapshot,
232
- cachedMatchesSnapshot,
233
- firstMatchId,
234
- hasPendingMatches,
235
- matchRouteReactivity,
230
+ matches,
231
+ pendingMatches,
232
+ cachedMatches,
233
+ firstId,
234
+ hasPending,
235
+ matchRouteDeps,
236
236
 
237
237
  // non-reactive state
238
- activeMatchStoresById,
239
- pendingMatchStoresById,
240
- cachedMatchStoresById,
238
+ matchStores,
239
+ pendingMatchStores,
240
+ cachedMatchStores,
241
241
 
242
242
  // compatibility "big" state
243
243
  __store,
244
244
 
245
245
  // per-key computed stores
246
- getMatchStoreByRouteId,
246
+ getRouteMatchStore,
247
247
 
248
248
  // methods
249
- setActiveMatches,
250
- setPendingMatches,
251
- setCachedMatches,
249
+ setMatches,
250
+ setPending,
251
+ setCached,
252
252
  }
253
253
 
254
254
  // initialize the active matches
255
- setActiveMatches(initialState.matches as Array<AnyRouteMatch>)
255
+ setMatches(initialState.matches as Array<AnyRouteMatch>)
256
256
  init?.(store)
257
257
 
258
258
  // setters to update non-reactive utilities in sync with the reactive stores
259
- function setActiveMatches(nextMatches: Array<AnyRouteMatch>) {
259
+ function setMatches(nextMatches: Array<AnyRouteMatch>) {
260
260
  reconcileMatchPool(
261
261
  nextMatches,
262
- activeMatchStoresById,
262
+ matchStores,
263
263
  matchesId,
264
264
  createMutableStore,
265
265
  batch,
266
266
  )
267
267
  }
268
268
 
269
- function setPendingMatches(nextMatches: Array<AnyRouteMatch>) {
269
+ function setPending(nextMatches: Array<AnyRouteMatch>) {
270
270
  reconcileMatchPool(
271
271
  nextMatches,
272
- pendingMatchStoresById,
273
- pendingMatchesId,
272
+ pendingMatchStores,
273
+ pendingIds,
274
274
  createMutableStore,
275
275
  batch,
276
276
  )
277
277
  }
278
278
 
279
- function setCachedMatches(nextMatches: Array<AnyRouteMatch>) {
279
+ function setCached(nextMatches: Array<AnyRouteMatch>) {
280
280
  reconcileMatchPool(
281
281
  nextMatches,
282
- cachedMatchStoresById,
283
- cachedMatchesId,
282
+ cachedMatchStores,
283
+ cachedIds,
284
284
  createMutableStore,
285
285
  batch,
286
286
  )
@@ -297,7 +297,7 @@ function readPoolMatches(
297
297
  for (const id of ids) {
298
298
  const matchStore = pool.get(id)
299
299
  if (matchStore) {
300
- matches.push(matchStore.state)
300
+ matches.push(matchStore.get())
301
301
  }
302
302
  }
303
303
  return matches
@@ -330,13 +330,13 @@ function reconcileMatchPool(
330
330
  }
331
331
 
332
332
  existing.routeId = nextMatch.routeId
333
- if (existing.state !== nextMatch) {
334
- existing.setState(() => nextMatch)
333
+ if (existing.get() !== nextMatch) {
334
+ existing.set(nextMatch)
335
335
  }
336
336
  }
337
337
 
338
- if (!arraysEqual(idStore.state, nextIds)) {
339
- idStore.setState(() => nextIds)
338
+ if (!arraysEqual(idStore.get(), nextIds)) {
339
+ idStore.set(nextIds)
340
340
  }
341
341
  })
342
342
  }
package/src/utils.ts CHANGED
@@ -215,6 +215,13 @@ export function functionalUpdate<TPrevious, TResult = TPrevious>(
215
215
  const hasOwn = Object.prototype.hasOwnProperty
216
216
  const isEnumerable = Object.prototype.propertyIsEnumerable
217
217
 
218
+ export function hasKeys(obj: Record<string, unknown>) {
219
+ for (const key in obj) {
220
+ if (hasOwn.call(obj, key)) return true
221
+ }
222
+ return false
223
+ }
224
+
218
225
  const createNull = () => Object.create(null)
219
226
  export const nullReplaceEqualDeep: typeof replaceEqualDeep = (prev, next) =>
220
227
  replaceEqualDeep(prev, next, createNull)
package/bin/intent.js DELETED
@@ -1,25 +0,0 @@
1
- #!/usr/bin/env node
2
- // Auto-generated by @tanstack/intent setup
3
- // Exposes the intent end-user CLI for consumers of this library.
4
- // Commit this file, then add to your package.json:
5
- // "bin": { "intent": "./bin/intent.js" }
6
- try {
7
- await import('@tanstack/intent/intent-library')
8
- } catch (e) {
9
- const isModuleNotFound =
10
- e?.code === 'ERR_MODULE_NOT_FOUND' || e?.code === 'MODULE_NOT_FOUND'
11
- const missingIntentLibrary =
12
- typeof e?.message === 'string' && e.message.includes('@tanstack/intent')
13
-
14
- if (isModuleNotFound && missingIntentLibrary) {
15
- console.error('@tanstack/intent is not installed.')
16
- console.error('')
17
- console.error('Install it as a dev dependency:')
18
- console.error(' npm add -D @tanstack/intent')
19
- console.error('')
20
- console.error('Or run directly:')
21
- console.error(' npx @tanstack/intent@latest list')
22
- process.exit(1)
23
- }
24
- throw e
25
- }