@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 +1 @@
1
- {"version":3,"file":"transformer.js","names":[],"sources":["../../../../src/ssr/serializer/transformer.ts"],"sourcesContent":["import { createPlugin } from 'seroval'\nimport { GLOBAL_TSR } from '../constants'\nimport type { Plugin, SerovalNode } from 'seroval'\nimport type {\n RegisteredConfigType,\n RegisteredSsr,\n SSROption,\n} from '../../router'\nimport type { LooseReturnType } from '../../utils'\nimport type { AnyRoute, ResolveAllSSR } from '../../route'\nimport type { RawStream } from './RawStream'\n\ndeclare const TSR_SERIALIZABLE: unique symbol\nexport type TSR_SERIALIZABLE = typeof TSR_SERIALIZABLE\n\nexport type TsrSerializable = { [TSR_SERIALIZABLE]: true }\nexport interface DefaultSerializable {\n number: number\n string: string\n boolean: boolean\n null: null\n undefined: undefined\n bigint: bigint\n Date: Date\n Uint8Array: Uint8Array\n RawStream: RawStream\n TsrSerializable: TsrSerializable\n}\n\nexport interface SerializableExtensions extends DefaultSerializable {}\n\nexport type Serializable = SerializableExtensions[keyof SerializableExtensions]\n\nexport type UnionizeSerializationAdaptersInput<\n TAdapters extends ReadonlyArray<AnySerializationAdapter>,\n> = TAdapters[number]['~types']['input']\n\n/**\n * Create a strongly-typed serialization adapter for SSR hydration.\n * Use to register custom types with the router serializer.\n */\nexport function createSerializationAdapter<\n TInput = unknown,\n TOutput = unknown,\n const TExtendsAdapters extends\n | ReadonlyArray<AnySerializationAdapter>\n | never = never,\n>(\n opts: CreateSerializationAdapterOptions<TInput, TOutput, TExtendsAdapters>,\n): SerializationAdapter<TInput, TOutput, TExtendsAdapters> {\n return opts as unknown as SerializationAdapter<\n TInput,\n TOutput,\n TExtendsAdapters\n >\n}\n\nexport interface CreateSerializationAdapterOptions<\n TInput,\n TOutput,\n TExtendsAdapters extends ReadonlyArray<AnySerializationAdapter> | never,\n> {\n key: string\n extends?: TExtendsAdapters\n test: (value: unknown) => value is TInput\n toSerializable: (\n value: TInput,\n ) => ValidateSerializable<\n TOutput,\n Serializable | UnionizeSerializationAdaptersInput<TExtendsAdapters>\n >\n fromSerializable: (value: TOutput) => TInput\n}\n\nexport type ValidateSerializable<T, TSerializable> =\n T extends ReadonlyArray<unknown>\n ? ResolveArrayShape<T, TSerializable, 'input'>\n : T extends TSerializable\n ? T\n : T extends (...args: Array<any>) => any\n ? 'Function is not serializable'\n : T extends Promise<any>\n ? ValidateSerializablePromise<T, TSerializable>\n : T extends ReadableStream<any>\n ? ValidateReadableStream<T, TSerializable>\n : T extends Set<any>\n ? ValidateSerializableSet<T, TSerializable>\n : T extends Map<any, any>\n ? ValidateSerializableMap<T, TSerializable>\n : T extends AsyncGenerator<any, any>\n ? ValidateSerializableAsyncGenerator<T, TSerializable>\n : {\n [K in keyof T]: ValidateSerializable<T[K], TSerializable>\n }\n\nexport type ValidateSerializableAsyncGenerator<T, TSerializable> =\n T extends AsyncGenerator<infer T, infer TReturn, infer TNext>\n ? AsyncGenerator<\n ValidateSerializable<T, TSerializable>,\n ValidateSerializable<TReturn, TSerializable>,\n TNext\n >\n : never\n\nexport type ValidateSerializablePromise<T, TSerializable> =\n T extends Promise<infer TAwaited>\n ? Promise<ValidateSerializable<TAwaited, TSerializable>>\n : never\n\nexport type ValidateReadableStream<T, TSerializable> =\n T extends ReadableStream<infer TStreamed>\n ? ReadableStream<ValidateSerializable<TStreamed, TSerializable>>\n : never\n\nexport type ValidateSerializableSet<T, TSerializable> =\n T extends Set<infer TItem>\n ? Set<ValidateSerializable<TItem, TSerializable>>\n : never\n\nexport type ValidateSerializableMap<T, TSerializable> =\n T extends Map<infer TKey, infer TValue>\n ? Map<\n ValidateSerializable<TKey, TSerializable>,\n ValidateSerializable<TValue, TSerializable>\n >\n : never\n\nexport type RegisteredReadableStream =\n unknown extends SerializerExtensions['ReadableStream']\n ? never\n : SerializerExtensions['ReadableStream']\n\nexport interface DefaultSerializerExtensions {\n ReadableStream: unknown\n}\n\nexport interface SerializerExtensions extends DefaultSerializerExtensions {}\n\nexport interface SerializationAdapter<\n TInput,\n TOutput,\n TExtendsAdapters extends ReadonlyArray<AnySerializationAdapter>,\n> {\n '~types': SerializationAdapterTypes<TInput, TOutput, TExtendsAdapters>\n key: string\n extends?: TExtendsAdapters\n test: (value: unknown) => value is TInput\n toSerializable: (value: TInput) => TOutput\n fromSerializable: (value: TOutput) => TInput\n}\n\nexport interface SerializationAdapterTypes<\n TInput,\n TOutput,\n TExtendsAdapters extends ReadonlyArray<AnySerializationAdapter>,\n> {\n input: TInput | UnionizeSerializationAdaptersInput<TExtendsAdapters>\n output: TOutput\n extends: TExtendsAdapters\n}\n\nexport type AnySerializationAdapter = SerializationAdapter<any, any, any>\n\n/** Create a Seroval plugin for server-side serialization only. */\nexport function makeSsrSerovalPlugin(\n serializationAdapter: AnySerializationAdapter,\n options: { didRun: boolean },\n): Plugin<any, SerovalNode> {\n return createPlugin<any, SerovalNode>({\n tag: '$TSR/t/' + serializationAdapter.key,\n test: serializationAdapter.test,\n parse: {\n stream(value, ctx) {\n return ctx.parse(serializationAdapter.toSerializable(value))\n },\n },\n serialize(node, ctx) {\n options.didRun = true\n return (\n GLOBAL_TSR +\n '.t.get(\"' +\n serializationAdapter.key +\n '\")(' +\n ctx.serialize(node) +\n ')'\n )\n },\n // we never deserialize on the server during SSR\n deserialize: undefined as never,\n })\n}\n\n/** Create a Seroval plugin for client/server symmetric (de)serialization. */\nexport function makeSerovalPlugin(\n serializationAdapter: AnySerializationAdapter,\n): Plugin<any, SerovalNode> {\n return createPlugin<any, SerovalNode>({\n tag: '$TSR/t/' + serializationAdapter.key,\n test: serializationAdapter.test,\n parse: {\n sync(value, ctx) {\n return ctx.parse(serializationAdapter.toSerializable(value))\n },\n async async(value, ctx) {\n return await ctx.parse(serializationAdapter.toSerializable(value))\n },\n stream(value, ctx) {\n return ctx.parse(serializationAdapter.toSerializable(value))\n },\n },\n // we don't generate JS code outside of SSR (for now)\n serialize: undefined as never,\n deserialize(node, ctx) {\n return serializationAdapter.fromSerializable(ctx.deserialize(node))\n },\n })\n}\n\nexport type ValidateSerializableInput<TRegister, T> = ValidateSerializable<\n T,\n RegisteredSerializableInput<TRegister>\n>\n\nexport type RegisteredSerializableInput<TRegister> =\n | (unknown extends RegisteredSerializationAdapters<TRegister>\n ? never\n : RegisteredSerializationAdapters<TRegister> extends ReadonlyArray<AnySerializationAdapter>\n ? RegisteredSerializationAdapters<TRegister>[number]['~types']['input']\n : never)\n | Serializable\n\nexport type RegisteredSerializationAdapters<TRegister> = RegisteredConfigType<\n TRegister,\n 'serializationAdapters'\n>\n\nexport type ValidateSerializableInputResult<TRegister, T> =\n ValidateSerializableResult<T, RegisteredSerializableInput<TRegister>>\n\nexport type ValidateSerializableResult<T, TSerializable> =\n T extends ReadonlyArray<unknown>\n ? ResolveArrayShape<T, TSerializable, 'result'>\n : T extends TSerializable\n ? T\n : unknown extends SerializerExtensions['ReadableStream']\n ? { [K in keyof T]: ValidateSerializableResult<T[K], TSerializable> }\n : T extends SerializerExtensions['ReadableStream']\n ? ReadableStream\n : { [K in keyof T]: ValidateSerializableResult<T[K], TSerializable> }\n\nexport type RegisteredSSROption<TRegister> =\n unknown extends RegisteredConfigType<TRegister, 'defaultSsr'>\n ? SSROption\n : RegisteredConfigType<TRegister, 'defaultSsr'>\n\nexport type ValidateSerializableLifecycleResult<\n TRegister,\n TParentRoute extends AnyRoute,\n TSSR,\n TFn,\n> =\n false extends RegisteredSsr<TRegister>\n ? any\n : ValidateSerializableLifecycleResultSSR<\n TRegister,\n TParentRoute,\n TSSR,\n TFn\n > extends infer TInput\n ? TInput\n : never\n\nexport type ValidateSerializableLifecycleResultSSR<\n TRegister,\n TParentRoute extends AnyRoute,\n TSSR,\n TFn,\n> =\n ResolveAllSSR<TParentRoute, TSSR> extends false\n ? any\n : RegisteredSSROption<TRegister> extends false\n ? any\n : ValidateSerializableInput<TRegister, LooseReturnType<TFn>>\n\ntype ResolveArrayShape<\n T extends ReadonlyArray<unknown>,\n TSerializable,\n TMode extends 'input' | 'result',\n> = number extends T['length']\n ? T extends Array<infer U>\n ? Array<ArrayModeResult<TMode, U, TSerializable>>\n : ReadonlyArray<ArrayModeResult<TMode, T[number], TSerializable>>\n : ResolveTupleShape<T, TSerializable, TMode>\n\ntype ResolveTupleShape<\n T extends ReadonlyArray<unknown>,\n TSerializable,\n TMode extends 'input' | 'result',\n> = T extends readonly [infer THead, ...infer TTail]\n ? readonly [\n ArrayModeResult<TMode, THead, TSerializable>,\n ...ResolveTupleShape<Readonly<TTail>, TSerializable, TMode>,\n ]\n : T\n\ntype ArrayModeResult<\n TMode extends 'input' | 'result',\n TValue,\n TSerializable,\n> = TMode extends 'input'\n ? ValidateSerializable<TValue, TSerializable>\n : ValidateSerializableResult<TValue, TSerializable>\n"],"mappings":";;;;;;;AAyCA,SAAgB,2BAOd,MACyD;AACzD,QAAO;;;AAkHT,SAAgB,qBACd,sBACA,SAC0B;AAC1B,QAAO,aAA+B;EACpC,KAAK,YAAY,qBAAqB;EACtC,MAAM,qBAAqB;EAC3B,OAAO,EACL,OAAO,OAAO,KAAK;AACjB,UAAO,IAAI,MAAM,qBAAqB,eAAe,MAAM,CAAC;KAE/D;EACD,UAAU,MAAM,KAAK;AACnB,WAAQ,SAAS;AACjB,UACE,aACA,cACA,qBAAqB,MACrB,SACA,IAAI,UAAU,KAAK,GACnB;;EAIJ,aAAa,KAAA;EACd,CAAC;;;AAIJ,SAAgB,kBACd,sBAC0B;AAC1B,QAAO,aAA+B;EACpC,KAAK,YAAY,qBAAqB;EACtC,MAAM,qBAAqB;EAC3B,OAAO;GACL,KAAK,OAAO,KAAK;AACf,WAAO,IAAI,MAAM,qBAAqB,eAAe,MAAM,CAAC;;GAE9D,MAAM,MAAM,OAAO,KAAK;AACtB,WAAO,MAAM,IAAI,MAAM,qBAAqB,eAAe,MAAM,CAAC;;GAEpE,OAAO,OAAO,KAAK;AACjB,WAAO,IAAI,MAAM,qBAAqB,eAAe,MAAM,CAAC;;GAE/D;EAED,WAAW,KAAA;EACX,YAAY,MAAM,KAAK;AACrB,UAAO,qBAAqB,iBAAiB,IAAI,YAAY,KAAK,CAAC;;EAEtE,CAAC"}
1
+ {"version":3,"file":"transformer.js","names":[],"sources":["../../../../src/ssr/serializer/transformer.ts"],"sourcesContent":["import { createPlugin } from 'seroval'\nimport { GLOBAL_TSR } from '../constants'\nimport type { Plugin, PluginInfo, SerovalNode } from 'seroval'\nimport type {\n RegisteredConfigType,\n RegisteredSsr,\n SSROption,\n} from '../../router'\nimport type { LooseReturnType } from '../../utils'\nimport type { AnyRoute, ResolveAllSSR } from '../../route'\nimport type { RawStream } from './RawStream'\n\ndeclare const TSR_SERIALIZABLE: unique symbol\nexport type TSR_SERIALIZABLE = typeof TSR_SERIALIZABLE\n\nexport type TsrSerializable = { [TSR_SERIALIZABLE]: true }\n\nexport interface DefaultSerializable {\n number: number\n string: string\n boolean: boolean\n null: null\n undefined: undefined\n bigint: bigint\n Date: Date\n Uint8Array: Uint8Array\n RawStream: RawStream\n TsrSerializable: TsrSerializable\n void: void\n}\n\nexport interface SerializableExtensions extends DefaultSerializable {}\n\nexport type Serializable = SerializableExtensions[keyof SerializableExtensions]\n\nexport type UnionizeSerializationAdaptersInput<\n TAdapters extends ReadonlyArray<AnySerializationAdapter>,\n> = TAdapters[number]['~types']['input']\n\n/**\n * Create a strongly-typed serialization adapter for SSR hydration.\n * Use to register custom types with the router serializer.\n */\nexport function createSerializationAdapter<\n TInput = unknown,\n TOutput = unknown,\n const TExtendsAdapters extends\n | ReadonlyArray<AnySerializationAdapter>\n | never = never,\n>(\n opts: CreateSerializationAdapterOptions<TInput, TOutput, TExtendsAdapters>,\n): SerializationAdapter<TInput, TOutput, TExtendsAdapters> {\n return opts as unknown as SerializationAdapter<\n TInput,\n TOutput,\n TExtendsAdapters\n >\n}\n\nexport interface CreateSerializationAdapterOptions<\n TInput,\n TOutput,\n TExtendsAdapters extends ReadonlyArray<AnySerializationAdapter> | never,\n> {\n key: string\n extends?: TExtendsAdapters\n test: (value: unknown) => value is TInput\n toSerializable: (\n value: TInput,\n ) => ValidateSerializable<\n TOutput,\n Serializable | UnionizeSerializationAdaptersInput<TExtendsAdapters>\n >\n fromSerializable: (value: TOutput) => TInput\n}\n\nexport type ValidateSerializable<T, TSerializable> = T extends TSerializable\n ? T\n : T extends (...args: Array<any>) => any\n ? SerializationError<'Function may not be serializable'>\n : T extends RegisteredReadableStream\n ? SerializationError<'JSX is not be serializable'>\n : T extends ReadonlyArray<any>\n ? ValidateSerializableArray<T, TSerializable>\n : T extends Promise<any>\n ? ValidateSerializablePromise<T, TSerializable>\n : T extends ReadableStream<any>\n ? ValidateReadableStream<T, TSerializable>\n : T extends Set<any>\n ? ValidateSerializableSet<T, TSerializable>\n : T extends Map<any, any>\n ? ValidateSerializableMap<T, TSerializable>\n : T extends AsyncGenerator<any, any>\n ? ValidateSerializableAsyncGenerator<T, TSerializable>\n : T extends object\n ? ValidateSerializableMapped<T, TSerializable>\n : SerializationError<'Type may not be serializable'>\n\nexport type ValidateSerializableAsyncGenerator<T, TSerializable> =\n T extends AsyncGenerator<infer T, infer TReturn, infer TNext>\n ? AsyncGenerator<\n ValidateSerializable<T, TSerializable>,\n ValidateSerializable<TReturn, TSerializable>,\n TNext\n >\n : never\n\nexport type ValidateSerializablePromise<T, TSerializable> =\n T extends Promise<infer TAwaited>\n ? Promise<ValidateSerializable<TAwaited, TSerializable>>\n : never\n\nexport type ValidateReadableStream<T, TSerializable> =\n T extends ReadableStream<infer TStreamed>\n ? ReadableStream<ValidateSerializable<TStreamed, TSerializable>>\n : never\n\nexport type ValidateSerializableSet<T, TSerializable> =\n T extends Set<infer TItem>\n ? Set<ValidateSerializable<TItem, TSerializable>>\n : never\n\nexport type ValidateSerializableMap<T, TSerializable> =\n T extends Map<infer TKey, infer TValue>\n ? Map<\n ValidateSerializable<TKey, TSerializable>,\n ValidateSerializable<TValue, TSerializable>\n >\n : never\n\nexport type ValidateSerializableArray<T, TSerializable> = T extends readonly [\n any,\n ...Array<any>,\n]\n ? ValidateSerializableMapped<T, TSerializable>\n : T extends Array<infer U>\n ? Array<ValidateSerializable<U, TSerializable>>\n : T extends ReadonlyArray<infer U>\n ? ReadonlyArray<ValidateSerializable<U, TSerializable>>\n : never\n\nexport type ValidateSerializableMapped<T, TSerializable> = {\n [K in keyof T]: ValidateSerializable<T[K], TSerializable>\n}\n\nconst SERIALIZATION_ERROR = Symbol.for('TSR_SERIALIZATION_ERROR')\n\nexport interface SerializationError<in out TMessage extends string> {\n [SERIALIZATION_ERROR]: TMessage\n}\n\nexport interface SerializationAdapter<\n TInput,\n TOutput,\n TExtendsAdapters extends ReadonlyArray<AnySerializationAdapter>,\n> {\n '~types': SerializationAdapterTypes<TInput, TOutput, TExtendsAdapters>\n key: string\n extends?: TExtendsAdapters\n test: (value: unknown) => value is TInput\n toSerializable: (value: TInput) => TOutput\n fromSerializable: (value: TOutput) => TInput\n}\n\nexport interface SerializationAdapterTypes<\n TInput,\n TOutput,\n TExtendsAdapters extends ReadonlyArray<AnySerializationAdapter>,\n> {\n input: TInput | UnionizeSerializationAdaptersInput<TExtendsAdapters>\n output: TOutput\n extends: TExtendsAdapters\n}\n\nexport type AnySerializationAdapter = SerializationAdapter<any, any, any>\n\nexport interface AdapterNode extends PluginInfo {\n v: SerovalNode\n}\n\n/** Create a Seroval plugin for server-side serialization only. */\n/* @__NO_SIDE_EFFECTS__ */\nexport function makeSsrSerovalPlugin(\n serializationAdapter: AnySerializationAdapter,\n options: { didRun: boolean },\n): Plugin<any, AdapterNode> {\n return /* @__PURE__ */ createPlugin<any, AdapterNode>({\n tag: '$TSR/t/' + serializationAdapter.key,\n test: serializationAdapter.test,\n parse: {\n stream(value, ctx, _data) {\n return {\n v: ctx.parse(serializationAdapter.toSerializable(value)),\n }\n },\n },\n serialize(node, ctx, _data) {\n options.didRun = true\n return (\n GLOBAL_TSR +\n '.t.get(\"' +\n serializationAdapter.key +\n '\")(' +\n ctx.serialize(node.v) +\n ')'\n )\n },\n // we never deserialize on the server during SSR\n deserialize: undefined as never,\n })\n}\n\n/** Create a Seroval plugin for client/server symmetric (de)serialization. */\n/* @__NO_SIDE_EFFECTS__ */\nexport function makeSerovalPlugin(\n serializationAdapter: AnySerializationAdapter,\n): Plugin<any, AdapterNode> {\n return /* @__PURE__ */ createPlugin<any, AdapterNode>({\n tag: '$TSR/t/' + serializationAdapter.key,\n test: serializationAdapter.test,\n parse: {\n sync(value, ctx, _data) {\n return {\n v: ctx.parse(serializationAdapter.toSerializable(value)),\n }\n },\n async async(value, ctx, _data) {\n return {\n v: await ctx.parse(serializationAdapter.toSerializable(value)),\n }\n },\n stream(value, ctx, _data) {\n return {\n v: ctx.parse(serializationAdapter.toSerializable(value)),\n }\n },\n },\n // we don't generate JS code outside of SSR (for now)\n serialize: undefined as never,\n deserialize(node, ctx, _data) {\n return serializationAdapter.fromSerializable(ctx.deserialize(node.v))\n },\n })\n}\n\nexport type ValidateSerializableInput<TRegister, T> = ValidateSerializable<\n T,\n RegisteredSerializableInput<TRegister>\n>\n\nexport type RegisteredSerializableInput<TRegister> =\n | (unknown extends RegisteredSerializationAdapters<TRegister>\n ? never\n : RegisteredSerializationAdapters<TRegister> extends ReadonlyArray<AnySerializationAdapter>\n ? RegisteredSerializationAdapters<TRegister>[number]['~types']['input']\n : never)\n | Serializable\n\nexport type RegisteredSerializationAdapters<TRegister> = RegisteredConfigType<\n TRegister,\n 'serializationAdapters'\n>\n\nexport type RegisteredSSROption<TRegister> =\n unknown extends RegisteredConfigType<TRegister, 'defaultSsr'>\n ? SSROption\n : RegisteredConfigType<TRegister, 'defaultSsr'>\n\nexport type ValidateSerializableLifecycleResult<\n TRegister,\n TParentRoute extends AnyRoute,\n TSSR,\n TFn,\n> =\n false extends RegisteredSsr<TRegister>\n ? any\n : ValidateSerializableLifecycleResultSSR<\n TRegister,\n TParentRoute,\n TSSR,\n TFn\n > extends infer TInput\n ? TInput\n : never\n\nexport type ValidateSerializableLifecycleResultSSR<\n TRegister,\n TParentRoute extends AnyRoute,\n TSSR,\n TFn,\n> =\n ResolveAllSSR<TParentRoute, TSSR> extends false\n ? any\n : RegisteredSSROption<TRegister> extends false\n ? any\n : ValidateSerializableInput<TRegister, LooseReturnType<TFn>>\n\nexport type RegisteredReadableStream =\n unknown extends SerializerExtensions['ReadableStream']\n ? never\n : SerializerExtensions['ReadableStream']\n\nexport interface DefaultSerializerExtensions {\n ReadableStream: unknown\n}\n\nexport interface SerializerExtensions extends DefaultSerializerExtensions {}\n"],"mappings":";;;;;;;AA2CA,SAAgB,2BAOd,MACyD;AACzD,QAAO;;;;AAkIT,SAAgB,qBACd,sBACA,SAC0B;AAC1B,QAAuB,6BAA+B;EACpD,KAAK,YAAY,qBAAqB;EACtC,MAAM,qBAAqB;EAC3B,OAAO,EACL,OAAO,OAAO,KAAK,OAAO;AACxB,UAAO,EACL,GAAG,IAAI,MAAM,qBAAqB,eAAe,MAAM,CAAC,EACzD;KAEJ;EACD,UAAU,MAAM,KAAK,OAAO;AAC1B,WAAQ,SAAS;AACjB,UACE,aACA,cACA,qBAAqB,MACrB,SACA,IAAI,UAAU,KAAK,EAAE,GACrB;;EAIJ,aAAa,KAAA;EACd,CAAC;;;;AAKJ,SAAgB,kBACd,sBAC0B;AAC1B,QAAuB,6BAA+B;EACpD,KAAK,YAAY,qBAAqB;EACtC,MAAM,qBAAqB;EAC3B,OAAO;GACL,KAAK,OAAO,KAAK,OAAO;AACtB,WAAO,EACL,GAAG,IAAI,MAAM,qBAAqB,eAAe,MAAM,CAAC,EACzD;;GAEH,MAAM,MAAM,OAAO,KAAK,OAAO;AAC7B,WAAO,EACL,GAAG,MAAM,IAAI,MAAM,qBAAqB,eAAe,MAAM,CAAC,EAC/D;;GAEH,OAAO,OAAO,KAAK,OAAO;AACxB,WAAO,EACL,GAAG,IAAI,MAAM,qBAAqB,eAAe,MAAM,CAAC,EACzD;;GAEJ;EAED,WAAW,KAAA;EACX,YAAY,MAAM,KAAK,OAAO;AAC5B,UAAO,qBAAqB,iBAAiB,IAAI,YAAY,KAAK,EAAE,CAAC;;EAExE,CAAC"}
@@ -41,7 +41,7 @@ async function hydrate(router) {
41
41
  router.ssr = { manifest };
42
42
  const nonce = document.querySelector("meta[property=\"csp-nonce\"]")?.content;
43
43
  router.options.ssr = { nonce };
44
- const matches = router.matchRoutes(router.stores.location.state);
44
+ const matches = router.matchRoutes(router.stores.location.get());
45
45
  const routeChunkPromise = Promise.all(matches.map((match) => router.loadRouteChunk(router.looseRoutesById[match.routeId])));
46
46
  function setMatchForcePending(match) {
47
47
  const pendingMinMs = router.looseRoutesById[match.routeId].options.pendingMinMs ?? router.options.defaultPendingMinMs;
@@ -86,8 +86,8 @@ async function hydrate(router) {
86
86
  });
87
87
  router.stores.setActiveMatches(matches);
88
88
  await router.options.hydrate?.(dehydratedData);
89
- const activeMatches = router.stores.activeMatchesSnapshot.state;
90
- const location = router.stores.location.state;
89
+ const activeMatches = router.stores.activeMatchesSnapshot.get();
90
+ const location = router.stores.location.get();
91
91
  await Promise.all(activeMatches.map(async (match) => {
92
92
  try {
93
93
  const route = router.looseRoutesById[match.routeId];
@@ -146,7 +146,7 @@ async function hydrate(router) {
146
146
  matches.forEach((match) => {
147
147
  match._nonReactive.dehydrated = void 0;
148
148
  });
149
- router.stores.resolvedLocation.setState(() => router.stores.location.state);
149
+ router.stores.resolvedLocation.set(router.stores.location.get());
150
150
  return routeChunkPromise;
151
151
  }
152
152
  const loadPromise = Promise.resolve().then(() => router.load()).catch((err) => {
@@ -163,10 +163,10 @@ async function hydrate(router) {
163
163
  match._nonReactive.displayPendingPromise = loadPromise;
164
164
  loadPromise.then(() => {
165
165
  router.batch(() => {
166
- if (router.stores.status.state === "pending") router.batch(() => {
167
- router.stores.status.setState(() => "idle");
168
- router.stores.resolvedLocation.setState(() => router.stores.location.state);
169
- });
166
+ if (router.stores.status.get() === "pending") {
167
+ router.stores.status.set("idle");
168
+ router.stores.resolvedLocation.set(router.stores.location.get());
169
+ }
170
170
  router.updateMatch(match.id, (prev) => ({
171
171
  ...prev,
172
172
  _displayPending: void 0,
@@ -1 +1 @@
1
- {"version":3,"file":"ssr-client.js","names":[],"sources":["../../../src/ssr/ssr-client.ts"],"sourcesContent":["import { invariant } from '../invariant'\nimport { isNotFound } from '../not-found'\nimport { createControlledPromise } from '../utils'\nimport { hydrateSsrMatchId } from './ssr-match-id'\nimport type { GLOBAL_SEROVAL, GLOBAL_TSR } from './constants'\nimport type { DehydratedMatch, TsrSsrGlobal } from './types'\nimport type { AnyRouteMatch } from '../Matches'\nimport type { AnyRouter } from '../router'\nimport type { RouteContextOptions } from '../route'\nimport type { AnySerializationAdapter } from './serializer/transformer'\n\ndeclare global {\n interface Window {\n [GLOBAL_TSR]?: TsrSsrGlobal\n [GLOBAL_SEROVAL]?: any\n }\n}\n\nfunction hydrateMatch(\n match: AnyRouteMatch,\n deyhydratedMatch: DehydratedMatch,\n): void {\n match.id = deyhydratedMatch.i\n match.__beforeLoadContext = deyhydratedMatch.b\n match.loaderData = deyhydratedMatch.l\n match.status = deyhydratedMatch.s\n match.ssr = deyhydratedMatch.ssr\n match.updatedAt = deyhydratedMatch.u\n match.error = deyhydratedMatch.e\n // Only hydrate global-not-found when a defined value is present in the\n // dehydrated payload. If omitted, preserve the value computed from the\n // current client location (important for SPA fallback HTML served at unknown\n // URLs, where dehydrated matches may come from `/` but client matching marks\n // root as globalNotFound).\n if (deyhydratedMatch.g !== undefined) {\n match.globalNotFound = deyhydratedMatch.g\n }\n}\n\nexport async function hydrate(router: AnyRouter): Promise<any> {\n if (!window.$_TSR) {\n if (process.env.NODE_ENV !== 'production') {\n throw new Error(\n 'Invariant failed: Expected to find bootstrap data on window.$_TSR, but we did not. Please file an issue!',\n )\n }\n\n invariant()\n }\n\n const serializationAdapters = router.options.serializationAdapters as\n | Array<AnySerializationAdapter>\n | undefined\n\n if (serializationAdapters?.length) {\n const fromSerializableMap = new Map()\n serializationAdapters.forEach((adapter) => {\n fromSerializableMap.set(adapter.key, adapter.fromSerializable)\n })\n window.$_TSR.t = fromSerializableMap\n window.$_TSR.buffer.forEach((script) => script())\n }\n window.$_TSR.initialized = true\n\n if (!window.$_TSR.router) {\n if (process.env.NODE_ENV !== 'production') {\n throw new Error(\n 'Invariant failed: Expected to find a dehydrated data on window.$_TSR.router, but we did not. Please file an issue!',\n )\n }\n\n invariant()\n }\n\n const dehydratedRouter = window.$_TSR.router\n dehydratedRouter.matches.forEach((dehydratedMatch) => {\n dehydratedMatch.i = hydrateSsrMatchId(dehydratedMatch.i)\n })\n if (dehydratedRouter.lastMatchId) {\n dehydratedRouter.lastMatchId = hydrateSsrMatchId(\n dehydratedRouter.lastMatchId,\n )\n }\n const { manifest, dehydratedData, lastMatchId } = dehydratedRouter\n\n router.ssr = {\n manifest,\n }\n const meta = document.querySelector('meta[property=\"csp-nonce\"]') as\n | HTMLMetaElement\n | undefined\n const nonce = meta?.content\n router.options.ssr = {\n nonce,\n }\n\n // Hydrate the router state\n const matches = router.matchRoutes(router.stores.location.state)\n\n // kick off loading the route chunks\n const routeChunkPromise = Promise.all(\n matches.map((match) =>\n router.loadRouteChunk(router.looseRoutesById[match.routeId]!),\n ),\n )\n\n function setMatchForcePending(match: AnyRouteMatch) {\n // usually the minPendingPromise is created in the Match component if a pending match is rendered\n // however, this might be too late if the match synchronously resolves\n const route = router.looseRoutesById[match.routeId]!\n const pendingMinMs =\n route.options.pendingMinMs ?? router.options.defaultPendingMinMs\n if (pendingMinMs) {\n const minPendingPromise = createControlledPromise<void>()\n match._nonReactive.minPendingPromise = minPendingPromise\n match._forcePending = true\n\n setTimeout(() => {\n minPendingPromise.resolve()\n // We've handled the minPendingPromise, so we can delete it\n router.updateMatch(match.id, (prev) => {\n prev._nonReactive.minPendingPromise = undefined\n return {\n ...prev,\n _forcePending: undefined,\n }\n })\n }, pendingMinMs)\n }\n }\n\n function setRouteSsr(match: AnyRouteMatch) {\n const route = router.looseRoutesById[match.routeId]\n if (route) {\n route.options.ssr = match.ssr\n }\n }\n // Right after hydration and before the first render, we need to rehydrate each match\n // First step is to reyhdrate loaderData and __beforeLoadContext\n let firstNonSsrMatchIndex: number | undefined = undefined\n matches.forEach((match) => {\n const dehydratedMatch = dehydratedRouter.matches.find(\n (d) => d.i === match.id,\n )\n if (!dehydratedMatch) {\n match._nonReactive.dehydrated = false\n match.ssr = false\n setRouteSsr(match)\n return\n }\n\n hydrateMatch(match, dehydratedMatch)\n setRouteSsr(match)\n\n match._nonReactive.dehydrated = match.ssr !== false\n\n if (match.ssr === 'data-only' || match.ssr === false) {\n if (firstNonSsrMatchIndex === undefined) {\n firstNonSsrMatchIndex = match.index\n setMatchForcePending(match)\n }\n }\n })\n\n router.stores.setActiveMatches(matches)\n\n // Allow the user to handle custom hydration data\n await router.options.hydrate?.(dehydratedData)\n\n // now that all necessary data is hydrated:\n // 1) fully reconstruct the route context\n // 2) execute `head()` and `scripts()` for each match\n const activeMatches = router.stores.activeMatchesSnapshot.state\n const location = router.stores.location.state\n await Promise.all(\n activeMatches.map(async (match) => {\n try {\n const route = router.looseRoutesById[match.routeId]!\n\n const parentMatch = activeMatches[match.index - 1]\n const parentContext = parentMatch?.context ?? router.options.context\n\n // `context()` was already executed by `matchRoutes`, however route context was not yet fully reconstructed\n // so run it again and merge route context\n if (route.options.context) {\n const contextFnContext: RouteContextOptions<any, any, any, any, any> =\n {\n deps: match.loaderDeps,\n params: match.params,\n context: parentContext ?? {},\n location,\n navigate: (opts: any) =>\n router.navigate({\n ...opts,\n _fromLocation: location,\n }),\n buildLocation: router.buildLocation,\n cause: match.cause,\n abortController: match.abortController,\n preload: false,\n matches,\n routeId: route.id,\n }\n match.__routeContext =\n route.options.context(contextFnContext) ?? undefined\n }\n\n match.context = {\n ...parentContext,\n ...match.__routeContext,\n ...match.__beforeLoadContext,\n }\n\n const assetContext = {\n ssr: router.options.ssr,\n matches: activeMatches,\n match,\n params: match.params,\n loaderData: match.loaderData,\n }\n const headFnContent = await route.options.head?.(assetContext)\n\n const scripts = await route.options.scripts?.(assetContext)\n\n match.meta = headFnContent?.meta\n match.links = headFnContent?.links\n match.headScripts = headFnContent?.scripts\n match.styles = headFnContent?.styles\n match.scripts = scripts\n } catch (err) {\n if (isNotFound(err)) {\n match.error = { isNotFound: true }\n console.error(\n `NotFound error during hydration for routeId: ${match.routeId}`,\n err,\n )\n } else {\n match.error = err as any\n console.error(\n `Error during hydration for route ${match.routeId}:`,\n err,\n )\n throw err\n }\n }\n }),\n )\n\n const isSpaMode = matches[matches.length - 1]!.id !== lastMatchId\n const hasSsrFalseMatches = matches.some((m) => m.ssr === false)\n // all matches have data from the server and we are not in SPA mode so we don't need to kick of router.load()\n if (!hasSsrFalseMatches && !isSpaMode) {\n matches.forEach((match) => {\n // remove the dehydrated flag since we won't run router.load() which would remove it\n match._nonReactive.dehydrated = undefined\n })\n // Mark the current location as resolved so that later load cycles\n // (e.g. preloads, invalidations) don't mistakenly detect a href change\n // (resolvedLocation defaults to undefined and router.load() is skipped\n // in the normal SSR hydration path).\n router.stores.resolvedLocation.setState(() => router.stores.location.state)\n return routeChunkPromise\n }\n\n // schedule router.load() to run after the next tick so we can store the promise in the match before loading starts\n const loadPromise = Promise.resolve()\n .then(() => router.load())\n .catch((err) => {\n console.error('Error during router hydration:', err)\n })\n\n // in SPA mode we need to keep the first match below the root route pending until router.load() is finished\n // this will prevent that other pending components are rendered but hydration is not blocked\n if (isSpaMode) {\n const match = matches[1]\n if (!match) {\n if (process.env.NODE_ENV !== 'production') {\n throw new Error(\n 'Invariant failed: Expected to find a match below the root match in SPA mode.',\n )\n }\n\n invariant()\n }\n setMatchForcePending(match)\n\n match._displayPending = true\n match._nonReactive.displayPendingPromise = loadPromise\n\n loadPromise.then(() => {\n router.batch(() => {\n // ensure router is not in status 'pending' anymore\n // this usually happens in Transitioner but if loading synchronously resolves,\n // Transitioner won't be rendered while loading so it cannot track the change from loading:true to loading:false\n if (router.stores.status.state === 'pending') {\n router.batch(() => {\n router.stores.status.setState(() => 'idle')\n router.stores.resolvedLocation.setState(\n () => router.stores.location.state,\n )\n })\n }\n // hide the pending component once the load is finished\n router.updateMatch(match.id, (prev) => ({\n ...prev,\n _displayPending: undefined,\n displayPendingPromise: undefined,\n }))\n })\n })\n }\n return routeChunkPromise\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ssr-client.js","names":[],"sources":["../../../src/ssr/ssr-client.ts"],"sourcesContent":["import { invariant } from '../invariant'\nimport { isNotFound } from '../not-found'\nimport { createControlledPromise } from '../utils'\nimport { hydrateSsrMatchId } from './ssr-match-id'\nimport type { GLOBAL_SEROVAL, GLOBAL_TSR } from './constants'\nimport type { DehydratedMatch, TsrSsrGlobal } from './types'\nimport type { AnyRouteMatch } from '../Matches'\nimport type { AnyRouter } from '../router'\nimport type { RouteContextOptions } from '../route'\nimport type { AnySerializationAdapter } from './serializer/transformer'\n\ndeclare global {\n interface Window {\n [GLOBAL_TSR]?: TsrSsrGlobal\n [GLOBAL_SEROVAL]?: any\n }\n}\n\nfunction hydrateMatch(\n match: AnyRouteMatch,\n deyhydratedMatch: DehydratedMatch,\n): void {\n match.id = deyhydratedMatch.i\n match.__beforeLoadContext = deyhydratedMatch.b\n match.loaderData = deyhydratedMatch.l\n match.status = deyhydratedMatch.s\n match.ssr = deyhydratedMatch.ssr\n match.updatedAt = deyhydratedMatch.u\n match.error = deyhydratedMatch.e\n // Only hydrate global-not-found when a defined value is present in the\n // dehydrated payload. If omitted, preserve the value computed from the\n // current client location (important for SPA fallback HTML served at unknown\n // URLs, where dehydrated matches may come from `/` but client matching marks\n // root as globalNotFound).\n if (deyhydratedMatch.g !== undefined) {\n match.globalNotFound = deyhydratedMatch.g\n }\n}\n\nexport async function hydrate(router: AnyRouter): Promise<any> {\n if (!window.$_TSR) {\n if (process.env.NODE_ENV !== 'production') {\n throw new Error(\n 'Invariant failed: Expected to find bootstrap data on window.$_TSR, but we did not. Please file an issue!',\n )\n }\n\n invariant()\n }\n\n const serializationAdapters = router.options.serializationAdapters as\n | Array<AnySerializationAdapter>\n | undefined\n\n if (serializationAdapters?.length) {\n const fromSerializableMap = new Map()\n serializationAdapters.forEach((adapter) => {\n fromSerializableMap.set(adapter.key, adapter.fromSerializable)\n })\n window.$_TSR.t = fromSerializableMap\n window.$_TSR.buffer.forEach((script) => script())\n }\n window.$_TSR.initialized = true\n\n if (!window.$_TSR.router) {\n if (process.env.NODE_ENV !== 'production') {\n throw new Error(\n 'Invariant failed: Expected to find a dehydrated data on window.$_TSR.router, but we did not. Please file an issue!',\n )\n }\n\n invariant()\n }\n\n const dehydratedRouter = window.$_TSR.router\n dehydratedRouter.matches.forEach((dehydratedMatch) => {\n dehydratedMatch.i = hydrateSsrMatchId(dehydratedMatch.i)\n })\n if (dehydratedRouter.lastMatchId) {\n dehydratedRouter.lastMatchId = hydrateSsrMatchId(\n dehydratedRouter.lastMatchId,\n )\n }\n const { manifest, dehydratedData, lastMatchId } = dehydratedRouter\n\n router.ssr = {\n manifest,\n }\n const meta = document.querySelector('meta[property=\"csp-nonce\"]') as\n | HTMLMetaElement\n | undefined\n const nonce = meta?.content\n router.options.ssr = {\n nonce,\n }\n\n // Hydrate the router state\n const matches = router.matchRoutes(router.stores.location.get())\n\n // kick off loading the route chunks\n const routeChunkPromise = Promise.all(\n matches.map((match) =>\n router.loadRouteChunk(router.looseRoutesById[match.routeId]!),\n ),\n )\n\n function setMatchForcePending(match: AnyRouteMatch) {\n // usually the minPendingPromise is created in the Match component if a pending match is rendered\n // however, this might be too late if the match synchronously resolves\n const route = router.looseRoutesById[match.routeId]!\n const pendingMinMs =\n route.options.pendingMinMs ?? router.options.defaultPendingMinMs\n if (pendingMinMs) {\n const minPendingPromise = createControlledPromise<void>()\n match._nonReactive.minPendingPromise = minPendingPromise\n match._forcePending = true\n\n setTimeout(() => {\n minPendingPromise.resolve()\n // We've handled the minPendingPromise, so we can delete it\n router.updateMatch(match.id, (prev) => {\n prev._nonReactive.minPendingPromise = undefined\n return {\n ...prev,\n _forcePending: undefined,\n }\n })\n }, pendingMinMs)\n }\n }\n\n function setRouteSsr(match: AnyRouteMatch) {\n const route = router.looseRoutesById[match.routeId]\n if (route) {\n route.options.ssr = match.ssr\n }\n }\n // Right after hydration and before the first render, we need to rehydrate each match\n // First step is to reyhdrate loaderData and __beforeLoadContext\n let firstNonSsrMatchIndex: number | undefined = undefined\n matches.forEach((match) => {\n const dehydratedMatch = dehydratedRouter.matches.find(\n (d) => d.i === match.id,\n )\n if (!dehydratedMatch) {\n match._nonReactive.dehydrated = false\n match.ssr = false\n setRouteSsr(match)\n return\n }\n\n hydrateMatch(match, dehydratedMatch)\n setRouteSsr(match)\n\n match._nonReactive.dehydrated = match.ssr !== false\n\n if (match.ssr === 'data-only' || match.ssr === false) {\n if (firstNonSsrMatchIndex === undefined) {\n firstNonSsrMatchIndex = match.index\n setMatchForcePending(match)\n }\n }\n })\n\n router.stores.setActiveMatches(matches)\n\n // Allow the user to handle custom hydration data\n await router.options.hydrate?.(dehydratedData)\n\n // now that all necessary data is hydrated:\n // 1) fully reconstruct the route context\n // 2) execute `head()` and `scripts()` for each match\n const activeMatches = router.stores.activeMatchesSnapshot.get()\n const location = router.stores.location.get()\n await Promise.all(\n activeMatches.map(async (match) => {\n try {\n const route = router.looseRoutesById[match.routeId]!\n\n const parentMatch = activeMatches[match.index - 1]\n const parentContext = parentMatch?.context ?? router.options.context\n\n // `context()` was already executed by `matchRoutes`, however route context was not yet fully reconstructed\n // so run it again and merge route context\n if (route.options.context) {\n const contextFnContext: RouteContextOptions<any, any, any, any, any> =\n {\n deps: match.loaderDeps,\n params: match.params,\n context: parentContext ?? {},\n location,\n navigate: (opts: any) =>\n router.navigate({\n ...opts,\n _fromLocation: location,\n }),\n buildLocation: router.buildLocation,\n cause: match.cause,\n abortController: match.abortController,\n preload: false,\n matches,\n routeId: route.id,\n }\n match.__routeContext =\n route.options.context(contextFnContext) ?? undefined\n }\n\n match.context = {\n ...parentContext,\n ...match.__routeContext,\n ...match.__beforeLoadContext,\n }\n\n const assetContext = {\n ssr: router.options.ssr,\n matches: activeMatches,\n match,\n params: match.params,\n loaderData: match.loaderData,\n }\n const headFnContent = await route.options.head?.(assetContext)\n\n const scripts = await route.options.scripts?.(assetContext)\n\n match.meta = headFnContent?.meta\n match.links = headFnContent?.links\n match.headScripts = headFnContent?.scripts\n match.styles = headFnContent?.styles\n match.scripts = scripts\n } catch (err) {\n if (isNotFound(err)) {\n match.error = { isNotFound: true }\n console.error(\n `NotFound error during hydration for routeId: ${match.routeId}`,\n err,\n )\n } else {\n match.error = err as any\n console.error(\n `Error during hydration for route ${match.routeId}:`,\n err,\n )\n throw err\n }\n }\n }),\n )\n\n const isSpaMode = matches[matches.length - 1]!.id !== lastMatchId\n const hasSsrFalseMatches = matches.some((m) => m.ssr === false)\n // all matches have data from the server and we are not in SPA mode so we don't need to kick of router.load()\n if (!hasSsrFalseMatches && !isSpaMode) {\n matches.forEach((match) => {\n // remove the dehydrated flag since we won't run router.load() which would remove it\n match._nonReactive.dehydrated = undefined\n })\n // Mark the current location as resolved so that later load cycles\n // (e.g. preloads, invalidations) don't mistakenly detect a href change\n // (resolvedLocation defaults to undefined and router.load() is skipped\n // in the normal SSR hydration path).\n router.stores.resolvedLocation.set(router.stores.location.get())\n return routeChunkPromise\n }\n\n // schedule router.load() to run after the next tick so we can store the promise in the match before loading starts\n const loadPromise = Promise.resolve()\n .then(() => router.load())\n .catch((err) => {\n console.error('Error during router hydration:', err)\n })\n\n // in SPA mode we need to keep the first match below the root route pending until router.load() is finished\n // this will prevent that other pending components are rendered but hydration is not blocked\n if (isSpaMode) {\n const match = matches[1]\n if (!match) {\n if (process.env.NODE_ENV !== 'production') {\n throw new Error(\n 'Invariant failed: Expected to find a match below the root match in SPA mode.',\n )\n }\n\n invariant()\n }\n setMatchForcePending(match)\n\n match._displayPending = true\n match._nonReactive.displayPendingPromise = loadPromise\n\n loadPromise.then(() => {\n router.batch(() => {\n // ensure router is not in status 'pending' anymore\n // this usually happens in Transitioner but if loading synchronously resolves,\n // Transitioner won't be rendered while loading so it cannot track the change from loading:true to loading:false\n if (router.stores.status.get() === 'pending') {\n router.stores.status.set('idle')\n router.stores.resolvedLocation.set(router.stores.location.get())\n }\n // hide the pending component once the load is finished\n router.updateMatch(match.id, (prev) => ({\n ...prev,\n _displayPending: undefined,\n displayPendingPromise: undefined,\n }))\n })\n })\n }\n return routeChunkPromise\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { DehydratedMatch } from './types.js';
2
2
  import { AnyRouter } from '../router.js';
3
3
  import { AnyRouteMatch } from '../Matches.js';
4
- import { Manifest } from '../manifest.js';
4
+ import { Manifest, RouterManagedTag } from '../manifest.js';
5
5
  declare module '../router' {
6
6
  interface ServerSsr {
7
7
  setRenderFinished: () => void;
@@ -17,9 +17,10 @@ declare module '../router' {
17
17
  }
18
18
  }
19
19
  export declare function dehydrateMatch(match: AnyRouteMatch): DehydratedMatch;
20
- export declare function attachRouterServerSsrUtils({ router, manifest, }: {
20
+ export declare function attachRouterServerSsrUtils({ router, manifest, getRequestAssets, }: {
21
21
  router: AnyRouter;
22
22
  manifest: Manifest | undefined;
23
+ getRequestAssets?: () => Array<RouterManagedTag> | undefined;
23
24
  }): void;
24
25
  /**
25
26
  * Get the origin for the request.
@@ -1,6 +1,7 @@
1
1
  import { decodePath } from "../utils.js";
2
2
  import { invariant } from "../invariant.js";
3
3
  import { createLRUCache } from "../lru-cache.js";
4
+ import { rootRouteId } from "../root.js";
4
5
  import { GLOBAL_TSR, TSR_SCRIPT_BARRIER_ID } from "./constants.js";
5
6
  import { makeSsrSerovalPlugin } from "./serializer/transformer.js";
6
7
  import { defaultSerovalPlugins } from "./serializer/seroval-plugins.js";
@@ -101,8 +102,21 @@ function getManifestCache(manifest) {
101
102
  manifestCaches.set(manifest, newCache);
102
103
  return newCache;
103
104
  }
104
- function attachRouterServerSsrUtils({ router, manifest }) {
105
- router.ssr = { manifest };
105
+ function attachRouterServerSsrUtils({ router, manifest, getRequestAssets }) {
106
+ router.ssr = { get manifest() {
107
+ const requestAssets = getRequestAssets?.();
108
+ if (!requestAssets?.length) return manifest;
109
+ return {
110
+ ...manifest,
111
+ routes: {
112
+ ...manifest?.routes,
113
+ [rootRouteId]: {
114
+ ...manifest?.routes?.[rootRouteId],
115
+ assets: [...requestAssets, ...manifest?.routes?.["__root__"]?.assets ?? []]
116
+ }
117
+ }
118
+ };
119
+ } };
106
120
  let _dehydrated = false;
107
121
  let _serializationFinished = false;
108
122
  const renderFinishedListeners = [];
@@ -120,12 +134,12 @@ function attachRouterServerSsrUtils({ router, manifest }) {
120
134
  const html = `<script${router.options.ssr?.nonce ? ` nonce='${router.options.ssr.nonce}'` : ""}>${script}<\/script>`;
121
135
  router.serverSsr.injectHtml(html);
122
136
  },
123
- dehydrate: async () => {
137
+ dehydrate: async (opts) => {
124
138
  if (_dehydrated) {
125
139
  if (process.env.NODE_ENV !== "production") throw new Error("Invariant failed: router is already dehydrated!");
126
140
  invariant();
127
141
  }
128
- let matchesToDehydrate = router.stores.activeMatchesSnapshot.state;
142
+ let matchesToDehydrate = router.stores.activeMatchesSnapshot.get();
129
143
  if (router.isShell()) matchesToDehydrate = matchesToDehydrate.slice(0, 1);
130
144
  const matches = matchesToDehydrate.map(dehydrateMatch);
131
145
  let manifestToDehydrate = void 0;
@@ -146,6 +160,13 @@ function attachRouterServerSsrUtils({ router, manifest }) {
146
160
  filteredRoutes = nextFilteredRoutes;
147
161
  }
148
162
  manifestToDehydrate = { routes: filteredRoutes };
163
+ if (opts?.requestAssets?.length) {
164
+ const existingRoot = manifestToDehydrate.routes[rootRouteId];
165
+ manifestToDehydrate.routes[rootRouteId] = {
166
+ ...existingRoot,
167
+ assets: [...opts.requestAssets, ...existingRoot?.assets ?? []]
168
+ };
169
+ }
149
170
  }
150
171
  const dehydratedRouter = {
151
172
  manifest: manifestToDehydrate,
@@ -158,7 +179,7 @@ function attachRouterServerSsrUtils({ router, manifest }) {
158
179
  _dehydrated = true;
159
180
  const trackPlugins = { didRun: false };
160
181
  const serializationAdapters = router.options.serializationAdapters;
161
- const plugins = serializationAdapters ? serializationAdapters.map((t) => makeSsrSerovalPlugin(t, trackPlugins)).concat(defaultSerovalPlugins) : defaultSerovalPlugins;
182
+ const plugins = serializationAdapters ? serializationAdapters.map((t) => /* @__PURE__ */ makeSsrSerovalPlugin(t, trackPlugins)).concat(defaultSerovalPlugins) : defaultSerovalPlugins;
162
183
  const signalSerializationComplete = () => {
163
184
  _serializationFinished = true;
164
185
  try {
@@ -179,15 +200,16 @@ function attachRouterServerSsrUtils({ router, manifest }) {
179
200
  if (trackPlugins.didRun) serialized = P_PREFIX + serialized + P_SUFFIX;
180
201
  scriptBuffer.enqueue(serialized);
181
202
  },
203
+ onError: (err) => {
204
+ console.error("Serialization error:", err);
205
+ if (err && err.stack) console.error(err.stack);
206
+ signalSerializationComplete();
207
+ },
182
208
  scopeId: SCOPE_ID,
183
209
  onDone: () => {
184
210
  scriptBuffer.enqueue(GLOBAL_TSR + ".e()");
185
211
  scriptBuffer.flush();
186
212
  signalSerializationComplete();
187
- },
188
- onError: (err) => {
189
- console.error("Serialization error:", err);
190
- signalSerializationComplete();
191
213
  }
192
214
  });
193
215
  },
@@ -1 +1 @@
1
- {"version":3,"file":"ssr-server.js","names":[],"sources":["../../../src/ssr/ssr-server.ts"],"sourcesContent":["import { crossSerializeStream, getCrossReferenceHeader } from 'seroval'\nimport { invariant } from '../invariant'\nimport { decodePath } from '../utils'\nimport { createLRUCache } from '../lru-cache'\nimport minifiedTsrBootStrapScript from './tsrScript?script-string'\nimport { GLOBAL_TSR, TSR_SCRIPT_BARRIER_ID } from './constants'\nimport { dehydrateSsrMatchId } from './ssr-match-id'\nimport { defaultSerovalPlugins } from './serializer/seroval-plugins'\nimport { makeSsrSerovalPlugin } from './serializer/transformer'\nimport type { LRUCache } from '../lru-cache'\nimport type { DehydratedMatch, DehydratedRouter } from './types'\nimport type { AnySerializationAdapter } from './serializer/transformer'\nimport type { AnyRouter } from '../router'\nimport type { AnyRouteMatch } from '../Matches'\nimport type { Manifest, RouterManagedTag } from '../manifest'\n\ndeclare module '../router' {\n interface ServerSsr {\n setRenderFinished: () => void\n cleanup: () => void\n }\n interface RouterEvents {\n onInjectedHtml: {\n type: 'onInjectedHtml'\n }\n onSerializationFinished: {\n type: 'onSerializationFinished'\n }\n }\n}\n\nconst SCOPE_ID = 'tsr'\n\nconst TSR_PREFIX = GLOBAL_TSR + '.router='\nconst P_PREFIX = GLOBAL_TSR + '.p(()=>'\nconst P_SUFFIX = ')'\n\nexport function dehydrateMatch(match: AnyRouteMatch): DehydratedMatch {\n const dehydratedMatch: DehydratedMatch = {\n i: dehydrateSsrMatchId(match.id),\n u: match.updatedAt,\n s: match.status,\n }\n\n const properties = [\n ['__beforeLoadContext', 'b'],\n ['loaderData', 'l'],\n ['error', 'e'],\n ['ssr', 'ssr'],\n ] as const\n\n for (const [key, shorthand] of properties) {\n if (match[key] !== undefined) {\n dehydratedMatch[shorthand] = match[key]\n }\n }\n if (match.globalNotFound) {\n dehydratedMatch.g = true\n }\n return dehydratedMatch\n}\n\nconst INITIAL_SCRIPTS = [\n getCrossReferenceHeader(SCOPE_ID),\n minifiedTsrBootStrapScript,\n]\n\nclass ScriptBuffer {\n private router: AnyRouter | undefined\n private _queue: Array<string>\n private _scriptBarrierLifted = false\n private _cleanedUp = false\n private _pendingMicrotask = false\n\n constructor(router: AnyRouter) {\n this.router = router\n // Copy INITIAL_SCRIPTS to avoid mutating the shared array\n this._queue = INITIAL_SCRIPTS.slice()\n }\n\n enqueue(script: string) {\n if (this._cleanedUp) return\n this._queue.push(script)\n // If barrier is lifted, schedule injection (if not already scheduled)\n if (this._scriptBarrierLifted && !this._pendingMicrotask) {\n this._pendingMicrotask = true\n queueMicrotask(() => {\n this._pendingMicrotask = false\n this.injectBufferedScripts()\n })\n }\n }\n\n liftBarrier() {\n if (this._scriptBarrierLifted || this._cleanedUp) return\n this._scriptBarrierLifted = true\n if (this._queue.length > 0 && !this._pendingMicrotask) {\n this._pendingMicrotask = true\n queueMicrotask(() => {\n this._pendingMicrotask = false\n this.injectBufferedScripts()\n })\n }\n }\n\n /**\n * Flushes any pending scripts synchronously.\n * Call this before emitting onSerializationFinished to ensure all scripts are injected.\n *\n * IMPORTANT: Only injects if the barrier has been lifted. Before the barrier is lifted,\n * scripts should remain in the queue so takeBufferedScripts() can retrieve them\n */\n flush() {\n if (!this._scriptBarrierLifted) return\n if (this._cleanedUp) return\n this._pendingMicrotask = false\n const scriptsToInject = this.takeAll()\n if (scriptsToInject && this.router?.serverSsr) {\n this.router.serverSsr.injectScript(scriptsToInject)\n }\n }\n\n takeAll() {\n const bufferedScripts = this._queue\n this._queue = []\n if (bufferedScripts.length === 0) {\n return undefined\n }\n // Optimization: if only one script, avoid join\n if (bufferedScripts.length === 1) {\n return bufferedScripts[0] + ';document.currentScript.remove()'\n }\n // Append cleanup script and join - avoid push() to not mutate then iterate\n return bufferedScripts.join(';') + ';document.currentScript.remove()'\n }\n\n injectBufferedScripts() {\n if (this._cleanedUp) return\n // Early return if queue is empty (avoids unnecessary takeAll() call)\n if (this._queue.length === 0) return\n const scriptsToInject = this.takeAll()\n if (scriptsToInject && this.router?.serverSsr) {\n this.router.serverSsr.injectScript(scriptsToInject)\n }\n }\n\n cleanup() {\n this._cleanedUp = true\n this._queue = []\n this.router = undefined\n }\n}\n\nconst isProd = process.env.NODE_ENV === 'production'\n\ntype FilteredRoutes = Manifest['routes']\n\ntype ManifestLRU = LRUCache<string, FilteredRoutes>\n\nconst MANIFEST_CACHE_SIZE = 100\nconst manifestCaches = new WeakMap<Manifest, ManifestLRU>()\n\nfunction getManifestCache(manifest: Manifest): ManifestLRU {\n const cache = manifestCaches.get(manifest)\n if (cache) return cache\n const newCache = createLRUCache<string, FilteredRoutes>(MANIFEST_CACHE_SIZE)\n manifestCaches.set(manifest, newCache)\n return newCache\n}\n\nexport function attachRouterServerSsrUtils({\n router,\n manifest,\n}: {\n router: AnyRouter\n manifest: Manifest | undefined\n}) {\n router.ssr = {\n manifest,\n }\n let _dehydrated = false\n let _serializationFinished = false\n const renderFinishedListeners: Array<() => void> = []\n const serializationFinishedListeners: Array<() => void> = []\n const scriptBuffer = new ScriptBuffer(router)\n let injectedHtmlBuffer = ''\n\n router.serverSsr = {\n injectHtml: (html: string) => {\n if (!html) return\n // Buffer the HTML so it can be retrieved via takeBufferedHtml()\n injectedHtmlBuffer += html\n // Emit event to notify subscribers that new HTML is available\n router.emit({\n type: 'onInjectedHtml',\n })\n },\n injectScript: (script: string) => {\n if (!script) return\n const html = `<script${router.options.ssr?.nonce ? ` nonce='${router.options.ssr.nonce}'` : ''}>${script}</script>`\n router.serverSsr!.injectHtml(html)\n },\n dehydrate: async () => {\n if (_dehydrated) {\n if (process.env.NODE_ENV !== 'production') {\n throw new Error('Invariant failed: router is already dehydrated!')\n }\n\n invariant()\n }\n let matchesToDehydrate = router.stores.activeMatchesSnapshot.state\n if (router.isShell()) {\n // In SPA mode we only want to dehydrate the root match\n matchesToDehydrate = matchesToDehydrate.slice(0, 1)\n }\n const matches = matchesToDehydrate.map(dehydrateMatch)\n\n let manifestToDehydrate: Manifest | undefined = undefined\n // For currently matched routes, send full manifest (preloads + assets)\n // For all other routes, only send assets (no preloads as they are handled via dynamic imports)\n if (manifest) {\n // Prod-only caching; in dev manifests may be replaced/updated (HMR)\n const currentRouteIdsList = matchesToDehydrate.map((m) => m.routeId)\n const manifestCacheKey = currentRouteIdsList.join('\\0')\n\n let filteredRoutes: FilteredRoutes | undefined\n\n if (isProd) {\n filteredRoutes = getManifestCache(manifest).get(manifestCacheKey)\n }\n\n if (!filteredRoutes) {\n const currentRouteIds = new Set(currentRouteIdsList)\n const nextFilteredRoutes: FilteredRoutes = {}\n\n for (const routeId in manifest.routes) {\n const routeManifest = manifest.routes[routeId]!\n if (currentRouteIds.has(routeId)) {\n nextFilteredRoutes[routeId] = routeManifest\n } else if (\n routeManifest.assets &&\n routeManifest.assets.length > 0\n ) {\n nextFilteredRoutes[routeId] = {\n assets: routeManifest.assets,\n }\n }\n }\n\n if (isProd) {\n getManifestCache(manifest).set(manifestCacheKey, nextFilteredRoutes)\n }\n\n filteredRoutes = nextFilteredRoutes\n }\n\n manifestToDehydrate = {\n routes: filteredRoutes,\n }\n }\n const dehydratedRouter: DehydratedRouter = {\n manifest: manifestToDehydrate,\n matches,\n }\n const lastMatchId = matchesToDehydrate[matchesToDehydrate.length - 1]?.id\n if (lastMatchId) {\n dehydratedRouter.lastMatchId = dehydrateSsrMatchId(lastMatchId)\n }\n const dehydratedData = await router.options.dehydrate?.()\n if (dehydratedData) {\n dehydratedRouter.dehydratedData = dehydratedData\n }\n _dehydrated = true\n\n const trackPlugins = { didRun: false }\n const serializationAdapters = router.options.serializationAdapters as\n | Array<AnySerializationAdapter>\n | undefined\n const plugins = serializationAdapters\n ? serializationAdapters\n .map((t) => makeSsrSerovalPlugin(t, trackPlugins))\n .concat(defaultSerovalPlugins)\n : defaultSerovalPlugins\n\n const signalSerializationComplete = () => {\n _serializationFinished = true\n try {\n serializationFinishedListeners.forEach((l) => l())\n router.emit({ type: 'onSerializationFinished' })\n } catch (err) {\n console.error('Serialization listener error:', err)\n } finally {\n serializationFinishedListeners.length = 0\n renderFinishedListeners.length = 0\n }\n }\n\n crossSerializeStream(dehydratedRouter, {\n refs: new Map(),\n plugins,\n onSerialize: (data, initial) => {\n let serialized = initial ? TSR_PREFIX + data : data\n if (trackPlugins.didRun) {\n serialized = P_PREFIX + serialized + P_SUFFIX\n }\n scriptBuffer.enqueue(serialized)\n },\n scopeId: SCOPE_ID,\n onDone: () => {\n scriptBuffer.enqueue(GLOBAL_TSR + '.e()')\n // Flush all pending scripts synchronously before signaling completion\n // This ensures all scripts are injected before onSerializationFinished is emitted\n scriptBuffer.flush()\n signalSerializationComplete()\n },\n onError: (err) => {\n console.error('Serialization error:', err)\n signalSerializationComplete()\n },\n })\n },\n isDehydrated() {\n return _dehydrated\n },\n isSerializationFinished() {\n return _serializationFinished\n },\n onRenderFinished: (listener) => renderFinishedListeners.push(listener),\n onSerializationFinished: (listener) =>\n serializationFinishedListeners.push(listener),\n setRenderFinished: () => {\n // Wrap in try-catch to ensure scriptBuffer.liftBarrier() is always called\n try {\n renderFinishedListeners.forEach((l) => l())\n } catch (err) {\n console.error('Error in render finished listener:', err)\n } finally {\n // Clear listeners after calling them to prevent memory leaks\n renderFinishedListeners.length = 0\n }\n scriptBuffer.liftBarrier()\n },\n takeBufferedScripts() {\n const scripts = scriptBuffer.takeAll()\n const serverBufferedScript: RouterManagedTag = {\n tag: 'script',\n attrs: {\n nonce: router.options.ssr?.nonce,\n className: '$tsr',\n id: TSR_SCRIPT_BARRIER_ID,\n },\n children: scripts,\n }\n return serverBufferedScript\n },\n liftScriptBarrier() {\n scriptBuffer.liftBarrier()\n },\n takeBufferedHtml() {\n if (!injectedHtmlBuffer) {\n return undefined\n }\n const buffered = injectedHtmlBuffer\n injectedHtmlBuffer = ''\n return buffered\n },\n cleanup() {\n // Guard against multiple cleanup calls\n if (!router.serverSsr) return\n renderFinishedListeners.length = 0\n serializationFinishedListeners.length = 0\n injectedHtmlBuffer = ''\n scriptBuffer.cleanup()\n router.serverSsr = undefined\n },\n }\n}\n\n/**\n * Get the origin for the request.\n *\n * SECURITY: We intentionally do NOT trust the Origin header for determining\n * the router's origin. The Origin header can be spoofed by attackers, which\n * could lead to SSRF-like vulnerabilities where redirects are constructed\n * using a malicious origin (CVE-2024-34351).\n *\n * Instead, we derive the origin from request.url, which is typically set by\n * the server infrastructure (not client-controlled headers).\n *\n * For applications behind proxies that need to trust forwarded headers,\n * use the router's `origin` option to explicitly configure a trusted origin.\n */\nexport function getOrigin(request: Request) {\n try {\n return new URL(request.url).origin\n } catch {}\n return 'http://localhost'\n}\n\n// server and browser can decode/encode characters differently in paths and search params.\n// Server generally strictly follows the WHATWG URL Standard, while browsers may differ for legacy reasons.\n// for example, in paths \"|\" is not encoded on the server but is encoded on chromium (and not on firefox) while \"대\" is encoded on both sides.\n// Another anomaly is that in Node new URLSearchParams and new URL also decode/encode characters differently.\n// new URLSearchParams() encodes \"|\" while new URL() does not, and in this instance\n// chromium treats search params differently than paths, i.e. \"|\" is not encoded in search params.\nexport function getNormalizedURL(url: string | URL, base?: string | URL) {\n // ensure backslashes are encoded correctly in the URL\n if (typeof url === 'string') url = url.replace('\\\\', '%5C')\n\n const rawUrl = new URL(url, base)\n const { path: decodedPathname, handledProtocolRelativeURL } = decodePath(\n rawUrl.pathname,\n )\n const searchParams = new URLSearchParams(rawUrl.search)\n const normalizedHref =\n decodedPathname +\n (searchParams.size > 0 ? '?' : '') +\n searchParams.toString() +\n rawUrl.hash\n\n return {\n url: new URL(normalizedHref, rawUrl.origin),\n handledProtocolRelativeURL,\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ssr-server.js","names":[],"sources":["../../../src/ssr/ssr-server.ts"],"sourcesContent":["import { crossSerializeStream, getCrossReferenceHeader } from 'seroval'\nimport { invariant } from '../invariant'\nimport { decodePath } from '../utils'\nimport { createLRUCache } from '../lru-cache'\nimport { rootRouteId } from '../root'\nimport minifiedTsrBootStrapScript from './tsrScript?script-string'\nimport { GLOBAL_TSR, TSR_SCRIPT_BARRIER_ID } from './constants'\nimport { dehydrateSsrMatchId } from './ssr-match-id'\nimport { defaultSerovalPlugins } from './serializer/seroval-plugins'\nimport { makeSsrSerovalPlugin } from './serializer/transformer'\nimport type { LRUCache } from '../lru-cache'\nimport type { DehydratedMatch, DehydratedRouter } from './types'\nimport type { AnySerializationAdapter } from './serializer/transformer'\nimport type { AnyRouter } from '../router'\nimport type { AnyRouteMatch } from '../Matches'\nimport type { Manifest, RouterManagedTag } from '../manifest'\n\ndeclare module '../router' {\n interface ServerSsr {\n setRenderFinished: () => void\n cleanup: () => void\n }\n interface RouterEvents {\n onInjectedHtml: {\n type: 'onInjectedHtml'\n }\n onSerializationFinished: {\n type: 'onSerializationFinished'\n }\n }\n}\n\nconst SCOPE_ID = 'tsr'\n\nconst TSR_PREFIX = GLOBAL_TSR + '.router='\nconst P_PREFIX = GLOBAL_TSR + '.p(()=>'\nconst P_SUFFIX = ')'\n\nexport function dehydrateMatch(match: AnyRouteMatch): DehydratedMatch {\n const dehydratedMatch: DehydratedMatch = {\n i: dehydrateSsrMatchId(match.id),\n u: match.updatedAt,\n s: match.status,\n }\n\n const properties = [\n ['__beforeLoadContext', 'b'],\n ['loaderData', 'l'],\n ['error', 'e'],\n ['ssr', 'ssr'],\n ] as const\n\n for (const [key, shorthand] of properties) {\n if (match[key] !== undefined) {\n dehydratedMatch[shorthand] = match[key]\n }\n }\n if (match.globalNotFound) {\n dehydratedMatch.g = true\n }\n return dehydratedMatch\n}\n\nconst INITIAL_SCRIPTS = [\n getCrossReferenceHeader(SCOPE_ID),\n minifiedTsrBootStrapScript,\n]\n\nclass ScriptBuffer {\n private router: AnyRouter | undefined\n private _queue: Array<string>\n private _scriptBarrierLifted = false\n private _cleanedUp = false\n private _pendingMicrotask = false\n\n constructor(router: AnyRouter) {\n this.router = router\n // Copy INITIAL_SCRIPTS to avoid mutating the shared array\n this._queue = INITIAL_SCRIPTS.slice()\n }\n\n enqueue(script: string) {\n if (this._cleanedUp) return\n this._queue.push(script)\n // If barrier is lifted, schedule injection (if not already scheduled)\n if (this._scriptBarrierLifted && !this._pendingMicrotask) {\n this._pendingMicrotask = true\n queueMicrotask(() => {\n this._pendingMicrotask = false\n this.injectBufferedScripts()\n })\n }\n }\n\n liftBarrier() {\n if (this._scriptBarrierLifted || this._cleanedUp) return\n this._scriptBarrierLifted = true\n if (this._queue.length > 0 && !this._pendingMicrotask) {\n this._pendingMicrotask = true\n queueMicrotask(() => {\n this._pendingMicrotask = false\n this.injectBufferedScripts()\n })\n }\n }\n\n /**\n * Flushes any pending scripts synchronously.\n * Call this before emitting onSerializationFinished to ensure all scripts are injected.\n *\n * IMPORTANT: Only injects if the barrier has been lifted. Before the barrier is lifted,\n * scripts should remain in the queue so takeBufferedScripts() can retrieve them\n */\n flush() {\n if (!this._scriptBarrierLifted) return\n if (this._cleanedUp) return\n this._pendingMicrotask = false\n const scriptsToInject = this.takeAll()\n if (scriptsToInject && this.router?.serverSsr) {\n this.router.serverSsr.injectScript(scriptsToInject)\n }\n }\n\n takeAll() {\n const bufferedScripts = this._queue\n this._queue = []\n if (bufferedScripts.length === 0) {\n return undefined\n }\n // Optimization: if only one script, avoid join\n if (bufferedScripts.length === 1) {\n return bufferedScripts[0] + ';document.currentScript.remove()'\n }\n // Append cleanup script and join - avoid push() to not mutate then iterate\n return bufferedScripts.join(';') + ';document.currentScript.remove()'\n }\n\n injectBufferedScripts() {\n if (this._cleanedUp) return\n // Early return if queue is empty (avoids unnecessary takeAll() call)\n if (this._queue.length === 0) return\n const scriptsToInject = this.takeAll()\n if (scriptsToInject && this.router?.serverSsr) {\n this.router.serverSsr.injectScript(scriptsToInject)\n }\n }\n\n cleanup() {\n this._cleanedUp = true\n this._queue = []\n this.router = undefined\n }\n}\n\nconst isProd = process.env.NODE_ENV === 'production'\n\ntype FilteredRoutes = Manifest['routes']\n\ntype ManifestLRU = LRUCache<string, FilteredRoutes>\n\nconst MANIFEST_CACHE_SIZE = 100\nconst manifestCaches = new WeakMap<Manifest, ManifestLRU>()\n\nfunction getManifestCache(manifest: Manifest): ManifestLRU {\n const cache = manifestCaches.get(manifest)\n if (cache) return cache\n const newCache = createLRUCache<string, FilteredRoutes>(MANIFEST_CACHE_SIZE)\n manifestCaches.set(manifest, newCache)\n return newCache\n}\n\nexport function attachRouterServerSsrUtils({\n router,\n manifest,\n getRequestAssets,\n}: {\n router: AnyRouter\n manifest: Manifest | undefined\n getRequestAssets?: () => Array<RouterManagedTag> | undefined\n}) {\n router.ssr = {\n get manifest() {\n const requestAssets = getRequestAssets?.()\n if (!requestAssets?.length) return manifest\n // Merge request-scoped assets into root route without mutating cached manifest\n return {\n ...manifest,\n routes: {\n ...manifest?.routes,\n [rootRouteId]: {\n ...manifest?.routes?.[rootRouteId],\n assets: [\n ...requestAssets,\n ...(manifest?.routes?.[rootRouteId]?.assets ?? []),\n ],\n },\n },\n }\n },\n }\n let _dehydrated = false\n let _serializationFinished = false\n const renderFinishedListeners: Array<() => void> = []\n const serializationFinishedListeners: Array<() => void> = []\n const scriptBuffer = new ScriptBuffer(router)\n let injectedHtmlBuffer = ''\n\n router.serverSsr = {\n injectHtml: (html: string) => {\n if (!html) return\n // Buffer the HTML so it can be retrieved via takeBufferedHtml()\n injectedHtmlBuffer += html\n // Emit event to notify subscribers that new HTML is available\n router.emit({\n type: 'onInjectedHtml',\n })\n },\n injectScript: (script: string) => {\n if (!script) return\n const html = `<script${router.options.ssr?.nonce ? ` nonce='${router.options.ssr.nonce}'` : ''}>${script}</script>`\n router.serverSsr!.injectHtml(html)\n },\n dehydrate: async (opts?: { requestAssets?: Array<RouterManagedTag> }) => {\n if (_dehydrated) {\n if (process.env.NODE_ENV !== 'production') {\n throw new Error('Invariant failed: router is already dehydrated!')\n }\n\n invariant()\n }\n let matchesToDehydrate = router.stores.activeMatchesSnapshot.get()\n if (router.isShell()) {\n // In SPA mode we only want to dehydrate the root match\n matchesToDehydrate = matchesToDehydrate.slice(0, 1)\n }\n const matches = matchesToDehydrate.map(dehydrateMatch)\n\n let manifestToDehydrate: Manifest | undefined = undefined\n // For currently matched routes, send full manifest (preloads + assets)\n // For all other routes, only send assets (no preloads as they are handled via dynamic imports)\n if (manifest) {\n // Prod-only caching; in dev manifests may be replaced/updated (HMR)\n const currentRouteIdsList = matchesToDehydrate.map((m) => m.routeId)\n const manifestCacheKey = currentRouteIdsList.join('\\0')\n\n let filteredRoutes: FilteredRoutes | undefined\n\n if (isProd) {\n filteredRoutes = getManifestCache(manifest).get(manifestCacheKey)\n }\n\n if (!filteredRoutes) {\n const currentRouteIds = new Set(currentRouteIdsList)\n const nextFilteredRoutes: FilteredRoutes = {}\n\n for (const routeId in manifest.routes) {\n const routeManifest = manifest.routes[routeId]!\n if (currentRouteIds.has(routeId)) {\n nextFilteredRoutes[routeId] = routeManifest\n } else if (\n routeManifest.assets &&\n routeManifest.assets.length > 0\n ) {\n nextFilteredRoutes[routeId] = {\n assets: routeManifest.assets,\n }\n }\n }\n\n if (isProd) {\n getManifestCache(manifest).set(manifestCacheKey, nextFilteredRoutes)\n }\n\n filteredRoutes = nextFilteredRoutes\n }\n\n manifestToDehydrate = {\n routes: filteredRoutes,\n }\n\n // Merge request-scoped assets into root route (without mutating cached manifest)\n if (opts?.requestAssets?.length) {\n const existingRoot = manifestToDehydrate.routes[rootRouteId]\n manifestToDehydrate.routes[rootRouteId] = {\n ...existingRoot,\n assets: [...opts.requestAssets, ...(existingRoot?.assets ?? [])],\n }\n }\n }\n const dehydratedRouter: DehydratedRouter = {\n manifest: manifestToDehydrate,\n matches,\n }\n const lastMatchId = matchesToDehydrate[matchesToDehydrate.length - 1]?.id\n if (lastMatchId) {\n dehydratedRouter.lastMatchId = dehydrateSsrMatchId(lastMatchId)\n }\n const dehydratedData = await router.options.dehydrate?.()\n if (dehydratedData) {\n dehydratedRouter.dehydratedData = dehydratedData\n }\n _dehydrated = true\n\n const trackPlugins = { didRun: false }\n const serializationAdapters = router.options.serializationAdapters as\n | Array<AnySerializationAdapter>\n | undefined\n const plugins = serializationAdapters\n ? serializationAdapters\n .map((t) => makeSsrSerovalPlugin(t, trackPlugins))\n .concat(defaultSerovalPlugins)\n : defaultSerovalPlugins\n\n const signalSerializationComplete = () => {\n _serializationFinished = true\n try {\n serializationFinishedListeners.forEach((l) => l())\n router.emit({ type: 'onSerializationFinished' })\n } catch (err) {\n console.error('Serialization listener error:', err)\n } finally {\n serializationFinishedListeners.length = 0\n renderFinishedListeners.length = 0\n }\n }\n\n crossSerializeStream(dehydratedRouter, {\n refs: new Map(),\n plugins,\n onSerialize: (data, initial) => {\n let serialized = initial ? TSR_PREFIX + data : data\n if (trackPlugins.didRun) {\n serialized = P_PREFIX + serialized + P_SUFFIX\n }\n scriptBuffer.enqueue(serialized)\n },\n onError: (err: unknown) => {\n console.error('Serialization error:', err)\n if (err && (err as any).stack) {\n console.error((err as any).stack)\n }\n signalSerializationComplete()\n },\n scopeId: SCOPE_ID,\n onDone: () => {\n scriptBuffer.enqueue(GLOBAL_TSR + '.e()')\n // Flush all pending scripts synchronously before signaling completion\n // This ensures all scripts are injected before onSerializationFinished is emitted\n scriptBuffer.flush()\n signalSerializationComplete()\n },\n })\n },\n isDehydrated() {\n return _dehydrated\n },\n isSerializationFinished() {\n return _serializationFinished\n },\n onRenderFinished: (listener) => renderFinishedListeners.push(listener),\n onSerializationFinished: (listener) =>\n serializationFinishedListeners.push(listener),\n setRenderFinished: () => {\n // Wrap in try-catch to ensure scriptBuffer.liftBarrier() is always called\n try {\n renderFinishedListeners.forEach((l) => l())\n } catch (err) {\n console.error('Error in render finished listener:', err)\n } finally {\n // Clear listeners after calling them to prevent memory leaks\n renderFinishedListeners.length = 0\n }\n scriptBuffer.liftBarrier()\n },\n takeBufferedScripts() {\n const scripts = scriptBuffer.takeAll()\n const serverBufferedScript: RouterManagedTag = {\n tag: 'script',\n attrs: {\n nonce: router.options.ssr?.nonce,\n className: '$tsr',\n id: TSR_SCRIPT_BARRIER_ID,\n },\n children: scripts,\n }\n return serverBufferedScript\n },\n liftScriptBarrier() {\n scriptBuffer.liftBarrier()\n },\n takeBufferedHtml() {\n if (!injectedHtmlBuffer) {\n return undefined\n }\n const buffered = injectedHtmlBuffer\n injectedHtmlBuffer = ''\n return buffered\n },\n cleanup() {\n // Guard against multiple cleanup calls\n if (!router.serverSsr) return\n renderFinishedListeners.length = 0\n serializationFinishedListeners.length = 0\n injectedHtmlBuffer = ''\n scriptBuffer.cleanup()\n router.serverSsr = undefined\n },\n }\n}\n\n/**\n * Get the origin for the request.\n *\n * SECURITY: We intentionally do NOT trust the Origin header for determining\n * the router's origin. The Origin header can be spoofed by attackers, which\n * could lead to SSRF-like vulnerabilities where redirects are constructed\n * using a malicious origin (CVE-2024-34351).\n *\n * Instead, we derive the origin from request.url, which is typically set by\n * the server infrastructure (not client-controlled headers).\n *\n * For applications behind proxies that need to trust forwarded headers,\n * use the router's `origin` option to explicitly configure a trusted origin.\n */\nexport function getOrigin(request: Request) {\n try {\n return new URL(request.url).origin\n } catch {}\n return 'http://localhost'\n}\n\n// server and browser can decode/encode characters differently in paths and search params.\n// Server generally strictly follows the WHATWG URL Standard, while browsers may differ for legacy reasons.\n// for example, in paths \"|\" is not encoded on the server but is encoded on chromium (and not on firefox) while \"대\" is encoded on both sides.\n// Another anomaly is that in Node new URLSearchParams and new URL also decode/encode characters differently.\n// new URLSearchParams() encodes \"|\" while new URL() does not, and in this instance\n// chromium treats search params differently than paths, i.e. \"|\" is not encoded in search params.\nexport function getNormalizedURL(url: string | URL, base?: string | URL) {\n // ensure backslashes are encoded correctly in the URL\n if (typeof url === 'string') url = url.replace('\\\\', '%5C')\n\n const rawUrl = new URL(url, base)\n const { path: decodedPathname, handledProtocolRelativeURL } = decodePath(\n rawUrl.pathname,\n )\n const searchParams = new URLSearchParams(rawUrl.search)\n const normalizedHref =\n decodedPathname +\n (searchParams.size > 0 ? '?' : '') +\n searchParams.toString() +\n rawUrl.hash\n\n return {\n url: new URL(normalizedHref, rawUrl.origin),\n handledProtocolRelativeURL,\n }\n}\n"],"mappings":"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"}
@@ -233,7 +233,10 @@ function transformStreamWithRouter(router, appStream, opts) {
233
233
  const html = router.serverSsr?.takeBufferedHtml();
234
234
  if (!html) return;
235
235
  if (isAppRendering || leftover || pendingClosingTags) appendRouterHtml(html);
236
- else safeEnqueue(html);
236
+ else {
237
+ flushPendingRouterHtml();
238
+ safeEnqueue(html);
239
+ }
237
240
  });
238
241
  stopListeningToSerializationFinished = router.subscribe("onSerializationFinished", () => {
239
242
  serializationFinished = true;
@@ -1 +1 @@
1
- {"version":3,"file":"transformStreamWithRouter.js","names":[],"sources":["../../../src/ssr/transformStreamWithRouter.ts"],"sourcesContent":["import { ReadableStream } from 'node:stream/web'\nimport { Readable } from 'node:stream'\nimport { TSR_SCRIPT_BARRIER_ID } from './constants'\nimport type { AnyRouter } from '../router'\n\nexport function transformReadableStreamWithRouter(\n router: AnyRouter,\n routerStream: ReadableStream,\n) {\n return transformStreamWithRouter(router, routerStream)\n}\n\nexport function transformPipeableStreamWithRouter(\n router: AnyRouter,\n routerStream: Readable,\n) {\n return Readable.fromWeb(\n transformStreamWithRouter(router, Readable.toWeb(routerStream)),\n )\n}\n\n// Use string constants for simple indexOf matching\nconst BODY_END_TAG = '</body>'\nconst HTML_END_TAG = '</html>'\n\n// Minimum length of a valid closing tag: </a> = 4 characters\nconst MIN_CLOSING_TAG_LENGTH = 4\n\n// Default timeout values (in milliseconds)\nconst DEFAULT_SERIALIZATION_TIMEOUT_MS = 60000\nconst DEFAULT_LIFETIME_TIMEOUT_MS = 60000\n\n// Module-level encoder (stateless, safe to reuse)\nconst textEncoder = new TextEncoder()\n\n/**\n * Finds the position just after the last valid HTML closing tag in the string.\n *\n * Valid closing tags match the pattern: </[a-zA-Z][\\w:.-]*>\n * Examples: </div>, </my-component>, </slot:name.nested>\n *\n * @returns Position after the last closing tag, or -1 if none found\n */\nfunction findLastClosingTagEnd(str: string): number {\n const len = str.length\n if (len < MIN_CLOSING_TAG_LENGTH) return -1\n\n let i = len - 1\n\n while (i >= MIN_CLOSING_TAG_LENGTH - 1) {\n // Look for > (charCode 62)\n if (str.charCodeAt(i) === 62) {\n // Look backwards for valid tag name characters\n let j = i - 1\n\n // Skip through valid tag name characters\n while (j >= 1) {\n const code = str.charCodeAt(j)\n // Check if it's a valid tag name char: [a-zA-Z0-9_:.-]\n if (\n (code >= 97 && code <= 122) || // a-z\n (code >= 65 && code <= 90) || // A-Z\n (code >= 48 && code <= 57) || // 0-9\n code === 95 || // _\n code === 58 || // :\n code === 46 || // .\n code === 45 // -\n ) {\n j--\n } else {\n break\n }\n }\n\n // Check if the first char after </ is a valid start char (letter only)\n const tagNameStart = j + 1\n if (tagNameStart < i) {\n const startCode = str.charCodeAt(tagNameStart)\n // Tag name must start with a letter (a-z or A-Z)\n if (\n (startCode >= 97 && startCode <= 122) ||\n (startCode >= 65 && startCode <= 90)\n ) {\n // Check for </ (charCodes: < = 60, / = 47)\n if (\n j >= 1 &&\n str.charCodeAt(j) === 47 &&\n str.charCodeAt(j - 1) === 60\n ) {\n return i + 1 // Return position after the closing >\n }\n }\n }\n }\n i--\n }\n return -1\n}\n\nexport function transformStreamWithRouter(\n router: AnyRouter,\n appStream: ReadableStream,\n opts?: {\n /** Timeout for serialization to complete after app render finishes (default: 60000ms) */\n timeoutMs?: number\n /** Maximum lifetime of the stream transform (default: 60000ms). Safety net for cleanup. */\n lifetimeMs?: number\n },\n) {\n // Check upfront if serialization already finished synchronously\n // This is the fast path for routes with no deferred data\n const serializationAlreadyFinished =\n router.serverSsr?.isSerializationFinished() ?? false\n\n // Take any HTML that was buffered before we started listening\n const initialBufferedHtml = router.serverSsr?.takeBufferedHtml()\n\n // True passthrough: if serialization already finished and nothing buffered,\n // we can avoid any decoding/scanning while still honoring cleanup + setRenderFinished.\n if (serializationAlreadyFinished && !initialBufferedHtml) {\n let cleanedUp = false\n let controller: ReadableStreamDefaultController<Uint8Array> | undefined\n let isStreamClosed = false\n let lifetimeTimeoutHandle: ReturnType<typeof setTimeout> | undefined\n\n const cleanup = () => {\n if (cleanedUp) return\n cleanedUp = true\n\n if (lifetimeTimeoutHandle !== undefined) {\n clearTimeout(lifetimeTimeoutHandle)\n lifetimeTimeoutHandle = undefined\n }\n\n router.serverSsr?.cleanup()\n }\n\n const safeClose = () => {\n if (isStreamClosed) return\n isStreamClosed = true\n try {\n controller?.close()\n } catch {\n // ignore\n }\n }\n\n const safeError = (error: unknown) => {\n if (isStreamClosed) return\n isStreamClosed = true\n try {\n controller?.error(error)\n } catch {\n // ignore\n }\n }\n\n const lifetimeMs = opts?.lifetimeMs ?? DEFAULT_LIFETIME_TIMEOUT_MS\n lifetimeTimeoutHandle = setTimeout(() => {\n if (!cleanedUp && !isStreamClosed) {\n console.warn(\n `SSR stream transform exceeded maximum lifetime (${lifetimeMs}ms), forcing cleanup`,\n )\n safeError(new Error('Stream lifetime exceeded'))\n cleanup()\n }\n }, lifetimeMs)\n\n const stream = new ReadableStream<Uint8Array>({\n start(c: ReadableStreamDefaultController<Uint8Array>) {\n controller = c\n },\n cancel() {\n isStreamClosed = true\n cleanup()\n },\n })\n\n ;(async () => {\n const reader = appStream.getReader()\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n if (cleanedUp || isStreamClosed) return\n controller?.enqueue(value as unknown as Uint8Array)\n }\n\n if (cleanedUp || isStreamClosed) return\n\n router.serverSsr?.setRenderFinished()\n safeClose()\n cleanup()\n } catch (error) {\n if (cleanedUp) return\n console.error('Error reading appStream:', error)\n router.serverSsr?.setRenderFinished()\n safeError(error)\n cleanup()\n } finally {\n reader.releaseLock()\n }\n })().catch((error) => {\n if (cleanedUp) return\n console.error('Error in stream transform:', error)\n safeError(error)\n cleanup()\n })\n\n return stream\n }\n\n let stopListeningToInjectedHtml: (() => void) | undefined\n let stopListeningToSerializationFinished: (() => void) | undefined\n let serializationTimeoutHandle: ReturnType<typeof setTimeout> | undefined\n let lifetimeTimeoutHandle: ReturnType<typeof setTimeout> | undefined\n let cleanedUp = false\n\n let controller: ReadableStreamDefaultController<any>\n let isStreamClosed = false\n\n const textDecoder = new TextDecoder()\n\n // concat'd router HTML; avoids array joins on each flush\n let pendingRouterHtml = initialBufferedHtml ?? ''\n\n // between-chunk text buffer; keep bounded to avoid unbounded memory\n let leftover = ''\n\n // captured closing tags from </body> onward\n let pendingClosingTags = ''\n\n // conservative cap: enough to hold any partial closing tag + a bit\n const MAX_LEFTOVER_CHARS = 2048\n\n let isAppRendering = true\n let streamBarrierLifted = false\n let serializationFinished = serializationAlreadyFinished\n\n function safeEnqueue(chunk: string | Uint8Array) {\n if (isStreamClosed) return\n if (typeof chunk === 'string') {\n controller.enqueue(textEncoder.encode(chunk))\n } else {\n controller.enqueue(chunk)\n }\n }\n\n function safeClose() {\n if (isStreamClosed) return\n isStreamClosed = true\n try {\n controller.close()\n } catch {\n // ignore\n }\n }\n\n function safeError(error: unknown) {\n if (isStreamClosed) return\n isStreamClosed = true\n try {\n controller.error(error)\n } catch {\n // ignore\n }\n }\n\n /**\n * Cleanup with guards; must be idempotent.\n */\n function cleanup() {\n if (cleanedUp) return\n cleanedUp = true\n\n try {\n stopListeningToInjectedHtml?.()\n stopListeningToSerializationFinished?.()\n } catch {\n // ignore\n }\n stopListeningToInjectedHtml = undefined\n stopListeningToSerializationFinished = undefined\n\n if (serializationTimeoutHandle !== undefined) {\n clearTimeout(serializationTimeoutHandle)\n serializationTimeoutHandle = undefined\n }\n if (lifetimeTimeoutHandle !== undefined) {\n clearTimeout(lifetimeTimeoutHandle)\n lifetimeTimeoutHandle = undefined\n }\n\n pendingRouterHtml = ''\n leftover = ''\n pendingClosingTags = ''\n\n router.serverSsr?.cleanup()\n }\n\n const stream = new ReadableStream({\n start(c: ReadableStreamDefaultController<any>) {\n controller = c\n },\n cancel() {\n isStreamClosed = true\n cleanup()\n },\n })\n\n function flushPendingRouterHtml() {\n if (!pendingRouterHtml) return\n safeEnqueue(pendingRouterHtml)\n pendingRouterHtml = ''\n }\n\n function appendRouterHtml(html: string) {\n if (!html) return\n pendingRouterHtml += html\n }\n\n /**\n * Finish only when app done and serialization complete.\n */\n function tryFinish() {\n if (isAppRendering || !serializationFinished) return\n if (cleanedUp || isStreamClosed) return\n\n if (serializationTimeoutHandle !== undefined) {\n clearTimeout(serializationTimeoutHandle)\n serializationTimeoutHandle = undefined\n }\n\n // Flush any remaining bytes in the TextDecoder\n const decoderRemainder = textDecoder.decode()\n\n if (leftover) safeEnqueue(leftover)\n if (decoderRemainder) safeEnqueue(decoderRemainder)\n flushPendingRouterHtml()\n if (pendingClosingTags) safeEnqueue(pendingClosingTags)\n\n safeClose()\n cleanup()\n }\n\n // Safety net: cleanup even if consumer never reads\n const lifetimeMs = opts?.lifetimeMs ?? DEFAULT_LIFETIME_TIMEOUT_MS\n lifetimeTimeoutHandle = setTimeout(() => {\n if (!cleanedUp && !isStreamClosed) {\n console.warn(\n `SSR stream transform exceeded maximum lifetime (${lifetimeMs}ms), forcing cleanup`,\n )\n safeError(new Error('Stream lifetime exceeded'))\n cleanup()\n }\n }, lifetimeMs)\n\n if (!serializationAlreadyFinished) {\n stopListeningToInjectedHtml = router.subscribe('onInjectedHtml', () => {\n if (cleanedUp || isStreamClosed) return\n const html = router.serverSsr?.takeBufferedHtml()\n if (!html) return\n\n // If we've already captured </body> (pendingClosingTags), we must keep appending\n // so injection stays before the stored closing tags.\n if (isAppRendering || leftover || pendingClosingTags) {\n appendRouterHtml(html)\n } else {\n safeEnqueue(html)\n }\n })\n\n stopListeningToSerializationFinished = router.subscribe(\n 'onSerializationFinished',\n () => {\n serializationFinished = true\n tryFinish()\n },\n )\n }\n\n // Transform the appStream\n ;(async () => {\n const reader = appStream.getReader()\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n\n if (cleanedUp || isStreamClosed) return\n\n const text =\n value instanceof Uint8Array\n ? textDecoder.decode(value, { stream: true })\n : String(value)\n\n // Fast path: most chunks have no pending left-over.\n const chunkString = leftover ? leftover + text : text\n\n if (!streamBarrierLifted) {\n if (chunkString.includes(TSR_SCRIPT_BARRIER_ID)) {\n streamBarrierLifted = true\n router.serverSsr?.liftScriptBarrier()\n }\n }\n\n // If we already saw </body>, everything else is part of tail; buffer it.\n if (pendingClosingTags) {\n pendingClosingTags += chunkString\n leftover = ''\n continue\n }\n\n const bodyEndIndex = chunkString.indexOf(BODY_END_TAG)\n const htmlEndIndex = chunkString.indexOf(HTML_END_TAG)\n\n if (\n bodyEndIndex !== -1 &&\n htmlEndIndex !== -1 &&\n bodyEndIndex < htmlEndIndex\n ) {\n pendingClosingTags = chunkString.slice(bodyEndIndex)\n safeEnqueue(chunkString.slice(0, bodyEndIndex))\n flushPendingRouterHtml()\n leftover = ''\n continue\n }\n\n const lastClosingTagEnd = findLastClosingTagEnd(chunkString)\n\n if (lastClosingTagEnd > 0) {\n safeEnqueue(chunkString.slice(0, lastClosingTagEnd))\n flushPendingRouterHtml()\n\n leftover = chunkString.slice(lastClosingTagEnd)\n if (leftover.length > MAX_LEFTOVER_CHARS) {\n // Ensure bounded memory even if a consumer streams long text sequences\n // without any closing tags. This may reduce injection granularity but is correct.\n safeEnqueue(leftover.slice(0, leftover.length - MAX_LEFTOVER_CHARS))\n leftover = leftover.slice(-MAX_LEFTOVER_CHARS)\n }\n } else {\n // No closing tag found; keep small tail to handle split closing tags,\n // but stream older bytes to prevent unbounded buffering.\n const combined = chunkString\n if (combined.length > MAX_LEFTOVER_CHARS) {\n const flushUpto = combined.length - MAX_LEFTOVER_CHARS\n safeEnqueue(combined.slice(0, flushUpto))\n leftover = combined.slice(flushUpto)\n } else {\n leftover = combined\n }\n }\n }\n\n if (cleanedUp || isStreamClosed) return\n\n isAppRendering = false\n router.serverSsr?.setRenderFinished()\n\n if (serializationFinished) {\n tryFinish()\n } else {\n const timeoutMs = opts?.timeoutMs ?? DEFAULT_SERIALIZATION_TIMEOUT_MS\n serializationTimeoutHandle = setTimeout(() => {\n if (!cleanedUp && !isStreamClosed) {\n console.error('Serialization timeout after app render finished')\n safeError(\n new Error('Serialization timeout after app render finished'),\n )\n cleanup()\n }\n }, timeoutMs)\n }\n } catch (error) {\n if (cleanedUp) return\n console.error('Error reading appStream:', error)\n isAppRendering = false\n router.serverSsr?.setRenderFinished()\n safeError(error)\n cleanup()\n } finally {\n reader.releaseLock()\n }\n })().catch((error) => {\n if (cleanedUp) return\n console.error('Error in stream transform:', error)\n safeError(error)\n cleanup()\n })\n\n return stream\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"transformStreamWithRouter.js","names":[],"sources":["../../../src/ssr/transformStreamWithRouter.ts"],"sourcesContent":["import { ReadableStream } from 'node:stream/web'\nimport { Readable } from 'node:stream'\nimport { TSR_SCRIPT_BARRIER_ID } from './constants'\nimport type { AnyRouter } from '../router'\n\nexport function transformReadableStreamWithRouter(\n router: AnyRouter,\n routerStream: ReadableStream,\n) {\n return transformStreamWithRouter(router, routerStream)\n}\n\nexport function transformPipeableStreamWithRouter(\n router: AnyRouter,\n routerStream: Readable,\n) {\n return Readable.fromWeb(\n transformStreamWithRouter(router, Readable.toWeb(routerStream)),\n )\n}\n\n// Use string constants for simple indexOf matching\nconst BODY_END_TAG = '</body>'\nconst HTML_END_TAG = '</html>'\n\n// Minimum length of a valid closing tag: </a> = 4 characters\nconst MIN_CLOSING_TAG_LENGTH = 4\n\n// Default timeout values (in milliseconds)\nconst DEFAULT_SERIALIZATION_TIMEOUT_MS = 60000\nconst DEFAULT_LIFETIME_TIMEOUT_MS = 60000\n\n// Module-level encoder (stateless, safe to reuse)\nconst textEncoder = new TextEncoder()\n\n/**\n * Finds the position just after the last valid HTML closing tag in the string.\n *\n * Valid closing tags match the pattern: </[a-zA-Z][\\w:.-]*>\n * Examples: </div>, </my-component>, </slot:name.nested>\n *\n * @returns Position after the last closing tag, or -1 if none found\n */\nfunction findLastClosingTagEnd(str: string): number {\n const len = str.length\n if (len < MIN_CLOSING_TAG_LENGTH) return -1\n\n let i = len - 1\n\n while (i >= MIN_CLOSING_TAG_LENGTH - 1) {\n // Look for > (charCode 62)\n if (str.charCodeAt(i) === 62) {\n // Look backwards for valid tag name characters\n let j = i - 1\n\n // Skip through valid tag name characters\n while (j >= 1) {\n const code = str.charCodeAt(j)\n // Check if it's a valid tag name char: [a-zA-Z0-9_:.-]\n if (\n (code >= 97 && code <= 122) || // a-z\n (code >= 65 && code <= 90) || // A-Z\n (code >= 48 && code <= 57) || // 0-9\n code === 95 || // _\n code === 58 || // :\n code === 46 || // .\n code === 45 // -\n ) {\n j--\n } else {\n break\n }\n }\n\n // Check if the first char after </ is a valid start char (letter only)\n const tagNameStart = j + 1\n if (tagNameStart < i) {\n const startCode = str.charCodeAt(tagNameStart)\n // Tag name must start with a letter (a-z or A-Z)\n if (\n (startCode >= 97 && startCode <= 122) ||\n (startCode >= 65 && startCode <= 90)\n ) {\n // Check for </ (charCodes: < = 60, / = 47)\n if (\n j >= 1 &&\n str.charCodeAt(j) === 47 &&\n str.charCodeAt(j - 1) === 60\n ) {\n return i + 1 // Return position after the closing >\n }\n }\n }\n }\n i--\n }\n return -1\n}\n\nexport function transformStreamWithRouter(\n router: AnyRouter,\n appStream: ReadableStream,\n opts?: {\n /** Timeout for serialization to complete after app render finishes (default: 60000ms) */\n timeoutMs?: number\n /** Maximum lifetime of the stream transform (default: 60000ms). Safety net for cleanup. */\n lifetimeMs?: number\n },\n) {\n // Check upfront if serialization already finished synchronously\n // This is the fast path for routes with no deferred data\n const serializationAlreadyFinished =\n router.serverSsr?.isSerializationFinished() ?? false\n\n // Take any HTML that was buffered before we started listening\n const initialBufferedHtml = router.serverSsr?.takeBufferedHtml()\n\n // True passthrough: if serialization already finished and nothing buffered,\n // we can avoid any decoding/scanning while still honoring cleanup + setRenderFinished.\n if (serializationAlreadyFinished && !initialBufferedHtml) {\n let cleanedUp = false\n let controller: ReadableStreamDefaultController<Uint8Array> | undefined\n let isStreamClosed = false\n let lifetimeTimeoutHandle: ReturnType<typeof setTimeout> | undefined\n\n const cleanup = () => {\n if (cleanedUp) return\n cleanedUp = true\n\n if (lifetimeTimeoutHandle !== undefined) {\n clearTimeout(lifetimeTimeoutHandle)\n lifetimeTimeoutHandle = undefined\n }\n\n router.serverSsr?.cleanup()\n }\n\n const safeClose = () => {\n if (isStreamClosed) return\n isStreamClosed = true\n try {\n controller?.close()\n } catch {\n // ignore\n }\n }\n\n const safeError = (error: unknown) => {\n if (isStreamClosed) return\n isStreamClosed = true\n try {\n controller?.error(error)\n } catch {\n // ignore\n }\n }\n\n const lifetimeMs = opts?.lifetimeMs ?? DEFAULT_LIFETIME_TIMEOUT_MS\n lifetimeTimeoutHandle = setTimeout(() => {\n if (!cleanedUp && !isStreamClosed) {\n console.warn(\n `SSR stream transform exceeded maximum lifetime (${lifetimeMs}ms), forcing cleanup`,\n )\n safeError(new Error('Stream lifetime exceeded'))\n cleanup()\n }\n }, lifetimeMs)\n\n const stream = new ReadableStream<Uint8Array>({\n start(c: ReadableStreamDefaultController<Uint8Array>) {\n controller = c\n },\n cancel() {\n isStreamClosed = true\n cleanup()\n },\n })\n\n ;(async () => {\n const reader = appStream.getReader()\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n if (cleanedUp || isStreamClosed) return\n controller?.enqueue(value as unknown as Uint8Array)\n }\n\n if (cleanedUp || isStreamClosed) return\n\n router.serverSsr?.setRenderFinished()\n safeClose()\n cleanup()\n } catch (error) {\n if (cleanedUp) return\n console.error('Error reading appStream:', error)\n router.serverSsr?.setRenderFinished()\n safeError(error)\n cleanup()\n } finally {\n reader.releaseLock()\n }\n })().catch((error) => {\n if (cleanedUp) return\n console.error('Error in stream transform:', error)\n safeError(error)\n cleanup()\n })\n\n return stream\n }\n\n let stopListeningToInjectedHtml: (() => void) | undefined\n let stopListeningToSerializationFinished: (() => void) | undefined\n let serializationTimeoutHandle: ReturnType<typeof setTimeout> | undefined\n let lifetimeTimeoutHandle: ReturnType<typeof setTimeout> | undefined\n let cleanedUp = false\n\n let controller: ReadableStreamDefaultController<any>\n let isStreamClosed = false\n\n const textDecoder = new TextDecoder()\n\n // concat'd router HTML; avoids array joins on each flush\n let pendingRouterHtml = initialBufferedHtml ?? ''\n\n // between-chunk text buffer; keep bounded to avoid unbounded memory\n let leftover = ''\n\n // captured closing tags from </body> onward\n let pendingClosingTags = ''\n\n // conservative cap: enough to hold any partial closing tag + a bit\n const MAX_LEFTOVER_CHARS = 2048\n\n let isAppRendering = true\n let streamBarrierLifted = false\n let serializationFinished = serializationAlreadyFinished\n\n function safeEnqueue(chunk: string | Uint8Array) {\n if (isStreamClosed) return\n if (typeof chunk === 'string') {\n controller.enqueue(textEncoder.encode(chunk))\n } else {\n controller.enqueue(chunk)\n }\n }\n\n function safeClose() {\n if (isStreamClosed) return\n isStreamClosed = true\n try {\n controller.close()\n } catch {\n // ignore\n }\n }\n\n function safeError(error: unknown) {\n if (isStreamClosed) return\n isStreamClosed = true\n try {\n controller.error(error)\n } catch {\n // ignore\n }\n }\n\n /**\n * Cleanup with guards; must be idempotent.\n */\n function cleanup() {\n if (cleanedUp) return\n cleanedUp = true\n\n try {\n stopListeningToInjectedHtml?.()\n stopListeningToSerializationFinished?.()\n } catch {\n // ignore\n }\n stopListeningToInjectedHtml = undefined\n stopListeningToSerializationFinished = undefined\n\n if (serializationTimeoutHandle !== undefined) {\n clearTimeout(serializationTimeoutHandle)\n serializationTimeoutHandle = undefined\n }\n if (lifetimeTimeoutHandle !== undefined) {\n clearTimeout(lifetimeTimeoutHandle)\n lifetimeTimeoutHandle = undefined\n }\n\n pendingRouterHtml = ''\n leftover = ''\n pendingClosingTags = ''\n\n router.serverSsr?.cleanup()\n }\n\n const stream = new ReadableStream({\n start(c: ReadableStreamDefaultController<any>) {\n controller = c\n },\n cancel() {\n isStreamClosed = true\n cleanup()\n },\n })\n\n function flushPendingRouterHtml() {\n if (!pendingRouterHtml) return\n safeEnqueue(pendingRouterHtml)\n pendingRouterHtml = ''\n }\n\n function appendRouterHtml(html: string) {\n if (!html) return\n pendingRouterHtml += html\n }\n\n /**\n * Finish only when app done and serialization complete.\n */\n function tryFinish() {\n if (isAppRendering || !serializationFinished) return\n if (cleanedUp || isStreamClosed) return\n\n if (serializationTimeoutHandle !== undefined) {\n clearTimeout(serializationTimeoutHandle)\n serializationTimeoutHandle = undefined\n }\n\n // Flush any remaining bytes in the TextDecoder\n const decoderRemainder = textDecoder.decode()\n\n if (leftover) safeEnqueue(leftover)\n if (decoderRemainder) safeEnqueue(decoderRemainder)\n flushPendingRouterHtml()\n if (pendingClosingTags) safeEnqueue(pendingClosingTags)\n\n safeClose()\n cleanup()\n }\n\n // Safety net: cleanup even if consumer never reads\n const lifetimeMs = opts?.lifetimeMs ?? DEFAULT_LIFETIME_TIMEOUT_MS\n lifetimeTimeoutHandle = setTimeout(() => {\n if (!cleanedUp && !isStreamClosed) {\n console.warn(\n `SSR stream transform exceeded maximum lifetime (${lifetimeMs}ms), forcing cleanup`,\n )\n safeError(new Error('Stream lifetime exceeded'))\n cleanup()\n }\n }, lifetimeMs)\n\n if (!serializationAlreadyFinished) {\n stopListeningToInjectedHtml = router.subscribe('onInjectedHtml', () => {\n if (cleanedUp || isStreamClosed) return\n const html = router.serverSsr?.takeBufferedHtml()\n if (!html) return\n\n // If we've already captured </body> (pendingClosingTags), we must keep appending\n // so injection stays before the stored closing tags.\n if (isAppRendering || leftover || pendingClosingTags) {\n appendRouterHtml(html)\n } else {\n // App is done rendering - flush any pending buffer first to maintain order,\n // then write the new HTML directly\n flushPendingRouterHtml()\n safeEnqueue(html)\n }\n })\n\n stopListeningToSerializationFinished = router.subscribe(\n 'onSerializationFinished',\n () => {\n serializationFinished = true\n tryFinish()\n },\n )\n }\n\n // Transform the appStream\n ;(async () => {\n const reader = appStream.getReader()\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n\n if (cleanedUp || isStreamClosed) return\n\n const text =\n value instanceof Uint8Array\n ? textDecoder.decode(value, { stream: true })\n : String(value)\n\n // Fast path: most chunks have no pending left-over.\n const chunkString = leftover ? leftover + text : text\n\n if (!streamBarrierLifted) {\n if (chunkString.includes(TSR_SCRIPT_BARRIER_ID)) {\n streamBarrierLifted = true\n router.serverSsr?.liftScriptBarrier()\n }\n }\n\n // If we already saw </body>, everything else is part of tail; buffer it.\n if (pendingClosingTags) {\n pendingClosingTags += chunkString\n leftover = ''\n continue\n }\n\n const bodyEndIndex = chunkString.indexOf(BODY_END_TAG)\n const htmlEndIndex = chunkString.indexOf(HTML_END_TAG)\n\n if (\n bodyEndIndex !== -1 &&\n htmlEndIndex !== -1 &&\n bodyEndIndex < htmlEndIndex\n ) {\n pendingClosingTags = chunkString.slice(bodyEndIndex)\n safeEnqueue(chunkString.slice(0, bodyEndIndex))\n flushPendingRouterHtml()\n leftover = ''\n continue\n }\n\n const lastClosingTagEnd = findLastClosingTagEnd(chunkString)\n\n if (lastClosingTagEnd > 0) {\n safeEnqueue(chunkString.slice(0, lastClosingTagEnd))\n flushPendingRouterHtml()\n\n leftover = chunkString.slice(lastClosingTagEnd)\n if (leftover.length > MAX_LEFTOVER_CHARS) {\n // Ensure bounded memory even if a consumer streams long text sequences\n // without any closing tags. This may reduce injection granularity but is correct.\n safeEnqueue(leftover.slice(0, leftover.length - MAX_LEFTOVER_CHARS))\n leftover = leftover.slice(-MAX_LEFTOVER_CHARS)\n }\n } else {\n // No closing tag found; keep small tail to handle split closing tags,\n // but stream older bytes to prevent unbounded buffering.\n const combined = chunkString\n if (combined.length > MAX_LEFTOVER_CHARS) {\n const flushUpto = combined.length - MAX_LEFTOVER_CHARS\n safeEnqueue(combined.slice(0, flushUpto))\n leftover = combined.slice(flushUpto)\n } else {\n leftover = combined\n }\n }\n }\n\n if (cleanedUp || isStreamClosed) return\n\n isAppRendering = false\n router.serverSsr?.setRenderFinished()\n\n if (serializationFinished) {\n tryFinish()\n } else {\n const timeoutMs = opts?.timeoutMs ?? DEFAULT_SERIALIZATION_TIMEOUT_MS\n serializationTimeoutHandle = setTimeout(() => {\n if (!cleanedUp && !isStreamClosed) {\n console.error('Serialization timeout after app render finished')\n safeError(\n new Error('Serialization timeout after app render finished'),\n )\n cleanup()\n }\n }, timeoutMs)\n }\n } catch (error) {\n if (cleanedUp) return\n console.error('Error reading appStream:', error)\n isAppRendering = false\n router.serverSsr?.setRenderFinished()\n safeError(error)\n cleanup()\n } finally {\n reader.releaseLock()\n }\n })().catch((error) => {\n if (cleanedUp) return\n console.error('Error in stream transform:', error)\n safeError(error)\n cleanup()\n })\n\n return stream\n}\n"],"mappings":"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"}
@@ -5,10 +5,10 @@ import { ParsedLocation } from './location.js';
5
5
  import { AnyRedirect } from './redirect.js';
6
6
  import { AnyRouteMatch } from './Matches.js';
7
7
  export interface RouterReadableStore<TValue> {
8
- readonly state: TValue;
8
+ get: () => TValue;
9
9
  }
10
10
  export interface RouterWritableStore<TValue> extends RouterReadableStore<TValue> {
11
- setState: (updater: (prev: TValue) => TValue) => void;
11
+ set: ((updater: (prev: TValue) => TValue) => void) & ((value: TValue) => void);
12
12
  }
13
13
  export type RouterBatchFn = (fn: () => void) => void;
14
14
  export type MutableStoreFactory = <TValue>(initialValue: TValue) => RouterWritableStore<TValue>;