{"version":3,"file":"ssr-server.cjs","sources":["../../src/ssr-server.ts"],"sourcesContent":["import { default as warning } from 'tiny-warning'\nimport {\n  TSR_DEFERRED_PROMISE,\n  defer,\n  isPlainArray,\n  isPlainObject,\n  pick,\n} from '@tanstack/router-core'\nimport jsesc from 'jsesc'\nimport { startSerializer } from '@tanstack/start-client'\nimport minifiedTsrBootStrapScript from './tsrScript?script-string'\nimport type {\n  ClientExtractedBaseEntry,\n  DehydratedRouter,\n  ResolvePromiseState,\n  SsrMatch,\n} from '@tanstack/start-client'\nimport type { AnyRouteMatch, AnyRouter } from '@tanstack/react-router'\nimport type { DeferredPromise, Manifest } from '@tanstack/router-core'\n\nexport type ServerExtractedEntry =\n  | ServerExtractedStream\n  | ServerExtractedPromise\n\nexport interface ServerExtractedBaseEntry extends ClientExtractedBaseEntry {\n  id: number\n  matchIndex: number\n}\n\nexport interface ServerExtractedStream extends ServerExtractedBaseEntry {\n  type: 'stream'\n  stream: ReadableStream\n}\n\nexport interface ServerExtractedPromise extends ServerExtractedBaseEntry {\n  type: 'promise'\n  promise: DeferredPromise<any>\n}\n\nexport function attachRouterServerSsrUtils(\n  router: AnyRouter,\n  manifest: Manifest | undefined,\n) {\n  router.ssr = {\n    manifest,\n    serializer: startSerializer,\n  }\n\n  router.serverSsr = {\n    injectedHtml: [],\n    streamedKeys: new Set(),\n    injectHtml: (getHtml) => {\n      const promise = Promise.resolve().then(getHtml)\n      router.serverSsr!.injectedHtml.push(promise)\n      router.emit({\n        type: 'onInjectedHtml',\n        promise,\n      })\n\n      return promise.then(() => {})\n    },\n    injectScript: (getScript, opts) => {\n      return router.serverSsr!.injectHtml(async () => {\n        const script = await getScript()\n        return `<script class='tsr-once'>${script}${\n          process.env.NODE_ENV === 'development' && (opts?.logScript ?? true)\n            ? `; console.info(\\`Injected From Server:\n${jsesc(script, { quotes: 'backtick' })}\\`)`\n            : ''\n        }; if (typeof __TSR_SSR__ !== 'undefined') __TSR_SSR__.cleanScripts()</script>`\n      })\n    },\n    streamValue: (key, value) => {\n      warning(\n        !router.serverSsr!.streamedKeys.has(key),\n        'Key has already been streamed: ' + key,\n      )\n\n      router.serverSsr!.streamedKeys.add(key)\n      router.serverSsr!.injectScript(\n        () =>\n          `__TSR_SSR__.streamedValues['${key}'] = { value: ${jsesc(\n            router.ssr!.serializer.stringify(value),\n            {\n              isScriptContext: true,\n              wrap: true,\n              json: true,\n            },\n          )}}`,\n      )\n    },\n    onMatchSettled,\n  }\n\n  router.serverSsr.injectScript(() => minifiedTsrBootStrapScript, {\n    logScript: false,\n  })\n}\n\nexport function dehydrateRouter(router: AnyRouter) {\n  const dehydratedRouter: DehydratedRouter = {\n    manifest: router.ssr!.manifest,\n    dehydratedData: router.options.dehydrate?.(),\n  }\n\n  router.serverSsr!.injectScript(\n    () =>\n      `__TSR_SSR__.dehydrated = ${jsesc(\n        router.ssr!.serializer.stringify(dehydratedRouter),\n        {\n          isScriptContext: true,\n          wrap: true,\n          json: true,\n        },\n      )}`,\n  )\n}\n\nexport function extractAsyncLoaderData(\n  loaderData: any,\n  ctx: {\n    match: AnyRouteMatch\n    router: AnyRouter\n  },\n) {\n  const extracted: Array<ServerExtractedEntry> = []\n\n  const replaced = replaceBy(loaderData, (value, path) => {\n    // If it's a stream, we need to tee it so we can read it multiple times\n    if (value instanceof ReadableStream) {\n      const [copy1, copy2] = value.tee()\n      const entry: ServerExtractedStream = {\n        type: 'stream',\n        path,\n        id: extracted.length,\n        matchIndex: ctx.match.index,\n        stream: copy2,\n      }\n\n      extracted.push(entry)\n      return copy1\n    } else if (value instanceof Promise) {\n      const deferredPromise = defer(value)\n      const entry: ServerExtractedPromise = {\n        type: 'promise',\n        path,\n        id: extracted.length,\n        matchIndex: ctx.match.index,\n        promise: deferredPromise,\n      }\n      extracted.push(entry)\n    }\n\n    return value\n  })\n\n  return { replaced, extracted }\n}\n\nexport function onMatchSettled(opts: {\n  router: AnyRouter\n  match: AnyRouteMatch\n}) {\n  const { router, match } = opts\n\n  let extracted: Array<ServerExtractedEntry> | undefined = undefined\n  let serializedLoaderData: any = undefined\n  if (match.loaderData !== undefined) {\n    const result = extractAsyncLoaderData(match.loaderData, {\n      router,\n      match,\n    })\n    match.loaderData = result.replaced\n    extracted = result.extracted\n    serializedLoaderData = extracted.reduce(\n      (acc: any, entry: ServerExtractedEntry) => {\n        return deepImmutableSetByPath(acc, ['temp', ...entry.path], undefined)\n      },\n      { temp: result.replaced },\n    ).temp\n  }\n\n  const initCode = `__TSR_SSR__.initMatch(${jsesc(\n    {\n      id: match.id,\n      __beforeLoadContext: router.ssr!.serializer.stringify(\n        match.__beforeLoadContext,\n      ),\n      loaderData: router.ssr!.serializer.stringify(serializedLoaderData),\n      error: router.ssr!.serializer.stringify(match.error),\n      extracted: extracted?.map((entry) => pick(entry, ['type', 'path'])),\n      updatedAt: match.updatedAt,\n      status: match.status,\n    } satisfies SsrMatch,\n    {\n      isScriptContext: true,\n      wrap: true,\n      json: true,\n    },\n  )})`\n\n  router.serverSsr!.injectScript(() => initCode)\n\n  if (extracted) {\n    extracted.forEach((entry) => {\n      if (entry.type === 'promise') return injectPromise(entry)\n      return injectStream(entry)\n    })\n  }\n\n  function injectPromise(entry: ServerExtractedPromise) {\n    router.serverSsr!.injectScript(async () => {\n      await entry.promise\n\n      return `__TSR_SSR__.resolvePromise(${jsesc(\n        {\n          matchId: match.id,\n          id: entry.id,\n          promiseState: entry.promise[TSR_DEFERRED_PROMISE],\n        } satisfies ResolvePromiseState,\n        {\n          isScriptContext: true,\n          wrap: true,\n          json: true,\n        },\n      )})`\n    })\n  }\n\n  function injectStream(entry: ServerExtractedStream) {\n    // Inject a promise that resolves when the stream is done\n    // We do this to keep the stream open until we're done\n    router.serverSsr!.injectHtml(async () => {\n      //\n      try {\n        const reader = entry.stream.getReader()\n        let chunk: ReadableStreamReadResult<any> | null = null\n        while (!(chunk = await reader.read()).done) {\n          if (chunk.value) {\n            const code = `__TSR_SSR__.injectChunk(${jsesc(\n              {\n                matchId: match.id,\n                id: entry.id,\n                chunk: chunk.value,\n              },\n              {\n                isScriptContext: true,\n                wrap: true,\n                json: true,\n              },\n            )})`\n\n            router.serverSsr!.injectScript(() => code)\n          }\n        }\n\n        router.serverSsr!.injectScript(\n          () =>\n            `__TSR_SSR__.closeStream(${jsesc(\n              {\n                matchId: match.id,\n                id: entry.id,\n              },\n              {\n                isScriptContext: true,\n                wrap: true,\n                json: true,\n              },\n            )})`,\n        )\n      } catch (err) {\n        console.error('stream read error', err)\n      }\n\n      return ''\n    })\n  }\n}\n\nfunction deepImmutableSetByPath<T>(obj: T, path: Array<string>, value: any): T {\n  // immutable set by path retaining array and object references\n  if (path.length === 0) {\n    return value\n  }\n\n  const [key, ...rest] = path\n\n  if (Array.isArray(obj)) {\n    return obj.map((item, i) => {\n      if (i === Number(key)) {\n        return deepImmutableSetByPath(item, rest, value)\n      }\n      return item\n    }) as T\n  }\n\n  if (isPlainObject(obj)) {\n    return {\n      ...obj,\n      [key!]: deepImmutableSetByPath((obj as any)[key!], rest, value),\n    }\n  }\n\n  return obj\n}\n\nexport function replaceBy<T>(\n  obj: T,\n  cb: (value: any, path: Array<string>) => any,\n  path: Array<string> = [],\n): T {\n  if (isPlainArray(obj)) {\n    return obj.map((value, i) => replaceBy(value, cb, [...path, `${i}`])) as any\n  }\n\n  if (isPlainObject(obj)) {\n    // Do not allow objects with illegal\n    const newObj: any = {}\n\n    for (const key in obj) {\n      newObj[key] = replaceBy(obj[key], cb, [...path, key])\n    }\n\n    return newObj\n  }\n\n  // // Detect classes, functions, and other non-serializable objects\n  // // and return undefined. Exclude some known types that are serializable\n  // if (\n  //   typeof obj === 'function' ||\n  //   (typeof obj === 'object' &&\n  //     ![Object, Promise, ReadableStream].includes((obj as any)?.constructor))\n  // ) {\n  //   console.info(obj)\n  //   warning(false, `Non-serializable value ☝️ found at ${path.join('.')}`)\n  //   return undefined as any\n  // }\n\n  const newObj = cb(obj, path)\n\n  if (newObj !== obj) {\n    return newObj\n  }\n\n  return obj\n}\n"],"names":["startSerializer","minifiedTsrBootStrapScript","defer","pick","TSR_DEFERRED_PROMISE","isPlainObject","isPlainArray","newObj"],"mappings":";;;;;;;AAuCgB,SAAA,2BACd,QACA,UACA;AACA,SAAO,MAAM;AAAA,IACX;AAAA,IACA,YAAYA,YAAAA;AAAAA,EACd;AAEA,SAAO,YAAY;AAAA,IACjB,cAAc,CAAC;AAAA,IACf,kCAAkB,IAAI;AAAA,IACtB,YAAY,CAAC,YAAY;AACvB,YAAM,UAAU,QAAQ,QAAQ,EAAE,KAAK,OAAO;AACvC,aAAA,UAAW,aAAa,KAAK,OAAO;AAC3C,aAAO,KAAK;AAAA,QACV,MAAM;AAAA,QACN;AAAA,MAAA,CACD;AAEM,aAAA,QAAQ,KAAK,MAAM;AAAA,MAAA,CAAE;AAAA,IAC9B;AAAA,IACA,cAAc,CAAC,WAAW,SAAS;AAC1B,aAAA,OAAO,UAAW,WAAW,YAAY;AACxC,cAAA,SAAS,MAAM,UAAU;AACxB,eAAA,4BAA4B,MAAM,GACvC,QAAQ,IAAI,aAAa,mBAAkB,6BAAM,cAAa,QAC1D;AAAA,EACZ,MAAM,QAAQ,EAAE,QAAQ,WAAY,CAAA,CAAC,QACzB,EACN;AAAA,MAAA,CACD;AAAA,IACH;AAAA,IACA,aAAa,CAAC,KAAK,UAAU;AAC3B;AAAA,QACE,CAAC,OAAO,UAAW,aAAa,IAAI,GAAG;AAAA,QACvC,oCAAoC;AAAA,MACtC;AAEO,aAAA,UAAW,aAAa,IAAI,GAAG;AACtC,aAAO,UAAW;AAAA,QAChB,MACE,+BAA+B,GAAG,iBAAiB;AAAA,UACjD,OAAO,IAAK,WAAW,UAAU,KAAK;AAAA,UACtC;AAAA,YACE,iBAAiB;AAAA,YACjB,MAAM;AAAA,YACN,MAAM;AAAA,UAAA;AAAA,QACR,CACD;AAAA,MACL;AAAA,IACF;AAAA,IACA;AAAA,EACF;AAEO,SAAA,UAAU,aAAa,MAAMC,WAA4B;AAAA,IAC9D,WAAW;AAAA,EAAA,CACZ;AACH;AAEO,SAAS,gBAAgB,QAAmB;;AACjD,QAAM,mBAAqC;AAAA,IACzC,UAAU,OAAO,IAAK;AAAA,IACtB,iBAAgB,kBAAO,SAAQ,cAAf;AAAA,EAClB;AAEA,SAAO,UAAW;AAAA,IAChB,MACE,4BAA4B;AAAA,MAC1B,OAAO,IAAK,WAAW,UAAU,gBAAgB;AAAA,MACjD;AAAA,QACE,iBAAiB;AAAA,QACjB,MAAM;AAAA,QACN,MAAM;AAAA,MAAA;AAAA,IACR,CACD;AAAA,EACL;AACF;AAEgB,SAAA,uBACd,YACA,KAIA;AACA,QAAM,YAAyC,CAAC;AAEhD,QAAM,WAAW,UAAU,YAAY,CAAC,OAAO,SAAS;AAEtD,QAAI,iBAAiB,gBAAgB;AACnC,YAAM,CAAC,OAAO,KAAK,IAAI,MAAM,IAAI;AACjC,YAAM,QAA+B;AAAA,QACnC,MAAM;AAAA,QACN;AAAA,QACA,IAAI,UAAU;AAAA,QACd,YAAY,IAAI,MAAM;AAAA,QACtB,QAAQ;AAAA,MACV;AAEA,gBAAU,KAAK,KAAK;AACb,aAAA;AAAA,IAAA,WACE,iBAAiB,SAAS;AAC7B,YAAA,kBAAkBC,iBAAM,KAAK;AACnC,YAAM,QAAgC;AAAA,QACpC,MAAM;AAAA,QACN;AAAA,QACA,IAAI,UAAU;AAAA,QACd,YAAY,IAAI,MAAM;AAAA,QACtB,SAAS;AAAA,MACX;AACA,gBAAU,KAAK,KAAK;AAAA,IAAA;AAGf,WAAA;AAAA,EAAA,CACR;AAEM,SAAA,EAAE,UAAU,UAAU;AAC/B;AAEO,SAAS,eAAe,MAG5B;AACK,QAAA,EAAE,QAAQ,MAAA,IAAU;AAE1B,MAAI,YAAqD;AACzD,MAAI,uBAA4B;AAC5B,MAAA,MAAM,eAAe,QAAW;AAC5B,UAAA,SAAS,uBAAuB,MAAM,YAAY;AAAA,MAEtD;AAAA,IAAA,CACD;AACD,UAAM,aAAa,OAAO;AAC1B,gBAAY,OAAO;AACnB,2BAAuB,UAAU;AAAA,MAC/B,CAAC,KAAU,UAAgC;AAClC,eAAA,uBAAuB,KAAK,CAAC,QAAQ,GAAG,MAAM,IAAI,GAAG,MAAS;AAAA,MACvE;AAAA,MACA,EAAE,MAAM,OAAO,SAAS;AAAA,IAAA,EACxB;AAAA,EAAA;AAGJ,QAAM,WAAW,yBAAyB;AAAA,IACxC;AAAA,MACE,IAAI,MAAM;AAAA,MACV,qBAAqB,OAAO,IAAK,WAAW;AAAA,QAC1C,MAAM;AAAA,MACR;AAAA,MACA,YAAY,OAAO,IAAK,WAAW,UAAU,oBAAoB;AAAA,MACjE,OAAO,OAAO,IAAK,WAAW,UAAU,MAAM,KAAK;AAAA,MACnD,WAAW,uCAAW,IAAI,CAAC,UAAUC,gBAAK,OAAO,CAAC,QAAQ,MAAM,CAAC;AAAA,MACjE,WAAW,MAAM;AAAA,MACjB,QAAQ,MAAM;AAAA,IAChB;AAAA,IACA;AAAA,MACE,iBAAiB;AAAA,MACjB,MAAM;AAAA,MACN,MAAM;AAAA,IAAA;AAAA,EAET,CAAA;AAEM,SAAA,UAAW,aAAa,MAAM,QAAQ;AAE7C,MAAI,WAAW;AACH,cAAA,QAAQ,CAAC,UAAU;AAC3B,UAAI,MAAM,SAAS,UAAW,QAAO,cAAc,KAAK;AACxD,aAAO,aAAa,KAAK;AAAA,IAAA,CAC1B;AAAA,EAAA;AAGH,WAAS,cAAc,OAA+B;AAC7C,WAAA,UAAW,aAAa,YAAY;AACzC,YAAM,MAAM;AAEZ,aAAO,8BAA8B;AAAA,QACnC;AAAA,UACE,SAAS,MAAM;AAAA,UACf,IAAI,MAAM;AAAA,UACV,cAAc,MAAM,QAAQC,WAAoB,oBAAA;AAAA,QAClD;AAAA,QACA;AAAA,UACE,iBAAiB;AAAA,UACjB,MAAM;AAAA,UACN,MAAM;AAAA,QAAA;AAAA,MAET,CAAA;AAAA,IAAA,CACF;AAAA,EAAA;AAGH,WAAS,aAAa,OAA8B;AAG3C,WAAA,UAAW,WAAW,YAAY;AAEnC,UAAA;AACI,cAAA,SAAS,MAAM,OAAO,UAAU;AACtC,YAAI,QAA8C;AAClD,eAAO,EAAE,QAAQ,MAAM,OAAO,KAAA,GAAQ,MAAM;AAC1C,cAAI,MAAM,OAAO;AACf,kBAAM,OAAO,2BAA2B;AAAA,cACtC;AAAA,gBACE,SAAS,MAAM;AAAA,gBACf,IAAI,MAAM;AAAA,gBACV,OAAO,MAAM;AAAA,cACf;AAAA,cACA;AAAA,gBACE,iBAAiB;AAAA,gBACjB,MAAM;AAAA,gBACN,MAAM;AAAA,cAAA;AAAA,YAET,CAAA;AAEM,mBAAA,UAAW,aAAa,MAAM,IAAI;AAAA,UAAA;AAAA,QAC3C;AAGF,eAAO,UAAW;AAAA,UAChB,MACE,2BAA2B;AAAA,YACzB;AAAA,cACE,SAAS,MAAM;AAAA,cACf,IAAI,MAAM;AAAA,YACZ;AAAA,YACA;AAAA,cACE,iBAAiB;AAAA,cACjB,MAAM;AAAA,cACN,MAAM;AAAA,YAAA;AAAA,UACR,CACD;AAAA,QACL;AAAA,eACO,KAAK;AACJ,gBAAA,MAAM,qBAAqB,GAAG;AAAA,MAAA;AAGjC,aAAA;AAAA,IAAA,CACR;AAAA,EAAA;AAEL;AAEA,SAAS,uBAA0B,KAAQ,MAAqB,OAAe;AAEzE,MAAA,KAAK,WAAW,GAAG;AACd,WAAA;AAAA,EAAA;AAGT,QAAM,CAAC,KAAK,GAAG,IAAI,IAAI;AAEnB,MAAA,MAAM,QAAQ,GAAG,GAAG;AACtB,WAAO,IAAI,IAAI,CAAC,MAAM,MAAM;AACtB,UAAA,MAAM,OAAO,GAAG,GAAG;AACd,eAAA,uBAAuB,MAAM,MAAM,KAAK;AAAA,MAAA;AAE1C,aAAA;AAAA,IAAA,CACR;AAAA,EAAA;AAGC,MAAAC,WAAAA,cAAc,GAAG,GAAG;AACf,WAAA;AAAA,MACL,GAAG;AAAA,MACH,CAAC,GAAI,GAAG,uBAAwB,IAAY,GAAI,GAAG,MAAM,KAAK;AAAA,IAChE;AAAA,EAAA;AAGK,SAAA;AACT;AAEO,SAAS,UACd,KACA,IACA,OAAsB,CAAA,GACnB;AACC,MAAAC,WAAAA,aAAa,GAAG,GAAG;AACrB,WAAO,IAAI,IAAI,CAAC,OAAO,MAAM,UAAU,OAAO,IAAI,CAAC,GAAG,MAAM,GAAG,CAAC,EAAE,CAAC,CAAC;AAAA,EAAA;AAGlE,MAAAD,WAAAA,cAAc,GAAG,GAAG;AAEtB,UAAME,UAAc,CAAC;AAErB,eAAW,OAAO,KAAK;AACrBA,cAAO,GAAG,IAAI,UAAU,IAAI,GAAG,GAAG,IAAI,CAAC,GAAG,MAAM,GAAG,CAAC;AAAA,IAAA;AAG/CA,WAAAA;AAAAA,EAAA;AAeH,QAAA,SAAS,GAAG,KAAK,IAAI;AAE3B,MAAI,WAAW,KAAK;AACX,WAAA;AAAA,EAAA;AAGF,SAAA;AACT;;;;;;"}