rwsdk 1.0.0-beta.27-test.20251116215153 → 1.0.0-beta.29

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 (30) hide show
  1. package/dist/lib/e2e/environment.mjs +6 -1
  2. package/dist/lib/e2e/release.d.mts +1 -0
  3. package/dist/lib/e2e/release.mjs +16 -3
  4. package/dist/lib/e2e/tarball.mjs +3 -10
  5. package/dist/lib/e2e/testHarness.mjs +6 -3
  6. package/dist/runtime/lib/db/typeInference/builders/alterTable.d.ts +13 -3
  7. package/dist/runtime/lib/db/typeInference/builders/columnDefinition.d.ts +34 -20
  8. package/dist/runtime/lib/db/typeInference/builders/createTable.d.ts +9 -2
  9. package/dist/runtime/lib/db/typeInference/database.d.ts +16 -2
  10. package/dist/runtime/lib/db/typeInference/typetests/alterTable.typetest.js +80 -5
  11. package/dist/runtime/lib/db/typeInference/typetests/createTable.typetest.js +102 -1
  12. package/dist/runtime/lib/db/typeInference/typetests/testUtils.d.ts +1 -0
  13. package/dist/runtime/lib/db/typeInference/utils.d.ts +59 -9
  14. package/dist/runtime/lib/links.d.ts +18 -25
  15. package/dist/runtime/lib/links.js +70 -42
  16. package/dist/runtime/lib/links.test.d.ts +1 -0
  17. package/dist/runtime/lib/links.test.js +20 -0
  18. package/dist/runtime/lib/realtime/worker.d.ts +1 -1
  19. package/dist/runtime/lib/router.d.ts +23 -9
  20. package/dist/runtime/lib/router.js +14 -36
  21. package/dist/runtime/lib/stitchDocumentAndAppStreams.d.ts +66 -0
  22. package/dist/runtime/lib/stitchDocumentAndAppStreams.js +167 -28
  23. package/dist/runtime/lib/stitchDocumentAndAppStreams.test.js +43 -9
  24. package/dist/runtime/worker.d.ts +3 -1
  25. package/dist/runtime/worker.js +1 -0
  26. package/dist/use-synced-state/worker.d.mts +1 -1
  27. package/dist/vite/constants.d.mts +1 -0
  28. package/dist/vite/constants.mjs +1 -0
  29. package/dist/vite/ssrBridgePlugin.mjs +9 -0
  30. package/package.json +2 -2
@@ -105,6 +105,72 @@ function splitStreamOnFirstNonHoistedTag(sourceStream) {
105
105
  });
106
106
  return [hoistedTagsStream, appBodyStream];
107
107
  }
