waku 1.0.0-beta.3 → 1.0.0-beta.5

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 (56) hide show
  1. package/README.md +7 -4
  2. package/dist/lib/utils/initial-rsc.d.ts +0 -1
  3. package/dist/lib/utils/initial-rsc.js +7 -7
  4. package/dist/lib/utils/initial-rsc.js.map +1 -1
  5. package/dist/lib/utils/prefetch-cache.d.ts +5 -10
  6. package/dist/lib/utils/prefetch-cache.js +6 -19
  7. package/dist/lib/utils/prefetch-cache.js.map +1 -1
  8. package/dist/lib/utils/stream.d.ts +0 -1
  9. package/dist/lib/utils/stream.js +0 -35
  10. package/dist/lib/utils/stream.js.map +1 -1
  11. package/dist/lib/vite-plugins/fs-router-typegen.js +4 -2
  12. package/dist/lib/vite-plugins/fs-router-typegen.js.map +1 -1
  13. package/dist/lib/vite-rsc/ssr.js +5 -10
  14. package/dist/lib/vite-rsc/ssr.js.map +1 -1
  15. package/dist/minimal/client-utils/fetch-store.d.ts +24 -0
  16. package/dist/minimal/client-utils/fetch-store.js +11 -0
  17. package/dist/minimal/client-utils/fetch-store.js.map +1 -0
  18. package/dist/minimal/client.d.ts +33 -32
  19. package/dist/minimal/client.js +115 -116
  20. package/dist/minimal/client.js.map +1 -1
  21. package/dist/router/base-types.d.ts +3 -1
  22. package/dist/router/base-types.js.map +1 -1
  23. package/dist/router/client-utils/build-route-href.d.ts +24 -0
  24. package/dist/router/client-utils/build-route-href.js +49 -0
  25. package/dist/router/client-utils/build-route-href.js.map +1 -0
  26. package/dist/router/client-utils/match-route-params.d.ts +14 -0
  27. package/dist/router/client-utils/match-route-params.js +49 -0
  28. package/dist/router/client-utils/match-route-params.js.map +1 -0
  29. package/dist/router/client-utils/search-codec-registry.d.ts +9 -0
  30. package/dist/router/client-utils/search-codec-registry.js +11 -0
  31. package/dist/router/client-utils/search-codec-registry.js.map +1 -0
  32. package/dist/router/client.d.ts +70 -28
  33. package/dist/router/client.js +154 -31
  34. package/dist/router/client.js.map +1 -1
  35. package/dist/router/{common.js → common-utils/route-path.js} +1 -3
  36. package/dist/router/common-utils/route-path.js.map +1 -0
  37. package/dist/router/create-pages-utils/inferred-path-types.d.ts +41 -6
  38. package/dist/router/create-pages-utils/inferred-path-types.js.map +1 -1
  39. package/dist/router/create-pages-utils/util-types.js.map +1 -0
  40. package/dist/router/create-pages.d.ts +10 -1
  41. package/dist/router/create-pages.js +35 -8
  42. package/dist/router/create-pages.js.map +1 -1
  43. package/dist/router/define-router.d.ts +1 -0
  44. package/dist/router/define-router.js +35 -3
  45. package/dist/router/define-router.js.map +1 -1
  46. package/dist/router/fs-router.d.ts +1 -0
  47. package/dist/router/fs-router.js.map +1 -1
  48. package/package.json +5 -5
  49. package/dist/lib/utils/rsc-stream.d.ts +0 -1
  50. package/dist/lib/utils/rsc-stream.js +0 -84
  51. package/dist/lib/utils/rsc-stream.js.map +0 -1
  52. package/dist/router/common.js.map +0 -1
  53. package/dist/router/util-types.js.map +0 -1
  54. /package/dist/router/{common.d.ts → common-utils/route-path.d.ts} +0 -0
  55. /package/dist/router/{util-types.d.ts → create-pages-utils/util-types.d.ts} +0 -0
  56. /package/dist/router/{util-types.js → create-pages-utils/util-types.js} +0 -0
package/README.md CHANGED
@@ -17,7 +17,7 @@ visit [waku.gg](https://waku.gg) or `npm create waku@latest`
17
17
 
18
18
  ## Getting started
19
19
 
20
- Start a new Waku project with the `create` command for your preferred package manager. It will scaffold a new project with our default [Waku starter](https://github.com/wakujs/waku/tree/main/examples/01_template).
20
+ Start a new Waku project with the `create` command for your preferred package manager. It will scaffold a new project with our default [Waku starter](https://github.com/wakujs/waku-examples/tree/main/fs-router/basic).
21
21
 
22
22
  ```sh
23
23
  npm create waku@latest
@@ -29,7 +29,7 @@ npm create waku@latest
29
29
  - `waku build` to generate a production build
30
30
  - `waku start` to serve the production build locally
31
31
 
32
- **Node.js version requirement:** `^26.0.0` or `^24.0.0` or `^22.13.0`
32
+ **Node.js version requirement:** `^26.0.0` or `^24.0.0` or `^22.15.0`
33
33
 
34
34
  ## Rendering
35
35
 
@@ -761,6 +761,8 @@ This allows you to have a `dynamic` slice component while keeping the rest of th
761
761
 
762
762
  The `<Link />` component should be used for internal links. It accepts a `to` prop for the destination and handles client-side navigation through Waku's router.
763
763
 
764
+ `to` is either a route string, or a structured `{ to, params, search, hash }` target for typed navigation (the same shape `router.push` accepts; see [Typed Routes](https://github.com/wakujs/waku/blob/main/docs/guides/typed-routes.mdx)).
765
+
764
766
  ```tsx
765
767
  // ./src/pages/index.tsx
766
768
  import { Link } from 'waku';
@@ -770,6 +772,7 @@ export default async function HomePage() {
770
772
  <>
771
773
  <h1>Home</h1>
772
774
  <Link to="/about">About</Link>
775
+ <Link to={{ to: '/search', search: { q: 'waku' } }}>Search</Link>
773
776
  </>
774
777
  );
775
778
  }
@@ -804,11 +807,11 @@ export const Component = () => {
804
807
 
805
808
  The `router` object also contains several methods for programmatic navigation:
806
809
 
807
- - `router.push(to: string)` - navigate to the provided route
810
+ - `router.push(to)` - navigate to the provided route. `to` is a route string, or a structured `{ to, params, search, hash }` target for typed navigation to dynamic routes (see [Typed Routes](https://github.com/wakujs/waku/blob/main/docs/guides/typed-routes.mdx))
808
811
 
809
812
  - `router.prefetch(to: string)` - prefetch the provided route
810
813
 
811
- - `router.replace(to: string)` - replace the current history entry
814
+ - `router.replace(to)` - replace the current history entry (same argument as `push`)
812
815
 
813
816
  - `router.reload()` - reload the current route
814
817
 
@@ -1,6 +1,5 @@
1
1
  export type InitialRscEntry = {
2
2
  response: Promise<Response>;
3
- close: () => void;
4
3
  debugId?: string;
5
4
  };
6
5
  export declare const consumeInitialRscEntry: () => InitialRscEntry | undefined;
@@ -9,6 +9,7 @@ export const consumeInitialRscEntry = ()=>{
9
9
  }
10
10
  return entry;
11
11
  };
12
+ // DEV: hold the stream ~5s so React's late debug-channel chunks settle before close. https://github.com/wakujs/waku/pull/2154
12
13
  export const createInitialRscEntryCode = (debugId)=>`
13
14
  (() => {
14
15
  const e = {};
@@ -20,13 +21,12 @@ export const createInitialRscEntryCode = (debugId)=>`
20
21
  d.forEach(f);
21
22
  d.length = 0;
22
23
  d.push = f;
23
- e.close = () => {
24
- if (document.readyState === 'loading') {
25
- document.addEventListener('DOMContentLoaded', () => c.close());
26
- } else {
27
- c.close();
28
- }
29
- };
24
+ const close = ${import.meta.env.DEV ? '() => setTimeout(() => c.close(), 5000)' : '() => c.close()'};
25
+ if (document.readyState === 'loading') {
26
+ document.addEventListener('DOMContentLoaded', close);
27
+ } else {
28
+ close();
29
+ }
30
30
  }
31
31
  })));
32
32
  ${debugId ? `e.debugId = ${JSON.stringify(debugId)};` : ''}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/lib/utils/initial-rsc.ts"],"sourcesContent":["// The initial RSC entry is emitted as an inline script during SSR. It carries\n// the streamed RSC payload of the first render as a `Response`, so the client\n// can hydrate without an extra round trip. It is consumed exactly once.\n\n// This is exported only for global-types.ts. It is not a public API.\nexport type InitialRscEntry = {\n response: Promise<Response>;\n close: () => void;\n debugId?: string;\n};\n\nexport const consumeInitialRscEntry = (): InitialRscEntry | undefined => {\n const entry = globalThis.__WAKU_INITIAL_RSC__;\n if (entry) {\n globalThis.__WAKU_INITIAL_RSC__ = undefined;\n }\n return entry;\n};\n\nexport const createInitialRscEntryCode = (debugId: string | undefined) =>\n `\n (() => {\n const e = {};\n e.response = Promise.resolve(new Response(new ReadableStream({\n start(c) {\n const d = (window.__FLIGHT_DATA ||= []);\n const t = new TextEncoder();\n const f = (s) => c.enqueue(typeof s === 'string' ? t.encode(s) : s);\n d.forEach(f);\n d.length = 0;\n d.push = f;\n e.close = () => {\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', () => c.close());\n } else {\n c.close();\n }\n };\n }\n })));\n ${debugId ? `e.debugId = ${JSON.stringify(debugId)};` : ''}\n return e;\n })()\n`\n .split('\\n')\n .map((line) => line.trim())\n .join('');\n"],"names":["consumeInitialRscEntry","entry","globalThis","__WAKU_INITIAL_RSC__","undefined","createInitialRscEntryCode","debugId","JSON","stringify","split","map","line","trim","join"],"mappings":"AAAA,8EAA8E;AAC9E,8EAA8E;AAC9E,wEAAwE;AAExE,qEAAqE;AAOrE,OAAO,MAAMA,yBAAyB;IACpC,MAAMC,QAAQC,WAAWC,oBAAoB;IAC7C,IAAIF,OAAO;QACTC,WAAWC,oBAAoB,GAAGC;IACpC;IACA,OAAOH;AACT,EAAE;AAEF,OAAO,MAAMI,4BAA4B,CAACC,UACxC,CAAC;;;;;;;;;;;;;;;;;;;;IAoBC,EAAEA,UAAU,CAAC,YAAY,EAAEC,KAAKC,SAAS,CAACF,SAAS,CAAC,CAAC,GAAG,GAAG;;;AAG/D,CAAC,CACIG,KAAK,CAAC,MACNC,GAAG,CAAC,CAACC,OAASA,KAAKC,IAAI,IACvBC,IAAI,CAAC,IAAI"}
