@tanstack/router-core 1.168.8 → 1.168.10

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 (79) hide show
  1. package/dist/cjs/hash-scroll.cjs +1 -1
  2. package/dist/cjs/hash-scroll.cjs.map +1 -1
  3. package/dist/cjs/index.d.cts +1 -1
  4. package/dist/cjs/load-matches.cjs +4 -4
  5. package/dist/cjs/load-matches.cjs.map +1 -1
  6. package/dist/cjs/router.cjs +44 -45
  7. package/dist/cjs/router.cjs.map +1 -1
  8. package/dist/cjs/router.d.cts +3 -1
  9. package/dist/cjs/scroll-restoration.cjs +1 -1
  10. package/dist/cjs/scroll-restoration.cjs.map +1 -1
  11. package/dist/cjs/ssr/createRequestHandler.cjs +2 -2
  12. package/dist/cjs/ssr/createRequestHandler.cjs.map +1 -1
  13. package/dist/cjs/ssr/serializer/RawStream.cjs +41 -32
  14. package/dist/cjs/ssr/serializer/RawStream.cjs.map +1 -1
  15. package/dist/cjs/ssr/serializer/RawStream.d.cts +12 -4
  16. package/dist/cjs/ssr/serializer/ShallowErrorPlugin.cjs.map +1 -1
  17. package/dist/cjs/ssr/serializer/ShallowErrorPlugin.d.cts +2 -2
  18. package/dist/cjs/ssr/serializer/seroval-plugins.cjs.map +1 -1
  19. package/dist/cjs/ssr/serializer/seroval-plugins.d.cts +2 -1
  20. package/dist/cjs/ssr/serializer/transformer.cjs +16 -14
  21. package/dist/cjs/ssr/serializer/transformer.cjs.map +1 -1
  22. package/dist/cjs/ssr/serializer/transformer.d.cts +24 -23
  23. package/dist/cjs/ssr/ssr-client.cjs +8 -8
  24. package/dist/cjs/ssr/ssr-client.cjs.map +1 -1
  25. package/dist/cjs/ssr/ssr-server.cjs +31 -9
  26. package/dist/cjs/ssr/ssr-server.cjs.map +1 -1
  27. package/dist/cjs/ssr/ssr-server.d.cts +3 -2
  28. package/dist/cjs/ssr/transformStreamWithRouter.cjs +4 -1
  29. package/dist/cjs/ssr/transformStreamWithRouter.cjs.map +1 -1
  30. package/dist/cjs/stores.cjs +27 -27
  31. package/dist/cjs/stores.cjs.map +1 -1
  32. package/dist/cjs/stores.d.cts +2 -2
  33. package/dist/esm/hash-scroll.js +1 -1
  34. package/dist/esm/hash-scroll.js.map +1 -1
  35. package/dist/esm/index.d.ts +1 -1
  36. package/dist/esm/load-matches.js +4 -4
  37. package/dist/esm/load-matches.js.map +1 -1
  38. package/dist/esm/router.d.ts +3 -1
  39. package/dist/esm/router.js +44 -45
  40. package/dist/esm/router.js.map +1 -1
  41. package/dist/esm/scroll-restoration.js +1 -1
  42. package/dist/esm/scroll-restoration.js.map +1 -1
  43. package/dist/esm/ssr/createRequestHandler.js +2 -2
  44. package/dist/esm/ssr/createRequestHandler.js.map +1 -1
  45. package/dist/esm/ssr/serializer/RawStream.d.ts +12 -4
  46. package/dist/esm/ssr/serializer/RawStream.js +41 -32
  47. package/dist/esm/ssr/serializer/RawStream.js.map +1 -1
  48. package/dist/esm/ssr/serializer/ShallowErrorPlugin.d.ts +2 -2
  49. package/dist/esm/ssr/serializer/ShallowErrorPlugin.js.map +1 -1
  50. package/dist/esm/ssr/serializer/seroval-plugins.d.ts +2 -1
  51. package/dist/esm/ssr/serializer/seroval-plugins.js.map +1 -1
  52. package/dist/esm/ssr/serializer/transformer.d.ts +24 -23
  53. package/dist/esm/ssr/serializer/transformer.js +16 -14
  54. package/dist/esm/ssr/serializer/transformer.js.map +1 -1
  55. package/dist/esm/ssr/ssr-client.js +8 -8
  56. package/dist/esm/ssr/ssr-client.js.map +1 -1
  57. package/dist/esm/ssr/ssr-server.d.ts +3 -2
  58. package/dist/esm/ssr/ssr-server.js +31 -9
  59. package/dist/esm/ssr/ssr-server.js.map +1 -1
  60. package/dist/esm/ssr/transformStreamWithRouter.js +4 -1
  61. package/dist/esm/ssr/transformStreamWithRouter.js.map +1 -1
  62. package/dist/esm/stores.d.ts +2 -2
  63. package/dist/esm/stores.js +28 -28
  64. package/dist/esm/stores.js.map +1 -1
  65. package/package.json +3 -3
  66. package/src/hash-scroll.ts +1 -1
  67. package/src/index.ts +1 -1
  68. package/src/load-matches.ts +6 -6
  69. package/src/router.ts +59 -57
  70. package/src/scroll-restoration.ts +1 -1
  71. package/src/ssr/createRequestHandler.ts +4 -5
  72. package/src/ssr/serializer/RawStream.ts +65 -56
  73. package/src/ssr/serializer/ShallowErrorPlugin.ts +2 -2
  74. package/src/ssr/serializer/seroval-plugins.ts +2 -1
  75. package/src/ssr/serializer/transformer.ts +71 -76
  76. package/src/ssr/ssr-client.ts +7 -11
  77. package/src/ssr/ssr-server.ts +39 -7
  78. package/src/ssr/transformStreamWithRouter.ts +3 -0
  79. package/src/stores.ts +34 -34
