@rsc-kit/core 0.30.38 → 0.30.42

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 (86) hide show
  1. package/dist/cache.d.ts +79 -2
  2. package/dist/cache.js +151 -5
  3. package/dist/cache.js.map +1 -1
  4. package/dist/changed.js +2 -1
  5. package/dist/changed.js.map +1 -1
  6. package/dist/conformance.d.ts +1 -0
  7. package/dist/conformance.js +27 -0
  8. package/dist/conformance.js.map +1 -1
  9. package/dist/flash.d.ts +18 -0
  10. package/dist/flash.js +29 -0
  11. package/dist/flash.js.map +1 -0
  12. package/dist/flashCookie.d.ts +17 -0
  13. package/dist/flashCookie.js +36 -0
  14. package/dist/flashCookie.js.map +1 -0
  15. package/dist/host.d.ts +28 -17
  16. package/dist/host.js +109 -31
  17. package/dist/host.js.map +1 -1
  18. package/dist/hostCalls.js +27 -11
  19. package/dist/hostCalls.js.map +1 -1
  20. package/dist/js/ActivityRouter.js +3 -3
  21. package/dist/js/ActivityRouter.js.map +1 -1
  22. package/dist/js/Changes.js +1 -1
  23. package/dist/js/Changes.js.map +1 -1
  24. package/dist/js/DefaultRouteError.d.ts +1 -1
  25. package/dist/js/DefaultRouteError.js.map +1 -1
  26. package/dist/js/Form.d.ts +2 -2
  27. package/dist/js/Form.js +5 -5
  28. package/dist/js/Form.js.map +1 -1
  29. package/dist/js/Link.js +2 -2
  30. package/dist/js/Link.js.map +1 -1
  31. package/dist/js/RedirectBoundary.js +2 -2
  32. package/dist/js/RedirectBoundary.js.map +1 -1
  33. package/dist/js/SegmentBoundary.js +5 -5
  34. package/dist/js/SegmentBoundary.js.map +1 -1
  35. package/dist/js/SlotBoundary.js +3 -3
  36. package/dist/js/SlotBoundary.js.map +1 -1
  37. package/dist/js/changesStore.js +1 -1
  38. package/dist/js/changesStore.js.map +1 -1
  39. package/dist/js/createViteRscApp.js +16 -17
  40. package/dist/js/createViteRscApp.js.map +1 -1
  41. package/dist/js/errors.d.ts +3 -1
  42. package/dist/js/errors.js +5 -2
  43. package/dist/js/errors.js.map +1 -1
  44. package/dist/js/navigate.d.ts +17 -2
  45. package/dist/js/navigate.js +50 -19
  46. package/dist/js/navigate.js.map +1 -1
  47. package/dist/js/nuqs.js +2 -2
  48. package/dist/js/nuqs.js.map +1 -1
  49. package/dist/js/pagePayload.js +2 -2
  50. package/dist/js/pagePayload.js.map +1 -1
  51. package/dist/js/payloadKey.d.ts +32 -0
  52. package/dist/js/payloadKey.js +61 -0
  53. package/dist/js/payloadKey.js.map +1 -0
  54. package/dist/js/refreshOn.js +3 -3
  55. package/dist/js/refreshOn.js.map +1 -1
  56. package/dist/js/section.d.ts +4 -4
  57. package/dist/js/section.js +2 -2
  58. package/dist/js/section.js.map +1 -1
  59. package/dist/js/updateStore.js +6 -2
  60. package/dist/js/updateStore.js.map +1 -1
  61. package/dist/js/useAppUpdate.js +1 -1
  62. package/dist/js/useAppUpdate.js.map +1 -1
  63. package/dist/js/useFlash.d.ts +16 -0
  64. package/dist/js/useFlash.js +56 -0
  65. package/dist/js/useFlash.js.map +1 -0
  66. package/dist/js/useOnline.js +1 -1
  67. package/dist/js/useOnline.js.map +1 -1
  68. package/dist/js/usePathname.js +1 -1
  69. package/dist/js/usePathname.js.map +1 -1
  70. package/dist/manifest.d.ts +7 -0
  71. package/dist/manifest.js.map +1 -1
  72. package/dist/pageCache.d.ts +47 -0
  73. package/dist/pageCache.js +177 -0
  74. package/dist/pageCache.js.map +1 -0
  75. package/dist/prerender.js +4 -0
  76. package/dist/prerender.js.map +1 -1
  77. package/dist/request.d.ts +36 -0
  78. package/dist/request.js +80 -0
  79. package/dist/request.js.map +1 -1
  80. package/dist/testing.d.ts +9 -0
  81. package/dist/testing.js +14 -2
  82. package/dist/testing.js.map +1 -1
  83. package/dist/vite.d.ts +27 -0
  84. package/dist/vite.js +120 -14
  85. package/dist/vite.js.map +1 -1
  86. package/package.json +11 -3
@@ -0,0 +1,61 @@
1
+ /**
2
+ * A payload's own url.
3
+ *
4
+ * One url answers with a document or with one of several payloads, told apart
5
+ * by `X-RSC*` request headers. A cache that keys on the url alone - Cloudflare
6
+ * ignores `Vary`, and so does a Cache Rule that caches HTML - would hand a
7
+ * payload to a visitor asking for the document, or the other way round, and
8
+ * the page breaks. So a payload request carries `?_rsc=<key>`, a hash of the
9
+ * headers that shape its answer: a different answer is a different url,
10
+ * whatever the cache in front does with `Vary`.
11
+ *
12
+ * The server strips the parameter before anything reads the url, so a page's
13
+ * search params never see it.
14
+ */
15
+ export const PAYLOAD_PARAM = "_rsc";
16
+ /**
17
+ * djb2 over every `X-RSC*` header, in base 36: short, stable, and the same in
18
+ * every runtime. Headers lowercases the names and iterates them sorted, so the
19
+ * client's plain object and the server's Headers give the same key.
20
+ */
21
+ export function payloadKey(headers) {
22
+ let hash = 5381;
23
+ new Headers(headers).forEach((value, name) => {
24
+ if (!name.startsWith("x-rsc"))
25
+ return;
26
+ for (const char of `${name}:${value}\n`)
27
+ hash = (hash * 33 + char.charCodeAt(0)) | 0;
28
+ });
29
+ return (hash >>> 0).toString(36);
30
+ }
31
+ /** The url a payload with these headers is asked for at: a path stays a path. */
32
+ export function keyedPayloadUrl(url, headers) {
33
+ const parsed = new URL(url, "http://payload.invalid");
34
+ parsed.searchParams.set(PAYLOAD_PARAM, payloadKey(headers));
35
+ return url[0] === "/" ? parsed.pathname + parsed.search + parsed.hash : parsed.href;
36
+ }
37
+ /**
38
+ * The request as the app should see it: a payload asked for at its own url
39
+ * (`?_rsc=<key>`, see js/payloadKey.ts) loses the key before routing, search
40
+ * params, a redirect's location or a cache key can read it. The key exists for
41
+ * caches in front that ignore Vary; to the page it is the same url.
42
+ */
43
+ export function withoutPayloadKey(request) {
44
+ if (request.method !== "GET" && request.method !== "HEAD")
45
+ return request;
46
+ const kept = stripped.get(request);
47
+ if (kept)
48
+ return kept;
49
+ const url = new URL(request.url);
50
+ if (!url.searchParams.has(PAYLOAD_PARAM))
51
+ return request;
52
+ url.searchParams.delete(PAYLOAD_PARAM);
53
+ const copy = new Request(url, { method: request.method, headers: request.headers, signal: request.signal });
54
+ stripped.set(request, copy);
55
+ return copy;
56
+ }
57
+ // The same copy for the same request, every time. What the app marks on the
58
+ // request it saw (a page that said notFound()) is asked about by the entry in
59
+ // front, which holds the request as it arrived, key and all.
60
+ const stripped = new WeakMap();
61
+ //# sourceMappingURL=payloadKey.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payloadKey.js","sourceRoot":"","sources":["../../src/js/payloadKey.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC;AAIpC;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,OAAqB;IAC9C,IAAI,IAAI,GAAG,IAAI,CAAC;IAEhB,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QAC3C,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;YAAE,OAAO;QAEtC,KAAK,MAAM,IAAI,IAAI,GAAG,IAAI,IAAI,KAAK,IAAI;YAAE,IAAI,GAAG,CAAC,IAAI,GAAG,EAAE,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACvF,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;AACnC,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,eAAe,CAAC,GAAW,EAAE,OAAqB;IAChE,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,EAAE,wBAAwB,CAAC,CAAC;IAEtD,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,aAAa,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC;IAE5D,OAAO,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,GAAG,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC;AACtF,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAgB;IAChD,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM;QAAE,OAAO,OAAO,CAAC;IAE1E,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAEnC,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC;IAEtB,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAEjC,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,aAAa,CAAC;QAAE,OAAO,OAAO,CAAC;IAEzD,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;IAEvC,MAAM,IAAI,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAE5G,QAAQ,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAE5B,OAAO,IAAI,CAAC;AACd,CAAC;AAED,4EAA4E;AAC5E,8EAA8E;AAC9E,6DAA6D;AAC7D,MAAM,QAAQ,GAAG,IAAI,OAAO,EAAoB,CAAC","sourcesContent":["/**\n * A payload's own url.\n *\n * One url answers with a document or with one of several payloads, told apart\n * by `X-RSC*` request headers. A cache that keys on the url alone - Cloudflare\n * ignores `Vary`, and so does a Cache Rule that caches HTML - would hand a\n * payload to a visitor asking for the document, or the other way round, and\n * the page breaks. So a payload request carries `?_rsc=<key>`, a hash of the\n * headers that shape its answer: a different answer is a different url,\n * whatever the cache in front does with `Vary`.\n *\n * The server strips the parameter before anything reads the url, so a page's\n * search params never see it.\n */\n\nexport const PAYLOAD_PARAM = \"_rsc\";\n\ntype HeaderSource = Headers | Record<string, string>;\n\n/**\n * djb2 over every `X-RSC*` header, in base 36: short, stable, and the same in\n * every runtime. Headers lowercases the names and iterates them sorted, so the\n * client's plain object and the server's Headers give the same key.\n */\nexport function payloadKey(headers: HeaderSource): string {\n let hash = 5381;\n\n new Headers(headers).forEach((value, name) => {\n if (!name.startsWith(\"x-rsc\")) return;\n\n for (const char of `${name}:${value}\\n`) hash = (hash * 33 + char.charCodeAt(0)) | 0;\n });\n\n return (hash >>> 0).toString(36);\n}\n\n/** The url a payload with these headers is asked for at: a path stays a path. */\nexport function keyedPayloadUrl(url: string, headers: HeaderSource): string {\n const parsed = new URL(url, \"http://payload.invalid\");\n\n parsed.searchParams.set(PAYLOAD_PARAM, payloadKey(headers));\n\n return url[0] === \"/\" ? parsed.pathname + parsed.search + parsed.hash : parsed.href;\n}\n\n/**\n * The request as the app should see it: a payload asked for at its own url\n * (`?_rsc=<key>`, see js/payloadKey.ts) loses the key before routing, search\n * params, a redirect's location or a cache key can read it. The key exists for\n * caches in front that ignore Vary; to the page it is the same url.\n */\nexport function withoutPayloadKey(request: Request): Request {\n if (request.method !== \"GET\" && request.method !== \"HEAD\") return request;\n\n const kept = stripped.get(request);\n\n if (kept) return kept;\n\n const url = new URL(request.url);\n\n if (!url.searchParams.has(PAYLOAD_PARAM)) return request;\n\n url.searchParams.delete(PAYLOAD_PARAM);\n\n const copy = new Request(url, { method: request.method, headers: request.headers, signal: request.signal });\n\n stripped.set(request, copy);\n\n return copy;\n}\n\n// The same copy for the same request, every time. What the app marks on the\n// request it saw (a page that said notFound()) is asked about by the entry in\n// front, which holds the request as it arrived, key and all.\nconst stripped = new WeakMap<Request, Request>();\n"]}
@@ -1,7 +1,7 @@
1
1
  import { Suspense, createElement } from "react";
