@rsc-kit/core 0.25.0 → 0.27.0

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 (47) hide show
  1. package/dist/changed.d.ts +260 -0
  2. package/dist/changed.js +605 -0
  3. package/dist/changed.js.map +1 -0
  4. package/dist/conformance.d.ts +2 -0
  5. package/dist/conformance.js +28 -0
  6. package/dist/conformance.js.map +1 -1
  7. package/dist/headers.d.ts +8 -0
  8. package/dist/headers.js +8 -0
  9. package/dist/headers.js.map +1 -1
  10. package/dist/host.d.ts +3 -1
  11. package/dist/host.js +21 -1
  12. package/dist/host.js.map +1 -1
  13. package/dist/js/Changes.d.ts +11 -0
  14. package/dist/js/Changes.js +19 -0
  15. package/dist/js/Changes.js.map +1 -0
  16. package/dist/js/changesStore.d.ts +9 -0
  17. package/dist/js/changesStore.js +178 -0
  18. package/dist/js/changesStore.js.map +1 -0
  19. package/dist/js/navigate.d.ts +1 -1
  20. package/dist/js/navigate.js +4 -1
  21. package/dist/js/navigate.js.map +1 -1
  22. package/dist/js/refreshOn.d.ts +21 -0
  23. package/dist/js/refreshOn.js +58 -0
  24. package/dist/js/refreshOn.js.map +1 -0
  25. package/dist/js/router.d.ts +2 -0
  26. package/dist/js/router.js +7 -0
  27. package/dist/js/router.js.map +1 -1
  28. package/dist/js/section.d.ts +21 -1
  29. package/dist/js/section.js +31 -6
  30. package/dist/js/section.js.map +1 -1
  31. package/dist/manifest.d.ts +6 -0
  32. package/dist/manifest.js.map +1 -1
  33. package/dist/request.d.ts +11 -0
  34. package/dist/request.js +17 -0
  35. package/dist/request.js.map +1 -1
  36. package/dist/sharedRenders.d.ts +9 -0
  37. package/dist/sharedRenders.js +41 -0
  38. package/dist/sharedRenders.js.map +1 -0
  39. package/dist/testHost.d.ts +22 -0
  40. package/dist/testHost.js +23 -0
  41. package/dist/testHost.js.map +1 -1
  42. package/dist/testing.d.ts +1 -1
  43. package/dist/testing.js +7 -1
  44. package/dist/testing.js.map +1 -1
  45. package/dist/vite.js +79 -11
  46. package/dist/vite.js.map +1 -1
  47. package/package.json +5 -1