@@ -1,5 +1,5 @@
1
1
  import { createPlugin, createStream } from 'seroval'
2
- import type { Plugin } from 'seroval'
2
+ import type { PluginData, PluginInfo, SerovalNode } from 'seroval'
3
3
 
4
4
  /**
5
5
  * Hint for RawStream encoding strategy during SSR serialization.
@@ -240,61 +240,71 @@ function toTextStream(readable: ReadableStream<Uint8Array>) {
240
240
  }
241
241
 
242
242
  // Factory plugin for binary mode
243
- const RawStreamFactoryBinaryPlugin = createPlugin<
243
+ const RawStreamFactoryBinaryPlugin = /* @__PURE__ */ createPlugin<
244
244
  Record<string, never>,
245
- undefined
245
+ PluginInfo
246
246
  >({
247
247
  tag: 'tss/RawStreamFactory',
248
248
  test(value) {
249
249
  return value === RAW_STREAM_FACTORY_BINARY
250
250
  },
251
251
  parse: {
252
- sync() {
253
- return undefined
252
+ sync(_value, _ctx, _data) {
253
+ return {}
254
254
  },
255
- async() {
256
- return Promise.resolve(undefined)
255
+ async async(_value, _ctx, _data) {
256
+ return {}
257
257
  },
258
- stream() {
259
- return undefined
258
+ stream(_value, _ctx, _data) {
259
+ return {}
260
260
  },
261
261
  },
262
- serialize() {
262
+ serialize(_node, _ctx, _data) {
263
263
  return FACTORY_BINARY
264
264
  },
265
- deserialize() {
265
+ deserialize(_node, _ctx, _data) {
266
266
  return RAW_STREAM_FACTORY_BINARY
267
267
  },
268
268
  })
269
269
 
270
270
  // Factory plugin for text mode
271
- const RawStreamFactoryTextPlugin = createPlugin<
271
+ const RawStreamFactoryTextPlugin = /* @__PURE__ */ createPlugin<
272
272
  Record<string, never>,
273
- undefined
273
+ PluginInfo
274
274
  >({
275
275
  tag: 'tss/RawStreamFactoryText',
276
276
  test(value) {
277
277
  return value === RAW_STREAM_FACTORY_TEXT
278
278
  },
279
279
  parse: {
280
- sync() {
281
- return undefined
280
+ sync(_value, _ctx, _data) {
281
+ return {}
282
282
  },
283
- async() {
284
- return Promise.resolve(undefined)
283
+ async async(_value, _ctx, _data) {
284
+ return {}
285
285
  },
286
- stream() {
287
- return undefined
286
+ stream(_value, _ctx, _data) {
287
+ return {}
288
288
  },
289
289
  },
290
- serialize() {
290
+ serialize(_node, _ctx, _data) {
291
291
  return FACTORY_TEXT
292
292
  },
293
- deserialize() {
293
+ deserialize(_node, _ctx, _data) {
294
294
  return RAW_STREAM_FACTORY_TEXT
295
295
  },
296
296
  })
297
297
 
298
+ export interface RawStreamSSRNode extends PluginInfo {
299
+ hint: SerovalNode
300
+ factory: SerovalNode
301
+ stream: SerovalNode
302
+ }
303
+
304
+ export interface RawStreamRPCNode extends PluginInfo {
305
+ streamId: SerovalNode
306
+ }
307
+
298
308
  /**
299
309
  * SSR Plugin - uses base64 or UTF-8+base64 encoding for chunks, delegates to seroval's stream mechanism.
300
310
  * Used during SSR when serializing to JavaScript code for HTML injection.
@@ -303,7 +313,10 @@ const RawStreamFactoryTextPlugin = createPlugin<
303
313
  * - 'binary': Always base64 encode (default)
304
314
  * - 'text': Try UTF-8 first, fallback to base64 for invalid UTF-8
305
315
  */
306
- export const RawStreamSSRPlugin: Plugin<any, any> = createPlugin({
316
+ export const RawStreamSSRPlugin = /* @__PURE__ */ createPlugin<
317
+ RawStream,
318
+ RawStreamSSRNode
319
+ >({
307
320
  tag: 'tss/RawStream',
308
321
  extends: [RawStreamFactoryBinaryPlugin, RawStreamFactoryTextPlugin],
309
322
 
@@ -312,19 +325,19 @@ export const RawStreamSSRPlugin: Plugin<any, any> = createPlugin({
312
325
  },
313
326
 
314
327
  parse: {
315
- sync(value: RawStream, ctx) {
328
+ sync(value: RawStream, ctx, _data) {
316
329
  // Sync parse not really supported for streams, return empty stream
317
330
  const factory =
318
331
  value.hint === 'text'
319
332
  ? RAW_STREAM_FACTORY_TEXT
320
333
  : RAW_STREAM_FACTORY_BINARY
321
334
  return {
322
- hint: value.hint,
335
+ hint: ctx.parse(value.hint),
323
336
  factory: ctx.parse(factory),
324
337
  stream: ctx.parse(createStream()),
325
338
  }
326
339
  },
327
- async async(value: RawStream, ctx) {
340
+ async async(value: RawStream, ctx, _data) {
328
341
  const factory =
329
342
  value.hint === 'text'
330
343
  ? RAW_STREAM_FACTORY_TEXT
@@ -334,12 +347,12 @@ export const RawStreamSSRPlugin: Plugin<any, any> = createPlugin({
334
347
  ? toTextStream(value.stream)
335
348
  : toBinaryStream(value.stream)
336
349
  return {
337
- hint: value.hint,
350
+ hint: await ctx.parse(value.hint),
338
351
  factory: await ctx.parse(factory),
339
352
  stream: await ctx.parse(encodedStream),
340
353
  }
341
354
  },
342
- stream(value: RawStream, ctx) {
355
+ stream(value: RawStream, ctx, _data) {
343
356
  const factory =
344
357
  value.hint === 'text'
345
358
  ? RAW_STREAM_FACTORY_TEXT
@@ -349,14 +362,14 @@ export const RawStreamSSRPlugin: Plugin<any, any> = createPlugin({
349
362
  ? toTextStream(value.stream)
350
363
  : toBinaryStream(value.stream)
351
364
  return {
352
- hint: value.hint,
365
+ hint: ctx.parse(value.hint),
353
366
  factory: ctx.parse(factory),
354
367
  stream: ctx.parse(encodedStream),
355
368
  }
356
369
  },
357
370
  },
358
371
 
359
- serialize(node: { hint: RawStreamHint; factory: any; stream: any }, ctx) {
372
+ serialize(node: RawStreamSSRNode, ctx, _data) {
360
373
  return (
361
374
  '(' +
362
375
  ctx.serialize(node.factory) +
@@ -366,23 +379,14 @@ export const RawStreamSSRPlugin: Plugin<any, any> = createPlugin({
366
379
  )
367
380
  },
368
381
 
369
- deserialize(
370
- node: { hint: RawStreamHint; factory: any; stream: any },
371
- ctx,
372
- ): any {
382
+ deserialize(node: RawStreamSSRNode, ctx, _data): any {
373
383
  const stream: ReturnType<typeof createStream> = ctx.deserialize(node.stream)
374
- return node.hint === 'text'
384
+ const hint = ctx.deserialize(node.hint)
385
+ return hint === 'text'
375
386
  ? RAW_STREAM_FACTORY_CONSTRUCTOR_TEXT(stream)
376
387
  : RAW_STREAM_FACTORY_CONSTRUCTOR_BINARY(stream)
377
388
  },
378
- }) as Plugin<any, any>
379
-
380
- /**
381
- * Node type for RPC plugin serialization
382
- */
383
- interface RawStreamRPCNode {
384
- streamId: number
385
- }
389
+ })
386
390
 
387
391
  /**
388
392
  * Creates an RPC plugin instance that registers raw streams with a multiplexer.
@@ -391,13 +395,12 @@ interface RawStreamRPCNode {
391
395
  *
392
396
  * @param onRawStream Callback invoked when a RawStream is encountered during serialization
393
397
  */
394
- export function createRawStreamRPCPlugin(
395
- onRawStream: OnRawStreamCallback,
396
- ): Plugin<any, any> {
398
+ /* @__NO_SIDE_EFFECTS__ */
399
+ export function createRawStreamRPCPlugin(onRawStream: OnRawStreamCallback) {
397
400
  // Own stream counter - sequential IDs starting at 1, independent of seroval internals
398
401
  let nextStreamId = 1
399
402
 
400
- return createPlugin({
403
+ return /* @__PURE__ */ createPlugin<RawStream, RawStreamRPCNode>({
401
404
  tag: 'tss/RawStream',
402
405
 
403
406
  test(value: unknown) {
@@ -405,15 +408,15 @@ export function createRawStreamRPCPlugin(
405
408
  },
406
409
 
407
410
  parse: {
408
- async(value: RawStream) {
411
+ async async(value: RawStream, ctx, _data: PluginData) {
409
412
  const streamId = nextStreamId++
410
413
  onRawStream(streamId, value.stream)
411
- return Promise.resolve({ streamId })
414
+ return { streamId: await ctx.parse(streamId) }
412
415
  },
413
- stream(value: RawStream) {
416
+ stream(value: RawStream, ctx, _data: PluginData) {
414
417
  const streamId = nextStreamId++
415
418
  onRawStream(streamId, value.stream)
416
- return { streamId }
419
+ return { streamId: ctx.parse(streamId) }
417
420
  },
418
421
  },
419
422
 
@@ -431,7 +434,7 @@ export function createRawStreamRPCPlugin(
431
434
  'RawStreamRPCPlugin.deserialize should not be called. Use createRawStreamDeserializePlugin on client.',
432
435
  )
433
436
  },
434
- }) as Plugin<any, any>
437
+ })
435
438
  }
436
439
 
437
440
  /**
@@ -442,8 +445,8 @@ export function createRawStreamRPCPlugin(
442
445
  */
443
446
  export function createRawStreamDeserializePlugin(
444
447
  getOrCreateStream: (id: number) => ReadableStream<Uint8Array>,
445
- ): Plugin<any, any> {
446
- return createPlugin({
448
+ ) {
449
+ return /* @__PURE__ */ createPlugin<any, RawStreamRPCNode>({
447
450
  tag: 'tss/RawStream',
448
451
 
449
452
  test: () => false, // Client never serializes RawStream
@@ -457,8 +460,14 @@ export function createRawStreamDeserializePlugin(
457
460
  )
458
461
  },
459
462
 
460
- deserialize(node: RawStreamRPCNode) {
461
- return getOrCreateStream(node.streamId)
463
+ deserialize(node, ctx, _data) {
464
+ // In normal seroval usage, ctx.deserialize exists.
465
+ // Some unit tests call plugin.deserialize directly with a minimal ctx.
466
+ const id =
467
+ typeof (ctx as any)?.deserialize === 'function'
468
+ ? (ctx as any).deserialize(node.streamId)
469
+ : (node as any).streamId
470
+ return getOrCreateStream(id as number)
462
471
  },
463
- }) as Plugin<any, any>
472
+ })
464
473
  }
@@ -1,7 +1,7 @@
1
1
  import { createPlugin } from 'seroval'
2
- import type { SerovalNode } from 'seroval'
2
+ import type { PluginInfo, SerovalNode } from 'seroval'
3
3
 
4
- export interface ErrorNode {
4
+ export interface ErrorNode extends PluginInfo {
5
5
  message: SerovalNode
6
6
  }
7
7
 
@@ -1,12 +1,13 @@
1
1
  import { ReadableStreamPlugin } from 'seroval-plugins/web'
2
2
  import { ShallowErrorPlugin } from './ShallowErrorPlugin'
3
3
  import { RawStreamSSRPlugin } from './RawStream'
4
+ import type { RawStream } from './RawStream'
4
5
  import type { Plugin } from 'seroval'
5
6
 
6
7
  export const defaultSerovalPlugins = [
7
8
  ShallowErrorPlugin as Plugin<Error, any>,
8
9
  // RawStreamSSRPlugin must come before ReadableStreamPlugin to match first
9
- RawStreamSSRPlugin,
10
+ RawStreamSSRPlugin as Plugin<RawStream, any>,
10
11
  // ReadableStreamNode is not exported by seroval
11
12
  ReadableStreamPlugin as Plugin<ReadableStream, any>,
12
13
  ]
@@ -1,6 +1,6 @@
1
1
  import { createPlugin } from 'seroval'
2
2
  import { GLOBAL_TSR } from '../constants'
3
- import type { Plugin, SerovalNode } from 'seroval'
3
+ import type { Plugin, PluginInfo, SerovalNode } from 'seroval'
4
4
  import type {
5
5
  RegisteredConfigType,
6
6
  RegisteredSsr,
@@ -14,6 +14,7 @@ declare const TSR_SERIALIZABLE: unique symbol
14
14
  export type TSR_SERIALIZABLE = typeof TSR_SERIALIZABLE
15
15
 
16
16
  export type TsrSerializable = { [TSR_SERIALIZABLE]: true }
17
+
17
18
  export interface DefaultSerializable {
18
19
  number: number
19
20
  string: string
@@ -25,6 +26,7 @@ export interface DefaultSerializable {
25
26
  Uint8Array: Uint8Array
26
27
  RawStream: RawStream
27
28
  TsrSerializable: TsrSerializable
29
+ void: void
28
30
  }
29
31
 
30
32
  export interface SerializableExtensions extends DefaultSerializable {}
@@ -72,13 +74,14 @@ export interface CreateSerializationAdapterOptions<
72
74
  fromSerializable: (value: TOutput) => TInput
73
75
  }
74
76
 
75
- export type ValidateSerializable<T, TSerializable> =
76
- T extends ReadonlyArray<unknown>
77
- ? ResolveArrayShape<T, TSerializable, 'input'>
78
- : T extends TSerializable
79
- ? T
80
- : T extends (...args: Array<any>) => any
81
- ? 'Function is not serializable'
77
+ export type ValidateSerializable<T, TSerializable> = T extends TSerializable
78
+ ? T
79
+ : T extends (...args: Array<any>) => any
80
+ ? SerializationError<'Function may not be serializable'>
81
+ : T extends RegisteredReadableStream
82
+ ? SerializationError<'JSX is not be serializable'>
83
+ : T extends ReadonlyArray<any>
84
+ ? ValidateSerializableArray<T, TSerializable>
82
85
  : T extends Promise<any>
83
86
  ? ValidateSerializablePromise<T, TSerializable>
84
87
  : T extends ReadableStream<any>
@@ -89,9 +92,9 @@ export type ValidateSerializable<T, TSerializable> =
89
92
  ? ValidateSerializableMap<T, TSerializable>
90
93
  : T extends AsyncGenerator<any, any>
91
94
  ? ValidateSerializableAsyncGenerator<T, TSerializable>
92
- : {
93
- [K in keyof T]: ValidateSerializable<T[K], TSerializable>
94
- }
95
+ : T extends object
96
+ ? ValidateSerializableMapped<T, TSerializable>
97
+ : SerializationError<'Type may not be serializable'>
95
98
 
96
99
  export type ValidateSerializableAsyncGenerator<T, TSerializable> =
97
100
  T extends AsyncGenerator<infer T, infer TReturn, infer TNext>
@@ -125,16 +128,26 @@ export type ValidateSerializableMap<T, TSerializable> =
125
128
  >
126
129
  : never
127
130
 
128
- export type RegisteredReadableStream =
129
- unknown extends SerializerExtensions['ReadableStream']
130
- ? never
131
- : SerializerExtensions['ReadableStream']
131
+ export type ValidateSerializableArray<T, TSerializable> = T extends readonly [
132
+ any,
133
+ ...Array<any>,
134
+ ]
135
+ ? ValidateSerializableMapped<T, TSerializable>
136
+ : T extends Array<infer U>
137
+ ? Array<ValidateSerializable<U, TSerializable>>
138
+ : T extends ReadonlyArray<infer U>
139
+ ? ReadonlyArray<ValidateSerializable<U, TSerializable>>
140
+ : never
132
141
 
133
- export interface DefaultSerializerExtensions {
134
- ReadableStream: unknown
142
+ export type ValidateSerializableMapped<T, TSerializable> = {
143
+ [K in keyof T]: ValidateSerializable<T[K], TSerializable>
135
144
  }
136
145
 
137
- export interface SerializerExtensions extends DefaultSerializerExtensions {}
146
+ const SERIALIZATION_ERROR = Symbol.for('TSR_SERIALIZATION_ERROR')
147
+
148
+ export interface SerializationError<in out TMessage extends string> {
149
+ [SERIALIZATION_ERROR]: TMessage
150
+ }
138
151
 
139
152
  export interface SerializationAdapter<
140
153
  TInput,
@@ -161,27 +174,34 @@ export interface SerializationAdapterTypes<
161
174
 
162
175
  export type AnySerializationAdapter = SerializationAdapter<any, any, any>
163
176
 
177
+ export interface AdapterNode extends PluginInfo {
178
+ v: SerovalNode
179
+ }
180
+
164
181
  /** Create a Seroval plugin for server-side serialization only. */
182
+ /* @__NO_SIDE_EFFECTS__ */
165
183
  export function makeSsrSerovalPlugin(
166
184
  serializationAdapter: AnySerializationAdapter,
167
185
  options: { didRun: boolean },
168
- ): Plugin<any, SerovalNode> {
169
- return createPlugin<any, SerovalNode>({
186
+ ): Plugin<any, AdapterNode> {
187
+ return /* @__PURE__ */ createPlugin<any, AdapterNode>({
170
188
  tag: '$TSR/t/' + serializationAdapter.key,
171
189
  test: serializationAdapter.test,
172
190
  parse: {
173
- stream(value, ctx) {
174
- return ctx.parse(serializationAdapter.toSerializable(value))
191
+ stream(value, ctx, _data) {
192
+ return {
193
+ v: ctx.parse(serializationAdapter.toSerializable(value)),
194
+ }
175
195
  },
176
196
  },
177
- serialize(node, ctx) {
197
+ serialize(node, ctx, _data) {
178
198
  options.didRun = true
179
199
  return (
180
200
  GLOBAL_TSR +
181
201
  '.t.get("' +
182
202
  serializationAdapter.key +
183
203
  '")(' +
184
- ctx.serialize(node) +
204
+ ctx.serialize(node.v) +
185
205
  ')'
186
206
  )
187
207
  },
@@ -191,27 +211,34 @@ export function makeSsrSerovalPlugin(
191
211
  }
192
212
 
193
213
  /** Create a Seroval plugin for client/server symmetric (de)serialization. */
214
+ /* @__NO_SIDE_EFFECTS__ */
194
215
  export function makeSerovalPlugin(
195
216
  serializationAdapter: AnySerializationAdapter,
196
- ): Plugin<any, SerovalNode> {
197
- return createPlugin<any, SerovalNode>({
217
+ ): Plugin<any, AdapterNode> {
218
+ return /* @__PURE__ */ createPlugin<any, AdapterNode>({
198
219
  tag: '$TSR/t/' + serializationAdapter.key,
199
220
  test: serializationAdapter.test,
200
221
  parse: {
201
- sync(value, ctx) {
202
- return ctx.parse(serializationAdapter.toSerializable(value))
222
+ sync(value, ctx, _data) {
223
+ return {
224
+ v: ctx.parse(serializationAdapter.toSerializable(value)),
225
+ }
203
226
  },
204
- async async(value, ctx) {
205
- return await ctx.parse(serializationAdapter.toSerializable(value))
227
+ async async(value, ctx, _data) {
228
+ return {
229
+ v: await ctx.parse(serializationAdapter.toSerializable(value)),
230
+ }
206
231
  },
207
- stream(value, ctx) {
208
- return ctx.parse(serializationAdapter.toSerializable(value))
232
+ stream(value, ctx, _data) {
233
+ return {
234
+ v: ctx.parse(serializationAdapter.toSerializable(value)),
235
+ }
209
236
  },
210
237
  },
211
238
  // we don't generate JS code outside of SSR (for now)
212
239
  serialize: undefined as never,
213
- deserialize(node, ctx) {
214
- return serializationAdapter.fromSerializable(ctx.deserialize(node))
240
+ deserialize(node, ctx, _data) {
241
+ return serializationAdapter.fromSerializable(ctx.deserialize(node.v))
215
242
  },
216
243
  })
217
244
  }
@@ -234,20 +261,6 @@ export type RegisteredSerializationAdapters<TRegister> = RegisteredConfigType<
234
261
  'serializationAdapters'
235
262
  >
236
263
 
237
- export type ValidateSerializableInputResult<TRegister, T> =
238
- ValidateSerializableResult<T, RegisteredSerializableInput<TRegister>>
239
-
240
- export type ValidateSerializableResult<T, TSerializable> =
241
- T extends ReadonlyArray<unknown>
242
- ? ResolveArrayShape<T, TSerializable, 'result'>
243
- : T extends TSerializable
244
- ? T
245
- : unknown extends SerializerExtensions['ReadableStream']
246
- ? { [K in keyof T]: ValidateSerializableResult<T[K], TSerializable> }
247
- : T extends SerializerExtensions['ReadableStream']
248
- ? ReadableStream
249
- : { [K in keyof T]: ValidateSerializableResult<T[K], TSerializable> }
250
-
251
264
  export type RegisteredSSROption<TRegister> =
252
265
  unknown extends RegisteredConfigType<TRegister, 'defaultSsr'>
253
266
  ? SSROption
@@ -282,31 +295,13 @@ export type ValidateSerializableLifecycleResultSSR<
282
295
  ? any
283
296
  : ValidateSerializableInput<TRegister, LooseReturnType<TFn>>
284
297
 
285
- type ResolveArrayShape<
286
- T extends ReadonlyArray<unknown>,
287
- TSerializable,
288
- TMode extends 'input' | 'result',
289
- > = number extends T['length']
290
- ? T extends Array<infer U>
291
- ? Array<ArrayModeResult<TMode, U, TSerializable>>
292
- : ReadonlyArray<ArrayModeResult<TMode, T[number], TSerializable>>
293
- : ResolveTupleShape<T, TSerializable, TMode>
294
-
295
- type ResolveTupleShape<
296
- T extends ReadonlyArray<unknown>,
297
- TSerializable,
298
- TMode extends 'input' | 'result',
299
- > = T extends readonly [infer THead, ...infer TTail]
300
- ? readonly [
301
- ArrayModeResult<TMode, THead, TSerializable>,
302
- ...ResolveTupleShape<Readonly<TTail>, TSerializable, TMode>,
303
- ]
304
- : T
305
-
306
- type ArrayModeResult<
307
- TMode extends 'input' | 'result',
308
- TValue,
309
- TSerializable,
310
- > = TMode extends 'input'
311
- ? ValidateSerializable<TValue, TSerializable>
312
- : ValidateSerializableResult<TValue, TSerializable>
298
+ export type RegisteredReadableStream =
299
+ unknown extends SerializerExtensions['ReadableStream']
300
+ ? never
301
+ : SerializerExtensions['ReadableStream']
302
+
303
+ export interface DefaultSerializerExtensions {
304
+ ReadableStream: unknown
305
+ }
306
+
307
+ export interface SerializerExtensions extends DefaultSerializerExtensions {}
@@ -95,7 +95,7 @@ export async function hydrate(router: AnyRouter): Promise<any> {
95
95
  }
96
96
 
97
97
  // Hydrate the router state
98
- const matches = router.matchRoutes(router.stores.location.state)
98
+ const matches = router.matchRoutes(router.stores.location.get())
99
99
 
100
100
  // kick off loading the route chunks
101
101
  const routeChunkPromise = Promise.all(
@@ -170,8 +170,8 @@ export async function hydrate(router: AnyRouter): Promise<any> {
170
170
  // now that all necessary data is hydrated:
171
171
  // 1) fully reconstruct the route context
172
172
  // 2) execute `head()` and `scripts()` for each match
173
- const activeMatches = router.stores.activeMatchesSnapshot.state
174
- const location = router.stores.location.state
173
+ const activeMatches = router.stores.activeMatchesSnapshot.get()
174
+ const location = router.stores.location.get()
175
175
  await Promise.all(
176
176
  activeMatches.map(async (match) => {
177
177
  try {
@@ -258,7 +258,7 @@ export async function hydrate(router: AnyRouter): Promise<any> {
258
258
  // (e.g. preloads, invalidations) don't mistakenly detect a href change
259
259
  // (resolvedLocation defaults to undefined and router.load() is skipped
260
260
  // in the normal SSR hydration path).
261
- router.stores.resolvedLocation.setState(() => router.stores.location.state)
261
+ router.stores.resolvedLocation.set(router.stores.location.get())
262
262
  return routeChunkPromise
263
263
  }
264
264
 
@@ -292,13 +292,9 @@ export async function hydrate(router: AnyRouter): Promise<any> {
292
292
  // ensure router is not in status 'pending' anymore
293
293
  // this usually happens in Transitioner but if loading synchronously resolves,
294
294
  // Transitioner won't be rendered while loading so it cannot track the change from loading:true to loading:false
295
- if (router.stores.status.state === 'pending') {
296
- router.batch(() => {
297
- router.stores.status.setState(() => 'idle')
298
- router.stores.resolvedLocation.setState(
299
- () => router.stores.location.state,
300
- )
301
- })
295
+ if (router.stores.status.get() === 'pending') {
296
+ router.stores.status.set('idle')
297
+ router.stores.resolvedLocation.set(router.stores.location.get())
302
298
  }
303
299
  // hide the pending component once the load is finished
304
300
  router.updateMatch(match.id, (prev) => ({
@@ -2,6 +2,7 @@ import { crossSerializeStream, getCrossReferenceHeader } from 'seroval'
2
2
  import { invariant } from '../invariant'
3
3
  import { decodePath } from '../utils'
4
4
  import { createLRUCache } from '../lru-cache'
5
+ import { rootRouteId } from '../root'
5
6
  import minifiedTsrBootStrapScript from './tsrScript?script-string'
6
7
  import { GLOBAL_TSR, TSR_SCRIPT_BARRIER_ID } from './constants'
7
8
  import { dehydrateSsrMatchId } from './ssr-match-id'
@@ -171,12 +172,31 @@ function getManifestCache(manifest: Manifest): ManifestLRU {
171
172
  export function attachRouterServerSsrUtils({
172
173
  router,
173
174
  manifest,
175
+ getRequestAssets,
174
176
  }: {
175
177
  router: AnyRouter
176
178
  manifest: Manifest | undefined
179
+ getRequestAssets?: () => Array<RouterManagedTag> | undefined
177
180
  }) {
178
181
  router.ssr = {
179
- manifest,
182
+ get manifest() {
183
+ const requestAssets = getRequestAssets?.()
184
+ if (!requestAssets?.length) return manifest
185
+ // Merge request-scoped assets into root route without mutating cached manifest
186
+ return {
187
+ ...manifest,
188
+ routes: {
189
+ ...manifest?.routes,
190
+ [rootRouteId]: {
191
+ ...manifest?.routes?.[rootRouteId],
192
+ assets: [
193
+ ...requestAssets,
194
+ ...(manifest?.routes?.[rootRouteId]?.assets ?? []),
195
+ ],
196
+ },
197
+ },
198
+ }
199
+ },
180
200
  }
181
201
  let _dehydrated = false
182
202
  let _serializationFinished = false
@@ -200,7 +220,7 @@ export function attachRouterServerSsrUtils({
200
220
  const html = `<script${router.options.ssr?.nonce ? ` nonce='${router.options.ssr.nonce}'` : ''}>${script}</script>`
201
221
  router.serverSsr!.injectHtml(html)
202
222
  },
203
- dehydrate: async () => {
223
+ dehydrate: async (opts?: { requestAssets?: Array<RouterManagedTag> }) => {
204
224
  if (_dehydrated) {
205
225
  if (process.env.NODE_ENV !== 'production') {
206
226
  throw new Error('Invariant failed: router is already dehydrated!')
@@ -208,7 +228,7 @@ export function attachRouterServerSsrUtils({
208
228
 
209
229
  invariant()
210
230
  }
211
- let matchesToDehydrate = router.stores.activeMatchesSnapshot.state
231
+ let matchesToDehydrate = router.stores.activeMatchesSnapshot.get()
212
232
  if (router.isShell()) {
213
233
  // In SPA mode we only want to dehydrate the root match
214
234
  matchesToDehydrate = matchesToDehydrate.slice(0, 1)
@@ -257,6 +277,15 @@ export function attachRouterServerSsrUtils({
257
277
  manifestToDehydrate = {
258
278
  routes: filteredRoutes,
259
279
  }
280
+
281
+ // Merge request-scoped assets into root route (without mutating cached manifest)
282
+ if (opts?.requestAssets?.length) {
283
+ const existingRoot = manifestToDehydrate.routes[rootRouteId]
284
+ manifestToDehydrate.routes[rootRouteId] = {
285
+ ...existingRoot,
286
+ assets: [...opts.requestAssets, ...(existingRoot?.assets ?? [])],
287
+ }
288
+ }
260
289
  }
261
290
  const dehydratedRouter: DehydratedRouter = {
262
291
  manifest: manifestToDehydrate,
@@ -305,6 +334,13 @@ export function attachRouterServerSsrUtils({
305
334
  }
306
335
  scriptBuffer.enqueue(serialized)
307
336
  },
337
+ onError: (err: unknown) => {
338
+ console.error('Serialization error:', err)
339
+ if (err && (err as any).stack) {
340
+ console.error((err as any).stack)
341
+ }
342
+ signalSerializationComplete()
343
+ },
308
344
  scopeId: SCOPE_ID,
309
345
  onDone: () => {
310
346
  scriptBuffer.enqueue(GLOBAL_TSR + '.e()')
@@ -313,10 +349,6 @@ export function attachRouterServerSsrUtils({
313
349
  scriptBuffer.flush()
314
350
  signalSerializationComplete()
315
351
  },
316
- onError: (err) => {
317
- console.error('Serialization error:', err)
318
- signalSerializationComplete()
319
- },
320
352
  })
321
353
  },
322
354
  isDehydrated() {