2
- import { connection, currentPageProps } from "../request";
3
- import { sign, versionSource } from "../changed";
4
- import { Changes } from "./Changes";
2
+ import { connection, currentPageProps } from "../request.js";
3
+ import { sign, versionSource } from "../changed.js";
4
+ import { Changes } from "./Changes.js";
5
5
  import { isNotFoundSignal } from "../notFound.js";
6
6
  /**
7
7
  * The names a region refreshes on, read for this render and signed for the tab.
@@ -1 +1 @@
1
- {"version":3,"file":"refreshOn.js","sourceRoot":"","sources":["../../src/js/refreshOn.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Suspense, createElement } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { connection, currentPageProps } from \"../request\";\nimport { sign, versionSource } from \"../changed\";\nimport type { SignedName } from \"../changed\";\nimport { Changes } from \"./Changes\";\nimport { isNotFoundSignal } from \"../notFound.js\";\n\n/** A prop as it arrives awaited: a page's `params` is a promise of them. */\ntype Awaited_<T, Else> = [T] extends [never]\n ? Else\n : unknown extends T\n ? Else // props that do not say: a section's own, with no params among them\n : T extends Promise<infer V>\n ? V\n : T extends undefined\n ? Else\n : T;\n\n/**\n * What a refreshOn function is given: the page's params and search params,\n * already awaited, beside whatever props the section was rendered with.\n *\n * Typed from P when it says what they are - a page's `PageProps<typeof\n * params>` - so `params.team` is the schema's type, not a string by hand.\n */\nexport type RefreshOnInput<P> = Omit<P, \"params\" | \"searchParams\"> & {\n params: Awaited_<P extends { params?: infer A } ? A : never, Record<string, string>>;\n searchParams: Awaited_<\n P extends { searchParams?: infer S } ? S : never,\n URLSearchParams | Record<string, unknown>\n >;\n};\n\n/** What a section or page refreshes on: names, or a function of its props that makes them. */\nexport type RefreshOnList<P> =\n string[] | ((input: RefreshOnInput<P>) => string[] | Promise<string[]>);\n\n/**\n * The names a region refreshes on, read for this render and signed for the tab.\n *\n * A server component, rendered beside the region: it works out the names\n * from the props, asks the source what version each is at, and hands them to\n * the client as `Changes`. Under its own Suspense boundary, so the ask never\n * holds up the region's paint - the data is what the visitor is waiting on,\n * not the version numbers behind it.\n *\n * Renders nothing when the source cannot answer - a backend that is down\n * for this one call. The region still renders; it just does not refresh on\n * its names until the next time it is rendered.\n */\nexport function RefreshOn<P>({\n target,\n refreshOn,\n props,\n}: {\n target: string;\n refreshOn: RefreshOnList<P>;\n props: P;\n}): ReactNode {\n return createElement(\n Suspense,\n { fallback: null },\n createElement(Resolve as never, { target, refreshOn, props }),\n );\n}\n\n/** Where createTestApp's watched() collects what a render's regions refresh on. */\nexport const WATCHED = Symbol.for(\"rsc-kit.test-watched\");\n\nconst warned = new Set<string>();\nconst emptyWarned = new Set<string>();\n\n/**\n * Never throws. An error here would become an error row inside the region's\n * payload, and the browser decodes that as the region failing - a blank\n * document for a version lookup that did not go through. Said once per\n * region instead, and the region renders without refreshing on anything.\n */\nasync function Resolve<P>({\n target,\n refreshOn,\n props,\n}: {\n target: string;\n refreshOn: RefreshOnList<P>;\n props: P;\n}): Promise<ReactNode> {\n try {\n // Per request, never at build. A version read at build is stale by the\n // time anyone loads the page, and a name signed at build is signed with\n // whatever key the build machine had. At build this suspends and the\n // names become a hole in the stored shell, filled per request.\n await connection();\n\n // The page's params and searchParams, under whatever the page passed: a\n // section rendered by its page is given nothing, and alone is given these.\n const given = { ...currentPageProps(), ...(props as object) } as Record<\n string,\n unknown\n >;\n\n // Awaited here, so `({ params }) => [\\`team:${params.team}\\`]` - the way\n // anyone writes it - means the team. A page's params and search params\n // are promises, and read without awaiting they made `team:undefined`:\n // one name for every team, so each refreshed on all the others' changes.\n // Awaiting costs nothing here; the names are made per request anyway.\n const input = {\n ...given,\n params: ((await given.params) ?? {}) as Record<string, string>,\n searchParams: ((await given.searchParams) ??\n new URLSearchParams()) as URLSearchParams,\n } as RefreshOnInput<P>;\n const unique = await namesFor(target, refreshOn, input);\n\n // A test asking what this page watches - createTestApp's watched() - is\n // told here, rather than left to dig the signed names out of the payload.\n const recording = (globalThis as { [WATCHED]?: Record<string, string[]> })[WATCHED];\n\n if (recording) recording[target] = unique;\n\n if (unique.length === 0) return null;\n\n // -1 is a version nothing is at, so every name comes back with its current version.\n const versions = await versionSource().changed(\n Object.fromEntries(unique.map((name) => [name, -1])),\n 0,\n );\n const signed: Record<string, SignedName> = {};\n\n for (const name of unique)\n signed[name] = [versions[name] ?? 0, await sign(name)];\n\n return createElement(Changes, { target, names: signed });\n } catch (error) {\n // A page that does not exist is not a page that cannot say what it\n // refreshes on: the same read that found no team is what the page says\n // notFound() about, and that is the answer. Warning for it was a line in the\n // log for every missing record.\n if (isNotFoundSignal(error)) return null;\n\n if (!warned.has(target)) {\n warned.add(target);\n console.warn(\n `[rsc-kit] what ${JSON.stringify(target)} refreshes on could not be read, so it will not: ` +\n (error instanceof Error ? error.message : String(error)),\n );\n }\n\n return null;\n }\n}\n\n/**\n * The names a region refreshes on, for this render: its list, or what its\n * function made of the input - unique, and only non-empty strings. Warns in\n * development when that is none at all.\n */\nexport async function namesFor<P>(\n target: string,\n refreshOn: RefreshOnList<P>,\n input: RefreshOnInput<P>,\n): Promise<string[]> {\n const named =\n typeof refreshOn === \"function\" ? await refreshOn(input) : refreshOn;\n const unique = [...new Set(named)].filter(\n (name) => typeof name === \"string\" && name !== \"\",\n );\n\n if (unique.length === 0) {\n // Usually a mistake that looks like working code - a param read under\n // the wrong name, a list built from an empty query - and the region\n // then refreshes on nothing. Said in development, once per region; a\n // region that means to watch nothing for some visitors can ignore it.\n if (\n (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env\n ?.NODE_ENV !== \"production\" &&\n !emptyWarned.has(target)\n ) {\n emptyWarned.add(target);\n console.warn(\n `[rsc-kit] ${JSON.stringify(target)}'s refreshOn gave no names, so it refreshes on nothing. ` +\n \"Check what it reads - a param under another name is undefined.\",\n );\n }\n\n return [];\n }\n\n return unique;\n}\n"]}
1
+ {"version":3,"file":"refreshOn.js","sourceRoot":"","sources":["../../src/js/refreshOn.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Suspense, createElement } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { connection, currentPageProps } from \"../request.js\";\nimport { sign, versionSource } from \"../changed.js\";\nimport type { SignedName } from \"../changed.js\";\nimport { Changes } from \"./Changes.js\";\nimport { isNotFoundSignal } from \"../notFound.js\";\n\n/** A prop as it arrives awaited: a page's `params` is a promise of them. */\ntype Awaited_<T, Else> = [T] extends [never]\n ? Else\n : unknown extends T\n ? Else // props that do not say: a section's own, with no params among them\n : T extends Promise<infer V>\n ? V\n : T extends undefined\n ? Else\n : T;\n\n/**\n * What a refreshOn function is given: the page's params and search params,\n * already awaited, beside whatever props the section was rendered with.\n *\n * Typed from P when it says what they are - a page's `PageProps<typeof\n * params>` - so `params.team` is the schema's type, not a string by hand.\n */\nexport type RefreshOnInput<P> = Omit<P, \"params\" | \"searchParams\"> & {\n params: Awaited_<P extends { params?: infer A } ? A : never, Record<string, string>>;\n searchParams: Awaited_<\n P extends { searchParams?: infer S } ? S : never,\n URLSearchParams | Record<string, unknown>\n >;\n};\n\n/** What a section or page refreshes on: names, or a function of its props that makes them. */\nexport type RefreshOnList<P> =\n string[] | ((input: RefreshOnInput<P>) => string[] | Promise<string[]>);\n\n/**\n * The names a region refreshes on, read for this render and signed for the tab.\n *\n * A server component, rendered beside the region: it works out the names\n * from the props, asks the source what version each is at, and hands them to\n * the client as `Changes`. Under its own Suspense boundary, so the ask never\n * holds up the region's paint - the data is what the visitor is waiting on,\n * not the version numbers behind it.\n *\n * Renders nothing when the source cannot answer - a backend that is down\n * for this one call. The region still renders; it just does not refresh on\n * its names until the next time it is rendered.\n */\nexport function RefreshOn<P>({\n target,\n refreshOn,\n props,\n}: {\n target: string;\n refreshOn: RefreshOnList<P>;\n props: P;\n}): ReactNode {\n return createElement(\n Suspense,\n { fallback: null },\n createElement(Resolve as never, { target, refreshOn, props }),\n );\n}\n\n/** Where createTestApp's watched() collects what a render's regions refresh on. */\nexport const WATCHED = Symbol.for(\"rsc-kit.test-watched\");\n\nconst warned = new Set<string>();\nconst emptyWarned = new Set<string>();\n\n/**\n * Never throws. An error here would become an error row inside the region's\n * payload, and the browser decodes that as the region failing - a blank\n * document for a version lookup that did not go through. Said once per\n * region instead, and the region renders without refreshing on anything.\n */\nasync function Resolve<P>({\n target,\n refreshOn,\n props,\n}: {\n target: string;\n refreshOn: RefreshOnList<P>;\n props: P;\n}): Promise<ReactNode> {\n try {\n // Per request, never at build. A version read at build is stale by the\n // time anyone loads the page, and a name signed at build is signed with\n // whatever key the build machine had. At build this suspends and the\n // names become a hole in the stored shell, filled per request.\n await connection();\n\n // The page's params and searchParams, under whatever the page passed: a\n // section rendered by its page is given nothing, and alone is given these.\n const given = { ...currentPageProps(), ...(props as object) } as Record<\n string,\n unknown\n >;\n\n // Awaited here, so `({ params }) => [\\`team:${params.team}\\`]` - the way\n // anyone writes it - means the team. A page's params and search params\n // are promises, and read without awaiting they made `team:undefined`:\n // one name for every team, so each refreshed on all the others' changes.\n // Awaiting costs nothing here; the names are made per request anyway.\n const input = {\n ...given,\n params: ((await given.params) ?? {}) as Record<string, string>,\n searchParams: ((await given.searchParams) ??\n new URLSearchParams()) as URLSearchParams,\n } as RefreshOnInput<P>;\n const unique = await namesFor(target, refreshOn, input);\n\n // A test asking what this page watches - createTestApp's watched() - is\n // told here, rather than left to dig the signed names out of the payload.\n const recording = (globalThis as { [WATCHED]?: Record<string, string[]> })[WATCHED];\n\n if (recording) recording[target] = unique;\n\n if (unique.length === 0) return null;\n\n // -1 is a version nothing is at, so every name comes back with its current version.\n const versions = await versionSource().changed(\n Object.fromEntries(unique.map((name) => [name, -1])),\n 0,\n );\n const signed: Record<string, SignedName> = {};\n\n for (const name of unique)\n signed[name] = [versions[name] ?? 0, await sign(name)];\n\n return createElement(Changes, { target, names: signed });\n } catch (error) {\n // A page that does not exist is not a page that cannot say what it\n // refreshes on: the same read that found no team is what the page says\n // notFound() about, and that is the answer. Warning for it was a line in the\n // log for every missing record.\n if (isNotFoundSignal(error)) return null;\n\n if (!warned.has(target)) {\n warned.add(target);\n console.warn(\n `[rsc-kit] what ${JSON.stringify(target)} refreshes on could not be read, so it will not: ` +\n (error instanceof Error ? error.message : String(error)),\n );\n }\n\n return null;\n }\n}\n\n/**\n * The names a region refreshes on, for this render: its list, or what its\n * function made of the input - unique, and only non-empty strings. Warns in\n * development when that is none at all.\n */\nexport async function namesFor<P>(\n target: string,\n refreshOn: RefreshOnList<P>,\n input: RefreshOnInput<P>,\n): Promise<string[]> {\n const named =\n typeof refreshOn === \"function\" ? await refreshOn(input) : refreshOn;\n const unique = [...new Set(named)].filter(\n (name) => typeof name === \"string\" && name !== \"\",\n );\n\n if (unique.length === 0) {\n // Usually a mistake that looks like working code - a param read under\n // the wrong name, a list built from an empty query - and the region\n // then refreshes on nothing. Said in development, once per region; a\n // region that means to watch nothing for some visitors can ignore it.\n if (\n (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env\n ?.NODE_ENV !== \"production\" &&\n !emptyWarned.has(target)\n ) {\n emptyWarned.add(target);\n console.warn(\n `[rsc-kit] ${JSON.stringify(target)}'s refreshOn gave no names, so it refreshes on nothing. ` +\n \"Check what it reads - a param under another name is undefined.\",\n );\n }\n\n return [];\n }\n\n return unique;\n}\n"]}
@@ -1,8 +1,8 @@
1
1
  import type { ComponentType } from "react";