@@ -0,0 +1,11 @@
1
+ /**
2
+ * What a rendered region is live on, handed to the tab's watcher.
3
+ *
4
+ * Renders nothing. Rendered by `RefreshOn` beside the region it describes,
5
+ * so it arrives in the same payload - with the page, and again with the
6
+ * region each time it is refreshed, carrying the versions that render saw.
7
+ */
8
+ export declare function Changes({ target, names, }: {
9
+ target: string;
10
+ names: Record<string, [number, string]>;
11
+ }): null;
@@ -0,0 +1,19 @@
1
+ "use client";
2
+ import { useEffect } from "react";
3
+ import { register } from "./changesStore";
4
+ /**
5
+ * What a rendered region is live on, handed to the tab's watcher.
6
+ *
7
+ * Renders nothing. Rendered by `RefreshOn` beside the region it describes,
8
+ * so it arrives in the same payload - with the page, and again with the
9
+ * region each time it is refreshed, carrying the versions that render saw.
10
+ */
11
+ export function Changes({ target, names, }) {
12
+ const key = JSON.stringify(names);
13
+ useEffect(() => register({
14
+ target,
15
+ names: JSON.parse(key),
16
+ }), [target, key]);
17
+ return null;
18
+ }
19
+ //# sourceMappingURL=Changes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Changes.js","sourceRoot":"","sources":["../../src/js/Changes.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C;;;;;;GAMG;AACH,MAAM,UAAU,OAAO,CAAC,EACtB,MAAM,EACN,KAAK,GAIN;IACC,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAElC,SAAS,CACP,GAAG,EAAE,CACH,QAAQ,CAAC;QACP,MAAM;QACN,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAqC;KAC3D,CAAC,EACJ,CAAC,MAAM,EAAE,GAAG,CAAC,CACd,CAAC;IAEF,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["\"use client\";\n\nimport { useEffect } from \"react\";\nimport { register } from \"./changesStore\";\n\n/**\n * What a rendered region is live on, handed to the tab's watcher.\n *\n * Renders nothing. Rendered by `RefreshOn` beside the region it describes,\n * so it arrives in the same payload - with the page, and again with the\n * region each time it is refreshed, carrying the versions that render saw.\n */\nexport function Changes({\n target,\n names,\n}: {\n target: string;\n names: Record<string, [number, string]>;\n}) {\n const key = JSON.stringify(names);\n\n useEffect(\n () =>\n register({\n target,\n names: JSON.parse(key) as Record<string, [number, string]>,\n }),\n [target, key],\n );\n\n return null;\n}\n"]}
@@ -0,0 +1,9 @@
1
+ /** The names a region is live on, as the server rendered them: version at render and signature. */
2
+ export interface Registration {
3
+ target: string;
4
+ names: Record<string, [version: number, signature: string]>;
5
+ }
6
+ /** Register what a rendered region is live on; the returned function forgets it. */
7
+ export declare function register(entry: Registration): () => void;
8
+ /** For tests. */
9
+ export declare function resetChanges(): void;
@@ -0,0 +1,178 @@
1
+ // The tab's side of live sections: what the page is showing, on one stream.
2
+ //
3
+ // Every section or page rendered with `live` registers its names here, with
4
+ // the version each was at and its signature. The union is what the stream
5
+ // watches; a version that moved refreshes exactly the regions live on that
6
+ // name, through the same refresh() a page calls itself. One stream per tab
7
+ // however many regions, reopened only when the set of names changes, closed
8
+ // while the tab is hidden and caught up when it is seen again.
9
+ import { refresh, refreshOnChange } from "./router";
10
+ const CHANGES_PATH = "/_rsc/changes";
11
+ const registrations = new Set();
12
+ let source = null;
13
+ let openedWith = "";
14
+ let settle;
15
+ let retry;
16
+ let failures = 0;
17
+ let listening = false;
18
+ /** Register what a rendered region is live on; the returned function forgets it. */
19
+ export function register(entry) {
20
+ registrations.add(entry);
21
+ schedule();
22
+ return () => {
23
+ registrations.delete(entry);
24
+ schedule();
25
+ };
26
+ }
27
+ function listen() {
28
+ if (listening || typeof document === "undefined")
29
+ return;
30
+ listening = true;
31
+ document.addEventListener("visibilitychange", () => {
32
+ if (!document.hidden)
33
+ return schedule();
34
+ // Hidden tabs do not hold a connection. Nothing is missed: seen again,
35
+ // the stream reopens at the versions this tab holds and every name that
36
+ // moved meanwhile is reported at once.
37
+ if (source)
38
+ note("refreshOn paused: tab hidden. It catches up when the tab is shown.");
39
+ close();
40
+ });
41
+ window.addEventListener("online", schedule);
42
+ }
43
+ /** Many regions register in one commit; one reconcile serves them all. */
44
+ function schedule() {
45
+ listen();
46
+ clearTimeout(settle);
47
+ settle = setTimeout(reconcile, 20);
48
+ }
49
+ /** Every name any region is live on, at the lowest version held - the one furthest behind decides. */
50
+ function union() {
51
+ const held = new Map();
52
+ for (const { names } of registrations) {
53
+ for (const [name, [version, signature]] of Object.entries(names)) {
54
+ const seen = held.get(name);
55
+ if (!seen || version < seen[0])
56
+ held.set(name, [version, signature]);
57
+ }
58
+ }
59
+ return [...held]
60
+ .map(([name, [version, signature]]) => [
61
+ name,
62
+ version,
63
+ signature,
64
+ ])
65
+ .sort((a, b) => (a[0] < b[0] ? -1 : 1));
66
+ }
67
+ function reconcile() {
68
+ const entries = union();
69
+ if (entries.length === 0 ||
70
+ (typeof document !== "undefined" && document.hidden) ||
71
+ typeof EventSource === "undefined") {
72
+ close();
73
+ return;
74
+ }
75
+ const key = JSON.stringify(entries);
76
+ // Something changed: a region came or went, or one re-rendered at a new
77
+ // version. Either way the set is new, and a failed stream gets a fresh try.
78
+ if (key !== openedWith)
79
+ failures = 0;
80
+ if (source && key === openedWith)
81
+ return;
82
+ if (import.meta.env?.DEV) {
83
+ const lines = [...registrations].map(({ target, names }) => ` ${target}: ${Object.entries(names)
84
+ .map(([name, [version]]) => `${name} @${version}`)
85
+ .join(", ")}`);
86
+ note(`refreshOn watching, by region:\n${lines.join("\n")}`);
87
+ }
88
+ open(key, entries);
89
+ }
90
+ /**
91
+ * Development only: what the page refreshes on, and why it stopped. A region
92
+ * that declared refreshOn but is missing from the list rendered no names -
93
+ * the server said why, once, in its own log.
94
+ */
95
+ function note(message) {
96
+ if (import.meta.env?.DEV)
97
+ console.info("[rsc-kit] " + message);
98
+ }
99
+ function open(key, entries) {
100
+ close();
101
+ openedWith = key;
102
+ const stream = new EventSource(CHANGES_PATH + "?w=" + encodeURIComponent(JSON.stringify(entries)));
103
+ source = stream;
104
+ stream.onopen = () => {
105
+ failures = 0;
106
+ };
107
+ stream.onmessage = (event) => {
108
+ let moved;
109
+ try {
110
+ moved = JSON.parse(event.data);
111
+ }
112
+ catch {
113
+ return;
114
+ }
115
+ if (typeof moved.name === "string" && typeof moved.version === "number")
116
+ apply(moved.name, moved.version);
117
+ };
118
+ stream.onerror = () => {
119
+ // CONNECTING: the browser is retrying a dropped connection itself. CLOSED:
120
+ // an error answer - a 403 for names signed by another deploy, a 502 - and
121
+ // only a new stream comes back. Three in a row and the page is left alone
122
+ // until something re-renders and registers afresh.
123
+ if (stream.readyState !== EventSource.CLOSED)
124
+ return;
125
+ stream.close();
126
+ if (source === stream)
127
+ source = null;
128
+ if (++failures > 3)
129
+ return;
130
+ retry = setTimeout(() => open(openedWith, entries), Math.min(30_000, 1000 * 2 ** (failures - 1)));
131
+ };
132
+ }
133
+ function close() {
134
+ clearTimeout(retry);
135
+ source?.close();
136
+ source = null;
137
+ openedWith = "";
138
+ }
139
+ /** Regions to refresh this tick, each with the changes that moved it: `stock@5`. */
140
+ let pending = null;
141
+ /** A version moved: every region on the name at another version refreshes, once per tick. */
142
+ function apply(name, version) {
143
+ for (const entry of registrations) {
144
+ const held = entry.names[name];
145
+ if (!held || held[0] === version)
146
+ continue;
147
+ held[0] = version;
148
+ const changes = (pending ??= new Map()).get(entry.target) ?? new Set();
149
+ changes.add(name + "@" + version);
150
+ pending.set(entry.target, changes);
151
+ }
152
+ if (!pending)
153
+ return;
154
+ queueMicrotask(() => {
155
+ const targets = pending;
156
+ pending = null;
157
+ if (!targets)
158
+ return;
159
+ // The page re-renders its sections with it, so refreshing both is twice.
160
+ if (targets.has("page")) {
161
+ void refresh("page").catch(() => { });
162
+ return;
163
+ }
164
+ // Saying which change asked lets every tab asking about the same one
165
+ // share a single render of a shared section.
166
+ for (const [target, changes] of targets) {
167
+ void refreshOnChange(target, [...changes].sort().join(",")).catch(() => { });
168
+ }
169
+ });
170
+ }
171
+ /** For tests. */
172
+ export function resetChanges() {
173
+ registrations.clear();
174
+ close();
175
+ failures = 0;
176
+ pending = null;
177
+ }
178
+ //# sourceMappingURL=changesStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"changesStore.js","sourceRoot":"","sources":["../../src/js/changesStore.ts"],"names":[],"mappings":"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","sourcesContent":["// The tab's side of live sections: what the page is showing, on one stream.\n//\n// Every section or page rendered with `live` registers its names here, with\n// the version each was at and its signature. The union is what the stream\n// watches; a version that moved refreshes exactly the regions live on that\n// name, through the same refresh() a page calls itself. One stream per tab\n// however many regions, reopened only when the set of names changes, closed\n// while the tab is hidden and caught up when it is seen again.\n\nimport { refresh, refreshOnChange } from \"./router\";\n\n/** The names a region is live on, as the server rendered them: version at render and signature. */\nexport interface Registration {\n target: string;\n names: Record<string, [version: number, signature: string]>;\n}\n\nconst CHANGES_PATH = \"/_rsc/changes\";\n\nconst registrations = new Set<Registration>();\n\nlet source: EventSource | null = null;\nlet openedWith = \"\";\nlet settle: ReturnType<typeof setTimeout> | undefined;\nlet retry: ReturnType<typeof setTimeout> | undefined;\nlet failures = 0;\nlet listening = false;\n\n/** Register what a rendered region is live on; the returned function forgets it. */\nexport function register(entry: Registration): () => void {\n registrations.add(entry);\n schedule();\n\n return () => {\n registrations.delete(entry);\n schedule();\n };\n}\n\nfunction listen(): void {\n if (listening || typeof document === \"undefined\") return;\n\n listening = true;\n document.addEventListener(\"visibilitychange\", () => {\n if (!document.hidden) return schedule();\n\n // Hidden tabs do not hold a connection. Nothing is missed: seen again,\n // the stream reopens at the versions this tab holds and every name that\n // moved meanwhile is reported at once.\n if (source)\n note(\n \"refreshOn paused: tab hidden. It catches up when the tab is shown.\",\n );\n close();\n });\n window.addEventListener(\"online\", schedule);\n}\n\n/** Many regions register in one commit; one reconcile serves them all. */\nfunction schedule(): void {\n listen();\n clearTimeout(settle);\n settle = setTimeout(reconcile, 20);\n}\n\n/** Every name any region is live on, at the lowest version held - the one furthest behind decides. */\nfunction union(): [string, number, string][] {\n const held = new Map<string, [number, string]>();\n\n for (const { names } of registrations) {\n for (const [name, [version, signature]] of Object.entries(names)) {\n const seen = held.get(name);\n\n if (!seen || version < seen[0]) held.set(name, [version, signature]);\n }\n }\n\n return [...held]\n .map(([name, [version, signature]]): [string, number, string] => [\n name,\n version,\n signature,\n ])\n .sort((a, b) => (a[0] < b[0] ? -1 : 1));\n}\n\nfunction reconcile(): void {\n const entries = union();\n\n if (\n entries.length === 0 ||\n (typeof document !== \"undefined\" && document.hidden) ||\n typeof EventSource === \"undefined\"\n ) {\n close();\n\n return;\n }\n\n const key = JSON.stringify(entries);\n\n // Something changed: a region came or went, or one re-rendered at a new\n // version. Either way the set is new, and a failed stream gets a fresh try.\n if (key !== openedWith) failures = 0;\n if (source && key === openedWith) return;\n\n if (import.meta.env?.DEV) {\n const lines = [...registrations].map(\n ({ target, names }) =>\n ` ${target}: ${Object.entries(names)\n .map(([name, [version]]) => `${name} @${version}`)\n .join(\", \")}`,\n );\n\n note(`refreshOn watching, by region:\\n${lines.join(\"\\n\")}`);\n }\n\n open(key, entries);\n}\n\n/**\n * Development only: what the page refreshes on, and why it stopped. A region\n * that declared refreshOn but is missing from the list rendered no names -\n * the server said why, once, in its own log.\n */\nfunction note(message: string): void {\n if (import.meta.env?.DEV) console.info(\"[rsc-kit] \" + message);\n}\n\nfunction open(key: string, entries: [string, number, string][]): void {\n close();\n openedWith = key;\n\n const stream = new EventSource(\n CHANGES_PATH + \"?w=\" + encodeURIComponent(JSON.stringify(entries)),\n );\n\n source = stream;\n\n stream.onopen = () => {\n failures = 0;\n };\n\n stream.onmessage = (event: MessageEvent) => {\n let moved: { name?: unknown; version?: unknown };\n\n try {\n moved = JSON.parse(event.data as string) as {\n name?: unknown;\n version?: unknown;\n };\n } catch {\n return;\n }\n\n if (typeof moved.name === \"string\" && typeof moved.version === \"number\")\n apply(moved.name, moved.version);\n };\n\n stream.onerror = () => {\n // CONNECTING: the browser is retrying a dropped connection itself. CLOSED:\n // an error answer - a 403 for names signed by another deploy, a 502 - and\n // only a new stream comes back. Three in a row and the page is left alone\n // until something re-renders and registers afresh.\n if (stream.readyState !== EventSource.CLOSED) return;\n\n stream.close();\n if (source === stream) source = null;\n if (++failures > 3) return;\n\n retry = setTimeout(\n () => open(openedWith, entries),\n Math.min(30_000, 1000 * 2 ** (failures - 1)),\n );\n };\n}\n\nfunction close(): void {\n clearTimeout(retry);\n source?.close();\n source = null;\n openedWith = \"\";\n}\n\n/** Regions to refresh this tick, each with the changes that moved it: `stock@5`. */\nlet pending: Map<string, Set<string>> | null = null;\n\n/** A version moved: every region on the name at another version refreshes, once per tick. */\nfunction apply(name: string, version: number): void {\n for (const entry of registrations) {\n const held = entry.names[name];\n\n if (!held || held[0] === version) continue;\n\n held[0] = version;\n\n const changes =\n (pending ??= new Map()).get(entry.target) ?? new Set<string>();\n\n changes.add(name + \"@\" + version);\n pending.set(entry.target, changes);\n }\n\n if (!pending) return;\n\n queueMicrotask(() => {\n const targets = pending;\n\n pending = null;\n if (!targets) return;\n\n // The page re-renders its sections with it, so refreshing both is twice.\n if (targets.has(\"page\")) {\n void refresh(\"page\").catch(() => {});\n\n return;\n }\n\n // Saying which change asked lets every tab asking about the same one\n // share a single render of a shared section.\n for (const [target, changes] of targets) {\n void refreshOnChange(target, [...changes].sort().join(\",\")).catch(\n () => {},\n );\n }\n });\n}\n\n/** For tests. */\nexport function resetChanges(): void {\n registrations.clear();\n close();\n failures = 0;\n pending = null;\n}\n"]}
@@ -139,7 +139,7 @@ export declare function applyRevalidated(target: string, tree: ReactNode): void;
139
139
  * form leaves the layouts alone, which is what makes it cheap and also why a