1
+ {"version":3,"sources":["../../../src/lib/utils/initial-rsc.ts"],"sourcesContent":["// The initial RSC entry is emitted as an inline script during SSR. It carries\n// the streamed RSC payload of the first render as a `Response`, so the client\n// can hydrate without an extra round trip. It is consumed exactly once.\n\n// This is exported only for global-types.ts. It is not a public API.\nexport type InitialRscEntry = {\n response: Promise<Response>;\n debugId?: string;\n};\n\nexport const consumeInitialRscEntry = (): InitialRscEntry | undefined => {\n const entry = globalThis.__WAKU_INITIAL_RSC__;\n if (entry) {\n globalThis.__WAKU_INITIAL_RSC__ = undefined;\n }\n return entry;\n};\n\n// DEV: hold the stream ~5s so React's late debug-channel chunks settle before close. https://github.com/wakujs/waku/pull/2154\nexport const createInitialRscEntryCode = (debugId: string | undefined) =>\n `\n (() => {\n const e = {};\n e.response = Promise.resolve(new Response(new ReadableStream({\n start(c) {\n const d = (window.__FLIGHT_DATA ||= []);\n const t = new TextEncoder();\n const f = (s) => c.enqueue(typeof s === 'string' ? t.encode(s) : s);\n d.forEach(f);\n d.length = 0;\n d.push = f;\n const close = ${import.meta.env.DEV ? '() => setTimeout(() => c.close(), 5000)' : '() => c.close()'};\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', close);\n } else {\n close();\n }\n }\n })));\n ${debugId ? `e.debugId = ${JSON.stringify(debugId)};` : ''}\n return e;\n })()\n`\n .split('\\n')\n .map((line) => line.trim())\n .join('');\n"],"names":["consumeInitialRscEntry","entry","globalThis","__WAKU_INITIAL_RSC__","undefined","createInitialRscEntryCode","debugId","env","DEV","JSON","stringify","split","map","line","trim","join"],"mappings":"AAAA,8EAA8E;AAC9E,8EAA8E;AAC9E,wEAAwE;AAExE,qEAAqE;AAMrE,OAAO,MAAMA,yBAAyB;IACpC,MAAMC,QAAQC,WAAWC,oBAAoB;IAC7C,IAAIF,OAAO;QACTC,WAAWC,oBAAoB,GAAGC;IACpC;IACA,OAAOH;AACT,EAAE;AAEF,8HAA8H;AAC9H,OAAO,MAAMI,4BAA4B,CAACC,UACxC,CAAC;;;;;;;;;;;sBAWmB,EAAE,YAAYC,GAAG,CAACC,GAAG,GAAG,4CAA4C,kBAAkB;;;;;;;;IAQxG,EAAEF,UAAU,CAAC,YAAY,EAAEG,KAAKC,SAAS,CAACJ,SAAS,CAAC,CAAC,GAAG,GAAG;;;AAG/D,CAAC,CACIK,KAAK,CAAC,MACNC,GAAG,CAAC,CAACC,OAASA,KAAKC,IAAI,IACvBC,IAAI,CAAC,IAAI"}
@@ -5,16 +5,11 @@ export declare const PREFETCH_LIMIT = 100;
5
5
  export type PrefetchEntry = {
6
6
  rscPath: string;
7
7
  rscParams: unknown;
8
- getElements: (store: never) => unknown;
8
+ elements: unknown;
9
9
  expireAt: number;
10
10
  };
11
- /**
12
- * Decode and cache a prefetch. `getStore` returns the entry's currently bound
13
- * store: the prefetch's now, the navigation's after `consumePrefetchEntry`.
14
- */
15
- export declare const addPrefetchEntry: <Store, Elements>(rscPath: string, rscParams: unknown, store: Store, decode: (getStore: () => Store) => Elements) => void;
11
+ /** Decode and cache a prefetch so a later navigation can reuse the tree. */
12
+ export declare const addPrefetchEntry: <Elements>(rscPath: string, rscParams: unknown, elements: Elements) => void;
16
13
  export declare const hasPrefetchEntry: (rscPath: string, rscParams: unknown) => boolean;
17
- /**
18
- * Consume a fresh prefetch, rebinding its decoded tree to the consumer's store.
19
- */
20
- export declare const consumePrefetchEntry: <Store, Elements>(rscPath: string, rscParams: unknown, store: Store) => Elements | undefined;
14
+ /** Consume a fresh prefetch, returning its eagerly decoded tree. */
15
+ export declare const consumePrefetchEntry: <Elements>(rscPath: string, rscParams: unknown) => Elements | undefined;
@@ -1,30 +1,20 @@
1
1
  // Client-side cache of prefetched navigations: a prefetch decodes the route
2
- // eagerly so a later navigation reuses it without re-fetching. Each entry keeps
3
- // a mutable store so the decoded tree's server actions bind to the consuming
4
- // navigation's store, not the prefetch's. Bounded by a ttl and a max size.
2
+ // eagerly so a later navigation reuses the decoded tree without re-fetching.
3
+ // Bounded by a ttl and a max size.
5
4
  /** How long (ms) a prefetched entry stays usable before it is discarded. */ export const PREFETCH_TTL = 1000 * 60;
6
5
  /** Maximum number of prefetched entries kept at once. */ export const PREFETCH_LIMIT = 100;
7
6
  const getCache = ()=>globalThis.__WAKU_PREFETCHED__ ||= [];
8
7
  const findFreshIndex = (cache, rscPath, rscParams, now)=>cache.findIndex((entry)=>entry.expireAt > now && entry.rscPath === rscPath && // rscParams is intentionally compared by reference.
9
8
  entry.rscParams === rscParams);