108
+ /**
109
+ * A utility that orchestrates and interleaves three ReadableStreams to produce a
110
+ * single, valid HTML response stream. It uses two special markers:
111
+ *
112
+ * - `startMarker`: Placed in the `outerHtml` stream (the document shell) to
113
+ * designate where the application's content should be injected.
114
+ * - `endMarker`: Injected into the `innerHtml` stream's RSC payload to signal
115
+ * the end of the initial, non-suspended render. This marker is needed for
116
+ * non-blocking hydration, as it allows the stitching process to send the
117
+ * client `<script>` tags before all suspended content has resolved.
118
+ *
119
+ * It manages three main stream readers:
120
+ *
121
+ * - `hoistedTagsReader`: Reads from the `hoistedTagsStream`, which contains only
122
+ * the hoisted meta tags (e.g., `<title>`, `<meta>`).
123
+ * - `outerReader`: Reads from the `outerHtml` stream, which is the server-rendered
124
+ * document shell (containing `<html>`, `<head>`, etc.).
125
+ * - `innerReader`: Reads from the `appBodyStream`, which contains the main
126
+ * application content, stripped of its hoisted tags.
127
+ *
128
+ * The function proceeds through a multi-phase state machine, managed by the
129
+ * `pump` function, to correctly interleave these streams.
130
+ *
131
+ * The state machine moves through the following phases:
132
+ *
133
+ * 1. `read-hoisted`:
134
+ * - **Goal:** Buffer all hoisted tags from the `hoistedTagsStream`.
135
+ * - **Action:** Reads from `hoistedTagsReader` and appends all content into
136
+ * the `hoistedTagsBuffer`. Does not enqueue anything yet.
137
+ * - **Transition:** Moves to `outer-head` when the stream is exhausted.
138
+ *
139
+ * 2. `outer-head`:
140
+ * - **Goal:** Stream the document up to the closing `</head>` tag, inject the
141
+ * hoisted tags, and then continue until the app `startMarker`.
142
+ * - **Action:** Reads from `outerReader`. When it finds `</head>`, it enqueues
143
+ * the content before it, then enqueues the `hoistedTagsBuffer`, and finally
144
+ * enqueues the `</head>` tag itself. It then continues reading from
145
+ * `outerReader` until it finds the `startMarker`.
146
+ * - **Transition:** Moves to `inner-shell` after finding and discarding the
147
+ * `startMarker`.
148
+ *
149
+ * 3. `inner-shell`:
150
+ * - **Goal:** Stream the initial, non-suspended part of the application.
151
+ * - **Action:** Switches to `innerReader`. It enqueues chunks until it finds
152
+ * the `endMarker`. Any content after the marker is stored in
153
+ * `innerSuspendedRemains`.
154
+ * - **Transition:** Moves to `outer-tail` after finding the `endMarker`.
155
+ *
156
+ * 4. `outer-tail`:
157
+ * - **Goal:** Stream the rest of the document's `<body>`, including client
158
+ * `<script>` tags.
159
+ * - **Action:** Switches back to `outerReader` and enqueues chunks until it
160
+ * finds the `</body>` tag.
161
+ * - **Transition:** Moves to `inner-suspended` after finding `</body>`.
162
+ *
163
+ * 5. `inner-suspended`:
164
+ * - **Goal:** Stream any suspended content from the React app.
165
+ * - **Action:** First enqueues any content from `innerSuspendedRemains`, then
166
+ * continues reading from `innerReader` until the stream is exhausted.
167
+ * - **Transition:** Moves to `outer-end` when the stream is exhausted.
168
+ *
169
+ * 6. `outer-end`:
170
+ * - **Goal:** Finish the document.
171
+ * - **Action:** Switches back to `outerReader` for the last time to send the
172
+ * closing `</body>` and `</html>` tags.
173
+ */
108
174
  export function stitchDocumentAndAppStreams(outerHtml, innerHtml, startMarker, endMarker) {
109
175
  const [hoistedTagsStream, appBodyStream] = splitStreamOnFirstNonHoistedTag(innerHtml);
110
176
  const decoder = new TextDecoder();
@@ -115,138 +181,211 @@ export function stitchDocumentAndAppStreams(outerHtml, innerHtml, startMarker, e
115
181
  let buffer = "";
116
182
  let outerBufferRemains = "";
117
183
  let innerSuspendedRemains = "";
118
- let phase = "enqueue-hoisted";
184
+ let hoistedTagsBuffer = "";
185
+ let hoistedTagsReady = false;
186
+ let phase = "read-hoisted";
119
187
  const pump = async (controller) => {
188
+ const enqueue = (text) => {
189
+ if (text) {
190
+ controller.enqueue(encoder.encode(text));
191
+ }
192
+ };
193
+ const flush = () => {
194
+ const flushIndex = buffer.lastIndexOf("\n");
195
+ if (flushIndex !== -1) {
196
+ enqueue(buffer.slice(0, flushIndex + 1));
197
+ buffer = buffer.slice(flushIndex + 1);
198
+ }
199
+ };
120
200
  try {
121
- if (phase === "enqueue-hoisted") {
201
+ if (phase === "read-hoisted") {
202
+ // Continuously read from the hoisted tags stream and buffer the
203
+ // content. Once the stream is finished, transition to the next phase.
122
204
  const { done, value } = await hoistedTagsReader.read();
205
+ // When the stream is done, we're ready to process the document head.
123
206
  if (done) {
207
+ hoistedTagsReady = true;
124
208
  phase = "outer-head";
125
209
  }
126
210
  else {
127
- controller.enqueue(value);
211
+ // Otherwise, keep appending to the buffer.
212
+ hoistedTagsBuffer += decoder.decode(value, { stream: true });
128
213
  }
129
214
  }
130
215
  else if (phase === "outer-head") {
216
+ // Read from the document stream. Search for the closing `</head>` tag
217
+ // to inject the buffered hoisted tags. Then, search for the
218
+ // `startMarker` to know when to start injecting the app shell. Once
219
+ // the marker is found, transition to the next phase.
131
220
  const { done, value } = await outerReader.read();
221
+ // Handle the case where the document stream ends.
132
222
  if (done) {
223
+ // If there's content left in the buffer, process it for markers.
133
224
  if (buffer) {
225
+ const headCloseIndex = buffer.indexOf("</head>");
226
+ if (headCloseIndex !== -1 &&
227
+ hoistedTagsReady &&
228
+ hoistedTagsBuffer) {
229
+ enqueue(buffer.slice(0, headCloseIndex));
230
+ enqueue(hoistedTagsBuffer);
231
+ hoistedTagsBuffer = "";
232
+ enqueue("</head>");
233
+ buffer = buffer.slice(headCloseIndex + "</head>".length);
234
+ }
134
235
  const markerIndex = buffer.indexOf(startMarker);
135
236
  if (markerIndex !== -1) {
136
- controller.enqueue(encoder.encode(buffer.slice(0, markerIndex)));
237
+ enqueue(buffer.slice(0, markerIndex));
137
238
  outerBufferRemains = buffer.slice(markerIndex + startMarker.length);
138
239
  }
139
240
  else {
140
- controller.enqueue(encoder.encode(buffer));
241
+ enqueue(buffer);
141
242
  }
142
243
  buffer = "";
143
244
  }
245
+ else if (hoistedTagsReady && hoistedTagsBuffer) {
246
+ enqueue(hoistedTagsBuffer);
247
+ hoistedTagsBuffer = "";
248
+ }
249
+ // Even if the stream ends, we must proceed to the app shell phase.
144
250
  phase = "inner-shell";
145
251
  }
146
252
  else {
253
+ // As chunks arrive, append them to the buffer.
147
254
  buffer += decoder.decode(value, { stream: true });
255
+ // Search for the closing head tag to inject hoisted tags.
256
+ const headCloseIndex = buffer.indexOf("</head>");
257
+ if (headCloseIndex !== -1 && hoistedTagsReady && hoistedTagsBuffer) {
258
+ enqueue(buffer.slice(0, headCloseIndex));
259
+ enqueue(hoistedTagsBuffer);
260
+ hoistedTagsBuffer = "";
261
+ enqueue("</head>");
262
+ buffer = buffer.slice(headCloseIndex + "</head>".length);
263
+ }
264
+ // Search for the start marker to switch to the app stream.
148
265
  const markerIndex = buffer.indexOf(startMarker);
149
266
  if (markerIndex !== -1) {
150
- controller.enqueue(encoder.encode(buffer.slice(0, markerIndex)));
267
+ enqueue(buffer.slice(0, markerIndex));
151
268
  outerBufferRemains = buffer.slice(markerIndex + startMarker.length);
152
269
  buffer = "";
153
270
  phase = "inner-shell";
154
271
  }
155
272
  else {
156
- const flushIndex = buffer.lastIndexOf("\n");
157
- if (flushIndex !== -1) {
158
- controller.enqueue(encoder.encode(buffer.slice(0, flushIndex + 1)));
159
- buffer = buffer.slice(flushIndex + 1);
160
- }
273
+ // If no marker is found yet, flush the buffer up to the last
274
+ // newline to keep the stream flowing.
275
+ flush();
161
276
  }
162
277
  }
163
278
  }
164
279
  else if (phase === "inner-shell") {
280
+ // Now read from the app stream. We send the initial part of the app
281
+ // content until we find the `endMarker`. This marker tells us that the
282
+ // non-suspended part of the app is rendered. Any content after this
283
+ // marker is considered suspended and is buffered. Then, transition.
165
284
  const { done, value } = await innerReader.read();
285
+ // Handle the case where the app stream ends.
166
286
  if (done) {
167
287
  if (buffer)
168
- controller.enqueue(encoder.encode(buffer));
288
+ enqueue(buffer);
169
289
  phase = "outer-tail";
170
290
  }
171
291
  else {
292
+ // As chunks arrive, append them to the buffer.
172
293
  buffer += decoder.decode(value, { stream: true });
173
294
  const markerIndex = buffer.indexOf(endMarker);
295
+ // If the end marker is found, enqueue content up to the marker,
296
+ // buffer the rest, and switch to the document tail phase.
174
297
  if (markerIndex !== -1) {
175
298
  const endOfMarkerIndex = markerIndex + endMarker.length;
176
- controller.enqueue(encoder.encode(buffer.slice(0, endOfMarkerIndex)));
299
+ enqueue(buffer.slice(0, endOfMarkerIndex));
177
300
  innerSuspendedRemains = buffer.slice(endOfMarkerIndex);
178
301
  buffer = "";
179
302
  phase = "outer-tail";
180
303
  }
181
304
  else {
182
- const flushIndex = buffer.lastIndexOf("\n");
183
- if (flushIndex !== -1) {
184
- controller.enqueue(encoder.encode(buffer.slice(0, flushIndex + 1)));
185
- buffer = buffer.slice(flushIndex + 1);
186
- }
305
+ // If no marker is found yet, flush the buffer.
306
+ flush();
187
307
  }
188
308
  }
189
309
  }
190
310
  else if (phase === "outer-tail") {
311
+ // Switch back to the document stream. The goal is to send the rest of
312
+ // the document's body, which critically includes the client-side
313
+ // `<script>` tags for hydration. We stream until we find the closing
314
+ // `</body>` tag and then transition.
315
+ // First, process any leftover buffer from the `outer-head` phase.
191
316
  if (outerBufferRemains) {
192
317
  buffer = outerBufferRemains;
193
318
  outerBufferRemains = "";
194
319
  }
195
320
  const { done, value } = await outerReader.read();
321
+ // Handle the case where the document stream ends.
196
322
  if (done) {
197
323
  if (buffer) {
324
+ // Search the remaining buffer for the closing body tag.
198
325
  const markerIndex = buffer.indexOf("</body>");
199
326
  if (markerIndex !== -1) {
200
- controller.enqueue(encoder.encode(buffer.slice(0, markerIndex)));
327
+ enqueue(buffer.slice(0, markerIndex));
201
328
  buffer = buffer.slice(markerIndex);
202
329
  }
203
330
  else {
204
- controller.enqueue(encoder.encode(buffer));
331
+ enqueue(buffer);
205
332
  buffer = "";
206
333
  }
207
334
  }
335
+ // Proceed to the suspended content phase.
208
336
  phase = "inner-suspended";
209
337
  }
210
338
  else {
339
+ // As chunks arrive, append them to the buffer.
211
340
  buffer += decoder.decode(value, { stream: true });
341
+ // Search for the closing body tag to switch to suspended content.
212
342
  const markerIndex = buffer.indexOf("</body>");
213
343
  if (markerIndex !== -1) {
214
- controller.enqueue(encoder.encode(buffer.slice(0, markerIndex)));
344
+ enqueue(buffer.slice(0, markerIndex));
215
345
  buffer = buffer.slice(markerIndex);
216
346
  phase = "inner-suspended";
217
347
  }
218
348
  else {
219
- const flushIndex = buffer.lastIndexOf("\n");
220
- if (flushIndex !== -1) {
221
- controller.enqueue(encoder.encode(buffer.slice(0, flushIndex + 1)));
222
- buffer = buffer.slice(flushIndex + 1);
223
- }
349
+ // If no marker is found yet, flush the buffer.
350
+ flush();
224
351
  }
225
352
  }
226
353
  }
227
354
  else if (phase === "inner-suspended") {
355
+ // Switch back to the app stream. First, send any buffered suspended
356
+ // content from the `inner-shell` phase. Then, stream the rest of the
357
+ // app content until it's finished. This is all the content that was
358
+ // behind a `<Suspense>` boundary.
359
+ // First, send any buffered suspended content from the `inner-shell` phase.
228
360
  if (innerSuspendedRemains) {
229
- controller.enqueue(encoder.encode(innerSuspendedRemains));
361
+ enqueue(innerSuspendedRemains);
230
362
  innerSuspendedRemains = "";
231
363
  }
232
364
  const { done, value } = await innerReader.read();
365
+ // When the app stream is done, transition to the final phase.
233
366
  if (done) {
234
367
  phase = "outer-end";
235
368
  }
236
369
  else {
370
+ // Otherwise, pass through the remaining app content directly.
237
371
  controller.enqueue(value);
238
372
  }
239
373
  }
240
374
  else if (phase === "outer-end") {
375
+ // Finally, switch back to the document stream one last time to send
376
+ // the closing `</body>` and `</html>` tags and finish the response.
377
+ // First, send any leftover buffer from the `outer-tail` phase.
241
378
  if (buffer) {
242
- controller.enqueue(encoder.encode(buffer));
379
+ enqueue(buffer);
243
380
  buffer = "";
244
381
  }
245
382
  const { done, value } = await outerReader.read();
383
+ // When the document stream is done, we're finished.
246
384
  if (done) {
247
385
  controller.close();
248
386
  return;
249
387
  }
388
+ // Otherwise, pass through the final document content.
250
389
  controller.enqueue(value);
251
390
  }
252
391
  await pump(controller);
@@ -45,7 +45,7 @@ describe("stitchDocumentAndAppStreams", () => {
45
45
  const startMarker = '<div id="rwsdk-app-start" />';
46
46
  const endMarker = '<div id="rwsdk-app-end"></div>';
47
47
  describe("meta tag hoisting", () => {
48
- it("extracts and prepends single title tag", async () => {
48
+ it("places hoisted tags inside head, after existing head content", async () => {
49
49
  const outerHtml = `<!DOCTYPE html>
50
50
  <html>
51
51
  <head>
@@ -59,10 +59,19 @@ describe("stitchDocumentAndAppStreams", () => {
59
59
  const innerHtml = `<title>Page Title</title><div>App content</div>${endMarker}`;
60
60
  const result = await streamToString(stitchDocumentAndAppStreams(stringToStream(outerHtml), stringToStream(innerHtml), startMarker, endMarker));
61
61
  expect(result).toContain(`<title>Page Title</title>`);
62
- expect(result.indexOf(`<title>Page Title</title>`)).toBeLessThan(result.indexOf(`<!DOCTYPE html>`));
62
+ expect(result).toMatch(/<head>[\s\S]*<meta charset="utf-8" \/>[\s\S]*<title>Page Title<\/title>[\s\S]*<\/head>/);
63
63
  expect(result).toContain(`<div>App content</div>`);
64
+ const doctypeIndex = result.indexOf(`<!DOCTYPE html>`);
65
+ const headIndex = result.indexOf(`<head>`);
66
+ const charsetIndex = result.indexOf(`<meta charset="utf-8" />`);
67
+ const titleIndex = result.indexOf(`<title>Page Title</title>`);
68
+ const headCloseIndex = result.indexOf(`</head>`);
69
+ expect(doctypeIndex).toBe(0);
70
+ expect(doctypeIndex).toBeLessThan(headIndex);
71
+ expect(charsetIndex).toBeLessThan(titleIndex);
72
+ expect(titleIndex).toBeLessThan(headCloseIndex);
64
73
  });
65
- it("extracts and prepends multiple hoisted tags", async () => {
74
+ it("places multiple hoisted tags inside head, after existing head content", async () => {
66
75
  const outerHtml = `<!DOCTYPE html>
67
76
  <html>
68
77
  <head>
@@ -78,9 +87,14 @@ describe("stitchDocumentAndAppStreams", () => {
78
87
  expect(result).toContain(`<title>Page Title</title>`);
79
88
  expect(result).toContain(`<meta name="description" content="Test" />`);
80
89
  expect(result).toContain(`<link rel="stylesheet" href="/styles.css" />`);
81
- const hoistedStart = result.indexOf(`<title>Page Title</title>`);
82
- const doctypeStart = result.indexOf(`<!DOCTYPE html>`);
83
- expect(hoistedStart).toBeLessThan(doctypeStart);
90
+ expect(result).toMatch(/<head>[\s\S]*<meta charset="utf-8" \/>[\s\S]*<title>Page Title<\/title>[\s\S]*<meta name="description" content="Test" \/>[\s\S]*<link rel="stylesheet" href="\/styles.css" \/>[\s\S]*<\/head>/);
91
+ const doctypeIndex = result.indexOf(`<!DOCTYPE html>`);
92
+ const charsetIndex = result.indexOf(`<meta charset="utf-8" />`);
93
+ const titleIndex = result.indexOf(`<title>Page Title</title>`);
94
+ const headCloseIndex = result.indexOf(`</head>`);
95
+ expect(doctypeIndex).toBe(0);
96
+ expect(charsetIndex).toBeLessThan(titleIndex);
97
+ expect(titleIndex).toBeLessThan(headCloseIndex);
84
98
  });
85
99
  it("handles app stream with no hoisted tags", async () => {
86
100
  const outerHtml = `<!DOCTYPE html>
@@ -117,9 +131,29 @@ describe("stitchDocumentAndAppStreams", () => {
117
131
  const result = await streamToString(stitchDocumentAndAppStreams(stringToStream(outerHtml), createChunkedStream(innerHtmlChunks), startMarker, endMarker));
118
132
  expect(result).toContain(`<title>Page Title</title>`);
119
133
  expect(result).toContain(`<meta name="description" content="Test" />`);
120
- const hoistedStart = result.indexOf(`<title>Page Title</title>`);
121
- const doctypeStart = result.indexOf(`<!DOCTYPE html>`);
122
- expect(hoistedStart).toBeLessThan(doctypeStart);
134
+ expect(result).toMatch(/<head>[\s\S]*<meta charset="utf-8" \/>[\s\S]*<title>Page Title<\/title>[\s\S]*<\/head>/);
135
+ const doctypeIndex = result.indexOf(`<!DOCTYPE html>`);
136
+ const charsetIndex = result.indexOf(`<meta charset="utf-8" />`);
137
+ const titleIndex = result.indexOf(`<title>Page Title</title>`);
138
+ const headCloseIndex = result.indexOf(`</head>`);
139
+ expect(doctypeIndex).toBe(0);
140
+ expect(charsetIndex).toBeLessThan(titleIndex);
141
+ expect(titleIndex).toBeLessThan(headCloseIndex);
142
+ });
143
+ it("ensures doctype is always first", async () => {
144
+ const outerHtml = `<!DOCTYPE html>
145
+ <html>
146
+ <head>
147
+ <meta charset="utf-8" />
148
+ </head>
149
+ <body>
150
+ ${startMarker}
151
+ <script src="/client.js"></script>
152
+ </body>
153
+ </html>`;
154
+ const innerHtml = `<title>Page Title</title><div>App content</div>${endMarker}`;
155
+ const result = await streamToString(stitchDocumentAndAppStreams(stringToStream(outerHtml), stringToStream(innerHtml), startMarker, endMarker));
156
+ expect(result.trim().startsWith(`<!DOCTYPE html>`)).toBe(true);
123
157
  });
124
158
  });
125
159
  describe("basic stitching flow", () => {
@@ -8,9 +8,11 @@ declare global {
8
8
  DB: D1Database;
9
9
  };
10
10
  }
11
- export declare const defineApp: <T extends RequestInfo = RequestInfo<any, DefaultAppContext>>(routes: Route<T>[]) => {
11
+ export type AppDefinition<Routes extends readonly Route<any>[], T extends RequestInfo> = {
12
12
  fetch: (request: Request, env: Env, cf: ExecutionContext) => Promise<Response>;
13
+ __rwRoutes: Routes;
13
14
  };
15
+ export declare const defineApp: <T extends RequestInfo = RequestInfo<any, DefaultAppContext>, Routes extends readonly Route<T>[] = readonly Route<T>[]>(routes: Routes) => AppDefinition<Routes, T>;
14
16
  export declare const DefaultDocument: React.FC<{
15
17
  children: React.ReactNode;
16
18
  }>;
@@ -12,6 +12,7 @@ import { generateNonce } from "./lib/utils";
12
12
  export * from "./requestInfo/types";
13
13
  export const defineApp = (routes) => {
14
14
  return {
15
+ __rwRoutes: routes,
15
16
  fetch: async (request, env, cf) => {
16
17
  globalThis.__webpack_require__ = ssrWebpackRequire;
17
18
  const router = defineRoutes(routes);
@@ -11,4 +11,4 @@ export type SyncStateRouteOptions = {
11
11
  * @param options Optional overrides for base path, reset path, and object name.
12
12
  * @returns Router entries for the sync state API and reset endpoint.
13
13
  */
14
- export declare const syncStateRoutes: (getNamespace: (env: Cloudflare.Env) => DurableObjectNamespace<SyncStateServer>, options?: SyncStateRouteOptions) => import("../runtime/lib/router.js").RouteDefinition<import("../runtime/worker.js").RequestInfo<any, import("../runtime/worker.js").DefaultAppContext>>[];
14
+ export declare const syncStateRoutes: (getNamespace: (env: Cloudflare.Env) => DurableObjectNamespace<SyncStateServer>, options?: SyncStateRouteOptions) => import("../runtime/lib/router.js").RouteDefinition<`/${string}`, import("../runtime/worker.js").RequestInfo<any, import("../runtime/worker.js").DefaultAppContext>>[];
@@ -1,2 +1,3 @@
1
1
  export declare const cloudflareBuiltInModules: string[];
2
2
  export declare const externalModules: string[];
3
+ export declare const externalModulesSet: Set<string>;
@@ -10,3 +10,4 @@ export const externalModules = [
10
10
  ...builtinModules,
11
11
  ...builtinModules.map((m) => `node:${m}`),
12
12
  ];
13
+ export const externalModulesSet = new Set(externalModules);
@@ -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:";
@@ -184,6 +185,14 @@ export const ssrBridgePlugin = ({ clientFiles, serverFiles, }) => {
184
185
  const normalized = site.specifier.startsWith("/@id/")
185
186
  ? site.specifier.slice("/@id/".length)
186
187
  : site.specifier;
188
+ // If the import is for a known external module, we must leave it
189
+ // as a bare specifier. Rewriting it with any prefix (`/@id/` or
190
+ // our virtual one) will break Vite's default externalization.
191
+ if (externalModulesSet.has(normalized)) {
192
+ const replacement = `import("${normalized}")`;
193
+ s.overwrite(site.start, site.end, replacement);
194
+ continue;
195
+ }
187
196
  // context(justinvdm, 11 Aug 2025):
188
197
  // - We replace __vite_ssr_import__ and __vite_ssr_dynamic_import__
189
198
  // with import() calls so that the module graph can be built
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rwsdk",
3
- "version": "1.0.0-beta.27-test.20251116215153",
3
+ "version": "1.0.0-beta.29",
4
4
  "description": "Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime",
5
5
  "type": "module",
6
6
  "bin": {
@@ -167,7 +167,7 @@
167
167
  "find-up": "~8.0.0",
168
168
  "fs-extra": "~11.3.0",
169
169
  "get-port": "^7.1.0",
170
- "glob": "~11.0.1",
170
+ "glob": "~11.1.0",
171
171
  "ignore": "~7.0.4",
172
172
  "jsonc-parser": "~3.3.1",
173
173
  "kysely": "~0.28.2",