140
140
  * count living in a layout will not move until you ask for 'all'.
141
141
  */
142
- export declare function refresh(target?: string): Promise<void>;
142
+ export declare function refresh(target?: string, changedBy?: string): Promise<void>;
143
143
  /**
144
144
  * Fetch a page's payload ahead of the navigation that may ask for it.
145
145
  *
@@ -957,7 +957,7 @@ export function applyRevalidated(target, tree) {
957
957
  * form leaves the layouts alone, which is what makes it cheap and also why a
958
958
  * count living in a layout will not move until you ask for 'all'.
959
959
  */
960
- export async function refresh(target = "page") {
960
+ export async function refresh(target = "page", changedBy) {
961
961
  const url = window.location.pathname + window.location.search;
962
962
  // Asked because the data moved on; what was fetched or held before is
963
963
  // from before.
@@ -968,6 +968,9 @@ export async function refresh(target = "page") {
968
968
  "X-RSC": "true",
969
969
  "X-RSC-Version": version,
970
970
  "X-RSC-Revalidate": target,
971
+ // Which change asked: tabs asking about the same one share a render
972
+ // of a shared section. Only ever set by the watcher.
973
+ ...(changedBy ? { "X-RSC-Changed": changedBy } : {}),
971
974
  },
972
975
  });
973
976
  if (!response.ok) {