10
- /**
11
- * Decode and cache a prefetch. `getStore` returns the entry's currently bound
12
- * store: the prefetch's now, the navigation's after `consumePrefetchEntry`.
13
- */ export const addPrefetchEntry = (rscPath, rscParams, store, decode)=>{
9
+ /** Decode and cache a prefetch so a later navigation can reuse the tree. */ export const addPrefetchEntry = (rscPath, rscParams, elements)=>{
14
10
  const cache = getCache();
15
11
  const now = Date.now();
16
- let currentStore = store;
17
- const elements = decode(()=>currentStore);
18
12
  // Mark as handled so a prefetch that is never consumed stays quiet.
19
13
  Promise.resolve(elements).catch(()=>{});
20
- const getElements = (nextStore)=>{
21
- currentStore = nextStore;
22
- return elements;
23
- };
24
14
  cache.push({
25
15
  rscPath,
26
16
  rscParams,
27
- getElements,
17
+ elements,
28
18
  expireAt: now + PREFETCH_TTL
29
19
  });
30
20
  while(cache.length > 0 && (cache.length > PREFETCH_LIMIT || cache[0].expireAt <= now)){
@@ -35,16 +25,13 @@ export const hasPrefetchEntry = (rscPath, rscParams)=>{
35
25
  const cache = getCache();
36
26
  return findFreshIndex(cache, rscPath, rscParams, Date.now()) >= 0;
37
27
  };
38
- /**
39
- * Consume a fresh prefetch, rebinding its decoded tree to the consumer's store.
40
- */ export const consumePrefetchEntry = (rscPath, rscParams, store)=>{
28
+ /** Consume a fresh prefetch, returning its eagerly decoded tree. */ export const consumePrefetchEntry = (rscPath, rscParams)=>{
41
29
  const cache = getCache();
42
30
  const index = findFreshIndex(cache, rscPath, rscParams, Date.now());
43
31
  if (index < 0) {
44
32
  return undefined;
45
33
  }
46
- const { getElements } = cache.splice(index, 1)[0];
47
- return getElements(store);
34
+ return cache.splice(index, 1)[0].elements;
48
35
  };
49
36
 
50
37
  //# sourceMappingURL=prefetch-cache.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/lib/utils/prefetch-cache.ts"],"sourcesContent":["// Client-side cache of prefetched navigations: a prefetch decodes the route\n// eagerly so a later navigation reuses it without re-fetching. Each entry keeps\n// a mutable store so the decoded tree's server actions bind to the consuming\n// navigation's store, not the prefetch's. Bounded by a ttl and a max size.\n\n/** How long (ms) a prefetched entry stays usable before it is discarded. */\nexport const PREFETCH_TTL = 1000 * 60;\n\n/** Maximum number of prefetched entries kept at once. */\nexport const PREFETCH_LIMIT = 100;\n\n// This is exported only for global-types.ts. It is not a public API.\nexport type PrefetchEntry = {\n rscPath: string;\n rscParams: unknown;\n getElements: (store: never) => unknown;\n expireAt: number;\n};\n\nconst getCache = (): PrefetchEntry[] => (globalThis.__WAKU_PREFETCHED__ ||= []);\n\nconst findFreshIndex = (\n cache: PrefetchEntry[],\n rscPath: string,\n rscParams: unknown,\n now: number,\n) =>\n cache.findIndex(\n (entry) =>\n entry.expireAt > now &&\n entry.rscPath === rscPath &&\n // rscParams is intentionally compared by reference.\n entry.rscParams === rscParams,\n );\n\n/**\n * Decode and cache a prefetch. `getStore` returns the entry's currently bound\n * store: the prefetch's now, the navigation's after `consumePrefetchEntry`.\n */\nexport const addPrefetchEntry = <Store, Elements>(\n rscPath: string,\n rscParams: unknown,\n store: Store,\n decode: (getStore: () => Store) => Elements,\n): void => {\n const cache = getCache();\n const now = Date.now();\n let currentStore = store;\n const elements = decode(() => currentStore);\n // Mark as handled so a prefetch that is never consumed stays quiet.\n Promise.resolve(elements).catch(() => {});\n const getElements = (nextStore: Store): Elements => {\n currentStore = nextStore;\n return elements;\n };\n cache.push({ rscPath, rscParams, getElements, expireAt: now + PREFETCH_TTL });\n while (\n cache.length > 0 &&\n (cache.length > PREFETCH_LIMIT || cache[0]!.expireAt <= now)\n ) {\n cache.shift();\n }\n};\n\nexport const hasPrefetchEntry = (\n rscPath: string,\n rscParams: unknown,\n): boolean => {\n const cache = getCache();\n return findFreshIndex(cache, rscPath, rscParams, Date.now()) >= 0;\n};\n\n/**\n * Consume a fresh prefetch, rebinding its decoded tree to the consumer's store.\n */\nexport const consumePrefetchEntry = <Store, Elements>(\n rscPath: string,\n rscParams: unknown,\n store: Store,\n): Elements | undefined => {\n const cache = getCache();\n const index = findFreshIndex(cache, rscPath, rscParams, Date.now());\n if (index < 0) {\n return undefined;\n }\n const { getElements } = cache.splice(index, 1)[0]!;\n return (getElements as (s: Store) => Elements)(store);\n};\n"],"names":["PREFETCH_TTL","PREFETCH_LIMIT","getCache","globalThis","__WAKU_PREFETCHED__","findFreshIndex","cache","rscPath","rscParams","now","findIndex","entry","expireAt","addPrefetchEntry","store","decode","Date","currentStore","elements","Promise","resolve","catch","getElements","nextStore","push","length","shift","hasPrefetchEntry","consumePrefetchEntry","index","undefined","splice"],"mappings":"AAAA,4EAA4E;AAC5E,gFAAgF;AAChF,6EAA6E;AAC7E,2EAA2E;AAE3E,0EAA0E,GAC1E,OAAO,MAAMA,eAAe,OAAO,GAAG;AAEtC,uDAAuD,GACvD,OAAO,MAAMC,iBAAiB,IAAI;AAUlC,MAAMC,WAAW,IAAwBC,WAAWC,mBAAmB,KAAK,EAAE;AAE9E,MAAMC,iBAAiB,CACrBC,OACAC,SACAC,WACAC,MAEAH,MAAMI,SAAS,CACb,CAACC,QACCA,MAAMC,QAAQ,GAAGH,OACjBE,MAAMJ,OAAO,KAAKA,WAClB,oDAAoD;QACpDI,MAAMH,SAAS,KAAKA;AAG1B;;;CAGC,GACD,OAAO,MAAMK,mBAAmB,CAC9BN,SACAC,WACAM,OACAC;IAEA,MAAMT,QAAQJ;IACd,MAAMO,MAAMO,KAAKP,GAAG;IACpB,IAAIQ,eAAeH;IACnB,MAAMI,WAAWH,OAAO,IAAME;IAC9B,oEAAoE;IACpEE,QAAQC,OAAO,CAACF,UAAUG,KAAK,CAAC,KAAO;IACvC,MAAMC,cAAc,CAACC;QACnBN,eAAeM;QACf,OAAOL;IACT;IACAZ,MAAMkB,IAAI,CAAC;QAAEjB;QAASC;QAAWc;QAAaV,UAAUH,MAAMT;IAAa;IAC3E,MACEM,MAAMmB,MAAM,GAAG,KACdnB,CAAAA,MAAMmB,MAAM,GAAGxB,kBAAkBK,KAAK,CAAC,EAAE,CAAEM,QAAQ,IAAIH,GAAE,EAC1D;QACAH,MAAMoB,KAAK;IACb;AACF,EAAE;AAEF,OAAO,MAAMC,mBAAmB,CAC9BpB,SACAC;IAEA,MAAMF,QAAQJ;IACd,OAAOG,eAAeC,OAAOC,SAASC,WAAWQ,KAAKP,GAAG,OAAO;AAClE,EAAE;AAEF;;CAEC,GACD,OAAO,MAAMmB,uBAAuB,CAClCrB,SACAC,WACAM;IAEA,MAAMR,QAAQJ;IACd,MAAM2B,QAAQxB,eAAeC,OAAOC,SAASC,WAAWQ,KAAKP,GAAG;IAChE,IAAIoB,QAAQ,GAAG;QACb,OAAOC;IACT;IACA,MAAM,EAAER,WAAW,EAAE,GAAGhB,MAAMyB,MAAM,CAACF,OAAO,EAAE,CAAC,EAAE;IACjD,OAAO,AAACP,YAAuCR;AACjD,EAAE"}
1
+ {"version":3,"sources":["../../../src/lib/utils/prefetch-cache.ts"],"sourcesContent":["// Client-side cache of prefetched navigations: a prefetch decodes the route\n// eagerly so a later navigation reuses the decoded tree without re-fetching.\n// Bounded by a ttl and a max size.\n\n/** How long (ms) a prefetched entry stays usable before it is discarded. */\nexport const PREFETCH_TTL = 1000 * 60;\n\n/** Maximum number of prefetched entries kept at once. */\nexport const PREFETCH_LIMIT = 100;\n\n// This is exported only for global-types.ts. It is not a public API.\nexport type PrefetchEntry = {\n rscPath: string;\n rscParams: unknown;\n elements: unknown;\n expireAt: number;\n};\n\nconst getCache = (): PrefetchEntry[] => (globalThis.__WAKU_PREFETCHED__ ||= []);\n\nconst findFreshIndex = (\n cache: PrefetchEntry[],\n rscPath: string,\n rscParams: unknown,\n now: number,\n) =>\n cache.findIndex(\n (entry) =>\n entry.expireAt > now &&\n entry.rscPath === rscPath &&\n // rscParams is intentionally compared by reference.\n entry.rscParams === rscParams,\n );\n\n/** Decode and cache a prefetch so a later navigation can reuse the tree. */\nexport const addPrefetchEntry = <Elements>(\n rscPath: string,\n rscParams: unknown,\n elements: Elements,\n): void => {\n const cache = getCache();\n const now = Date.now();\n // Mark as handled so a prefetch that is never consumed stays quiet.\n Promise.resolve(elements).catch(() => {});\n cache.push({ rscPath, rscParams, elements, expireAt: now + PREFETCH_TTL });\n while (\n cache.length > 0 &&\n (cache.length > PREFETCH_LIMIT || cache[0]!.expireAt <= now)\n ) {\n cache.shift();\n }\n};\n\nexport const hasPrefetchEntry = (\n rscPath: string,\n rscParams: unknown,\n): boolean => {\n const cache = getCache();\n return findFreshIndex(cache, rscPath, rscParams, Date.now()) >= 0;\n};\n\n/** Consume a fresh prefetch, returning its eagerly decoded tree. */\nexport const consumePrefetchEntry = <Elements>(\n rscPath: string,\n rscParams: unknown,\n): Elements | undefined => {\n const cache = getCache();\n const index = findFreshIndex(cache, rscPath, rscParams, Date.now());\n if (index < 0) {\n return undefined;\n }\n return cache.splice(index, 1)[0]!.elements as Elements;\n};\n"],"names":["PREFETCH_TTL","PREFETCH_LIMIT","getCache","globalThis","__WAKU_PREFETCHED__","findFreshIndex","cache","rscPath","rscParams","now","findIndex","entry","expireAt","addPrefetchEntry","elements","Date","Promise","resolve","catch","push","length","shift","hasPrefetchEntry","consumePrefetchEntry","index","undefined","splice"],"mappings":"AAAA,4EAA4E;AAC5E,6EAA6E;AAC7E,mCAAmC;AAEnC,0EAA0E,GAC1E,OAAO,MAAMA,eAAe,OAAO,GAAG;AAEtC,uDAAuD,GACvD,OAAO,MAAMC,iBAAiB,IAAI;AAUlC,MAAMC,WAAW,IAAwBC,WAAWC,mBAAmB,KAAK,EAAE;AAE9E,MAAMC,iBAAiB,CACrBC,OACAC,SACAC,WACAC,MAEAH,MAAMI,SAAS,CACb,CAACC,QACCA,MAAMC,QAAQ,GAAGH,OACjBE,MAAMJ,OAAO,KAAKA,WAClB,oDAAoD;QACpDI,MAAMH,SAAS,KAAKA;AAG1B,0EAA0E,GAC1E,OAAO,MAAMK,mBAAmB,CAC9BN,SACAC,WACAM;IAEA,MAAMR,QAAQJ;IACd,MAAMO,MAAMM,KAAKN,GAAG;IACpB,oEAAoE;IACpEO,QAAQC,OAAO,CAACH,UAAUI,KAAK,CAAC,KAAO;IACvCZ,MAAMa,IAAI,CAAC;QAAEZ;QAASC;QAAWM;QAAUF,UAAUH,MAAMT;IAAa;IACxE,MACEM,MAAMc,MAAM,GAAG,KACdd,CAAAA,MAAMc,MAAM,GAAGnB,kBAAkBK,KAAK,CAAC,EAAE,CAAEM,QAAQ,IAAIH,GAAE,EAC1D;QACAH,MAAMe,KAAK;IACb;AACF,EAAE;AAEF,OAAO,MAAMC,mBAAmB,CAC9Bf,SACAC;IAEA,MAAMF,QAAQJ;IACd,OAAOG,eAAeC,OAAOC,SAASC,WAAWO,KAAKN,GAAG,OAAO;AAClE,EAAE;AAEF,kEAAkE,GAClE,OAAO,MAAMc,uBAAuB,CAClChB,SACAC;IAEA,MAAMF,QAAQJ;IACd,MAAMsB,QAAQnB,eAAeC,OAAOC,SAASC,WAAWO,KAAKN,GAAG;IAChE,IAAIe,QAAQ,GAAG;QACb,OAAOC;IACT;IACA,OAAOnB,MAAMoB,MAAM,CAACF,OAAO,EAAE,CAAC,EAAE,CAAEV,QAAQ;AAC5C,EAAE"}
@@ -2,6 +2,5 @@ export declare const stringToStream: (str: string) => ReadableStream;
2
2
  export declare const streamToBytes: (stream: ReadableStream) => Promise<Uint8Array>;
3
3
  export declare const bytesToStream: (bytes: Uint8Array) => ReadableStream;
4
4
  export declare function batchReadableStream(input: ReadableStream<Uint8Array>): ReadableStream<Uint8Array>;
5
- export declare function deferReadableStream<T>(stream: ReadableStream<T>, promise: Promise<void>): ReadableStream<T>;
6
5
  export declare function produceMultiplexedStream(fn: (callback: (key: string, stream: ReadableStream) => Promise<void>) => Promise<void>): ReadableStream<Uint8Array>;
7
6
  export declare function consumeMultiplexedStream(frameStream: ReadableStream<Uint8Array>, callback: (key: string, stream: ReadableStream<Uint8Array>) => Promise<void>): Promise<void>;
@@ -71,41 +71,6 @@ export function batchReadableStream(input) {
71
71
  }
72
72
  }));
73
73
  }
74
- export function deferReadableStream(stream, promise) {
75
- const reader = stream.getReader();
76
- let canceled = false;
77
- return new ReadableStream({
78
- async pull (controller) {
79
- try {
80
- const { done, value } = await reader.read();
81
- if (done) {
82
- await promise;
83
- if (!canceled) {
84
- controller.close();
85
- }
86
- reader.releaseLock();
87
- return;
88
- }
89
- if (!canceled) {
90
- controller.enqueue(value);
91
- }
92
- } catch (error) {
93
- if (!canceled) {
94
- controller.error(error);
95
- }
96
- reader.releaseLock();
97
- }
98
- },
99
- async cancel (reason) {
100
- canceled = true;
101
- try {
102
- await reader.cancel(reason);
103
- } finally{
104
- reader.releaseLock();
105
- }
106
- }
107
- });
108
- }
109
74
  // Stream Multiplexer
110
75
  const FRAME_START = 0x01;
