rwsdk 1.0.0-beta.3 → 1.0.0-beta.30

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 (126) hide show
  1. package/dist/lib/constants.d.mts +1 -0
  2. package/dist/lib/constants.mjs +7 -4
  3. package/dist/lib/e2e/browser.mjs +6 -2
  4. package/dist/lib/e2e/constants.d.mts +16 -0
  5. package/dist/lib/e2e/constants.mjs +77 -0
  6. package/dist/lib/e2e/dev.mjs +37 -49
  7. package/dist/lib/e2e/environment.d.mts +2 -0
  8. package/dist/lib/e2e/environment.mjs +202 -65
  9. package/dist/lib/e2e/index.d.mts +1 -0
  10. package/dist/lib/e2e/index.mjs +1 -0
  11. package/dist/lib/e2e/poll.d.mts +1 -1
  12. package/dist/lib/e2e/release.d.mts +1 -0
  13. package/dist/lib/e2e/release.mjs +16 -32
  14. package/dist/lib/e2e/tarball.mjs +2 -34
  15. package/dist/lib/e2e/testHarness.d.mts +36 -4
  16. package/dist/lib/e2e/testHarness.mjs +216 -128
  17. package/dist/lib/e2e/utils.d.mts +1 -0
  18. package/dist/lib/e2e/utils.mjs +15 -0
  19. package/dist/runtime/client/client.d.ts +35 -0
  20. package/dist/runtime/client/client.js +35 -0
  21. package/dist/runtime/client/navigation.d.ts +49 -0
  22. package/dist/runtime/client/navigation.js +80 -31
  23. package/dist/runtime/entries/clientSSR.d.ts +1 -0
  24. package/dist/runtime/entries/clientSSR.js +3 -0
  25. package/dist/runtime/entries/router.d.ts +1 -0
  26. package/dist/runtime/entries/routerClient.d.ts +1 -0
  27. package/dist/runtime/entries/routerClient.js +1 -0
  28. package/dist/runtime/entries/worker.d.ts +2 -0
  29. package/dist/runtime/entries/worker.js +2 -0
  30. package/dist/runtime/imports/__mocks__/use-client-lookup.d.ts +6 -0
  31. package/dist/runtime/imports/__mocks__/use-client-lookup.js +6 -0
  32. package/dist/runtime/lib/db/SqliteDurableObject.d.ts +2 -2
  33. package/dist/runtime/lib/db/SqliteDurableObject.js +2 -2
  34. package/dist/runtime/lib/db/createDb.d.ts +1 -2
  35. package/dist/runtime/lib/db/createDb.js +4 -0
  36. package/dist/runtime/lib/db/typeInference/builders/alterTable.d.ts +13 -3
  37. package/dist/runtime/lib/db/typeInference/builders/columnDefinition.d.ts +35 -21
  38. package/dist/runtime/lib/db/typeInference/builders/createTable.d.ts +9 -2
  39. package/dist/runtime/lib/db/typeInference/database.d.ts +16 -2
  40. package/dist/runtime/lib/db/typeInference/typetests/alterTable.typetest.js +80 -5
  41. package/dist/runtime/lib/db/typeInference/typetests/createTable.typetest.js +104 -2
  42. package/dist/runtime/lib/db/typeInference/typetests/testUtils.d.ts +1 -0
  43. package/dist/runtime/lib/db/typeInference/utils.d.ts +59 -9
  44. package/dist/runtime/lib/links.d.ts +18 -7
  45. package/dist/runtime/lib/links.js +70 -24
  46. package/dist/runtime/lib/links.test.js +20 -0
  47. package/dist/runtime/lib/manifest.d.ts +1 -1
  48. package/dist/runtime/lib/manifest.js +7 -4
  49. package/dist/runtime/lib/realtime/client.js +8 -2
  50. package/dist/runtime/lib/realtime/worker.d.ts +1 -1
  51. package/dist/runtime/lib/router.d.ts +147 -33
  52. package/dist/runtime/lib/router.js +169 -20
  53. package/dist/runtime/lib/router.test.js +241 -0
  54. package/dist/runtime/lib/stitchDocumentAndAppStreams.d.ts +66 -0
  55. package/dist/runtime/lib/stitchDocumentAndAppStreams.js +302 -35
  56. package/dist/runtime/lib/stitchDocumentAndAppStreams.test.d.ts +1 -0
  57. package/dist/runtime/lib/stitchDocumentAndAppStreams.test.js +418 -0
  58. package/dist/runtime/lib/{rwContext.d.ts → types.d.ts} +1 -0
  59. package/dist/runtime/lib/types.js +1 -0
  60. package/dist/runtime/render/renderDocumentHtmlStream.d.ts +1 -1
  61. package/dist/runtime/render/renderToStream.d.ts +4 -2
  62. package/dist/runtime/render/renderToStream.js +53 -24
  63. package/dist/runtime/render/renderToString.d.ts +3 -1
  64. package/dist/runtime/requestInfo/types.d.ts +4 -1
  65. package/dist/runtime/requestInfo/utils.d.ts +9 -0
  66. package/dist/runtime/requestInfo/utils.js +44 -0
  67. package/dist/runtime/requestInfo/worker.js +3 -2
  68. package/dist/runtime/script.d.ts +1 -3
  69. package/dist/runtime/script.js +1 -10
  70. package/dist/runtime/state.d.ts +3 -0
  71. package/dist/runtime/state.js +13 -0
  72. package/dist/runtime/worker.d.ts +3 -1
  73. package/dist/runtime/worker.js +26 -0
  74. package/dist/scripts/debug-sync.mjs +18 -20
  75. package/dist/scripts/worker-run.d.mts +1 -1
  76. package/dist/scripts/worker-run.mjs +52 -113
  77. package/dist/use-synced-state/SyncStateServer.d.mts +20 -0
  78. package/dist/use-synced-state/SyncStateServer.mjs +124 -0
  79. package/dist/use-synced-state/__tests__/SyncStateServer.test.d.mts +1 -0
  80. package/dist/use-synced-state/__tests__/SyncStateServer.test.mjs +109 -0
  81. package/dist/use-synced-state/__tests__/useSyncState.test.d.ts +1 -0
  82. package/dist/use-synced-state/__tests__/useSyncState.test.js +115 -0
  83. package/dist/use-synced-state/__tests__/useSyncedState.test.d.ts +1 -0
  84. package/dist/use-synced-state/__tests__/useSyncedState.test.js +115 -0
  85. package/dist/use-synced-state/__tests__/worker.test.d.mts +1 -0
  86. package/dist/use-synced-state/__tests__/worker.test.mjs +69 -0
  87. package/dist/use-synced-state/client.d.ts +28 -0
  88. package/dist/use-synced-state/client.js +39 -0
  89. package/dist/use-synced-state/constants.d.mts +1 -0
  90. package/dist/use-synced-state/constants.mjs +1 -0
  91. package/dist/use-synced-state/useSyncState.d.ts +20 -0
  92. package/dist/use-synced-state/useSyncState.js +58 -0
  93. package/dist/use-synced-state/useSyncedState.d.ts +20 -0
  94. package/dist/use-synced-state/useSyncedState.js +58 -0
  95. package/dist/use-synced-state/worker.d.mts +14 -0
  96. package/dist/use-synced-state/worker.mjs +73 -0
  97. package/dist/vite/buildApp.mjs +34 -2
  98. package/dist/vite/configPlugin.mjs +8 -14
  99. package/dist/vite/constants.d.mts +1 -0
  100. package/dist/vite/constants.mjs +1 -0
  101. package/dist/vite/directiveModulesDevPlugin.mjs +1 -1
  102. package/dist/vite/envResolvers.d.mts +11 -0
  103. package/dist/vite/envResolvers.mjs +20 -0
  104. package/dist/vite/getViteEsbuild.mjs +2 -1
  105. package/dist/vite/hmrStabilityPlugin.d.mts +2 -0
  106. package/dist/vite/hmrStabilityPlugin.mjs +68 -0
  107. package/dist/vite/knownDepsResolverPlugin.d.mts +0 -6
  108. package/dist/vite/knownDepsResolverPlugin.mjs +1 -12
  109. package/dist/vite/linkerPlugin.d.mts +2 -1
  110. package/dist/vite/linkerPlugin.mjs +11 -3
  111. package/dist/vite/linkerPlugin.test.mjs +15 -0
  112. package/dist/vite/miniflareHMRPlugin.mjs +1 -38
  113. package/dist/vite/moveStaticAssetsPlugin.mjs +14 -4
  114. package/dist/vite/redwoodPlugin.mjs +6 -10
  115. package/dist/vite/runDirectivesScan.mjs +59 -14
  116. package/dist/vite/ssrBridgePlugin.mjs +122 -34
  117. package/dist/vite/ssrBridgeWrapPlugin.d.mts +2 -0
  118. package/dist/vite/ssrBridgeWrapPlugin.mjs +85 -0
  119. package/dist/vite/staleDepRetryPlugin.d.mts +2 -0
  120. package/dist/vite/staleDepRetryPlugin.mjs +69 -0
  121. package/dist/vite/statePlugin.d.mts +4 -0
  122. package/dist/vite/statePlugin.mjs +62 -0
  123. package/package.json +26 -10
  124. package/dist/vite/manifestPlugin.d.mts +0 -4
  125. package/dist/vite/manifestPlugin.mjs +0 -63
  126. /package/dist/runtime/lib/{rwContext.js → links.test.d.ts} +0 -0