2
- import type { RefreshOnList } from "./refreshOn";
3
- import type { ApiParams, PageProps } from "../routeSchema";
4
- import type { RoutePattern } from "../routes";
5
- export type { RefreshOnInput, RefreshOnList } from "./refreshOn";
2
+ import type { RefreshOnList } from "./refreshOn.js";
3
+ import type { ApiParams, PageProps } from "../routeSchema.js";
4
+ import type { RoutePattern } from "../routes.js";
5
+ export type { RefreshOnInput, RefreshOnList } from "./refreshOn.js";
6
6
  /**
7
7
  * A page's `export const refreshOn`, typed from its own url schema or from
8
8
  * its route's pattern:
@@ -1,6 +1,6 @@
1
1
  import { Fragment, createElement } from "react";
2
- import { SlotBoundary } from "./SlotBoundary";
3
- import { RefreshOn } from "./refreshOn";
2
+ import { SlotBoundary } from "./SlotBoundary.js";
3
+ import { RefreshOn } from "./refreshOn.js";
4
4
  /**
5
5
  * Mark a region of a page as separately refreshable.
6
6
  *
@@ -1 +1 @@
1
- {"version":3,"file":"section.js","sourceRoot":"","sources":["../../src/js/section.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAEhD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AA2BxC;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,6EAA6E;AAC7E,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;AAE5D,yDAAyD;AACzD,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC;AAoB1D,MAAM,UAAU,OAAO,CACrB,IAAY,EACZ,SAA2B,EAC3B,OAAO,GAAsB,EAAE;IAE/B,MAAM,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC;IAE9B,2EAA2E;IAC3E,gEAAgE;IAChE,MAAM,KAAK,GAAqB,SAAS;QACvC,CAAC,CAAC,SAAS,gBAAgB,CAAC,KAAQ;YAChC,OAAO,aAAa,CAClB,QAAQ,EACR,IAAI,EACJ,aAAa,CACX,SAAiC,EACjC,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAW,CAC9D,EACD,aAAa,CACX,SAAiC,EACjC,EAAE,GAAG,EAAE,SAAS,EAAE,GAAI,KAAgB,EAAW,CAClD,CACF,CAAC;QACJ,CAAC;QACH,CAAC,CAAC,SAAS,CAAC;IAEd,2EAA2E;IAC3E,qDAAqD;IACrD,SAAS,OAAO,CAAC,KAAQ;QACvB,sEAAsE;QACtE,oDAAoD;QACpD,OAAO,aAAa,CAClB,YAA+C,EAC/C,EAAE,IAAI,EAAE,EACR,aAAa,CAAC,KAA6B,EAAE,KAAc,CAAC,CAC7D,CAAC;IACJ,CAAC;IAED,OAAO,CAAC,WAAW,GAAG,WAAW,IAAI,GAAG,CAAC;IACzC,sEAAsE;IACrE,OAA8C,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC;IAC/D,IAAI,OAAO,CAAC,MAAM;QACf,OAA8C,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;IAEjE,OAAO,OAA2B,CAAC;AACrC,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,gBAAgB,CAC9B,QAAiB;IAEjB,IAAI,OAAO,QAAQ,KAAK,UAAU;QAAE,OAAO,SAAS,CAAC;IAErD,OAAQ,QAA+C,CAAC,KAAK,CACT,CAAC;AACvD,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,eAAe,CAAC,QAAiB;IAC/C,OAAO,CACL,OAAO,QAAQ,KAAK,UAAU;QAC7B,QAA+C,CAAC,MAAM,CAAC,KAAK,IAAI,CAClE,CAAC;AACJ,CAAC","sourcesContent":["import { Fragment, createElement } from \"react\";\nimport type { ComponentType, ReactNode } from \"react\";\nimport { SlotBoundary } from \"./SlotBoundary\";\nimport { RefreshOn } from \"./refreshOn\";\nimport type { RefreshOnList } from \"./refreshOn\";\nimport type { ApiParams, PageProps } from \"../routeSchema\";\nimport type { RoutePattern } from \"../routes\";\n\nexport type { RefreshOnInput, RefreshOnList } from \"./refreshOn\";\n\n/**\n * A page's `export const refreshOn`, typed from its own url schema or from\n * its route's pattern:\n *\n * export const params = z.object({ team: z.string() })\n * export const refreshOn: PageRefreshOn<typeof params> = ({ params }) => [`team:${params.team}:repos`]\n *\n * // no schema: the pattern names the params, each a string\n * export const refreshOn: PageRefreshOn<'/[team]/[app]/[env]'> = ({ params }) => [`app:${params.app}`]\n *\n * The pattern is checked against the routes the build found, so one that is\n * not a page - or a param it does not have, `params.ap` - fails the\n * typecheck. `params` arrives awaited; with neither, a record of strings.\n */\nexport type PageRefreshOn<P extends RoutePattern | object = never, S = never> = [P] extends [never]\n ? RefreshOnList<PageProps<never, S>>\n : [P] extends [string]\n ? RefreshOnList<{ params: Promise<ApiParams<P>>; searchParams: Promise<URLSearchParams> }>\n : RefreshOnList<PageProps<P, S>>;\n\n/**\n * Mark a region of a page as separately refreshable.\n *\n * Deliberately not a client module. The boundary it wraps things in is one,\n * but the things it wraps are server components — an async component that\n * fetches its own data. Marking this file \"use client\" would turn every one of\n * them into a client reference, which async components cannot be.\n *\n * export default section('orders', async function Orders() { ... })\n *\n * The page renders it like any component. What it buys is a name the server\n * can address: `Rsc::revalidate('orders')` re-renders this and nothing else,\n * and the answer replaces it in place.\n *\n * With `refreshOn`, it also says what it depends on, and refreshes itself when\n * that changes - from a webhook, a job, another visitor - with nothing polling:\n *\n * export default section('repos', Repos, {\n * refreshOn: ({ params }) => [`team:${params.team}:repos`],\n * })\n *\n * The name has to be unique within the page. It does not have to be unique in\n * the app: the component is carried on the wrapper it returns, so the server\n * resolves a section through the module the route declares rather than by\n * looking the name up in a registry every section in the app also writes to.\n * Two pages may both call theirs 'stats'.\n */\n\n/** Where the unwrapped component hangs off the wrapper section() returns. */\nconst INNER = Symbol.for(\"@rsc-kit/core.section-component\");\n\n/** Set on the wrapper of a section declared `shared`. */\nconst SHARED = Symbol.for(\"@rsc-kit/core.section-shared\");\n\nexport interface SectionOptions<P> {\n /**\n * What this section refreshes on: names for the data it shows, or a\n * function of the section's props - the page's params and searchParams -\n * that makes them. When one is said to have changed, the section refreshes.\n */\n refreshOn?: RefreshOnList<P>;\n /**\n * The section renders the same for everyone allowed to see the page, so\n * when a change makes many tabs ask for it, one render answers them all.\n * Each tab's guards still run on its own request; only the render is\n * shared. Never set it on a section that shows anything per visitor - their\n * name, their data, what they may do: the first tab's render is what every\n * other tab is shown.\n */\n shared?: boolean;\n}\n\nexport function section<P extends Record<string, unknown>>(\n name: string,\n Component: ComponentType<P>,\n options: SectionOptions<P> = {},\n): ComponentType<P> {\n const { refreshOn } = options;\n\n // The names render inside the boundary, with the component, so a refresh -\n // which renders the inside alone - carries the versions it saw.\n const Inner: ComponentType<P> = refreshOn\n ? function RefreshOnSection(props: P): ReactNode {\n return createElement(\n Fragment,\n null,\n createElement(\n RefreshOn as ComponentType<never>,\n { key: \"refreshOn\", target: name, refreshOn, props } as never,\n ),\n createElement(\n Component as ComponentType<never>,\n { key: \"section\", ...(props as object) } as never,\n ),\n );\n }\n : Component;\n\n // The boundary wraps the component here rather than at the call site, so a\n // page renders a section exactly like anything else.\n function Section(props: P): ReactNode {\n // createElement cannot line its overloads up with a component generic\n // over its own props. The call is the ordinary one.\n return createElement(\n SlotBoundary as ComponentType<{ name: string }>,\n { name },\n createElement(Inner as ComponentType<never>, props as never),\n );\n }\n\n Section.displayName = `Section(${name})`;\n // Reached through the route's own module, never through a shared map.\n (Section as unknown as Record<symbol, unknown>)[INNER] = Inner;\n if (options.shared)\n (Section as unknown as Record<symbol, unknown>)[SHARED] = true;\n\n return Section as ComponentType<P>;\n}\n\n/**\n * The component inside a section module, without its boundary.\n *\n * Revalidation renders this rather than the wrapper: the client swaps what is\n * inside the boundary, so returning the wrapper would nest a new boundary\n * inside the old one on every refresh.\n *\n * Takes the module's own export rather than a name. A name-keyed registry is\n * written to by every section in the app at bundle load — the generated entry\n * imports them all eagerly — so a lookup by name reached any page's region from\n * any url, bounded only by whatever guard happened to sit on the url asked for.\n */\nexport function sectionComponent(\n exported: unknown,\n): ComponentType<Record<string, unknown>> | undefined {\n if (typeof exported !== \"function\") return undefined;\n\n return (exported as unknown as Record<symbol, unknown>)[INNER] as\n ComponentType<Record<string, unknown>> | undefined;\n}\n\n/** Whether a section module's export was declared `shared`. */\nexport function isSharedSection(exported: unknown): boolean {\n return (\n typeof exported === \"function\" &&\n (exported as unknown as Record<symbol, unknown>)[SHARED] === true\n );\n}\n"]}
1
+ {"version":3,"file":"section.js","sourceRoot":"","sources":["../../src/js/section.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAEhD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AA2B3C;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,6EAA6E;AAC7E,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;AAE5D,yDAAyD;AACzD,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC;AAoB1D,MAAM,UAAU,OAAO,CACrB,IAAY,EACZ,SAA2B,EAC3B,OAAO,GAAsB,EAAE;IAE/B,MAAM,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC;IAE9B,2EAA2E;IAC3E,gEAAgE;IAChE,MAAM,KAAK,GAAqB,SAAS;QACvC,CAAC,CAAC,SAAS,gBAAgB,CAAC,KAAQ;YAChC,OAAO,aAAa,CAClB,QAAQ,EACR,IAAI,EACJ,aAAa,CACX,SAAiC,EACjC,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAW,CAC9D,EACD,aAAa,CACX,SAAiC,EACjC,EAAE,GAAG,EAAE,SAAS,EAAE,GAAI,KAAgB,EAAW,CAClD,CACF,CAAC;QACJ,CAAC;QACH,CAAC,CAAC,SAAS,CAAC;IAEd,2EAA2E;IAC3E,qDAAqD;IACrD,SAAS,OAAO,CAAC,KAAQ;QACvB,sEAAsE;QACtE,oDAAoD;QACpD,OAAO,aAAa,CAClB,YAA+C,EAC/C,EAAE,IAAI,EAAE,EACR,aAAa,CAAC,KAA6B,EAAE,KAAc,CAAC,CAC7D,CAAC;IACJ,CAAC;IAED,OAAO,CAAC,WAAW,GAAG,WAAW,IAAI,GAAG,CAAC;IACzC,sEAAsE;IACrE,OAA8C,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC;IAC/D,IAAI,OAAO,CAAC,MAAM;QACf,OAA8C,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;IAEjE,OAAO,OAA2B,CAAC;AACrC,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,gBAAgB,CAC9B,QAAiB;IAEjB,IAAI,OAAO,QAAQ,KAAK,UAAU;QAAE,OAAO,SAAS,CAAC;IAErD,OAAQ,QAA+C,CAAC,KAAK,CACT,CAAC;AACvD,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,eAAe,CAAC,QAAiB;IAC/C,OAAO,CACL,OAAO,QAAQ,KAAK,UAAU;QAC7B,QAA+C,CAAC,MAAM,CAAC,KAAK,IAAI,CAClE,CAAC;AACJ,CAAC","sourcesContent":["import { Fragment, createElement } from \"react\";\nimport type { ComponentType, ReactNode } from \"react\";\nimport { SlotBoundary } from \"./SlotBoundary.js\";\nimport { RefreshOn } from \"./refreshOn.js\";\nimport type { RefreshOnList } from \"./refreshOn.js\";\nimport type { ApiParams, PageProps } from \"../routeSchema.js\";\nimport type { RoutePattern } from \"../routes.js\";\n\nexport type { RefreshOnInput, RefreshOnList } from \"./refreshOn.js\";\n\n/**\n * A page's `export const refreshOn`, typed from its own url schema or from\n * its route's pattern:\n *\n * export const params = z.object({ team: z.string() })\n * export const refreshOn: PageRefreshOn<typeof params> = ({ params }) => [`team:${params.team}:repos`]\n *\n * // no schema: the pattern names the params, each a string\n * export const refreshOn: PageRefreshOn<'/[team]/[app]/[env]'> = ({ params }) => [`app:${params.app}`]\n *\n * The pattern is checked against the routes the build found, so one that is\n * not a page - or a param it does not have, `params.ap` - fails the\n * typecheck. `params` arrives awaited; with neither, a record of strings.\n */\nexport type PageRefreshOn<P extends RoutePattern | object = never, S = never> = [P] extends [never]\n ? RefreshOnList<PageProps<never, S>>\n : [P] extends [string]\n ? RefreshOnList<{ params: Promise<ApiParams<P>>; searchParams: Promise<URLSearchParams> }>\n : RefreshOnList<PageProps<P, S>>;\n\n/**\n * Mark a region of a page as separately refreshable.\n *\n * Deliberately not a client module. The boundary it wraps things in is one,\n * but the things it wraps are server components — an async component that\n * fetches its own data. Marking this file \"use client\" would turn every one of\n * them into a client reference, which async components cannot be.\n *\n * export default section('orders', async function Orders() { ... })\n *\n * The page renders it like any component. What it buys is a name the server\n * can address: `Rsc::revalidate('orders')` re-renders this and nothing else,\n * and the answer replaces it in place.\n *\n * With `refreshOn`, it also says what it depends on, and refreshes itself when\n * that changes - from a webhook, a job, another visitor - with nothing polling:\n *\n * export default section('repos', Repos, {\n * refreshOn: ({ params }) => [`team:${params.team}:repos`],\n * })\n *\n * The name has to be unique within the page. It does not have to be unique in\n * the app: the component is carried on the wrapper it returns, so the server\n * resolves a section through the module the route declares rather than by\n * looking the name up in a registry every section in the app also writes to.\n * Two pages may both call theirs 'stats'.\n */\n\n/** Where the unwrapped component hangs off the wrapper section() returns. */\nconst INNER = Symbol.for(\"@rsc-kit/core.section-component\");\n\n/** Set on the wrapper of a section declared `shared`. */\nconst SHARED = Symbol.for(\"@rsc-kit/core.section-shared\");\n\nexport interface SectionOptions<P> {\n /**\n * What this section refreshes on: names for the data it shows, or a\n * function of the section's props - the page's params and searchParams -\n * that makes them. When one is said to have changed, the section refreshes.\n */\n refreshOn?: RefreshOnList<P>;\n /**\n * The section renders the same for everyone allowed to see the page, so\n * when a change makes many tabs ask for it, one render answers them all.\n * Each tab's guards still run on its own request; only the render is\n * shared. Never set it on a section that shows anything per visitor - their\n * name, their data, what they may do: the first tab's render is what every\n * other tab is shown.\n */\n shared?: boolean;\n}\n\nexport function section<P extends Record<string, unknown>>(\n name: string,\n Component: ComponentType<P>,\n options: SectionOptions<P> = {},\n): ComponentType<P> {\n const { refreshOn } = options;\n\n // The names render inside the boundary, with the component, so a refresh -\n // which renders the inside alone - carries the versions it saw.\n const Inner: ComponentType<P> = refreshOn\n ? function RefreshOnSection(props: P): ReactNode {\n return createElement(\n Fragment,\n null,\n createElement(\n RefreshOn as ComponentType<never>,\n { key: \"refreshOn\", target: name, refreshOn, props } as never,\n ),\n createElement(\n Component as ComponentType<never>,\n { key: \"section\", ...(props as object) } as never,\n ),\n );\n }\n : Component;\n\n // The boundary wraps the component here rather than at the call site, so a\n // page renders a section exactly like anything else.\n function Section(props: P): ReactNode {\n // createElement cannot line its overloads up with a component generic\n // over its own props. The call is the ordinary one.\n return createElement(\n SlotBoundary as ComponentType<{ name: string }>,\n { name },\n createElement(Inner as ComponentType<never>, props as never),\n );\n }\n\n Section.displayName = `Section(${name})`;\n // Reached through the route's own module, never through a shared map.\n (Section as unknown as Record<symbol, unknown>)[INNER] = Inner;\n if (options.shared)\n (Section as unknown as Record<symbol, unknown>)[SHARED] = true;\n\n return Section as ComponentType<P>;\n}\n\n/**\n * The component inside a section module, without its boundary.\n *\n * Revalidation renders this rather than the wrapper: the client swaps what is\n * inside the boundary, so returning the wrapper would nest a new boundary\n * inside the old one on every refresh.\n *\n * Takes the module's own export rather than a name. A name-keyed registry is\n * written to by every section in the app at bundle load — the generated entry\n * imports them all eagerly — so a lookup by name reached any page's region from\n * any url, bounded only by whatever guard happened to sit on the url asked for.\n */\nexport function sectionComponent(\n exported: unknown,\n): ComponentType<Record<string, unknown>> | undefined {\n if (typeof exported !== \"function\") return undefined;\n\n return (exported as unknown as Record<symbol, unknown>)[INNER] as\n ComponentType<Record<string, unknown>> | undefined;\n}\n\n/** Whether a section module's export was declared `shared`. */\nexport function isSharedSection(exported: unknown): boolean {\n return (\n typeof exported === \"function\" &&\n (exported as unknown as Record<symbol, unknown>)[SHARED] === true\n );\n}\n"]}
@@ -11,6 +11,7 @@
11
11
  * across a deploy is running javascript whose remaining chunks are gone. It
12
12
  * works until it navigates somewhere that needs one.
13
13
  */
14
+ import { keyedPayloadUrl } from './payloadKey.js';
14
15
  let updated = false;
15
16
  const listeners = new Set();
16
17
  /** A boolean, so React compares snapshots by value and does not loop. */
@@ -46,9 +47,12 @@ async function ask() {
46
47
  if (!build)
47
48
  return announce();
48
49
  try {
49
- const response = await fetch(window.location.href, {
50
+ const headers = { 'X-RSC': '1', 'X-RSC-Version': build };
51
+ // Its own url, as every payload request has: a cache that ignores Vary
52
+ // would answer the document's 200 and hide the 409 this asks for.
53
+ const response = await fetch(keyedPayloadUrl(window.location.href, headers), {
50
54
  method: 'HEAD',
51
- headers: { 'X-RSC': '1', 'X-RSC-Version': build },
55
+ headers,
52
56
  cache: 'no-store',
53
57
  });
54
58
  if (response.status === 409)
@@ -1 +1 @@
1
- {"version":3,"file":"updateStore.js","sourceRoot":"","sources":["../../src/js/updateStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,IAAI,OAAO,GAAG,KAAK,CAAA;AACnB,MAAM,SAAS,GAAG,IAAI,GAAG,EAAc,CAAA;AAEvC,yEAAyE;AACzE,SAAS,QAAQ;IACf,IAAI,OAAO;QAAE,OAAM;IAEnB,OAAO,GAAG,IAAI,CAAA;IACd,SAAS,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAA;AACjC,CAAC;AAED;;;;;;;GAOG;AACH,IAAI,UAAU,GAAyB,IAAI,CAAA;AAE3C,KAAK,UAAU,OAAO;IACpB,wEAAwE;IACxE,gEAAgE;IAChE,IAAI,OAAO;QAAE,OAAM;IACnB,IAAI,UAAU;QAAE,OAAO,UAAU,CAAA;IAEjC,UAAU,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE;QAC9B,UAAU,GAAG,IAAI,CAAA;IACnB,CAAC,CAAC,CAAA;IAEF,OAAO,UAAU,CAAA;AACnB,CAAC;AAED,KAAK,UAAU,GAAG;IAChB,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,4BAA4B,CAAC,EAAE,YAAY,CAAC,SAAS,CAAC,CAAA;IAE3F,IAAI,CAAC,KAAK;QAAE,OAAO,QAAQ,EAAE,CAAA;IAE7B,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE;YACjD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,eAAe,EAAE,KAAK,EAAE;YACjD,KAAK,EAAE,UAAU;SAClB,CAAC,CAAA;QAEF,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG;YAAE,QAAQ,EAAE,CAAA;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,QAAQ,EAAE,CAAA;IACZ,CAAC;AACH,CAAC;AAED,IAAI,OAAO,SAAS,KAAK,WAAW,IAAI,eAAe,IAAI,SAAS,EAAE,CAAC;IACrE,SAAS,CAAC,aAAa,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC,KAAmB,EAAE,EAAE;QAC1E,IAAK,KAAK,CAAC,IAAiC,EAAE,IAAI,KAAK,iBAAiB;YAAE,KAAK,OAAO,EAAE,CAAA;IAC1F,CAAC,CAAC,CAAA;IAEF,wEAAwE;IACxE,0EAA0E;IAC1E,0EAA0E;IAC1E,2EAA2E;IAC3E,mEAAmE;IACnE,MAAM,gBAAgB,GAAG,SAAS,CAAC,aAAa,CAAC,UAAU,KAAK,IAAI,CAAA;IAEpE,SAAS,CAAC,aAAa,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,GAAG,EAAE;QAChE,IAAI,gBAAgB;YAAE,KAAK,OAAO,EAAE,CAAA;IACtC,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,OAAO,OAAO,CAAA;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS;IACvB,QAAQ,EAAE,CAAA;AACZ,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,QAAoB;IACrD,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;IAEvB,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;AACzC,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,eAAe;IAC7B,OAAO,KAAK,CAAA;AACd,CAAC","sourcesContent":["/**\n * Whether a newer build is live while this page is still running the old one.\n *\n * Free of React for the same reason onlineStore is: the value arrives from a\n * service worker message, which has nothing to do with rendering, and the hook\n * that reads it lives next door.\n *\n * The situation it reports is real rather than theoretical. This package's\n * worker activates immediately rather than waiting for every tab to close, and\n * activating sweeps the previous build's cache — so a page that has been open\n * across a deploy is running javascript whose remaining chunks are gone. It\n * works until it navigates somewhere that needs one.\n */\n\nlet updated = false\nconst listeners = new Set<() => void>()\n\n/** A boolean, so React compares snapshots by value and does not loop. */\nfunction announce(): void {\n if (updated) return\n\n updated = true\n listeners.forEach((fn) => fn())\n}\n\n/**\n * The worker's news is about the worker: a newer one took over. Whether\n * this PAGE is stale is a different question, and the server answers it -\n * a page loaded from the network while the new worker was still installing\n * is already the new build, and told to reload it reloaded into itself.\n * One request, with the build the document says it is: 409 is stale, 200\n * is current, and no answer at all is treated as stale, the safe reading.\n */\nlet confirming: Promise<void> | null = null\n\nasync function confirm(): Promise<void> {\n // One deploy is two signals - the worker's message and controllerchange\n // - and was two requests. One in flight at a time answers both.\n if (updated) return\n if (confirming) return confirming\n\n confirming = ask().finally(() => {\n confirming = null\n })\n\n return confirming\n}\n\nasync function ask(): Promise<void> {\n const build = document.querySelector('meta[name=\"rsc-kit:build\"]')?.getAttribute('content')\n\n if (!build) return announce()\n\n try {\n const response = await fetch(window.location.href, {\n method: 'HEAD',\n headers: { 'X-RSC': '1', 'X-RSC-Version': build },\n cache: 'no-store',\n })\n\n if (response.status === 409) announce()\n } catch {\n announce()\n }\n}\n\nif (typeof navigator !== 'undefined' && 'serviceWorker' in navigator) {\n navigator.serviceWorker.addEventListener('message', (event: MessageEvent) => {\n if ((event.data as { type?: string } | null)?.type === 'rsc-kit:updated') void confirm()\n })\n\n // A worker that took control after this page loaded is the same news by\n // another route — it fires when the page was open across a deploy and the\n // message was posted before this listener existed. Only when a worker was\n // already in control when the page loaded: the first worker a visitor ever\n // gets claims the page too, and that is an install, not an update.\n const controlledAtLoad = navigator.serviceWorker.controller !== null\n\n navigator.serviceWorker.addEventListener('controllerchange', () => {\n if (controlledAtLoad) void confirm()\n })\n}\n\nexport function isUpdated(): boolean {\n return updated\n}\n\n/**\n * The server said so itself - a 409 to this page's build on a request the\n * visitor did not make. No confirmation needed: the answer is the\n * confirmation. The next navigation is a document load.\n */\nexport function markStale(): void {\n announce()\n}\n\nexport function subscribeToUpdates(callback: () => void): () => void {\n listeners.add(callback)\n\n return () => listeners.delete(callback)\n}\n\n/** The server render's answer. A fresh document is by definition not stale. */\nexport function updatedOnServer(): boolean {\n return false\n}\n"]}
1
+ {"version":3,"file":"updateStore.js","sourceRoot":"","sources":["../../src/js/updateStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEjD,IAAI,OAAO,GAAG,KAAK,CAAA;AACnB,MAAM,SAAS,GAAG,IAAI,GAAG,EAAc,CAAA;AAEvC,yEAAyE;AACzE,SAAS,QAAQ;IACf,IAAI,OAAO;QAAE,OAAM;IAEnB,OAAO,GAAG,IAAI,CAAA;IACd,SAAS,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAA;AACjC,CAAC;AAED;;;;;;;GAOG;AACH,IAAI,UAAU,GAAyB,IAAI,CAAA;AAE3C,KAAK,UAAU,OAAO;IACpB,wEAAwE;IACxE,gEAAgE;IAChE,IAAI,OAAO;QAAE,OAAM;IACnB,IAAI,UAAU;QAAE,OAAO,UAAU,CAAA;IAEjC,UAAU,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE;QAC9B,UAAU,GAAG,IAAI,CAAA;IACnB,CAAC,CAAC,CAAA;IAEF,OAAO,UAAU,CAAA;AACnB,CAAC;AAED,KAAK,UAAU,GAAG;IAChB,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,4BAA4B,CAAC,EAAE,YAAY,CAAC,SAAS,CAAC,CAAA;IAE3F,IAAI,CAAC,KAAK;QAAE,OAAO,QAAQ,EAAE,CAAA;IAE7B,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,eAAe,EAAE,KAAK,EAAE,CAAA;QACxD,uEAAuE;QACvE,kEAAkE;QAClE,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE;YAC3E,MAAM,EAAE,MAAM;YACd,OAAO;YACP,KAAK,EAAE,UAAU;SAClB,CAAC,CAAA;QAEF,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG;YAAE,QAAQ,EAAE,CAAA;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,QAAQ,EAAE,CAAA;IACZ,CAAC;AACH,CAAC;AAED,IAAI,OAAO,SAAS,KAAK,WAAW,IAAI,eAAe,IAAI,SAAS,EAAE,CAAC;IACrE,SAAS,CAAC,aAAa,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC,KAAmB,EAAE,EAAE;QAC1E,IAAK,KAAK,CAAC,IAAiC,EAAE,IAAI,KAAK,iBAAiB;YAAE,KAAK,OAAO,EAAE,CAAA;IAC1F,CAAC,CAAC,CAAA;IAEF,wEAAwE;IACxE,0EAA0E;IAC1E,0EAA0E;IAC1E,2EAA2E;IAC3E,mEAAmE;IACnE,MAAM,gBAAgB,GAAG,SAAS,CAAC,aAAa,CAAC,UAAU,KAAK,IAAI,CAAA;IAEpE,SAAS,CAAC,aAAa,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,GAAG,EAAE;QAChE,IAAI,gBAAgB;YAAE,KAAK,OAAO,EAAE,CAAA;IACtC,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,OAAO,OAAO,CAAA;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS;IACvB,QAAQ,EAAE,CAAA;AACZ,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,QAAoB;IACrD,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;IAEvB,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;AACzC,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,eAAe;IAC7B,OAAO,KAAK,CAAA;AACd,CAAC","sourcesContent":["/**\n * Whether a newer build is live while this page is still running the old one.\n *\n * Free of React for the same reason onlineStore is: the value arrives from a\n * service worker message, which has nothing to do with rendering, and the hook\n * that reads it lives next door.\n *\n * The situation it reports is real rather than theoretical. This package's\n * worker activates immediately rather than waiting for every tab to close, and\n * activating sweeps the previous build's cache — so a page that has been open\n * across a deploy is running javascript whose remaining chunks are gone. It\n * works until it navigates somewhere that needs one.\n */\n\nimport { keyedPayloadUrl } from './payloadKey.js'\n\nlet updated = false\nconst listeners = new Set<() => void>()\n\n/** A boolean, so React compares snapshots by value and does not loop. */\nfunction announce(): void {\n if (updated) return\n\n updated = true\n listeners.forEach((fn) => fn())\n}\n\n/**\n * The worker's news is about the worker: a newer one took over. Whether\n * this PAGE is stale is a different question, and the server answers it -\n * a page loaded from the network while the new worker was still installing\n * is already the new build, and told to reload it reloaded into itself.\n * One request, with the build the document says it is: 409 is stale, 200\n * is current, and no answer at all is treated as stale, the safe reading.\n */\nlet confirming: Promise<void> | null = null\n\nasync function confirm(): Promise<void> {\n // One deploy is two signals - the worker's message and controllerchange\n // - and was two requests. One in flight at a time answers both.\n if (updated) return\n if (confirming) return confirming\n\n confirming = ask().finally(() => {\n confirming = null\n })\n\n return confirming\n}\n\nasync function ask(): Promise<void> {\n const build = document.querySelector('meta[name=\"rsc-kit:build\"]')?.getAttribute('content')\n\n if (!build) return announce()\n\n try {\n const headers = { 'X-RSC': '1', 'X-RSC-Version': build }\n // Its own url, as every payload request has: a cache that ignores Vary\n // would answer the document's 200 and hide the 409 this asks for.\n const response = await fetch(keyedPayloadUrl(window.location.href, headers), {\n method: 'HEAD',\n headers,\n cache: 'no-store',\n })\n\n if (response.status === 409) announce()\n } catch {\n announce()\n }\n}\n\nif (typeof navigator !== 'undefined' && 'serviceWorker' in navigator) {\n navigator.serviceWorker.addEventListener('message', (event: MessageEvent) => {\n if ((event.data as { type?: string } | null)?.type === 'rsc-kit:updated') void confirm()\n })\n\n // A worker that took control after this page loaded is the same news by\n // another route — it fires when the page was open across a deploy and the\n // message was posted before this listener existed. Only when a worker was\n // already in control when the page loaded: the first worker a visitor ever\n // gets claims the page too, and that is an install, not an update.\n const controlledAtLoad = navigator.serviceWorker.controller !== null\n\n navigator.serviceWorker.addEventListener('controllerchange', () => {\n if (controlledAtLoad) void confirm()\n })\n}\n\nexport function isUpdated(): boolean {\n return updated\n}\n\n/**\n * The server said so itself - a 409 to this page's build on a request the\n * visitor did not make. No confirmation needed: the answer is the\n * confirmation. The next navigation is a document load.\n */\nexport function markStale(): void {\n announce()\n}\n\nexport function subscribeToUpdates(callback: () => void): () => void {\n listeners.add(callback)\n\n return () => listeners.delete(callback)\n}\n\n/** The server render's answer. A fresh document is by definition not stale. */\nexport function updatedOnServer(): boolean {\n return false\n}\n"]}
@@ -12,7 +12,7 @@
12
12
  * knows a new version activated.
13
13
  */
14
14
  import { useSyncExternalStore } from "react";
15
- import { isUpdated, subscribeToUpdates, updatedOnServer } from "./updateStore";
15
+ import { isUpdated, subscribeToUpdates, updatedOnServer } from "./updateStore.js";
16
16
  export function useAppUpdate() {
17
17
  const updated = useSyncExternalStore(subscribeToUpdates, isUpdated, updatedOnServer);
18
18
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"useAppUpdate.js","sourceRoot":"","sources":["../../src/js/useAppUpdate.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAE/E,MAAM,UAAU,YAAY;IAC1B,MAAM,OAAO,GAAG,oBAAoB,CAAC,kBAAkB,EAAE,SAAS,EAAE,eAAe,CAAC,CAAC;IAErF,OAAO;QACL,OAAO;QACP,qEAAqE;QACrE,oEAAoE;QACpE,6BAA6B;QAC7B,MAAM,EAAE,GAAG,EAAE;YACX,IAAI,OAAO,MAAM,KAAK,WAAW;gBAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;QAC9D,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["\"use client\";\n\n/**\n * Whether a newer build is live, and how to get it.\n *\n * const { updated, reload } = useAppUpdate()\n *\n * if (updated) return <button onClick={reload}>A new version is ready</button>\n *\n * Reported rather than acted on. Reloading out from under someone mid-form is\n * worse than the staleness it fixes, so this package will not do it for you —\n * what it will do is tell you, which nothing else can, because only the worker\n * knows a new version activated.\n */\n\nimport { useSyncExternalStore } from \"react\";\nimport { isUpdated, subscribeToUpdates, updatedOnServer } from \"./updateStore\";\n\nexport function useAppUpdate(): { updated: boolean; reload: () => void } {\n const updated = useSyncExternalStore(subscribeToUpdates, isUpdated, updatedOnServer);\n\n return {\n updated,\n // location.reload() rather than a router navigation: the point is to\n // re-fetch the document and its javascript, and a client navigation\n // deliberately does neither.\n reload: () => {\n if (typeof window !== \"undefined\") window.location.reload();\n },\n };\n}\n"]}
1
+ {"version":3,"file":"useAppUpdate.js","sourceRoot":"","sources":["../../src/js/useAppUpdate.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAElF,MAAM,UAAU,YAAY;IAC1B,MAAM,OAAO,GAAG,oBAAoB,CAAC,kBAAkB,EAAE,SAAS,EAAE,eAAe,CAAC,CAAC;IAErF,OAAO;QACL,OAAO;QACP,qEAAqE;QACrE,oEAAoE;QACpE,6BAA6B;QAC7B,MAAM,EAAE,GAAG,EAAE;YACX,IAAI,OAAO,MAAM,KAAK,WAAW;gBAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;QAC9D,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["\"use client\";\n\n/**\n * Whether a newer build is live, and how to get it.\n *\n * const { updated, reload } = useAppUpdate()\n *\n * if (updated) return <button onClick={reload}>A new version is ready</button>\n *\n * Reported rather than acted on. Reloading out from under someone mid-form is\n * worse than the staleness it fixes, so this package will not do it for you —\n * what it will do is tell you, which nothing else can, because only the worker\n * knows a new version activated.\n */\n\nimport { useSyncExternalStore } from \"react\";\nimport { isUpdated, subscribeToUpdates, updatedOnServer } from \"./updateStore.js\";\n\nexport function useAppUpdate(): { updated: boolean; reload: () => void } {\n const updated = useSyncExternalStore(subscribeToUpdates, isUpdated, updatedOnServer);\n\n return {\n updated,\n // location.reload() rather than a router navigation: the point is to\n // re-fetch the document and its javascript, and a client navigation\n // deliberately does neither.\n reload: () => {\n if (typeof window !== \"undefined\") window.location.reload();\n },\n };\n}\n"]}
@@ -0,0 +1,16 @@
1
+ import { type FlashMessage } from "../flashCookie.js";
2
+ export type { FlashKind, FlashMessage } from "../flashCookie.js";
3
+ /**
4
+ * Hands each message flash() queued to `onMessage`, once, and clears it.
5
+ *
6
+ * useFlash(({ message, kind }) => (kind === 'error' ? toast.error : toast.success)(message))
7
+ *
8
+ * Put it in a client component in the root layout: it looks on mount, after
9
+ * every navigation (an action's redirect is one), and whenever the cookie
10
+ * changes where the browser can say so - an action that stays on the page.
11
+ * Render the message as text; it is a cookie, and a cookie can be set by
12
+ * anything on the domain.
13
+ */
14
+ export declare function useFlash(onMessage: (message: FlashMessage) => void): void;
15
+ /** The queued messages, taken: read, then cleared, so each is shown once. */
16
+ export declare function takeFlash(): FlashMessage[];
@@ -0,0 +1,56 @@
1
+ "use client";
2
+ import { useEffect, useRef } from "react";
3
+ import { FLASH_COOKIE, readFlash } from "../flashCookie.js";
4
+ /**
5
+ * Hands each message flash() queued to `onMessage`, once, and clears it.
6
+ *
7
+ * useFlash(({ message, kind }) => (kind === 'error' ? toast.error : toast.success)(message))
8
+ *
9
+ * Put it in a client component in the root layout: it looks on mount, after
10
+ * every navigation (an action's redirect is one), and whenever the cookie
11
+ * changes where the browser can say so - an action that stays on the page.
12
+ * Render the message as text; it is a cookie, and a cookie can be set by
13
+ * anything on the domain.
14
+ */
15
+ export function useFlash(onMessage) {
16
+ const latest = useRef(onMessage);
17
+ latest.current = onMessage;
18
+ useEffect(() => {
19
+ const take = () => {
20
+ for (const message of takeFlash())
21
+ latest.current(message);
22
+ };
23
+ take();
24
+ // CookieStore says when a response set the cookie, even with no
25
+ // navigation. Where it is missing (or the page is not a secure context),
26
+ // a navigation is still the moment that matters.
27
+ const store = globalThis.cookieStore;
28
+ window.addEventListener("rsc-navigate", take);
29
+ window.addEventListener("popstate", take);
30
+ store?.addEventListener("change", take);
31
+ return () => {
32
+ window.removeEventListener("rsc-navigate", take);
33
+ window.removeEventListener("popstate", take);
34
+ store?.removeEventListener("change", take);
35
+ };
36
+ }, []);
37
+ }
38
+ /** The queued messages, taken: read, then cleared, so each is shown once. */
39
+ export function takeFlash() {
40
+ if (typeof document === "undefined")
41
+ return [];
42
+ const prefix = `${FLASH_COOKIE}=`;
43
+ const raw = document.cookie.split("; ").find((part) => part.startsWith(prefix));
44
+ if (!raw)
45
+ return [];
46
+ document.cookie = `${FLASH_COOKIE}=; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Path=/; SameSite=Lax`;
47
+ let value;
48
+ try {
49
+ value = decodeURIComponent(raw.slice(prefix.length));
50
+ }
51
+ catch {
52
+ return [];
53
+ }
54
+ return readFlash(value);
55
+ }
56
+ //# sourceMappingURL=useFlash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFlash.js","sourceRoot":"","sources":["../../src/js/useFlash.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,SAAS,EAAqB,MAAM,mBAAmB,CAAC;AAI/E;;;;;;;;;;GAUG;AACH,MAAM,UAAU,QAAQ,CAAC,SAA0C;IACjE,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;IAEjC,MAAM,CAAC,OAAO,GAAG,SAAS,CAAC;IAE3B,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,IAAI,GAAG,GAAG,EAAE;YAChB,KAAK,MAAM,OAAO,IAAI,SAAS,EAAE;gBAAE,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAC7D,CAAC,CAAC;QAEF,IAAI,EAAE,CAAC;QAEP,gEAAgE;QAChE,yEAAyE;QACzE,iDAAiD;QACjD,MAAM,KAAK,GAAI,UAA4C,CAAC,WAAW,CAAC;QAExE,MAAM,CAAC,gBAAgB,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;QAC9C,MAAM,CAAC,gBAAgB,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QAC1C,KAAK,EAAE,gBAAgB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAExC,OAAO,GAAG,EAAE;YACV,MAAM,CAAC,mBAAmB,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;YACjD,MAAM,CAAC,mBAAmB,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;YAC7C,KAAK,EAAE,mBAAmB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAC7C,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;AACT,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,SAAS;IACvB,IAAI,OAAO,QAAQ,KAAK,WAAW;QAAE,OAAO,EAAE,CAAC;IAE/C,MAAM,MAAM,GAAG,GAAG,YAAY,GAAG,CAAC;IAClC,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;IAEhF,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,CAAC;IAEpB,QAAQ,CAAC,MAAM,GAAG,GAAG,YAAY,2EAA2E,CAAC;IAE7G,IAAI,KAAa,CAAC;IAElB,IAAI,CAAC;QACH,KAAK,GAAG,kBAAkB,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC;AAC1B,CAAC","sourcesContent":["\"use client\";\n\nimport { useEffect, useRef } from \"react\";\nimport { FLASH_COOKIE, readFlash, type FlashMessage } from \"../flashCookie.js\";\n\nexport type { FlashKind, FlashMessage } from \"../flashCookie.js\";\n\n/**\n * Hands each message flash() queued to `onMessage`, once, and clears it.\n *\n * useFlash(({ message, kind }) => (kind === 'error' ? toast.error : toast.success)(message))\n *\n * Put it in a client component in the root layout: it looks on mount, after\n * every navigation (an action's redirect is one), and whenever the cookie\n * changes where the browser can say so - an action that stays on the page.\n * Render the message as text; it is a cookie, and a cookie can be set by\n * anything on the domain.\n */\nexport function useFlash(onMessage: (message: FlashMessage) => void): void {\n const latest = useRef(onMessage);\n\n latest.current = onMessage;\n\n useEffect(() => {\n const take = () => {\n for (const message of takeFlash()) latest.current(message);\n };\n\n take();\n\n // CookieStore says when a response set the cookie, even with no\n // navigation. Where it is missing (or the page is not a secure context),\n // a navigation is still the moment that matters.\n const store = (globalThis as { cookieStore?: EventTarget }).cookieStore;\n\n window.addEventListener(\"rsc-navigate\", take);\n window.addEventListener(\"popstate\", take);\n store?.addEventListener(\"change\", take);\n\n return () => {\n window.removeEventListener(\"rsc-navigate\", take);\n window.removeEventListener(\"popstate\", take);\n store?.removeEventListener(\"change\", take);\n };\n }, []);\n}\n\n/** The queued messages, taken: read, then cleared, so each is shown once. */\nexport function takeFlash(): FlashMessage[] {\n if (typeof document === \"undefined\") return [];\n\n const prefix = `${FLASH_COOKIE}=`;\n const raw = document.cookie.split(\"; \").find((part) => part.startsWith(prefix));\n\n if (!raw) return [];\n\n document.cookie = `${FLASH_COOKIE}=; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Path=/; SameSite=Lax`;\n\n let value: string;\n\n try {\n value = decodeURIComponent(raw.slice(prefix.length));\n } catch {\n return [];\n }\n\n return readFlash(value);\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { useSyncExternalStore } from "react";
3
- import { isReachable, subscribeReachable } from "./onlineStore";
3
+ import { isReachable, subscribeReachable } from "./onlineStore.js";
4
4
  /** The server is reachable from itself; assuming otherwise would render an
5
5
  * offline state into the HTML and mismatch on hydration. */
6
6
  function getServerSnapshot() {
@@ -1 +1 @@
1
- {"version":3,"file":"useOnline.js","sourceRoot":"","sources":["../../src/js/useOnline.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAEhE;4DAC4D;AAC5D,SAAS,iBAAiB;IACxB,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS;IACvB,OAAO,oBAAoB,CAAC,kBAAkB,EAAE,WAAW,EAAE,iBAAiB,CAAC,CAAC;AAClF,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,OAAO,CAAC,SAAS,EAAE,CAAC;AACtB,CAAC","sourcesContent":["\"use client\";\n\nimport { useSyncExternalStore } from \"react\";\nimport { isReachable, subscribeReachable } from \"./onlineStore\";\n\n/** The server is reachable from itself; assuming otherwise would render an\n * offline state into the HTML and mismatch on hydration. */\nfunction getServerSnapshot(): boolean {\n return true;\n}\n\n/**\n * Whether the app can reach the server, as the router last observed it.\n *\n * See onlineStore.ts for why this is not `navigator.onLine`.\n */\nexport function useOnline(): boolean {\n return useSyncExternalStore(subscribeReachable, isReachable, getServerSnapshot);\n}\n\nexport function useOffline(): boolean {\n return !useOnline();\n}\n"]}
1
+ {"version":3,"file":"useOnline.js","sourceRoot":"","sources":["../../src/js/useOnline.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEnE;4DAC4D;AAC5D,SAAS,iBAAiB;IACxB,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS;IACvB,OAAO,oBAAoB,CAAC,kBAAkB,EAAE,WAAW,EAAE,iBAAiB,CAAC,CAAC;AAClF,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,OAAO,CAAC,SAAS,EAAE,CAAC;AACtB,CAAC","sourcesContent":["\"use client\";\n\nimport { useSyncExternalStore } from \"react\";\nimport { isReachable, subscribeReachable } from \"./onlineStore.js\";\n\n/** The server is reachable from itself; assuming otherwise would render an\n * offline state into the HTML and mismatch on hydration. */\nfunction getServerSnapshot(): boolean {\n return true;\n}\n\n/**\n * Whether the app can reach the server, as the router last observed it.\n *\n * See onlineStore.ts for why this is not `navigator.onLine`.\n */\nexport function useOnline(): boolean {\n return useSyncExternalStore(subscribeReachable, isReachable, getServerSnapshot);\n}\n\nexport function useOffline(): boolean {\n return !useOnline();\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { useSyncExternalStore } from "react";
3
- import { useRenderedPathname } from "./PathnameProvider";
3
+ import { useRenderedPathname } from "./PathnameProvider.js";
4
4
  let currentPathname = typeof window !== "undefined" ? window.location.pathname : "/";
5
5
  const listeners = new Set();
6
6
  function notify() {
@@ -1 +1 @@
1
- {"version":3,"file":"usePathname.js","sourceRoot":"","sources":["../../src/js/usePathname.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEzD,IAAI,eAAe,GAAG,OAAO,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC;AACrF,MAAM,SAAS,GAAG,IAAI,GAAG,EAAc,CAAC;AAExC,SAAS,MAAM;IACb,eAAe,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAC3C,SAAS,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;IAClC,MAAM,CAAC,gBAAgB,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;IAChD,MAAM,CAAC,gBAAgB,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;AAC9C,CAAC;AAED,SAAS,SAAS,CAAC,QAAoB;IACrC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACxB,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;AAC1C,CAAC;AAED,SAAS,WAAW;IAClB,OAAO,eAAe,CAAC;AACzB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW;IACzB,MAAM,QAAQ,GAAG,mBAAmB,EAAE,CAAC;IAEvC,OAAO,oBAAoB,CAAC,SAAS,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC;AACtE,CAAC","sourcesContent":["\"use client\";\n\nimport { useSyncExternalStore } from \"react\";\nimport { useRenderedPathname } from \"./PathnameProvider\";\n\nlet currentPathname = typeof window !== \"undefined\" ? window.location.pathname : \"/\";\nconst listeners = new Set<() => void>();\n\nfunction notify(): void {\n currentPathname = window.location.pathname;\n listeners.forEach((fn) => fn());\n}\n\nif (typeof window !== \"undefined\") {\n window.addEventListener(\"rsc-navigate\", notify);\n window.addEventListener(\"popstate\", notify);\n}\n\nfunction subscribe(callback: () => void): () => void {\n listeners.add(callback);\n return () => listeners.delete(callback);\n}\n\nfunction getSnapshot(): string {\n return currentPathname;\n}\n\n/**\n * The current pathname, live across navigations.\n *\n * The server snapshot comes from the url being rendered rather than a\n * constant. It used to be \"/\" — so a nav bar on any other page rendered with\n * the wrong link marked active, and only hydration put it right.\n */\nexport function usePathname(): string {\n const rendered = useRenderedPathname();\n\n return useSyncExternalStore(subscribe, getSnapshot, () => rendered);\n}\n"]}
1
+ {"version":3,"file":"usePathname.js","sourceRoot":"","sources":["../../src/js/usePathname.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,IAAI,eAAe,GAAG,OAAO,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC;AACrF,MAAM,SAAS,GAAG,IAAI,GAAG,EAAc,CAAC;AAExC,SAAS,MAAM;IACb,eAAe,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAC3C,SAAS,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;IAClC,MAAM,CAAC,gBAAgB,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;IAChD,MAAM,CAAC,gBAAgB,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;AAC9C,CAAC;AAED,SAAS,SAAS,CAAC,QAAoB;IACrC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACxB,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;AAC1C,CAAC;AAED,SAAS,WAAW;IAClB,OAAO,eAAe,CAAC;AACzB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW;IACzB,MAAM,QAAQ,GAAG,mBAAmB,EAAE,CAAC;IAEvC,OAAO,oBAAoB,CAAC,SAAS,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC;AACtE,CAAC","sourcesContent":["\"use client\";\n\nimport { useSyncExternalStore } from \"react\";\nimport { useRenderedPathname } from \"./PathnameProvider.js\";\n\nlet currentPathname = typeof window !== \"undefined\" ? window.location.pathname : \"/\";\nconst listeners = new Set<() => void>();\n\nfunction notify(): void {\n currentPathname = window.location.pathname;\n listeners.forEach((fn) => fn());\n}\n\nif (typeof window !== \"undefined\") {\n window.addEventListener(\"rsc-navigate\", notify);\n window.addEventListener(\"popstate\", notify);\n}\n\nfunction subscribe(callback: () => void): () => void {\n listeners.add(callback);\n return () => listeners.delete(callback);\n}\n\nfunction getSnapshot(): string {\n return currentPathname;\n}\n\n/**\n * The current pathname, live across navigations.\n *\n * The server snapshot comes from the url being rendered rather than a\n * constant. It used to be \"/\" — so a nav bar on any other page rendered with\n * the wrong link marked active, and only hydration put it right.\n */\nexport function usePathname(): string {\n const rendered = useRenderedPathname();\n\n return useSyncExternalStore(subscribe, getSnapshot, () => rendered);\n}\n"]}
@@ -74,6 +74,13 @@ export interface ManifestRoute {
74
74
  * only the bundle can run it.
75
75
  */
76
76
  staticParams: boolean;
77
+ /**
78
+ * Seconds a shared cache may keep this page: the smallest
79
+ * `export const revalidate` across its layouts and the page itself, as Next
80
+ * takes it. Absent when none says, or one says 0 - rendered per request, as
81
+ * before. A page with it is not frozen at build; see pageCache.ts.
82
+ */
83
+ revalidate?: number;
77
84
  }
78
85
  export interface ManifestIntercept {
79
86
  component: string;
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,EAAE;AACF,6EAA6E;AAC7E,6EAA6E;AAC7E,+EAA+E;AAC/E,gFAAgF;AAChF,uCAAuC;AACvC,EAAE;AACF,6EAA6E;AAC7E,+EAA+E;AAC/E,oBAAoB","sourcesContent":["// The shape of routes.json — what the build discovered, for a host to read.\n//\n// The build already walks app/ to generate its entries, and every host needs\n// the same facts: which url a component answers, what layouts wrap it, which\n// slots and sections belong to it. Laravel used to scan the tree a second time\n// to work that out; a JS host would have had to write a third walk. This is the\n// one answer, and these are its types.\n//\n// Urls are segments rather than a pattern string, because the pattern is the\n// host's dialect: Laravel writes {slug}, Hono writes :slug, and neither is the\n// build's business.\n\nexport interface RouteSegment {\n /**\n * `host`: a `[name]` directory at the top of app/. Bound from the request's\n * host, never from a path segment - so `example.com/nope` is a 404 rather\n * than a tenant called \"nope\". See hostRouting.\n */\n type: \"static\" | \"param\" | \"catchAll\" | \"host\";\n value: string;\n}\n\nexport interface ManifestRoute {\n component: string;\n segments: RouteSegment[];\n layouts: string[];\n loadings: string[];\n /**\n * `error.tsx` files above this route, outermost first.\n *\n * The nearest one to a failure catches it, the same way the nearest\n * `loading.tsx` is the fallback. Optional: a manifest from a build before\n * error boundaries existed has none.\n */\n errors?: string[];\n /**\n * `not-found.tsx` files above this route, outermost first.\n *\n * A `notFound()` from this route's page or guard is answered by the nearest\n * one, rendered inside the layouts above it; the way `error.tsx` is the\n * nearest above a failure. A url no route owns has no route to ask, and is\n * answered by the one at the root. Optional: a manifest from a build before\n * this has none, and the root one answers everything.\n */\n notFounds?: string[];\n /**\n * `middleware.ts` files above this route, outermost first.\n *\n * Run before anything at or below them renders, on every path. A check is\n * not UI, and making it a layout meant the client could decline it: layouts\n * are skipped on a partial navigation, and what gets skipped is named in a\n * header nothing can verify.\n */\n middleware: string[];\n slots: Record<string, string>;\n sections: string[];\n /**\n * The host's route-config file beside this page, if it named one, and the\n * ancestor ones that also apply — outermost first, this page's excluded.\n *\n * Relative to the project root: an absolute path is true only on the machine\n * that produced it, and building in a container is ordinary.\n */\n config: string | null;\n ancestorConfigs: string[];\n /**\n * Host middleware names for this route, outermost first.\n *\n * Declared in a route.ts beside or above the page. The engine does not know\n * what they mean — they are the host's own vocabulary — it only runs them\n * past the host before anything at or below this route renders.\n *\n * Empty on a route that named none, and on every route in an app that never\n * wrote a route.ts, which is why this needs no flag.\n *\n * Optional because registration boots from the PREVIOUS build's manifest: a\n * shape change takes two builds to settle, and a required field would make\n * the first of those a hard failure rather than a route with no guards.\n */\n hostMiddleware?: string[];\n /**\n * Whether the page exports generateStaticParams.\n *\n * Recorded here so a host can plan a build — which routes to ask for urls,\n * which to leave on demand — without loading the server bundle first. The\n * function itself is reached through the bundle's getStaticParams(), because\n * only the bundle can run it.\n */\n staticParams: boolean;\n}\n\nexport interface ManifestIntercept {\n component: string;\n slot: string;\n segments: RouteSegment[];\n /** (.) same level, (..) one up, (...) from the root. */\n marker: string;\n}\n\n/**\n * A `route.ts` — an api endpoint rather than a page.\n *\n * Separate from `routes` because it is matched before them and answered\n * without rendering anything: no layouts, no payload, no client. A url cannot\n * be both, and the build refuses one that is.\n */\nexport interface ManifestApiRoute {\n /** The module name, as the engine's registry keys it. */\n name: string;\n /**\n * The file that answers, relative to the source directory - `app/api/x/route.ts`,\n * or `app/sitemap.ts` for a route the build synthesised from a metadata file\n * - so a line about the route can say where to look.\n */\n source?: string;\n segments: RouteSegment[];\n /** Which methods the file exports, so a 405 can name the rest. */\n methods: string[];\n /**\n * `middleware.ts` files above this route, outermost first.\n *\n * The same chain a page in that directory runs. A route.ts sits among the\n * pages it belongs with, so a guard on the directory covers it too —\n * anything else would mean adding an endpoint under a guarded path silently\n * opened a hole in it.\n */\n middleware: string[];\n /** Host middleware above this route (`export const middleware = [...]`), outermost first. */\n hostMiddleware?: string[];\n}\n\nexport interface RouteManifest {\n version: number;\n build: {\n output: string;\n exportPath: string;\n payloadName: string;\n /**\n * The site's own hosts, bare and lower-case. A request from any other\n * host is matched with that host's segment in front of its path - see\n * hostRouting. Absent or empty: every request is the site's own.\n */\n hosts?: string[];\n /**\n * Whether every response says what built it: `X-Powered-By: rsc-kit` and\n * a generator meta tag in the document. The name only, never the version.\n * `X-RSC-Kit` (how a response was served) is sent regardless.\n */\n identify?: boolean;\n /**\n * Whether any page or section declares `refreshOn`, so the server signs\n * names - and must refuse to serve, in production, without a key every\n * instance shares.\n */\n refreshOn?: boolean;\n /**\n * rscKit({ headers }): set on every response, and handed to an edge with\n * every page it serves from its cache. Fixed for the build.\n */\n headers?: Record<string, string>;\n };\n routes: ManifestRoute[];\n intercepts: ManifestIntercept[];\n /** Optional: a manifest from a build before api routes existed has none. */\n apis?: ManifestApiRoute[];\n}\n"]}
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,EAAE;AACF,6EAA6E;AAC7E,6EAA6E;AAC7E,+EAA+E;AAC/E,gFAAgF;AAChF,uCAAuC;AACvC,EAAE;AACF,6EAA6E;AAC7E,+EAA+E;AAC/E,oBAAoB","sourcesContent":["// The shape of routes.json — what the build discovered, for a host to read.\n//\n// The build already walks app/ to generate its entries, and every host needs\n// the same facts: which url a component answers, what layouts wrap it, which\n// slots and sections belong to it. Laravel used to scan the tree a second time\n// to work that out; a JS host would have had to write a third walk. This is the\n// one answer, and these are its types.\n//\n// Urls are segments rather than a pattern string, because the pattern is the\n// host's dialect: Laravel writes {slug}, Hono writes :slug, and neither is the\n// build's business.\n\nexport interface RouteSegment {\n /**\n * `host`: a `[name]` directory at the top of app/. Bound from the request's\n * host, never from a path segment - so `example.com/nope` is a 404 rather\n * than a tenant called \"nope\". See hostRouting.\n */\n type: \"static\" | \"param\" | \"catchAll\" | \"host\";\n value: string;\n}\n\nexport interface ManifestRoute {\n component: string;\n segments: RouteSegment[];\n layouts: string[];\n loadings: string[];\n /**\n * `error.tsx` files above this route, outermost first.\n *\n * The nearest one to a failure catches it, the same way the nearest\n * `loading.tsx` is the fallback. Optional: a manifest from a build before\n * error boundaries existed has none.\n */\n errors?: string[];\n /**\n * `not-found.tsx` files above this route, outermost first.\n *\n * A `notFound()` from this route's page or guard is answered by the nearest\n * one, rendered inside the layouts above it; the way `error.tsx` is the\n * nearest above a failure. A url no route owns has no route to ask, and is\n * answered by the one at the root. Optional: a manifest from a build before\n * this has none, and the root one answers everything.\n */\n notFounds?: string[];\n /**\n * `middleware.ts` files above this route, outermost first.\n *\n * Run before anything at or below them renders, on every path. A check is\n * not UI, and making it a layout meant the client could decline it: layouts\n * are skipped on a partial navigation, and what gets skipped is named in a\n * header nothing can verify.\n */\n middleware: string[];\n slots: Record<string, string>;\n sections: string[];\n /**\n * The host's route-config file beside this page, if it named one, and the\n * ancestor ones that also apply — outermost first, this page's excluded.\n *\n * Relative to the project root: an absolute path is true only on the machine\n * that produced it, and building in a container is ordinary.\n */\n config: string | null;\n ancestorConfigs: string[];\n /**\n * Host middleware names for this route, outermost first.\n *\n * Declared in a route.ts beside or above the page. The engine does not know\n * what they mean — they are the host's own vocabulary — it only runs them\n * past the host before anything at or below this route renders.\n *\n * Empty on a route that named none, and on every route in an app that never\n * wrote a route.ts, which is why this needs no flag.\n *\n * Optional because registration boots from the PREVIOUS build's manifest: a\n * shape change takes two builds to settle, and a required field would make\n * the first of those a hard failure rather than a route with no guards.\n */\n hostMiddleware?: string[];\n /**\n * Whether the page exports generateStaticParams.\n *\n * Recorded here so a host can plan a build — which routes to ask for urls,\n * which to leave on demand — without loading the server bundle first. The\n * function itself is reached through the bundle's getStaticParams(), because\n * only the bundle can run it.\n */\n staticParams: boolean;\n /**\n * Seconds a shared cache may keep this page: the smallest\n * `export const revalidate` across its layouts and the page itself, as Next\n * takes it. Absent when none says, or one says 0 - rendered per request, as\n * before. A page with it is not frozen at build; see pageCache.ts.\n */\n revalidate?: number;\n}\n\nexport interface ManifestIntercept {\n component: string;\n slot: string;\n segments: RouteSegment[];\n /** (.) same level, (..) one up, (...) from the root. */\n marker: string;\n}\n\n/**\n * A `route.ts` — an api endpoint rather than a page.\n *\n * Separate from `routes` because it is matched before them and answered\n * without rendering anything: no layouts, no payload, no client. A url cannot\n * be both, and the build refuses one that is.\n */\nexport interface ManifestApiRoute {\n /** The module name, as the engine's registry keys it. */\n name: string;\n /**\n * The file that answers, relative to the source directory - `app/api/x/route.ts`,\n * or `app/sitemap.ts` for a route the build synthesised from a metadata file\n * - so a line about the route can say where to look.\n */\n source?: string;\n segments: RouteSegment[];\n /** Which methods the file exports, so a 405 can name the rest. */\n methods: string[];\n /**\n * `middleware.ts` files above this route, outermost first.\n *\n * The same chain a page in that directory runs. A route.ts sits among the\n * pages it belongs with, so a guard on the directory covers it too —\n * anything else would mean adding an endpoint under a guarded path silently\n * opened a hole in it.\n */\n middleware: string[];\n /** Host middleware above this route (`export const middleware = [...]`), outermost first. */\n hostMiddleware?: string[];\n}\n\nexport interface RouteManifest {\n version: number;\n build: {\n output: string;\n exportPath: string;\n payloadName: string;\n /**\n * The site's own hosts, bare and lower-case. A request from any other\n * host is matched with that host's segment in front of its path - see\n * hostRouting. Absent or empty: every request is the site's own.\n */\n hosts?: string[];\n /**\n * Whether every response says what built it: `X-Powered-By: rsc-kit` and\n * a generator meta tag in the document. The name only, never the version.\n * `X-RSC-Kit` (how a response was served) is sent regardless.\n */\n identify?: boolean;\n /**\n * Whether any page or section declares `refreshOn`, so the server signs\n * names - and must refuse to serve, in production, without a key every\n * instance shares.\n */\n refreshOn?: boolean;\n /**\n * rscKit({ headers }): set on every response, and handed to an edge with\n * every page it serves from its cache. Fixed for the build.\n */\n headers?: Record<string, string>;\n };\n routes: ManifestRoute[];\n intercepts: ManifestIntercept[];\n /** Optional: a manifest from a build before api routes existed has none. */\n apis?: ManifestApiRoute[];\n}\n"]}
@@ -0,0 +1,47 @@
1
+ /** What a request for a `revalidate` page carries from the moment it arrives. */
2
+ export interface PageCaching {
3
+ /** Seconds a shared cache may keep the page. */
4
+ revalidate: number;
5
+ /** The url path, for the log line. */
6
+ path: string;
7
+ /** A check that failed before anything rendered, or after the page said notFound(). */
8
+ refused: string | null;
9
+ }
10
+ /**
11
+ * Decide, for a request as it arrived, whether its answer may be judged at all.
12
+ *
13
+ * Null for anything but a GET or HEAD of a page that exports revalidate - the
14
+ * answer is then what it always was. Keyed by the request the app sees, so the
15
+ * entry in front, which renders a 404, finds it again.
16
+ */
17
+ export declare function cachingFor(arrived: Request, revalidate: number | undefined): PageCaching | null;
18
+ /** What cachingFor() decided for this request, if anything. */
19
+ export declare function cachingOf(request: Request): PageCaching | null;
20
+ /**
21
+ * Note what the render has read so far, for an answer that will be finished
22
+ * somewhere else: the page said notFound(), and the entry in front renders
23
+ * not-found.tsx in a scope of its own, where these reads are gone.
24
+ */
25
+ export declare function noteReadsSoFar(entry: PageCaching): void;
26
+ /**
27
+ * What a public answer says: kept by shared caches for N seconds, refreshed
28
+ * in the background for a day after that, never by the browser. A 404 for at
29
+ * most a minute, so a scanner walking random urls cannot hold pages out.
30
+ */
31
+ export declare function publicCacheControl(revalidate: number, status: number): string;
32
+ /**
33
+ * Finish the answer to a `revalidate` page: rendered whole, then judged.
34
+ *
35
+ * Called inside the request's scope, after every header the request will get
36
+ * is on the answer - cookies included - so what it judges is what is sent.
37
+ * Reading the body to its end is what waits for the render: a Suspense'd
38
+ * child that reads cookies() is still running when the headers exist.
39
+ */
40
+ export declare function finishCacheable(request: Request, response: Response, entry: PageCaching,
41
+ /**
42
+ * Whether the answer turned out to be not-found.tsx, asked once the body is
43
+ * read: a page can say notFound() in the middle of its stream, after a 200
44
+ * went out. A payload is a 200 either way - the router reads the page from
45
+ * it - but a not-found one is kept no longer than a 404.
46
+ */
47
+ saidNotFound?: () => boolean): Promise<Response>;