111
76
  const FRAME_CHUNK = 0x02;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/lib/utils/stream.ts"],"sourcesContent":["// Utility functions for web streams (not Node.js streams)\n\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder();\n\nexport const stringToStream = (str: string): ReadableStream => {\n return new ReadableStream({\n start(controller) {\n controller.enqueue(encoder.encode(str));\n controller.close();\n },\n });\n};\n\nexport const streamToBytes = async (\n stream: ReadableStream,\n): Promise<Uint8Array> => {\n const reader = stream.getReader();\n const chunks: Uint8Array[] = [];\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n break;\n }\n if (!(value instanceof Uint8Array)) {\n throw new Error('Unexpected buffer type');\n }\n chunks.push(value);\n }\n return concatUint8Array(chunks);\n};\n\nexport const bytesToStream = (bytes: Uint8Array): ReadableStream =>\n new ReadableStream({\n start(controller) {\n controller.enqueue(bytes);\n controller.close();\n },\n });\n\nfunction concatUint8Array(chunks: readonly Uint8Array[]): Uint8Array {\n if (chunks.length === 1) {\n return chunks[0]!;\n }\n const total = chunks.reduce((n, chunk) => n + chunk.byteLength, 0);\n const out = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n out.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return out;\n}\n\nexport function batchReadableStream(\n input: ReadableStream<Uint8Array>,\n): ReadableStream<Uint8Array> {\n const buffer: Uint8Array[] = [];\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const flushBuffer = (\n controller: TransformStreamDefaultController<Uint8Array>,\n ): void => {\n clearTimeout(timer);\n timer = undefined;\n if (buffer.length) {\n try {\n controller.enqueue(concatUint8Array(buffer));\n } catch {\n // ignore errors\n // ref: https://github.com/wakujs/waku/pull/1863#discussion_r2634546953\n }\n buffer.length = 0;\n }\n };\n\n return input.pipeThrough(\n new TransformStream({\n transform(chunk, controller) {\n buffer.push(chunk);\n if (!timer) {\n timer = setTimeout(() => flushBuffer(controller));\n }\n },\n flush(controller) {\n flushBuffer(controller);\n },\n }),\n );\n}\n\nexport function deferReadableStream<T>(\n stream: ReadableStream<T>,\n promise: Promise<void>,\n): ReadableStream<T> {\n const reader = stream.getReader();\n let canceled = false;\n return new ReadableStream<T>({\n async pull(controller) {\n try {\n const { done, value } = await reader.read();\n if (done) {\n await promise;\n if (!canceled) {\n controller.close();\n }\n reader.releaseLock();\n return;\n }\n if (!canceled) {\n controller.enqueue(value);\n }\n } catch (error) {\n if (!canceled) {\n controller.error(error);\n }\n reader.releaseLock();\n }\n },\n async cancel(reason) {\n canceled = true;\n try {\n await reader.cancel(reason);\n } finally {\n reader.releaseLock();\n }\n },\n });\n}\n\n// Stream Multiplexer\n\nconst FRAME_START = 0x01;\nconst FRAME_CHUNK = 0x02;\nconst FRAME_END = 0x03;\nconst FRAME_ERROR = 0x04;\n\nfunction encodeStart(key: string): Uint8Array {\n const keyBytes = encoder.encode(key);\n const out = new Uint8Array(1 + 2 + keyBytes.length);\n out[0] = FRAME_START;\n new DataView(out.buffer).setUint16(1, keyBytes.length);\n out.set(keyBytes, 3);\n return out;\n}\n\nfunction encodeEnd(key: string): Uint8Array {\n const keyBytes = encoder.encode(key);\n const out = new Uint8Array(1 + 2 + keyBytes.length);\n out[0] = FRAME_END;\n new DataView(out.buffer).setUint16(1, keyBytes.length);\n out.set(keyBytes, 3);\n return out;\n}\n\nfunction encodeChunk(key: string, chunk: Uint8Array): Uint8Array {\n const keyBytes = encoder.encode(key);\n const out = new Uint8Array(1 + 2 + keyBytes.length + 4 + chunk.length);\n let offset = 0;\n out[offset++] = FRAME_CHUNK;\n new DataView(out.buffer).setUint16(offset, keyBytes.length);\n offset += 2;\n out.set(keyBytes, offset);\n offset += keyBytes.length;\n new DataView(out.buffer).setUint32(offset, chunk.length);\n offset += 4;\n out.set(chunk, offset);\n return out;\n}\n\nfunction encodeError(key: string, error: unknown): Uint8Array {\n const keyBytes = encoder.encode(key);\n const payload = encoder.encode(String(error));\n const out = new Uint8Array(1 + 2 + keyBytes.length + 4 + payload.length);\n let offset = 0;\n out[offset++] = FRAME_ERROR;\n new DataView(out.buffer).setUint16(offset, keyBytes.length);\n offset += 2;\n out.set(keyBytes, offset);\n offset += keyBytes.length;\n new DataView(out.buffer).setUint32(offset, payload.length);\n offset += 4;\n out.set(payload, offset);\n return out;\n}\n\nfunction createFrameDispatcher(\n onStart: (key: string) => void,\n onChunk: (key: string, chunk: Uint8Array) => void,\n onEnd: (key: string) => void,\n onError: (key: string, error: string) => void,\n): (data: Uint8Array) => void {\n let buffer: Uint8Array = new Uint8Array(0);\n\n return (data: Uint8Array): void => {\n buffer = concatUint8Array([buffer, data]);\n\n while (buffer.length > 0) {\n const frameType = buffer[0]!;\n // Need at least frameType + 2 byte key length\n if (buffer.length < 3) {\n break;\n }\n const keyLen = new DataView(buffer.buffer, buffer.byteOffset).getUint16(\n 1,\n );\n const headerLen = 1 + 2 + keyLen;\n if (buffer.length < headerLen) {\n break;\n }\n const key = decoder.decode(buffer.slice(3, 3 + keyLen));\n\n if (frameType === FRAME_START) {\n onStart(key);\n buffer = buffer.slice(headerLen);\n continue;\n }\n if (frameType === FRAME_END) {\n onEnd(key);\n buffer = buffer.slice(headerLen);\n continue;\n }\n // chunk or error: need 4 byte payload length\n if (buffer.length < headerLen + 4) {\n break;\n }\n const payloadLen = new DataView(\n buffer.buffer,\n buffer.byteOffset,\n ).getUint32(headerLen);\n const totalLen = headerLen + 4 + payloadLen;\n if (buffer.length < totalLen) {\n break;\n }\n const payload = buffer.slice(headerLen + 4, totalLen);\n\n if (frameType === FRAME_CHUNK) {\n onChunk(key, payload);\n } else if (frameType === FRAME_ERROR) {\n onError(key, decoder.decode(payload));\n } else {\n throw new Error(`Unknown frame type: ${frameType}`);\n }\n buffer = buffer.slice(totalLen);\n }\n };\n}\n\nexport function produceMultiplexedStream(\n fn: (\n callback: (key: string, stream: ReadableStream) => Promise<void>,\n ) => Promise<void>,\n): ReadableStream<Uint8Array> {\n let controller: ReadableStreamDefaultController<Uint8Array>;\n\n const frameStream = new ReadableStream<Uint8Array>({\n start(c) {\n controller = c;\n },\n });\n\n const callback = async (key: string, stream: ReadableStream) => {\n controller.enqueue(encodeStart(key));\n const reader = stream.getReader();\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n break;\n }\n if (!(value instanceof Uint8Array)) {\n throw new Error('Unexpected buffer type');\n }\n controller.enqueue(encodeChunk(key, value));\n }\n controller.enqueue(encodeEnd(key));\n } catch (err) {\n controller.enqueue(encodeError(key, err));\n }\n };\n\n fn(callback).then(\n () => controller.close(),\n (err) => controller.error(err),\n );\n\n return frameStream;\n}\n\nexport async function consumeMultiplexedStream(\n frameStream: ReadableStream<Uint8Array>,\n callback: (key: string, stream: ReadableStream<Uint8Array>) => Promise<void>,\n): Promise<void> {\n const controllers = new Map<\n string,\n ReadableStreamDefaultController<Uint8Array>\n >();\n const promises: Promise<void>[] = [];\n const dispatchFrame = createFrameDispatcher(\n // onStart\n (key) => {\n const stream = new ReadableStream<Uint8Array>({\n start(c) {\n controllers.set(key, c);\n },\n });\n promises.push(callback(key, stream));\n },\n // onChunk\n (key, chunk) => {\n controllers.get(key)?.enqueue(chunk);\n },\n // onEnd\n (key) => {\n controllers.get(key)?.close();\n controllers.delete(key);\n },\n // onError\n (key, error) => {\n controllers.get(key)?.error(error);\n controllers.delete(key);\n },\n );\n\n const reader = frameStream.getReader();\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n break;\n }\n dispatchFrame(value);\n }\n\n await Promise.all(promises);\n}\n"],"names":["encoder","TextEncoder","decoder","TextDecoder","stringToStream","str","ReadableStream","start","controller","enqueue","encode","close","streamToBytes","stream","reader","getReader","chunks","value","done","read","Uint8Array","Error","push","concatUint8Array","bytesToStream","bytes","length","total","reduce","n","chunk","byteLength","out","offset","set","batchReadableStream","input","buffer","timer","flushBuffer","clearTimeout","undefined","pipeThrough","TransformStream","transform","setTimeout","flush","deferReadableStream","promise","canceled","pull","releaseLock","error","cancel","reason","FRAME_START","FRAME_CHUNK","FRAME_END","FRAME_ERROR","encodeStart","key","keyBytes","DataView","setUint16","encodeEnd","encodeChunk","setUint32","encodeError","payload","String","createFrameDispatcher","onStart","onChunk","onEnd","onError","data","frameType","keyLen","byteOffset","getUint16","headerLen","decode","slice","payloadLen","getUint32","totalLen","produceMultiplexedStream","fn","frameStream","c","callback","err","then","consumeMultiplexedStream","controllers","Map","promises","dispatchFrame","get","delete","Promise","all"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/lib/utils/stream.ts"],"sourcesContent":["// Utility functions for web streams (not Node.js streams)\n\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder();\n\nexport const stringToStream = (str: string): ReadableStream => {\n return new ReadableStream({\n start(controller) {\n controller.enqueue(encoder.encode(str));\n controller.close();\n },\n });\n};\n\nexport const streamToBytes = async (\n stream: ReadableStream,\n): Promise<Uint8Array> => {\n const reader = stream.getReader();\n const chunks: Uint8Array[] = [];\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n break;\n }\n if (!(value instanceof Uint8Array)) {\n throw new Error('Unexpected buffer type');\n }\n chunks.push(value);\n }\n return concatUint8Array(chunks);\n};\n\nexport const bytesToStream = (bytes: Uint8Array): ReadableStream =>\n new ReadableStream({\n start(controller) {\n controller.enqueue(bytes);\n controller.close();\n },\n });\n\nfunction concatUint8Array(chunks: readonly Uint8Array[]): Uint8Array {\n if (chunks.length === 1) {\n return chunks[0]!;\n }\n const total = chunks.reduce((n, chunk) => n + chunk.byteLength, 0);\n const out = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n out.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return out;\n}\n\nexport function batchReadableStream(\n input: ReadableStream<Uint8Array>,\n): ReadableStream<Uint8Array> {\n const buffer: Uint8Array[] = [];\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const flushBuffer = (\n controller: TransformStreamDefaultController<Uint8Array>,\n ): void => {\n clearTimeout(timer);\n timer = undefined;\n if (buffer.length) {\n try {\n controller.enqueue(concatUint8Array(buffer));\n } catch {\n // ignore errors\n // ref: https://github.com/wakujs/waku/pull/1863#discussion_r2634546953\n }\n buffer.length = 0;\n }\n };\n\n return input.pipeThrough(\n new TransformStream({\n transform(chunk, controller) {\n buffer.push(chunk);\n if (!timer) {\n timer = setTimeout(() => flushBuffer(controller));\n }\n },\n flush(controller) {\n flushBuffer(controller);\n },\n }),\n );\n}\n\n// Stream Multiplexer\n\nconst FRAME_START = 0x01;\nconst FRAME_CHUNK = 0x02;\nconst FRAME_END = 0x03;\nconst FRAME_ERROR = 0x04;\n\nfunction encodeStart(key: string): Uint8Array {\n const keyBytes = encoder.encode(key);\n const out = new Uint8Array(1 + 2 + keyBytes.length);\n out[0] = FRAME_START;\n new DataView(out.buffer).setUint16(1, keyBytes.length);\n out.set(keyBytes, 3);\n return out;\n}\n\nfunction encodeEnd(key: string): Uint8Array {\n const keyBytes = encoder.encode(key);\n const out = new Uint8Array(1 + 2 + keyBytes.length);\n out[0] = FRAME_END;\n new DataView(out.buffer).setUint16(1, keyBytes.length);\n out.set(keyBytes, 3);\n return out;\n}\n\nfunction encodeChunk(key: string, chunk: Uint8Array): Uint8Array {\n const keyBytes = encoder.encode(key);\n const out = new Uint8Array(1 + 2 + keyBytes.length + 4 + chunk.length);\n let offset = 0;\n out[offset++] = FRAME_CHUNK;\n new DataView(out.buffer).setUint16(offset, keyBytes.length);\n offset += 2;\n out.set(keyBytes, offset);\n offset += keyBytes.length;\n new DataView(out.buffer).setUint32(offset, chunk.length);\n offset += 4;\n out.set(chunk, offset);\n return out;\n}\n\nfunction encodeError(key: string, error: unknown): Uint8Array {\n const keyBytes = encoder.encode(key);\n const payload = encoder.encode(String(error));\n const out = new Uint8Array(1 + 2 + keyBytes.length + 4 + payload.length);\n let offset = 0;\n out[offset++] = FRAME_ERROR;\n new DataView(out.buffer).setUint16(offset, keyBytes.length);\n offset += 2;\n out.set(keyBytes, offset);\n offset += keyBytes.length;\n new DataView(out.buffer).setUint32(offset, payload.length);\n offset += 4;\n out.set(payload, offset);\n return out;\n}\n\nfunction createFrameDispatcher(\n onStart: (key: string) => void,\n onChunk: (key: string, chunk: Uint8Array) => void,\n onEnd: (key: string) => void,\n onError: (key: string, error: string) => void,\n): (data: Uint8Array) => void {\n let buffer: Uint8Array = new Uint8Array(0);\n\n return (data: Uint8Array): void => {\n buffer = concatUint8Array([buffer, data]);\n\n while (buffer.length > 0) {\n const frameType = buffer[0]!;\n // Need at least frameType + 2 byte key length\n if (buffer.length < 3) {\n break;\n }\n const keyLen = new DataView(buffer.buffer, buffer.byteOffset).getUint16(\n 1,\n );\n const headerLen = 1 + 2 + keyLen;\n if (buffer.length < headerLen) {\n break;\n }\n const key = decoder.decode(buffer.slice(3, 3 + keyLen));\n\n if (frameType === FRAME_START) {\n onStart(key);\n buffer = buffer.slice(headerLen);\n continue;\n }\n if (frameType === FRAME_END) {\n onEnd(key);\n buffer = buffer.slice(headerLen);\n continue;\n }\n // chunk or error: need 4 byte payload length\n if (buffer.length < headerLen + 4) {\n break;\n }\n const payloadLen = new DataView(\n buffer.buffer,\n buffer.byteOffset,\n ).getUint32(headerLen);\n const totalLen = headerLen + 4 + payloadLen;\n if (buffer.length < totalLen) {\n break;\n }\n const payload = buffer.slice(headerLen + 4, totalLen);\n\n if (frameType === FRAME_CHUNK) {\n onChunk(key, payload);\n } else if (frameType === FRAME_ERROR) {\n onError(key, decoder.decode(payload));\n } else {\n throw new Error(`Unknown frame type: ${frameType}`);\n }\n buffer = buffer.slice(totalLen);\n }\n };\n}\n\nexport function produceMultiplexedStream(\n fn: (\n callback: (key: string, stream: ReadableStream) => Promise<void>,\n ) => Promise<void>,\n): ReadableStream<Uint8Array> {\n let controller: ReadableStreamDefaultController<Uint8Array>;\n\n const frameStream = new ReadableStream<Uint8Array>({\n start(c) {\n controller = c;\n },\n });\n\n const callback = async (key: string, stream: ReadableStream) => {\n controller.enqueue(encodeStart(key));\n const reader = stream.getReader();\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n break;\n }\n if (!(value instanceof Uint8Array)) {\n throw new Error('Unexpected buffer type');\n }\n controller.enqueue(encodeChunk(key, value));\n }\n controller.enqueue(encodeEnd(key));\n } catch (err) {\n controller.enqueue(encodeError(key, err));\n }\n };\n\n fn(callback).then(\n () => controller.close(),\n (err) => controller.error(err),\n );\n\n return frameStream;\n}\n\nexport async function consumeMultiplexedStream(\n frameStream: ReadableStream<Uint8Array>,\n callback: (key: string, stream: ReadableStream<Uint8Array>) => Promise<void>,\n): Promise<void> {\n const controllers = new Map<\n string,\n ReadableStreamDefaultController<Uint8Array>\n >();\n const promises: Promise<void>[] = [];\n const dispatchFrame = createFrameDispatcher(\n // onStart\n (key) => {\n const stream = new ReadableStream<Uint8Array>({\n start(c) {\n controllers.set(key, c);\n },\n });\n promises.push(callback(key, stream));\n },\n // onChunk\n (key, chunk) => {\n controllers.get(key)?.enqueue(chunk);\n },\n // onEnd\n (key) => {\n controllers.get(key)?.close();\n controllers.delete(key);\n },\n // onError\n (key, error) => {\n controllers.get(key)?.error(error);\n controllers.delete(key);\n },\n );\n\n const reader = frameStream.getReader();\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n break;\n }\n dispatchFrame(value);\n }\n\n await Promise.all(promises);\n}\n"],"names":["encoder","TextEncoder","decoder","TextDecoder","stringToStream","str","ReadableStream","start","controller","enqueue","encode","close","streamToBytes","stream","reader","getReader","chunks","value","done","read","Uint8Array","Error","push","concatUint8Array","bytesToStream","bytes","length","total","reduce","n","chunk","byteLength","out","offset","set","batchReadableStream","input","buffer","timer","flushBuffer","clearTimeout","undefined","pipeThrough","TransformStream","transform","setTimeout","flush","FRAME_START","FRAME_CHUNK","FRAME_END","FRAME_ERROR","encodeStart","key","keyBytes","DataView","setUint16","encodeEnd","encodeChunk","setUint32","encodeError","error","payload","String","createFrameDispatcher","onStart","onChunk","onEnd","onError","data","frameType","keyLen","byteOffset","getUint16","headerLen","decode","slice","payloadLen","getUint32","totalLen","produceMultiplexedStream","fn","frameStream","c","callback","err","then","consumeMultiplexedStream","controllers","Map","promises","dispatchFrame","get","delete","Promise","all"],"mappings":"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"}