@@ -1,6 +1,7 @@
1
1
  import debug from "debug";
2
2
  import MagicString from "magic-string";
3
3
  import { INTERMEDIATE_SSR_BRIDGE_PATH } from "../lib/constants.mjs";
4
+ import { externalModulesSet } from "./constants.mjs";
4
5
  import { findSsrImportCallSites } from "./findSsrSpecifiers.mjs";
5
6
  const log = debug("rwsdk:vite:ssr-bridge-plugin");
6
7
  export const VIRTUAL_SSR_PREFIX = "virtual:rwsdk:ssr:";
@@ -10,9 +11,32 @@ export const ssrBridgePlugin = ({ clientFiles, serverFiles, }) => {
10
11
  const ssrBridgePlugin = {
11
12
  name: "rwsdk:ssr-bridge",
12
13
  enforce: "pre",
13
- async configureServer(server) {
14
+ configureServer(server) {
14
15
  devServer = server;
16
+ const ssrHot = server.environments.ssr.hot;
17
+ const originalSsrHotSend = ssrHot.send;
18
+ // Chain the SSR's full reload behaviour to the worker
19
+ ssrHot.send = (...args) => {
20
+ if (typeof args[0] === "object" && args[0].type === "full-reload") {
21
+ for (const envName of ["worker", "ssr"]) {
22
+ const moduleGraph = server.environments[envName].moduleGraph;
23
+ moduleGraph.invalidateAll();
24
+ }
25
+ log("SSR full-reload detected, propagating to worker");
26
+ // context(justinvdm, 21 Oct 2025): By sending the full-reload event
27
+ // to the worker, we ensure that the worker's module runner cache is
28
+ // invalidated, as it would have been if this were a full-reload event
29
+ // from the worker.
30
+ server.environments.worker.hot.send.apply(server.environments.worker.hot, args);
31
+ }
32
+ return originalSsrHotSend.apply(ssrHot, args);
33
+ };
15
34
  log("Configured dev server");
35
+ const originalRun = devServer.environments.ssr.depsOptimizer?.run;
36
+ devServer.environments.ssr.depsOptimizer.run = async () => {
37
+ originalRun();
38
+ devServer.environments.worker.depsOptimizer.run();
39
+ };
16
40
  },
17
41
  config(_, { command, isPreview }) {
18
42
  isDev = !isPreview && command === "serve";
@@ -48,11 +72,20 @@ export const ssrBridgePlugin = ({ clientFiles, serverFiles, }) => {
48
72
  log("Worker environment esbuild configuration complete");
49
73
  }
50
74
  },
51
- async resolveId(id) {
75
+ async resolveId(id, importer) {
52
76
  // Skip during directive scanning to avoid performance issues
53
77
  if (process.env.RWSDK_DIRECTIVE_SCAN_ACTIVE) {
54
78
  return;
55
79
  }
80
+ // context(justinvdm, 19 Nov 2025):
81
+ // Ensure platform-specific modules are always treated as external in the
82
+ // SSR environment. This is critical for builds, where we produce a
83
+ // standalone SSR bundle. Without this, Vite might try to bundle these
84
+ // virtual modules or fail to resolve them.
85
+ if (this.environment.name === "ssr" && externalModulesSet.has(id)) {
86
+ log("SSR environment: marking %s as external", id);
87
+ return { id, external: true };
88
+ }
56
89
  if (isDev) {
57
90
  // context(justinvdm, 27 May 2025): In dev, we need to dynamically load
58
91
  // SSR modules, so we return the virtual id so that the dynamic loading
@@ -107,46 +140,101 @@ export const ssrBridgePlugin = ({ clientFiles, serverFiles, }) => {
107
140
  if (id.startsWith(VIRTUAL_SSR_PREFIX) &&
108
141
  this.environment.name === "worker") {
109
142
  const realId = id.slice(VIRTUAL_SSR_PREFIX.length);
110
- const idForFetch = realId.endsWith(".css.js")
143
+ let idForFetch = realId.endsWith(".css.js")
111
144
  ? realId.slice(0, -3)
112
145
  : realId;
113
146
  log("Virtual SSR module load: id=%s, realId=%s, idForFetch=%s", id, realId, idForFetch);
114
147
  if (isDev) {
115
- log("Dev mode: fetching SSR module for realPath=%s", idForFetch);
116
- const result = await devServer?.environments.ssr.fetchModule(idForFetch);
117
- process.env.VERBOSE &&
118
- log("Fetch module result: id=%s, result=%O", idForFetch, result);
119
- const code = "code" in result ? result.code : undefined;
120
- if (idForFetch.endsWith(".css") &&
121
- !idForFetch.endsWith(".module.css")) {
122
- process.env.VERBOSE &&
123
- log("Plain CSS file, returning empty module for %s", idForFetch);
124
- return "export default {};";
148
+ // from the SSR environment, which is crucial for things like server
149
+ // components.
150
+ try {
151
+ const ssrOptimizer = devServer.environments.ssr.depsOptimizer;
152
+ // context(justinvdm, 20 Oct 2025): This is the fix for the stale
153
+ // dependency issue. The root cause is the "unhashed-to-hashed"
154
+ // transition. Our worker code imports a clean ID
155
+ // (`rwsdk/__ssr_bridge`), but we expect to fetch the hashed,
156
+ // optimized version from the SSR environment. When a re-optimization
157
+ // happens, Vite's `fetchModule` (running in the SSR env) finds a
158
+ // "ghost node" in its module graph for the clean ID and incorrectly
159
+ // re-uses its stale, hashed `id` property.
160
+ //
161
+ // To fix this, we manually resolve the hashed path here, before
162
+ // asking the SSR env to process the module. We look into the SSR
163
+ // optimizer's metadata to find the correct, up-to-date hash and
164
+ // construct the path ourselves. This ensures the SSR env is
165
+ // always working with the correct, versioned ID, bypassing the
166
+ // faulty ghost node lookup.
167
+ if (ssrOptimizer &&
168
+ Object.prototype.hasOwnProperty.call(ssrOptimizer.metadata.optimized, realId)) {
169
+ const depInfo = ssrOptimizer.metadata.optimized[realId];
170
+ idForFetch = ssrOptimizer.getOptimizedDepId(depInfo);
171
+ log("Manually resolved %s to hashed path for fetchModule: %s", realId, idForFetch);
172
+ }
173
+ log("Virtual SSR module load: id=%s, realId=%s, idForFetch=%s", id, realId, idForFetch);
174
+ log("Dev mode: fetching SSR module for realPath=%s", idForFetch);
175
+ // We use `fetchModule` with `cached: false` as a safeguard. Since
176
+ // we're in a `load` hook, we know the worker-side cache for this
177
+ // virtual module is stale. `cached: false` ensures that we also
178
+ // bypass any potentially stale transform result in the SSR
179
+ // environment's cache, guaranteeing we get the freshest possible
180
+ // code.
181
+ const result = await devServer.environments.ssr.fetchModule(idForFetch, undefined, { cached: false });
182
+ if ("code" in result) {
183
+ log("Fetched SSR module code length: %d", result.code?.length || 0);
184
+ const code = result.code;
185
+ if (idForFetch.endsWith(".css") &&
186
+ !idForFetch.endsWith(".module.css")) {
187
+ process.env.VERBOSE &&
188
+ log("Plain CSS file, returning empty module for %s", idForFetch);
189
+ return "export default {};";
190
+ }
191
+ const s = new MagicString(code || "");
192
+ const callsites = findSsrImportCallSites(idForFetch, code || "", log);
193
+ for (const site of callsites) {
194
+ const normalized = site.specifier.startsWith("/@id/")
195
+ ? site.specifier.slice("/@id/".length)
196
+ : site.specifier;
197
+ // If the import is for a known external module, we must leave it
198
+ // as a bare specifier. Rewriting it with any prefix (`/@id/` or
199
+ // our virtual one) will break Vite's default externalization.
200
+ if (externalModulesSet.has(normalized)) {
201
+ const replacement = `import("${normalized}")`;
202
+ s.overwrite(site.start, site.end, replacement);
203
+ continue;
204
+ }
205
+ // context(justinvdm, 11 Aug 2025):
206
+ // - We replace __vite_ssr_import__ and __vite_ssr_dynamic_import__
207
+ // with import() calls so that the module graph can be built
208
+ // correctly (vite looks for imports and import()s to build module
209
+ // graph)
210
+ // - We prepend /@id/$VIRTUAL_SSR_PREFIX to the specifier so that we
211
+ // can stay within the SSR subgraph of the worker module graph
212
+ const replacement = `import("/@id/${VIRTUAL_SSR_PREFIX}${normalized}")`;
213
+ s.overwrite(site.start, site.end, replacement);
214
+ }
215
+ const out = s.toString();
216
+ process.env.VERBOSE &&
217
+ log("Transformed SSR module code for realId=%s: %s", realId, out);
218
+ return {
219
+ code: out,
220
+ map: null, // Sourcemaps are handled by fetchModule's inlining
221
+ };
222
+ }
223
+ else {
224
+ // This case can be hit if the module is already cached. We may
225
+ // need to handle this more gracefully, but for now we'll just
226
+ // return an empty module.
227
+ log("SSR module %s was already cached. Returning empty.", idForFetch);
228
+ return "export default {}";
229
+ }
125
230
  }
126
- log("Fetched SSR module code length: %d", code?.length || 0);
127
- const s = new MagicString(code || "");
128
- const callsites = findSsrImportCallSites(idForFetch, code || "", log);
129
- for (const site of callsites) {
130
- const normalized = site.specifier.startsWith("/@id/")
131
- ? site.specifier.slice("/@id/".length)
132
- : site.specifier;
133
- // context(justinvdm, 11 Aug 2025):
134
- // - We replace __vite_ssr_import__ and __vite_ssr_dynamic_import__
135
- // with import() calls so that the module graph can be built
136
- // correctly (vite looks for imports and import()s to build module
137
- // graph)
138
- // - We prepend /@id/$VIRTUAL_SSR_PREFIX to the specifier so that we
139
- // can stay within the SSR subgraph of the worker module graph
140
- const replacement = `import("/@id/${VIRTUAL_SSR_PREFIX}${normalized}")`;
141
- s.overwrite(site.start, site.end, replacement);
231
+ catch (e) {
232
+ log("Error fetching SSR module for realPath=%s: %s", id, e);
233
+ throw e;
142
234
  }
143
- const out = s.toString();
144
- log("Transformed SSR module code length: %d", out.length);
145
- process.env.VERBOSE &&
146
- log("Transformed SSR module code for realId=%s: %s", realId, out);
147
- return out;
148
235
  }
149
236
  }
237
+ return;
150
238
  },
151
239
  };
152
240
  return ssrBridgePlugin;
@@ -0,0 +1,2 @@
1
+ import type { Plugin } from "vite";
2
+ export declare const ssrBridgeWrapPlugin: () => Plugin;
@@ -0,0 +1,85 @@
1
+ import { Lang, parse as sgParse } from "@ast-grep/napi";
2
+ import debug from "debug";
3
+ import MagicString from "magic-string";
4
+ const log = debug("rwsdk:vite:ssr-bridge-wrap");
5
+ export const ssrBridgeWrapPlugin = () => {
6
+ return {
7
+ name: "rwsdk:ssr-bridge-wrap",
8
+ apply: "build",
9
+ renderChunk(code, chunk) {
10
+ try {
11
+ if (!chunk.fileName.endsWith("ssr_bridge.js")) {
12
+ return null;
13
+ }
14
+ const s = new MagicString(code);
15
+ // Use AST parsing to find actual import statements (not in comments)
16
+ const root = sgParse(Lang.JavaScript, code);
17
+ // Find all import statements using AST patterns
18
+ const importPatterns = [
19
+ 'import { $$$ } from "$MODULE"',
20
+ "import { $$$ } from '$MODULE'",
21
+ 'import $DEFAULT from "$MODULE"',
22
+ "import $DEFAULT from '$MODULE'",
23
+ 'import * as $NS from "$MODULE"',
24
+ "import * as $NS from '$MODULE'",
25
+ 'import "$MODULE"',
26
+ "import '$MODULE'",
27
+ ];
28
+ let lastImportEnd = -1;
29
+ for (const pattern of importPatterns) {
30
+ const matches = root.root().findAll(pattern);
31
+ for (const match of matches) {
32
+ const range = match.range();
33
+ if (range.end.index > lastImportEnd) {
34
+ lastImportEnd = range.end.index;
35
+ }
36
+ }
37
+ }
38
+ // Find the export statement using AST
39
+ const exportPatterns = [
40
+ "export { $$$ }",
41
+ 'export { $$$ } from "$MODULE"',
42
+ "export { $$$ } from '$MODULE'",
43
+ ];
44
+ let exportStart = -1;
45
+ let exportEnd = -1;
46
+ for (const pattern of exportPatterns) {
47
+ const matches = root.root().findAll(pattern);
48
+ for (const match of matches) {
49
+ const range = match.range();
50
+ // Check if this export contains our target symbols
51
+ const text = match.text();
52
+ if (text.includes("renderHtmlStream") &&
53
+ text.includes("ssrLoadModule") &&
54
+ text.includes("ssrWebpackRequire")) {
55
+ exportStart = range.start.index;
56
+ exportEnd = range.end.index;
57
+ break;
58
+ }
59
+ }
60
+ if (exportStart !== -1)
61
+ break;
62
+ }
63
+ const banner = `export const { renderHtmlStream, ssrLoadModule, ssrWebpackRequire, ssrGetModuleExport, createThenableFromReadableStream } = (function() {`;
64
+ const footer = `return { renderHtmlStream, ssrLoadModule, ssrWebpackRequire, ssrGetModuleExport, createThenableFromReadableStream };\n})();`;
65
+ // Insert banner after the last import (or at the beginning if no imports)
66
+ const insertIndex = lastImportEnd === -1 ? 0 : lastImportEnd;
67
+ s.appendLeft(insertIndex, banner + "\n");
68
+ // Append footer at the end
69
+ s.append(footer);
70
+ // Remove the original export statement if found
71
+ if (exportStart !== -1 && exportEnd !== -1) {
72
+ s.remove(exportStart, exportEnd);
73
+ }
74
+ return {
75
+ code: s.toString(),
76
+ map: s.generateMap(),
77
+ };
78
+ }
79
+ catch (e) {
80
+ console.error("Error in ssrBridgeWrapPlugin:", e);
81
+ throw e;
82
+ }
83
+ },
84
+ };
85
+ };
@@ -0,0 +1,2 @@
1
+ import { type Plugin } from "vite";
2
+ export declare function staleDepRetryPlugin(): Plugin;
@@ -0,0 +1,69 @@
1
+ import debug from "debug";
2
+ const log = debug("rwsdk:vite:stale-dep-retry-plugin");
3
+ let stabilityPromise = null;
4
+ let stabilityResolver = null;
5
+ let debounceTimer = null;
6
+ const DEBOUNCE_MS = 500;
7
+ function startWaitingForStability() {
8
+ if (!stabilityPromise) {
9
+ log("Starting to wait for server stability...");
10
+ stabilityPromise = new Promise((resolve) => {
11
+ stabilityResolver = resolve;
12
+ });
13
+ // Start the timer. If it fires, we're stable.
14
+ debounceTimer = setTimeout(finishWaiting, DEBOUNCE_MS);
15
+ }
16
+ }
17
+ function activityDetected() {
18
+ if (stabilityPromise) {
19
+ // If we're waiting for stability, reset the timer.
20
+ log("Activity detected, resetting stability timer.");
21
+ if (debounceTimer)
22
+ clearTimeout(debounceTimer);
23
+ debounceTimer = setTimeout(finishWaiting, DEBOUNCE_MS);
24
+ }
25
+ }
26
+ function finishWaiting() {
27
+ if (stabilityResolver) {
28
+ log("Server appears stable. Resolving promise.");
29
+ stabilityResolver();
30
+ }
31
+ stabilityPromise = null;
32
+ stabilityResolver = null;
33
+ debounceTimer = null;
34
+ }
35
+ export function staleDepRetryPlugin() {
36
+ return {
37
+ name: "rws-vite-plugin:stale-dep-retry",
38
+ apply: "serve",
39
+ // Monitor server activity by tapping into the transform hook. This is a
40
+ // reliable indicator that Vite is busy processing modules.
41
+ transform() {
42
+ activityDetected();
43
+ return null;
44
+ },
45
+ configureServer(server) {
46
+ // Return a function to ensure our middleware is placed after internal middlewares
47
+ return () => {
48
+ server.middlewares.use(async function rwsdkStaleBundleErrorHandler(err, req, res, next) {
49
+ if (err &&
50
+ typeof err.message === "string" &&
51
+ err.message.includes("new version of the pre-bundle")) {
52
+ log("Caught stale pre-bundle error. Waiting for server to stabilize...");
53
+ startWaitingForStability();
54
+ await stabilityPromise;
55
+ log("Server stabilized. Sending full-reload and redirecting.");
56
+ // Signal the client to do a full page reload.
57
+ server.environments.client.hot.send({
58
+ type: "full-reload",
59
+ });
60
+ res.writeHead(307, { Location: req.originalUrl || req.url });
61
+ res.end();
62
+ return;
63
+ }
64
+ next(err);
65
+ });
66
+ };
67
+ },
68
+ };
69
+ }
@@ -0,0 +1,4 @@
1
+ import type { Plugin } from "vite";
2
+ export declare const statePlugin: ({ projectRootDir, }: {
3
+ projectRootDir: string;
4
+ }) => Plugin;
@@ -0,0 +1,62 @@
1
+ import debug from "debug";
2
+ import fs from "node:fs/promises";
3
+ import { RW_STATE_EXPORT_PATH } from "../lib/constants.mjs";
4
+ import { maybeResolveEnvImport } from "./envResolvers.mjs";
5
+ const log = debug("rwsdk:vite:state-plugin");
6
+ const VIRTUAL_STATE_PREFIX = "virtual:rwsdk:state:";
7
+ export const statePlugin = ({ projectRootDir, }) => {
8
+ let isDev = false;
9
+ const stateModulePath = maybeResolveEnvImport({
10
+ id: RW_STATE_EXPORT_PATH,
11
+ envName: "worker",
12
+ projectRootDir,
13
+ });
14
+ if (!stateModulePath) {
15
+ throw new Error(`[rwsdk] State module path not found for project root: ${projectRootDir}. This is likely a bug in RedwoodSDK. Please report this issue at https://github.com/redwoodjs/sdk/issues/new`);
16
+ }
17
+ return {
18
+ name: "rwsdk:state",
19
+ enforce: "pre",
20
+ config(_, { command, isPreview }) {
21
+ isDev = !isPreview && command === "serve";
22
+ },
23
+ configEnvironment(env, config) {
24
+ if (env === "worker") {
25
+ config.optimizeDeps ??= {};
26
+ config.optimizeDeps.esbuildOptions ??= {};
27
+ config.optimizeDeps.esbuildOptions.plugins ??= [];
28
+ config.optimizeDeps.esbuildOptions.plugins.push({
29
+ name: "rwsdk-state-external",
30
+ setup(build) {
31
+ build.onResolve({
32
+ // context(justinvdm, 13 Oct 2025): Vite dep optimizer slugifies the export path
33
+ filter: new RegExp(`^(${RW_STATE_EXPORT_PATH}|${VIRTUAL_STATE_PREFIX}.*)$`),
34
+ }, (args) => {
35
+ log("Marking as external: %s", args.path);
36
+ return {
37
+ path: args.path,
38
+ external: true,
39
+ };
40
+ });
41
+ },
42
+ });
43
+ }
44
+ },
45
+ resolveId(id) {
46
+ if (id === RW_STATE_EXPORT_PATH) {
47
+ if (isDev && this.environment.name === "worker") {
48
+ return `${VIRTUAL_STATE_PREFIX}${id}`;
49
+ }
50
+ else {
51
+ return stateModulePath;
52
+ }
53
+ }
54
+ },
55
+ async load(id) {
56
+ if (id.startsWith(VIRTUAL_STATE_PREFIX)) {
57
+ log("Loading virtual state module from %s", stateModulePath);
58
+ return await fs.readFile(stateModulePath, "utf-8");
59
+ }
60
+ },
61
+ };
62
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rwsdk",
3
- "version": "1.0.0-beta.3",
3
+ "version": "1.0.0-beta.30",
4
4
  "description": "Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime",
5
5
  "type": "module",
6
6
  "bin": {
@@ -36,6 +36,10 @@
36
36
  "types": "./dist/runtime/entries/client.d.ts",
37
37
  "default": "./dist/runtime/entries/client.js"
38
38
  },
39
+ "./__state": {
40
+ "types": "./dist/runtime/state.d.ts",
41
+ "default": "./dist/runtime/state.js"
42
+ },
39
43
  "./__ssr": {
40
44
  "react-server": "./dist/runtime/entries/no-react-server.js",
41
45
  "types": "./dist/runtime/entries/ssr.d.ts",
@@ -54,7 +58,8 @@
54
58
  },
55
59
  "./router": {
56
60
  "types": "./dist/runtime/entries/router.d.ts",
57
- "default": "./dist/runtime/entries/router.js"
61
+ "workerd": "./dist/runtime/entries/router.js",
62
+ "default": "./dist/runtime/entries/routerClient.js"
58
63
  },
59
64
  "./auth": {
60
65
  "types": "./dist/runtime/entries/auth.d.ts",
@@ -99,6 +104,14 @@
99
104
  "./realtime/durableObject": {
100
105
  "types": "./dist/runtime/lib/realtime/durableObject.d.ts",
101
106
  "default": "./dist/runtime/lib/realtime/durableObject.js"
107
+ },
108
+ "./use-synced-state/client": {
109
+ "types": "./dist/use-synced-state/client.d.ts",
110
+ "default": "./dist/use-synced-state/client.js"
111
+ },
112
+ "./use-synced-state/worker": {
113
+ "types": "./dist/use-synced-state/worker.d.ts",
114
+ "default": "./dist/use-synced-state/worker.js"
102
115
  }
103
116
  },
104
117
  "keywords": [
@@ -139,18 +152,22 @@
139
152
  "@puppeteer/browsers": "~2.10.0",
140
153
  "@types/decompress": "~4.2.7",
141
154
  "@types/fs-extra": "~11.0.4",
155
+ "@types/glob": "^8.1.0",
142
156
  "@types/react": "~19.1.2",
143
157
  "@types/react-dom": "~19.1.2",
144
158
  "@types/react-is": "~19.0.0",
145
159
  "@vitejs/plugin-react": "~5.0.0",
160
+ "capnweb": "~0.2.0",
146
161
  "chokidar": "~4.0.0",
147
162
  "debug": "~4.4.0",
163
+ "decompress": "~4.2.1",
148
164
  "enhanced-resolve": "~5.18.1",
149
165
  "eventsource-parser": "~3.0.0",
150
166
  "execa": "~9.6.0",
151
167
  "find-up": "~8.0.0",
152
168
  "fs-extra": "~11.3.0",
153
- "glob": "~11.0.1",
169
+ "get-port": "^7.1.0",
170
+ "glob": "~11.1.0",
154
171
  "ignore": "~7.0.4",
155
172
  "jsonc-parser": "~3.3.1",
156
173
  "kysely": "~0.28.2",
@@ -166,14 +183,13 @@
166
183
  "ts-morph": "~27.0.0",
167
184
  "unique-names-generator": "~4.7.1",
168
185
  "vibe-rules": "~0.3.0",
169
- "vite-tsconfig-paths": "~5.1.4",
170
- "decompress": "~4.2.1"
186
+ "vite-tsconfig-paths": "~5.1.4"
171
187
  },
172
188
  "peerDependencies": {
173
- "@cloudflare/vite-plugin": "^1.12.4",
174
- "react": ">=19.2.0-canary-3fb190f7-20250908 <20.0.0",
175
- "react-dom": ">=19.2.0-canary-3fb190f7-20250908 <20.0.0",
176
- "react-server-dom-webpack": ">=19.2.0-canary-3fb190f7-20250908 <20.0.0",
189
+ "@cloudflare/vite-plugin": "^1.13.10",
190
+ "react": ">=19.2.0-0 <19.3.0 || >=19.3.0-0 <20.0.0",
191
+ "react-dom": ">=19.2.0-0 <19.3.0 || >=19.3.0-0 <20.0.0",
192
+ "react-server-dom-webpack": ">=19.2.0-0 <19.3.0 || >=19.3.0-0 <20.0.0",
177
193
  "vite": "^6.2.6 || 7.x",
178
194
  "wrangler": "^4.35.0"
179
195
  },
@@ -189,7 +205,7 @@
189
205
  "semver": "~7.7.1",
190
206
  "tsx": "~4.20.0",
191
207
  "typescript": "~5.9.0",
192
- "vite": "7.1.6",
208
+ "vite": "~7.2.0",
193
209
  "vitest": "~3.2.0"
194
210
  }
195
211
  }
@@ -1,4 +0,0 @@
1
- import { type Plugin } from "vite";
2
- export declare const manifestPlugin: ({ projectRootDir, }: {
3
- projectRootDir: string;
4
- }) => Plugin;
@@ -1,63 +0,0 @@
1
- import debug from "debug";
2
- const log = debug("rwsdk:vite:manifest-plugin");
3
- const virtualModuleId = "virtual:rwsdk:manifest.js";
4
- const resolvedVirtualModuleId = "\0" + virtualModuleId;
5
- export const manifestPlugin = ({ projectRootDir, }) => {
6
- let isBuild = false;
7
- return {
8
- name: "rwsdk:vite:manifest-plugin",
9
- enforce: "pre",
10
- configResolved(config) {
11
- isBuild = config.command === "build";
12
- },
13
- resolveId(id) {
14
- // Skip during directive scanning to avoid performance issues
15
- if (process.env.RWSDK_DIRECTIVE_SCAN_ACTIVE) {
16
- return;
17
- }
18
- if (id === virtualModuleId) {
19
- return resolvedVirtualModuleId;
20
- }
21
- },
22
- async load(id) {
23
- // Skip during directive scanning to avoid performance issues
24
- if (process.env.RWSDK_DIRECTIVE_SCAN_ACTIVE) {
25
- return;
26
- }
27
- if (id === resolvedVirtualModuleId) {
28
- if (isBuild) {
29
- // context(justinvdm, 28 Aug 2025): During the build, we don't have
30
- // the manifest yet. We insert a placeholder that the linker plugin
31
- // will replace in the final phase.
32
- log("Returning manifest placeholder for build");
33
- return `export default "__RWSDK_MANIFEST_PLACEHOLDER__"`;
34
- }
35
- // In dev, we can return an empty object.
36
- log("Not a build, returning empty manifest");
37
- return `export default {}`;
38
- }
39
- },
40
- configEnvironment(name, config) {
41
- if (name !== "worker" && name !== "ssr") {
42
- return;
43
- }
44
- log("Configuring environment: name=%s", name);
45
- config.optimizeDeps ??= {};
46
- config.optimizeDeps.esbuildOptions ??= {};
47
- config.optimizeDeps.esbuildOptions.plugins ??= [];
48
- config.optimizeDeps.esbuildOptions.plugins.push({
49
- name: "rwsdk:manifest:esbuild",
50
- setup(build) {
51
- log("Setting up esbuild plugin for environment: %s", name);
52
- build.onResolve({ filter: /^virtual:rwsdk:manifest\.js$/ }, () => {
53
- log("Resolving virtual manifest module in esbuild");
54
- return {
55
- path: "virtual:rwsdk:manifest.js",
56
- external: true,
57
- };
58
- });
59
- },
60
- });
61
- },
62
- };
63
- };