@@ -192,7 +192,7 @@ export async function generateFsRouterTypes(pagesDir) {
192
192
  '// deno-fmt-ignore-file',
193
193
  '// biome-ignore format: generated types do not need formatting',
194
194
  '// prettier-ignore',
195
- hasAnyGetConfig ? "import type { PathsForPages, GetConfigResponse } from 'waku/router';" : "import type { PathsForPages } from 'waku/router';"
195
+ hasAnyGetConfig ? "import type { PathsForPages, GetConfigResponse, SearchCodecsForPages } from 'waku/router';" : "import type { PathsForPages } from 'waku/router';"
196
196
  ];
197
197
  if (hasAnyGetConfig) {
198
198
  lines.push('');
@@ -214,7 +214,9 @@ export async function generateFsRouterTypes(pagesDir) {
214
214
  }
215
215
  }
216
216
  lines[lines.length - 1] += ';';
217
- lines.push('', '// prettier-ignore', "declare module 'waku/router' {", ' interface RouteConfig {', ' paths: PathsForPages<Page>;', ' }', ' interface CreatePagesConfig {', ' pages: Page;', ' }', '}', '');
217
+ lines.push('', '// prettier-ignore', "declare module 'waku/router' {", ' interface RouteConfig {', ' paths: PathsForPages<Page>;', ' }', ' interface CreatePagesConfig {', ' pages: Page;', ' }', ...hasAnyGetConfig ? [
218
+ ' interface SearchCodecsConfig extends SearchCodecsForPages<Page> {}'
219
+ ] : [], '}', '');
218
220
  const result = lines.join('\n');
219
221
  return result;
220
222
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/lib/vite-plugins/fs-router-typegen.ts"],"sourcesContent":["// waku/router is technically a separate library from waku core.\n// This file is an exception placed here so that fsRouter users\n// get automatic type generation with better DX.\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { readdir, writeFile } from 'node:fs/promises';\nimport { parse, transformWithOxc } from 'vite';\nimport type { ParseResult, Plugin } from 'vite';\nimport { EXTENSIONS, SRC_PAGES, SRC_SERVER_ENTRY } from '../constants.js';\nimport { getGrouplessPath } from '../utils/create-pages.js';\nimport { isIgnoredPath } from '../utils/fs-router.js';\nimport { joinPath } from '../utils/path.js';\n\ntype ProgramNode = ParseResult['program'];\ntype ImportDeclaration = ProgramNode['body'][number] & {\n type: 'ImportDeclaration';\n};\ntype ImportSpecifier = ImportDeclaration['specifiers'][number] & {\n type: 'ImportSpecifier';\n};\ntype ExportNamedDeclaration = ProgramNode['body'][number] & {\n type: 'ExportNamedDeclaration';\n};\ntype ExportSpecifier = ExportNamedDeclaration['specifiers'][number] & {\n type: 'ExportSpecifier';\n};\n\n// https://tc39.es/ecma262/multipage/ecmascript-language-lexical-grammar.html#sec-names-and-keywords\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#identifiers\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#reserved_words\nexport function toIdentifier(input: string): string {\n // Strip the file extension\n let identifier = input.includes('.')\n ? input.split('.').slice(0, -1).join('.')\n : input;\n // Replace any characters besides letters, numbers, underscores, and dollar signs with underscores\n identifier = identifier.replace(/[^\\p{L}\\p{N}_$]/gu, '_');\n // Ensure it starts with a letter\n if (/^\\d/.test(identifier)) {\n identifier = '_' + identifier;\n }\n // Turn it into PascalCase\n // Since the first letter is uppercased, it will not be a reserved word\n return (\n 'File_' +\n identifier\n .split('_')\n .map((part) => {\n if (part[0] === undefined) {\n return '';\n }\n return part[0].toUpperCase() + part.slice(1);\n })\n .join('')\n );\n}\n\nexport function getImportModuleNames(filePaths: string[]): {\n [k: string]: string;\n} {\n const moduleNameCount: { [k: string]: number } = {};\n const moduleNames: { [k: string]: string } = {};\n for (const filePath of filePaths) {\n let identifier = toIdentifier(filePath);\n moduleNameCount[identifier] = (moduleNameCount[identifier] ?? -1) + 1;\n if (moduleNameCount[identifier]) {\n identifier = `${identifier}_${moduleNameCount[identifier]}`;\n }\n try {\n moduleNames[filePath.replace(/^\\//, '')] = identifier;\n } catch (e) {\n console.log(e);\n }\n }\n return moduleNames;\n}\n\nconst parseModule = async (filePath: string) => {\n const source = readFileSync(filePath, 'utf8');\n const lang: 'jsx' | 'ts' | 'tsx' = filePath.endsWith('.tsx')\n ? 'tsx'\n : filePath.endsWith('.ts')\n ? 'ts'\n : 'jsx';\n const transformed = await transformWithOxc(source, filePath, {\n lang,\n jsx: 'preserve',\n });\n return (await parse(filePath, transformed.code, { lang } as never)).program;\n};\n\nconst getImportedName = (specifier: ImportSpecifier) =>\n specifier.imported.type === 'Identifier'\n ? specifier.imported.name\n : String(specifier.imported.value);\n\nconst getExportedName = (specifier: ExportSpecifier) =>\n specifier.exported.type === 'Identifier'\n ? specifier.exported.name\n : String(specifier.exported.value);\n\nexport const fsRouterTypegenPlugin = (opts: { srcDir: string }): Plugin => {\n return {\n name: 'waku:vite-plugins:fs-router-typegen',\n apply: 'serve',\n configureServer(server) {\n const srcDir = joinPath(server.config.root, opts.srcDir);\n const pagesDir = joinPath(srcDir, SRC_PAGES);\n\n const outputFile = joinPath(srcDir, 'pages.gen.ts');\n const updateGeneratedFile = async (file: string | undefined) => {\n // skip when the changed file is the generated file itself\n if (file && outputFile.endsWith(file)) {\n return;\n }\n // skip when the entries file exists or pages dir does not exist\n if (!existsSync(pagesDir) || !(await detectFsRouterUsage(srcDir))) {\n return;\n }\n const generation = await generateFsRouterTypes(pagesDir);\n if (!generation) {\n // skip failures\n return;\n }\n await writeFile(outputFile, generation, 'utf-8');\n };\n\n server.watcher.on('change', async (file) => {\n await updateGeneratedFile(file);\n });\n server.watcher.on('add', async (file) => {\n await updateGeneratedFile(file);\n });\n server.watcher.on('unlink', async (file) => {\n await updateGeneratedFile(file);\n });\n void updateGeneratedFile(undefined);\n },\n };\n};\n\nexport async function detectFsRouterUsage(srcDir: string): Promise<boolean> {\n const existingServerEntry = EXTENSIONS.map((ext) =>\n joinPath(srcDir, SRC_SERVER_ENTRY + ext),\n ).find((entriesFile) => existsSync(entriesFile));\n\n // managed mode if no entry\n if (!existingServerEntry) {\n return true;\n }\n\n try {\n const file = await parseModule(existingServerEntry);\n const usesFsRouter = file.body.some((node) => {\n if (node.type !== 'ImportDeclaration') {\n return false;\n }\n if (\n node.source.type !== 'Literal' ||\n typeof node.source.value !== 'string' ||\n !node.source.value.startsWith('waku')\n ) {\n return false;\n }\n return node.specifiers.some((specifier) => {\n if (\n specifier.type !== 'ImportSpecifier' ||\n specifier.local.type !== 'Identifier'\n ) {\n return false;\n }\n return getImportedName(specifier) === 'fsRouter';\n });\n });\n return usesFsRouter;\n } catch {\n return false;\n }\n}\n\nexport async function generateFsRouterTypes(pagesDir: string) {\n // Recursively collect `.tsx` files in the given directory\n const collectFiles = async (\n dir: string,\n files: string[] = [],\n ): Promise<string[]> => {\n // TODO revisit recursive option for readdir once more stable\n // https://nodejs.org/docs/latest-v20.x/api/fs.html#direntparentpath\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = joinPath(dir, entry.name);\n if (entry.isDirectory()) {\n await collectFiles(fullPath, files);\n } else {\n if (entry.name.endsWith('.tsx')) {\n files.push(fullPath.slice(pagesDir.length));\n }\n }\n }\n return files;\n };\n\n const fileExportsGetConfig = async (filePath: string) => {\n const file = await parseModule(pagesDir + filePath);\n return file.body.some((node) => {\n if (node.type !== 'ExportNamedDeclaration') {\n return false;\n }\n if (\n node.declaration?.type === 'VariableDeclaration' &&\n node.declaration.declarations.some(\n (decl) =>\n decl.id.type === 'Identifier' && decl.id.name === 'getConfig',\n )\n ) {\n return true;\n }\n if (\n node.declaration?.type === 'FunctionDeclaration' &&\n node.declaration.id?.name === 'getConfig'\n ) {\n return true;\n }\n return node.specifiers.some(\n (specifier) =>\n specifier.type === 'ExportSpecifier' &&\n getExportedName(specifier) === 'getConfig',\n );\n });\n };\n\n const generateFile = async (filePaths: string[]): Promise<string | null> => {\n const fileInfo: { path: string; src: string; hasGetConfig: boolean }[] = [];\n const moduleNames = getImportModuleNames(filePaths);\n\n for (const filePath of filePaths) {\n // where to import the component from\n const src = filePath.replace(/^\\//, '');\n let hasGetConfig: boolean;\n try {\n hasGetConfig = await fileExportsGetConfig(filePath);\n } catch {\n return null;\n }\n\n if (\n filePath.endsWith('/_layout.tsx') ||\n isIgnoredPath(filePath.split('/'))\n ) {\n continue;\n } else if (filePath.endsWith('/index.tsx')) {\n const path = filePath.slice(0, -'/index.tsx'.length);\n fileInfo.push({\n path: getGrouplessPath(path) || '/',\n src,\n hasGetConfig,\n });\n } else {\n fileInfo.push({\n path: getGrouplessPath(filePath.replace('.tsx', '')),\n src,\n hasGetConfig,\n });\n }\n }\n\n const hasAnyGetConfig = fileInfo.some((file) => file.hasGetConfig);\n const lines: string[] = [\n '// deno-fmt-ignore-file',\n '// biome-ignore format: generated types do not need formatting',\n '// prettier-ignore',\n hasAnyGetConfig\n ? \"import type { PathsForPages, GetConfigResponse } from 'waku/router';\"\n : \"import type { PathsForPages } from 'waku/router';\",\n ];\n\n if (hasAnyGetConfig) {\n lines.push('');\n for (const file of fileInfo) {\n if (!file.hasGetConfig) {\n continue;\n }\n const moduleName = moduleNames[file.src];\n lines.push(\n `// prettier-ignore`,\n `import type { getConfig as ${moduleName}_getConfig } from './${SRC_PAGES}/${file.src.replace('.tsx', '')}';`,\n );\n }\n }\n\n lines.push('', '// prettier-ignore', 'type Page =');\n for (const file of fileInfo) {\n const moduleName = moduleNames[file.src];\n if (file.hasGetConfig) {\n lines.push(\n `| ({ path: '${file.path}' } & GetConfigResponse<typeof ${moduleName}_getConfig>)`,\n );\n } else {\n lines.push(`| { path: '${file.path}'; render: 'static' }`);\n }\n }\n lines[lines.length - 1] += ';';\n\n lines.push(\n '',\n '// prettier-ignore',\n \"declare module 'waku/router' {\",\n ' interface RouteConfig {',\n ' paths: PathsForPages<Page>;',\n ' }',\n ' interface CreatePagesConfig {',\n ' pages: Page;',\n ' }',\n '}',\n '',\n );\n\n const result = lines.join('\\n');\n\n return result;\n };\n\n const files = await collectFiles(pagesDir);\n if (!files.length) {\n return;\n }\n const generation = await generateFile(files);\n return generation;\n}\n"],"names":["existsSync","readFileSync","readdir","writeFile","parse","transformWithOxc","EXTENSIONS","SRC_PAGES","SRC_SERVER_ENTRY","getGrouplessPath","isIgnoredPath","joinPath","toIdentifier","input","identifier","includes","split","slice","join","replace","test","map","part","undefined","toUpperCase","getImportModuleNames","filePaths","moduleNameCount","moduleNames","filePath","e","console","log","parseModule","source","lang","endsWith","transformed","jsx","code","program","getImportedName","specifier","imported","type","name","String","value","getExportedName","exported","fsRouterTypegenPlugin","opts","apply","configureServer","server","srcDir","config","root","pagesDir","outputFile","updateGeneratedFile","file","detectFsRouterUsage","generation","generateFsRouterTypes","watcher","on","existingServerEntry","ext","find","entriesFile","usesFsRouter","body","some","node","startsWith","specifiers","local","collectFiles","dir","files","entries","withFileTypes","entry","fullPath","isDirectory","push","length","fileExportsGetConfig","declaration","declarations","decl","id","generateFile","fileInfo","src","hasGetConfig","path","hasAnyGetConfig","lines","moduleName","result"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/lib/vite-plugins/fs-router-typegen.ts"],"sourcesContent":["// waku/router is technically a separate library from waku core.\n// This file is an exception placed here so that fsRouter users\n// get automatic type generation with better DX.\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { readdir, writeFile } from 'node:fs/promises';\nimport { parse, transformWithOxc } from 'vite';\nimport type { ParseResult, Plugin } from 'vite';\nimport { EXTENSIONS, SRC_PAGES, SRC_SERVER_ENTRY } from '../constants.js';\nimport { getGrouplessPath } from '../utils/create-pages.js';\nimport { isIgnoredPath } from '../utils/fs-router.js';\nimport { joinPath } from '../utils/path.js';\n\ntype ProgramNode = ParseResult['program'];\ntype ImportDeclaration = ProgramNode['body'][number] & {\n type: 'ImportDeclaration';\n};\ntype ImportSpecifier = ImportDeclaration['specifiers'][number] & {\n type: 'ImportSpecifier';\n};\ntype ExportNamedDeclaration = ProgramNode['body'][number] & {\n type: 'ExportNamedDeclaration';\n};\ntype ExportSpecifier = ExportNamedDeclaration['specifiers'][number] & {\n type: 'ExportSpecifier';\n};\n\n// https://tc39.es/ecma262/multipage/ecmascript-language-lexical-grammar.html#sec-names-and-keywords\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#identifiers\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#reserved_words\nexport function toIdentifier(input: string): string {\n // Strip the file extension\n let identifier = input.includes('.')\n ? input.split('.').slice(0, -1).join('.')\n : input;\n // Replace any characters besides letters, numbers, underscores, and dollar signs with underscores\n identifier = identifier.replace(/[^\\p{L}\\p{N}_$]/gu, '_');\n // Ensure it starts with a letter\n if (/^\\d/.test(identifier)) {\n identifier = '_' + identifier;\n }\n // Turn it into PascalCase\n // Since the first letter is uppercased, it will not be a reserved word\n return (\n 'File_' +\n identifier\n .split('_')\n .map((part) => {\n if (part[0] === undefined) {\n return '';\n }\n return part[0].toUpperCase() + part.slice(1);\n })\n .join('')\n );\n}\n\nexport function getImportModuleNames(filePaths: string[]): {\n [k: string]: string;\n} {\n const moduleNameCount: { [k: string]: number } = {};\n const moduleNames: { [k: string]: string } = {};\n for (const filePath of filePaths) {\n let identifier = toIdentifier(filePath);\n moduleNameCount[identifier] = (moduleNameCount[identifier] ?? -1) + 1;\n if (moduleNameCount[identifier]) {\n identifier = `${identifier}_${moduleNameCount[identifier]}`;\n }\n try {\n moduleNames[filePath.replace(/^\\//, '')] = identifier;\n } catch (e) {\n console.log(e);\n }\n }\n return moduleNames;\n}\n\nconst parseModule = async (filePath: string) => {\n const source = readFileSync(filePath, 'utf8');\n const lang: 'jsx' | 'ts' | 'tsx' = filePath.endsWith('.tsx')\n ? 'tsx'\n : filePath.endsWith('.ts')\n ? 'ts'\n : 'jsx';\n const transformed = await transformWithOxc(source, filePath, {\n lang,\n jsx: 'preserve',\n });\n return (await parse(filePath, transformed.code, { lang } as never)).program;\n};\n\nconst getImportedName = (specifier: ImportSpecifier) =>\n specifier.imported.type === 'Identifier'\n ? specifier.imported.name\n : String(specifier.imported.value);\n\nconst getExportedName = (specifier: ExportSpecifier) =>\n specifier.exported.type === 'Identifier'\n ? specifier.exported.name\n : String(specifier.exported.value);\n\nexport const fsRouterTypegenPlugin = (opts: { srcDir: string }): Plugin => {\n return {\n name: 'waku:vite-plugins:fs-router-typegen',\n apply: 'serve',\n configureServer(server) {\n const srcDir = joinPath(server.config.root, opts.srcDir);\n const pagesDir = joinPath(srcDir, SRC_PAGES);\n\n const outputFile = joinPath(srcDir, 'pages.gen.ts');\n const updateGeneratedFile = async (file: string | undefined) => {\n // skip when the changed file is the generated file itself\n if (file && outputFile.endsWith(file)) {\n return;\n }\n // skip when the entries file exists or pages dir does not exist\n if (!existsSync(pagesDir) || !(await detectFsRouterUsage(srcDir))) {\n return;\n }\n const generation = await generateFsRouterTypes(pagesDir);\n if (!generation) {\n // skip failures\n return;\n }\n await writeFile(outputFile, generation, 'utf-8');\n };\n\n server.watcher.on('change', async (file) => {\n await updateGeneratedFile(file);\n });\n server.watcher.on('add', async (file) => {\n await updateGeneratedFile(file);\n });\n server.watcher.on('unlink', async (file) => {\n await updateGeneratedFile(file);\n });\n void updateGeneratedFile(undefined);\n },\n };\n};\n\nexport async function detectFsRouterUsage(srcDir: string): Promise<boolean> {\n const existingServerEntry = EXTENSIONS.map((ext) =>\n joinPath(srcDir, SRC_SERVER_ENTRY + ext),\n ).find((entriesFile) => existsSync(entriesFile));\n\n // managed mode if no entry\n if (!existingServerEntry) {\n return true;\n }\n\n try {\n const file = await parseModule(existingServerEntry);\n const usesFsRouter = file.body.some((node) => {\n if (node.type !== 'ImportDeclaration') {\n return false;\n }\n if (\n node.source.type !== 'Literal' ||\n typeof node.source.value !== 'string' ||\n !node.source.value.startsWith('waku')\n ) {\n return false;\n }\n return node.specifiers.some((specifier) => {\n if (\n specifier.type !== 'ImportSpecifier' ||\n specifier.local.type !== 'Identifier'\n ) {\n return false;\n }\n return getImportedName(specifier) === 'fsRouter';\n });\n });\n return usesFsRouter;\n } catch {\n return false;\n }\n}\n\nexport async function generateFsRouterTypes(pagesDir: string) {\n // Recursively collect `.tsx` files in the given directory\n const collectFiles = async (\n dir: string,\n files: string[] = [],\n ): Promise<string[]> => {\n // TODO revisit recursive option for readdir once more stable\n // https://nodejs.org/docs/latest-v20.x/api/fs.html#direntparentpath\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = joinPath(dir, entry.name);\n if (entry.isDirectory()) {\n await collectFiles(fullPath, files);\n } else {\n if (entry.name.endsWith('.tsx')) {\n files.push(fullPath.slice(pagesDir.length));\n }\n }\n }\n return files;\n };\n\n const fileExportsGetConfig = async (filePath: string) => {\n const file = await parseModule(pagesDir + filePath);\n return file.body.some((node) => {\n if (node.type !== 'ExportNamedDeclaration') {\n return false;\n }\n if (\n node.declaration?.type === 'VariableDeclaration' &&\n node.declaration.declarations.some(\n (decl) =>\n decl.id.type === 'Identifier' && decl.id.name === 'getConfig',\n )\n ) {\n return true;\n }\n if (\n node.declaration?.type === 'FunctionDeclaration' &&\n node.declaration.id?.name === 'getConfig'\n ) {\n return true;\n }\n return node.specifiers.some(\n (specifier) =>\n specifier.type === 'ExportSpecifier' &&\n getExportedName(specifier) === 'getConfig',\n );\n });\n };\n\n const generateFile = async (filePaths: string[]): Promise<string | null> => {\n const fileInfo: { path: string; src: string; hasGetConfig: boolean }[] = [];\n const moduleNames = getImportModuleNames(filePaths);\n\n for (const filePath of filePaths) {\n // where to import the component from\n const src = filePath.replace(/^\\//, '');\n let hasGetConfig: boolean;\n try {\n hasGetConfig = await fileExportsGetConfig(filePath);\n } catch {\n return null;\n }\n\n if (\n filePath.endsWith('/_layout.tsx') ||\n isIgnoredPath(filePath.split('/'))\n ) {\n continue;\n } else if (filePath.endsWith('/index.tsx')) {\n const path = filePath.slice(0, -'/index.tsx'.length);\n fileInfo.push({\n path: getGrouplessPath(path) || '/',\n src,\n hasGetConfig,\n });\n } else {\n fileInfo.push({\n path: getGrouplessPath(filePath.replace('.tsx', '')),\n src,\n hasGetConfig,\n });\n }\n }\n\n const hasAnyGetConfig = fileInfo.some((file) => file.hasGetConfig);\n const lines: string[] = [\n '// deno-fmt-ignore-file',\n '// biome-ignore format: generated types do not need formatting',\n '// prettier-ignore',\n hasAnyGetConfig\n ? \"import type { PathsForPages, GetConfigResponse, SearchCodecsForPages } from 'waku/router';\"\n : \"import type { PathsForPages } from 'waku/router';\",\n ];\n\n if (hasAnyGetConfig) {\n lines.push('');\n for (const file of fileInfo) {\n if (!file.hasGetConfig) {\n continue;\n }\n const moduleName = moduleNames[file.src];\n lines.push(\n `// prettier-ignore`,\n `import type { getConfig as ${moduleName}_getConfig } from './${SRC_PAGES}/${file.src.replace('.tsx', '')}';`,\n );\n }\n }\n\n lines.push('', '// prettier-ignore', 'type Page =');\n for (const file of fileInfo) {\n const moduleName = moduleNames[file.src];\n if (file.hasGetConfig) {\n lines.push(\n `| ({ path: '${file.path}' } & GetConfigResponse<typeof ${moduleName}_getConfig>)`,\n );\n } else {\n lines.push(`| { path: '${file.path}'; render: 'static' }`);\n }\n }\n lines[lines.length - 1] += ';';\n\n lines.push(\n '',\n '// prettier-ignore',\n \"declare module 'waku/router' {\",\n ' interface RouteConfig {',\n ' paths: PathsForPages<Page>;',\n ' }',\n ' interface CreatePagesConfig {',\n ' pages: Page;',\n ' }',\n ...(hasAnyGetConfig\n ? [\n ' interface SearchCodecsConfig extends SearchCodecsForPages<Page> {}',\n ]\n : []),\n '}',\n '',\n );\n\n const result = lines.join('\\n');\n\n return result;\n };\n\n const files = await collectFiles(pagesDir);\n if (!files.length) {\n return;\n }\n const generation = await generateFile(files);\n return generation;\n}\n"],"names":["existsSync","readFileSync","readdir","writeFile","parse","transformWithOxc","EXTENSIONS","SRC_PAGES","SRC_SERVER_ENTRY","getGrouplessPath","isIgnoredPath","joinPath","toIdentifier","input","identifier","includes","split","slice","join","replace","test","map","part","undefined","toUpperCase","getImportModuleNames","filePaths","moduleNameCount","moduleNames","filePath","e","console","log","parseModule","source","lang","endsWith","transformed","jsx","code","program","getImportedName","specifier","imported","type","name","String","value","getExportedName","exported","fsRouterTypegenPlugin","opts","apply","configureServer","server","srcDir","config","root","pagesDir","outputFile","updateGeneratedFile","file","detectFsRouterUsage","generation","generateFsRouterTypes","watcher","on","existingServerEntry","ext","find","entriesFile","usesFsRouter","body","some","node","startsWith","specifiers","local","collectFiles","dir","files","entries","withFileTypes","entry","fullPath","isDirectory","push","length","fileExportsGetConfig","declaration","declarations","decl","id","generateFile","fileInfo","src","hasGetConfig","path","hasAnyGetConfig","lines","moduleName","result"],"mappings":"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"}
@@ -7,18 +7,13 @@ import htmlShell from 'virtual:vite-rsc-waku/html-shell';
7
7
  import { INTERNAL_ServerRoot } from '../../minimal/client.js';
8
8
  import { getErrorInfo } from '../utils/custom-errors.js';
9
9
  import { sanitizeLog } from '../utils/log.js';
10
- import { waitForRootPrerequisites } from '../utils/rsc-stream.js';
11
10
  import { getBootstrapPreamble } from '../utils/ssr.js';
12
- import { batchReadableStream, deferReadableStream } from '../utils/stream.js';
11
+ import { batchReadableStream } from '../utils/stream.js';
13
12
  function createFromReadableStream(stream) {
14
- let resolve;
15
- const promise = new Promise((r)=>{
16
- resolve = r;
17
- });
18
- const deferredStream = deferReadableStream(stream, promise);
19
- const root = createFromReadableStreamBase(deferredStream);
20
- waitForRootPrerequisites(root).then(resolve, resolve);
21
- return root;
13
+ // DEV: hold the stream ~5s so React's late debug-channel chunks settle before close. https://github.com/wakujs/waku/pull/2154
14
+ return createFromReadableStreamBase(import.meta.env.DEV ? stream.pipeThrough(new TransformStream({
15
+ flush: ()=>new Promise((r)=>setTimeout(r, 5000))
16
+ })) : stream);
22
17
  }
23
18
  // This code runs on `ssr` environment,
24
19
  // i.e. it runs on server but without `react-server` condition.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/lib/vite-rsc/ssr.tsx"],"sourcesContent":["import { type ReactNode, captureOwnerStack, use } from 'react';\nimport { createFromReadableStream as createFromReadableStreamBase } from '@vitejs/plugin-rsc/ssr';\nimport type { ReactFormState } from 'react-dom/client';\nimport { renderToReadableStream } from 'react-dom/server.edge';\nimport { injectRSCPayload } from 'rsc-html-stream/server';\nimport htmlShell from 'virtual:vite-rsc-waku/html-shell';\nimport { INTERNAL_ServerRoot } from '../../minimal/client.js';\nimport { getErrorInfo } from '../utils/custom-errors.js';\nimport { sanitizeLog } from '../utils/log.js';\nimport { waitForRootPrerequisites } from '../utils/rsc-stream.js';\nimport { getBootstrapPreamble } from '../utils/ssr.js';\nimport { batchReadableStream, deferReadableStream } from '../utils/stream.js';\n\nfunction createFromReadableStream<T>(\n stream: ReadableStream<Uint8Array>,\n): Promise<T> {\n let resolve!: () => void;\n const promise = new Promise<void>((r) => {\n resolve = r;\n });\n const deferredStream = deferReadableStream(stream, promise);\n const root = createFromReadableStreamBase<T>(deferredStream);\n waitForRootPrerequisites(root).then(resolve, resolve);\n return root;\n}\n\ntype RenderHtmlStream = (\n rscStream: ReadableStream<Uint8Array>,\n rscHtmlStream: ReadableStream<Uint8Array>,\n options: {\n rscPath: string | undefined;\n formState: ReactFormState | undefined;\n nonce: string | undefined;\n extraScriptContent: string | undefined;\n debugId: string | undefined;\n },\n) => Promise<{ stream: ReadableStream; status: number | undefined }>;\n\ntype RscElementsPayload = Record<string, unknown>;\ntype RscHtmlPayload = ReactNode;\n\n// This code runs on `ssr` environment,\n// i.e. it runs on server but without `react-server` condition.\n// These utilities are used by `rsc` environment through\n// `import.meta.viteRsc.loadModule` API.\n\nexport const renderHtmlStream: RenderHtmlStream = async (\n rscStream,\n rscHtmlStream,\n options,\n) => {\n const [stream1, stream2] = rscStream.tee();\n\n let elementsPromise: Promise<RscElementsPayload>;\n let htmlPromise: Promise<RscHtmlPayload>;\n\n // deserialize RSC stream back to React VDOM\n function SsrRoot() {\n // RSC stream needs to be deserialized inside SSR component.\n // This is for ReactDomServer preinit/preload (e.g. client reference modulepreload, css)\n // https://github.com/facebook/react/pull/31799#discussion_r1886166075\n elementsPromise ??= createFromReadableStream<RscElementsPayload>(stream1);\n htmlPromise ??= createFromReadableStream<RscHtmlPayload>(rscHtmlStream);\n return (\n <INTERNAL_ServerRoot elementsPromise={elementsPromise}>\n {use(htmlPromise)}\n </INTERNAL_ServerRoot>\n );\n }\n\n // render html\n const bootstrapScriptContent = await loadBootstrapScriptContent();\n let htmlStream: Awaited<ReturnType<typeof renderToReadableStream>>;\n let status: number | undefined;\n try {\n htmlStream = await renderToReadableStream(<SsrRoot />, {\n bootstrapScriptContent:\n getBootstrapPreamble({\n hydrate: true,\n debugId: options.debugId,\n }) +\n bootstrapScriptContent +\n (options.extraScriptContent || ''),\n onError: (e: unknown) => {\n if (\n e &&\n typeof e === 'object' &&\n 'digest' in e &&\n typeof e.digest === 'string'\n ) {\n return e.digest;\n }\n console.error(\n '[SSR Error]',\n sanitizeLog(captureOwnerStack?.() || ''),\n '\\n',\n sanitizeLog(e),\n );\n },\n ...(options.nonce ? { nonce: options.nonce } : {}),\n ...(options.formState ? { formState: options.formState } : {}),\n });\n } catch (e) {\n const info = getErrorInfo(e);\n if (info?.location) {\n // keep unstable_redirect error as http redirection\n throw e;\n }\n status = info?.status || 500;\n // SSR empty html and go full CSR on browser, which can revive RSC errors.\n const ssrErrorRoot = (\n <html>\n <body></body>\n </html>\n );\n htmlStream = await renderToReadableStream(ssrErrorRoot, {\n bootstrapScriptContent:\n getBootstrapPreamble({\n hydrate: false,\n }) +\n bootstrapScriptContent +\n (options.extraScriptContent || ''),\n ...(options.nonce ? { nonce: options.nonce } : {}),\n });\n }\n const responseStream: ReadableStream<Uint8Array> = htmlStream.pipeThrough(\n injectRSCPayload(\n batchReadableStream(stream2),\n options.nonce ? { nonce: options.nonce } : {},\n ),\n );\n\n return { stream: responseStream, status };\n};\n\nexport async function renderHtmlFallback() {\n const bootstrapScriptContent = await loadBootstrapScriptContent();\n const html = htmlShell.replace(\n '</body>',\n () => `<script>${bootstrapScriptContent}</script></body>`,\n );\n return html;\n}\n\nfunction loadBootstrapScriptContent(): Promise<string> {\n return import.meta.viteRsc.loadBootstrapScriptContent('index');\n}\n"],"names":["captureOwnerStack","use","createFromReadableStream","createFromReadableStreamBase","renderToReadableStream","injectRSCPayload","htmlShell","INTERNAL_ServerRoot","getErrorInfo","sanitizeLog","waitForRootPrerequisites","getBootstrapPreamble","batchReadableStream","deferReadableStream","stream","resolve","promise","Promise","r","deferredStream","root","then","renderHtmlStream","rscStream","rscHtmlStream","options","stream1","stream2","tee","elementsPromise","htmlPromise","SsrRoot","bootstrapScriptContent","loadBootstrapScriptContent","htmlStream","status","hydrate","debugId","extraScriptContent","onError","e","digest","console","error","nonce","formState","info","location","ssrErrorRoot","html","body","responseStream","pipeThrough","renderHtmlFallback","replace","viteRsc"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/lib/vite-rsc/ssr.tsx"],"sourcesContent":["import { type ReactNode, captureOwnerStack, use } from 'react';\nimport { createFromReadableStream as createFromReadableStreamBase } from '@vitejs/plugin-rsc/ssr';\nimport type { ReactFormState } from 'react-dom/client';\nimport { renderToReadableStream } from 'react-dom/server.edge';\nimport { injectRSCPayload } from 'rsc-html-stream/server';\nimport htmlShell from 'virtual:vite-rsc-waku/html-shell';\nimport { INTERNAL_ServerRoot } from '../../minimal/client.js';\nimport { getErrorInfo } from '../utils/custom-errors.js';\nimport { sanitizeLog } from '../utils/log.js';\nimport { getBootstrapPreamble } from '../utils/ssr.js';\nimport { batchReadableStream } from '../utils/stream.js';\n\nfunction createFromReadableStream<T>(\n stream: ReadableStream<Uint8Array>,\n): Promise<T> {\n // DEV: hold the stream ~5s so React's late debug-channel chunks settle before close. https://github.com/wakujs/waku/pull/2154\n return createFromReadableStreamBase<T>(\n import.meta.env.DEV\n ? stream.pipeThrough(\n new TransformStream({\n flush: () => new Promise((r) => setTimeout(r, 5000)),\n }),\n )\n : stream,\n );\n}\n\ntype RenderHtmlStream = (\n rscStream: ReadableStream<Uint8Array>,\n rscHtmlStream: ReadableStream<Uint8Array>,\n options: {\n rscPath: string | undefined;\n formState: ReactFormState | undefined;\n nonce: string | undefined;\n extraScriptContent: string | undefined;\n debugId: string | undefined;\n },\n) => Promise<{ stream: ReadableStream; status: number | undefined }>;\n\ntype RscElementsPayload = Record<string, unknown>;\ntype RscHtmlPayload = ReactNode;\n\n// This code runs on `ssr` environment,\n// i.e. it runs on server but without `react-server` condition.\n// These utilities are used by `rsc` environment through\n// `import.meta.viteRsc.loadModule` API.\n\nexport const renderHtmlStream: RenderHtmlStream = async (\n rscStream,\n rscHtmlStream,\n options,\n) => {\n const [stream1, stream2] = rscStream.tee();\n\n let elementsPromise: Promise<RscElementsPayload>;\n let htmlPromise: Promise<RscHtmlPayload>;\n\n // deserialize RSC stream back to React VDOM\n function SsrRoot() {\n // RSC stream needs to be deserialized inside SSR component.\n // This is for ReactDomServer preinit/preload (e.g. client reference modulepreload, css)\n // https://github.com/facebook/react/pull/31799#discussion_r1886166075\n elementsPromise ??= createFromReadableStream<RscElementsPayload>(stream1);\n htmlPromise ??= createFromReadableStream<RscHtmlPayload>(rscHtmlStream);\n return (\n <INTERNAL_ServerRoot elementsPromise={elementsPromise}>\n {use(htmlPromise)}\n </INTERNAL_ServerRoot>\n );\n }\n\n // render html\n const bootstrapScriptContent = await loadBootstrapScriptContent();\n let htmlStream: Awaited<ReturnType<typeof renderToReadableStream>>;\n let status: number | undefined;\n try {\n htmlStream = await renderToReadableStream(<SsrRoot />, {\n bootstrapScriptContent:\n getBootstrapPreamble({\n hydrate: true,\n debugId: options.debugId,\n }) +\n bootstrapScriptContent +\n (options.extraScriptContent || ''),\n onError: (e: unknown) => {\n if (\n e &&\n typeof e === 'object' &&\n 'digest' in e &&\n typeof e.digest === 'string'\n ) {\n return e.digest;\n }\n console.error(\n '[SSR Error]',\n sanitizeLog(captureOwnerStack?.() || ''),\n '\\n',\n sanitizeLog(e),\n );\n },\n ...(options.nonce ? { nonce: options.nonce } : {}),\n ...(options.formState ? { formState: options.formState } : {}),\n });\n } catch (e) {\n const info = getErrorInfo(e);\n if (info?.location) {\n // keep unstable_redirect error as http redirection\n throw e;\n }\n status = info?.status || 500;\n // SSR empty html and go full CSR on browser, which can revive RSC errors.\n const ssrErrorRoot = (\n <html>\n <body></body>\n </html>\n );\n htmlStream = await renderToReadableStream(ssrErrorRoot, {\n bootstrapScriptContent:\n getBootstrapPreamble({\n hydrate: false,\n }) +\n bootstrapScriptContent +\n (options.extraScriptContent || ''),\n ...(options.nonce ? { nonce: options.nonce } : {}),\n });\n }\n const responseStream: ReadableStream<Uint8Array> = htmlStream.pipeThrough(\n injectRSCPayload(\n batchReadableStream(stream2),\n options.nonce ? { nonce: options.nonce } : {},\n ),\n );\n\n return { stream: responseStream, status };\n};\n\nexport async function renderHtmlFallback() {\n const bootstrapScriptContent = await loadBootstrapScriptContent();\n const html = htmlShell.replace(\n '</body>',\n () => `<script>${bootstrapScriptContent}</script></body>`,\n );\n return html;\n}\n\nfunction loadBootstrapScriptContent(): Promise<string> {\n return import.meta.viteRsc.loadBootstrapScriptContent('index');\n}\n"],"names":["captureOwnerStack","use","createFromReadableStream","createFromReadableStreamBase","renderToReadableStream","injectRSCPayload","htmlShell","INTERNAL_ServerRoot","getErrorInfo","sanitizeLog","getBootstrapPreamble","batchReadableStream","stream","env","DEV","pipeThrough","TransformStream","flush","Promise","r","setTimeout","renderHtmlStream","rscStream","rscHtmlStream","options","stream1","stream2","tee","elementsPromise","htmlPromise","SsrRoot","bootstrapScriptContent","loadBootstrapScriptContent","htmlStream","status","hydrate","debugId","extraScriptContent","onError","e","digest","console","error","nonce","formState","info","location","ssrErrorRoot","html","body","responseStream","renderHtmlFallback","replace","viteRsc"],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ export declare const ENTRY = "e";
2
+ export declare const SET_ELEMENTS = "s";
3
+ export declare const FETCH_ENHANCERS = "f";
4
+ export declare const FETCH_RSC_INPUT_TRANSFORMERS = "t";
5
+ export declare const CALL_SERVER_ELEMENTS_LISTENERS = "l";
6
+ export type SetElements = (updater: (prev: Promise<Record<string, unknown>>) => Promise<Record<string, unknown>>) => void;
7
+ export type FetchEnhancer = (fetchFn: typeof fetch) => typeof fetch;
8
+ type FetchEnhancers = Set<FetchEnhancer>;
9
+ export type FetchRscInputTransformer = (rscPath: string, rscParams: unknown, prefetchOnly: boolean) => readonly [rscPath: string, rscParams: unknown, prefetchOnly: boolean];
10
+ type FetchRscInputTransformers = Set<FetchRscInputTransformer>;
11
+ type CallServerElementsListeners = Set<(elements: Record<string, unknown>) => void>;
12
+ export type FetchRscStore = {
13
+ [ENTRY]?: [
14
+ rscPath: string,
15
+ rscParams: unknown,
16
+ elementsPromise: Promise<Record<string, unknown>>
17
+ ];
18
+ [SET_ELEMENTS]?: SetElements;
19
+ [FETCH_ENHANCERS]?: FetchEnhancers;
20
+ [FETCH_RSC_INPUT_TRANSFORMERS]?: FetchRscInputTransformers;
21
+ [CALL_SERVER_ELEMENTS_LISTENERS]?: CallServerElementsListeners;
22
+ };
23
+ export declare const fetchRscStore: FetchRscStore;
24
+ export {};
@@ -0,0 +1,11 @@
1
+ export const ENTRY = 'e';
2
+ export const SET_ELEMENTS = 's';
3
+ export const FETCH_ENHANCERS = 'f';
4
+ export const FETCH_RSC_INPUT_TRANSFORMERS = 't';
5
+ export const CALL_SERVER_ELEMENTS_LISTENERS = 'l';
6
+ // Internal module-level RSC store. This module is intentionally absent from the
7
+ // package "exports" map, so it stays private to consumers while tests can still
8
+ // import it to reset state.
9
+ export const fetchRscStore = {};
10
+
11
+ //# sourceMappingURL=fetch-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/minimal/client-utils/fetch-store.ts"],"sourcesContent":["export const ENTRY = 'e';\nexport const SET_ELEMENTS = 's';\nexport const FETCH_ENHANCERS = 'f';\nexport const FETCH_RSC_INPUT_TRANSFORMERS = 't';\nexport const CALL_SERVER_ELEMENTS_LISTENERS = 'l';\n\nexport type SetElements = (\n updater: (\n prev: Promise<Record<string, unknown>>,\n ) => Promise<Record<string, unknown>>,\n) => void;\n\nexport type FetchEnhancer = (fetchFn: typeof fetch) => typeof fetch;\ntype FetchEnhancers = Set<FetchEnhancer>;\n\nexport type FetchRscInputTransformer = (\n rscPath: string,\n rscParams: unknown,\n prefetchOnly: boolean,\n) => readonly [rscPath: string, rscParams: unknown, prefetchOnly: boolean];\ntype FetchRscInputTransformers = Set<FetchRscInputTransformer>;\n\ntype CallServerElementsListeners = Set<\n (elements: Record<string, unknown>) => void\n>;\n\nexport type FetchRscStore = {\n [ENTRY]?: [\n rscPath: string,\n rscParams: unknown,\n elementsPromise: Promise<Record<string, unknown>>,\n ];\n [SET_ELEMENTS]?: SetElements;\n [FETCH_ENHANCERS]?: FetchEnhancers;\n [FETCH_RSC_INPUT_TRANSFORMERS]?: FetchRscInputTransformers;\n [CALL_SERVER_ELEMENTS_LISTENERS]?: CallServerElementsListeners;\n};\n\n// Internal module-level RSC store. This module is intentionally absent from the\n// package \"exports\" map, so it stays private to consumers while tests can still\n// import it to reset state.\nexport const fetchRscStore: FetchRscStore = {};\n"],"names":["ENTRY","SET_ELEMENTS","FETCH_ENHANCERS","FETCH_RSC_INPUT_TRANSFORMERS","CALL_SERVER_ELEMENTS_LISTENERS","fetchRscStore"],"mappings":"AAAA,OAAO,MAAMA,QAAQ,IAAI;AACzB,OAAO,MAAMC,eAAe,IAAI;AAChC,OAAO,MAAMC,kBAAkB,IAAI;AACnC,OAAO,MAAMC,+BAA+B,IAAI;AAChD,OAAO,MAAMC,iCAAiC,IAAI;AAkClD,gFAAgF;AAChF,gFAAgF;AAChF,4BAA4B;AAC5B,OAAO,MAAMC,gBAA+B,CAAC,EAAE"}