rwsdk 1.0.0-beta.5 → 1.0.0-beta.50

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 (157) hide show
  1. package/bin/rw-scripts.mjs +13 -13
  2. package/dist/lib/constants.d.mts +1 -0
  3. package/dist/lib/constants.mjs +7 -4
  4. package/dist/lib/e2e/browser.mjs +6 -2
  5. package/dist/lib/e2e/constants.d.mts +4 -0
  6. package/dist/lib/e2e/constants.mjs +49 -12
  7. package/dist/lib/e2e/dev.mjs +49 -57
  8. package/dist/lib/e2e/environment.d.mts +2 -0
  9. package/dist/lib/e2e/environment.mjs +201 -64
  10. package/dist/lib/e2e/index.d.mts +2 -0
  11. package/dist/lib/e2e/index.mjs +2 -0
  12. package/dist/lib/e2e/poll.d.mts +1 -1
  13. package/dist/lib/e2e/release.d.mts +1 -0
  14. package/dist/lib/e2e/release.mjs +57 -52
  15. package/dist/lib/e2e/tarball.mjs +2 -34
  16. package/dist/lib/e2e/testHarness.d.mts +39 -3
  17. package/dist/lib/e2e/testHarness.mjs +239 -92
  18. package/dist/lib/e2e/utils.d.mts +1 -0
  19. package/dist/lib/e2e/utils.mjs +15 -0
  20. package/dist/lib/normalizeModulePath.mjs +1 -1
  21. package/dist/runtime/client/client.d.ts +64 -2
  22. package/dist/runtime/client/client.js +156 -15
  23. package/dist/runtime/client/navigation.d.ts +45 -0
  24. package/dist/runtime/client/navigation.js +68 -14
  25. package/dist/runtime/client/navigationCache.d.ts +68 -0
  26. package/dist/runtime/client/navigationCache.js +294 -0
  27. package/dist/runtime/client/navigationCache.test.js +469 -0
  28. package/dist/runtime/client/types.d.ts +26 -5
  29. package/dist/runtime/client/types.js +8 -1
  30. package/dist/runtime/entries/no-react-server-ssr-bridge.d.ts +0 -0
  31. package/dist/runtime/entries/no-react-server-ssr-bridge.js +2 -0
  32. package/dist/runtime/entries/no-react-server.js +3 -1
  33. package/dist/runtime/entries/react-server-only.js +1 -1
  34. package/dist/runtime/entries/router.d.ts +1 -0
  35. package/dist/runtime/entries/routerClient.d.ts +1 -0
  36. package/dist/runtime/entries/routerClient.js +1 -0
  37. package/dist/runtime/entries/worker.d.ts +4 -0
  38. package/dist/runtime/entries/worker.js +4 -0
  39. package/dist/runtime/imports/__mocks__/use-client-lookup.d.ts +6 -0
  40. package/dist/runtime/imports/__mocks__/use-client-lookup.js +6 -0
  41. package/dist/runtime/lib/db/SqliteDurableObject.d.ts +2 -2
  42. package/dist/runtime/lib/db/SqliteDurableObject.js +2 -2
  43. package/dist/runtime/lib/db/createDb.d.ts +1 -2
  44. package/dist/runtime/lib/db/createDb.js +4 -0
  45. package/dist/runtime/lib/db/typeInference/builders/alterTable.d.ts +13 -3
  46. package/dist/runtime/lib/db/typeInference/builders/columnDefinition.d.ts +35 -21
  47. package/dist/runtime/lib/db/typeInference/builders/createTable.d.ts +9 -2
  48. package/dist/runtime/lib/db/typeInference/database.d.ts +16 -2
  49. package/dist/runtime/lib/db/typeInference/typetests/alterTable.typetest.js +80 -5
  50. package/dist/runtime/lib/db/typeInference/typetests/createTable.typetest.js +104 -2
  51. package/dist/runtime/lib/db/typeInference/typetests/testUtils.d.ts +1 -0
  52. package/dist/runtime/lib/db/typeInference/utils.d.ts +59 -9
  53. package/dist/runtime/lib/links.d.ts +21 -7
  54. package/dist/runtime/lib/links.js +84 -26
  55. package/dist/runtime/lib/links.test.d.ts +1 -0
  56. package/dist/runtime/lib/links.test.js +20 -0
  57. package/dist/runtime/lib/manifest.d.ts +1 -1
  58. package/dist/runtime/lib/manifest.js +7 -4
  59. package/dist/runtime/lib/realtime/client.js +28 -6
  60. package/dist/runtime/lib/realtime/worker.d.ts +1 -1
  61. package/dist/runtime/lib/router.d.ts +154 -35
  62. package/dist/runtime/lib/router.js +491 -105
  63. package/dist/runtime/lib/router.test.js +611 -1
  64. package/dist/runtime/lib/stitchDocumentAndAppStreams.d.ts +66 -0
  65. package/dist/runtime/lib/stitchDocumentAndAppStreams.js +302 -35
  66. package/dist/runtime/lib/stitchDocumentAndAppStreams.test.d.ts +1 -0
  67. package/dist/runtime/lib/stitchDocumentAndAppStreams.test.js +418 -0
  68. package/dist/runtime/lib/{rwContext.d.ts → types.d.ts} +1 -0
  69. package/dist/runtime/lib/types.js +1 -0
  70. package/dist/runtime/register/client.d.ts +1 -1
  71. package/dist/runtime/register/client.js +10 -3
  72. package/dist/runtime/register/worker.js +13 -4
  73. package/dist/runtime/render/normalizeActionResult.js +8 -1
  74. package/dist/runtime/render/renderDocumentHtmlStream.d.ts +1 -1
  75. package/dist/runtime/render/renderToStream.d.ts +4 -2
  76. package/dist/runtime/render/renderToStream.js +53 -24
  77. package/dist/runtime/render/renderToString.d.ts +3 -6
  78. package/dist/runtime/requestInfo/types.d.ts +5 -1
  79. package/dist/runtime/requestInfo/utils.d.ts +9 -0
  80. package/dist/runtime/requestInfo/utils.js +45 -0
  81. package/dist/runtime/requestInfo/worker.d.ts +0 -1
  82. package/dist/runtime/requestInfo/worker.js +5 -11
  83. package/dist/runtime/script.d.ts +1 -3
  84. package/dist/runtime/script.js +1 -10
  85. package/dist/runtime/server.d.ts +52 -0
  86. package/dist/runtime/server.js +88 -0
  87. package/dist/runtime/state.d.ts +3 -0
  88. package/dist/runtime/state.js +13 -0
  89. package/dist/runtime/worker.d.ts +3 -1
  90. package/dist/runtime/worker.js +45 -2
  91. package/dist/scripts/debug-sync.mjs +18 -20
  92. package/dist/scripts/worker-run.d.mts +1 -1
  93. package/dist/scripts/worker-run.mjs +59 -113
  94. package/dist/use-synced-state/SyncedStateServer.d.mts +36 -0
  95. package/dist/use-synced-state/SyncedStateServer.mjs +196 -0
  96. package/dist/use-synced-state/__tests__/SyncStateServer.test.d.mts +1 -0
  97. package/dist/use-synced-state/__tests__/SyncStateServer.test.mjs +116 -0
  98. package/dist/use-synced-state/__tests__/useSyncState.test.d.ts +1 -0
  99. package/dist/use-synced-state/__tests__/useSyncState.test.js +115 -0
  100. package/dist/use-synced-state/__tests__/useSyncedState.test.d.ts +1 -0
  101. package/dist/use-synced-state/__tests__/useSyncedState.test.js +115 -0
  102. package/dist/use-synced-state/__tests__/worker.test.d.mts +1 -0
  103. package/dist/use-synced-state/__tests__/worker.test.mjs +70 -0
  104. package/dist/use-synced-state/client-core.d.ts +29 -0
  105. package/dist/use-synced-state/client-core.js +103 -0
  106. package/dist/use-synced-state/client.d.ts +3 -0
  107. package/dist/use-synced-state/client.js +4 -0
  108. package/dist/use-synced-state/constants.d.mts +1 -0
  109. package/dist/use-synced-state/constants.mjs +1 -0
  110. package/dist/use-synced-state/useSyncedState.d.ts +21 -0
  111. package/dist/use-synced-state/useSyncedState.js +64 -0
  112. package/dist/use-synced-state/worker.d.mts +14 -0
  113. package/dist/use-synced-state/worker.mjs +135 -0
  114. package/dist/vite/buildApp.mjs +34 -2
  115. package/dist/vite/cloudflarePreInitPlugin.d.mts +11 -0
  116. package/dist/vite/cloudflarePreInitPlugin.mjs +40 -0
  117. package/dist/vite/configPlugin.mjs +9 -14
  118. package/dist/vite/constants.d.mts +1 -0
  119. package/dist/vite/constants.mjs +1 -0
  120. package/dist/vite/createDirectiveLookupPlugin.mjs +10 -7
  121. package/dist/vite/devServerTimingPlugin.mjs +4 -0
  122. package/dist/vite/diagnosticAssetGraphPlugin.d.mts +4 -0
  123. package/dist/vite/diagnosticAssetGraphPlugin.mjs +41 -0
  124. package/dist/vite/directiveModulesDevPlugin.mjs +9 -1
  125. package/dist/vite/directivesPlugin.mjs +4 -4
  126. package/dist/vite/envResolvers.d.mts +11 -0
  127. package/dist/vite/envResolvers.mjs +20 -0
  128. package/dist/vite/getViteEsbuild.mjs +2 -1
  129. package/dist/vite/hmrStabilityPlugin.d.mts +2 -0
  130. package/dist/vite/hmrStabilityPlugin.mjs +73 -0
  131. package/dist/vite/injectVitePreamblePlugin.mjs +0 -4
  132. package/dist/vite/knownDepsResolverPlugin.d.mts +0 -6
  133. package/dist/vite/knownDepsResolverPlugin.mjs +25 -17
  134. package/dist/vite/linkerPlugin.d.mts +2 -1
  135. package/dist/vite/linkerPlugin.mjs +11 -3
  136. package/dist/vite/linkerPlugin.test.mjs +15 -0
  137. package/dist/vite/miniflareHMRPlugin.mjs +6 -38
  138. package/dist/vite/moveStaticAssetsPlugin.mjs +35 -4
  139. package/dist/vite/redwoodPlugin.mjs +9 -11
  140. package/dist/vite/redwoodPlugin.test.mjs +4 -4
  141. package/dist/vite/runDirectivesScan.mjs +75 -19
  142. package/dist/vite/ssrBridgePlugin.mjs +132 -40
  143. package/dist/vite/ssrBridgeWrapPlugin.d.mts +2 -0
  144. package/dist/vite/ssrBridgeWrapPlugin.mjs +85 -0
  145. package/dist/vite/staleDepRetryPlugin.d.mts +2 -0
  146. package/dist/vite/staleDepRetryPlugin.mjs +74 -0
  147. package/dist/vite/statePlugin.d.mts +4 -0
  148. package/dist/vite/statePlugin.mjs +62 -0
  149. package/dist/vite/transformClientComponents.test.mjs +32 -0
  150. package/dist/vite/transformJsxScriptTagsPlugin.mjs +0 -5
  151. package/dist/vite/transformServerFunctions.mjs +66 -4
  152. package/dist/vite/transformServerFunctions.test.mjs +35 -0
  153. package/dist/vite/virtualPlugin.mjs +6 -7
  154. package/package.json +41 -19
  155. package/dist/vite/manifestPlugin.d.mts +0 -4
  156. package/dist/vite/manifestPlugin.mjs +0 -63
  157. /package/dist/runtime/{lib/rwContext.js → client/navigationCache.test.d.ts} +0 -0
@@ -0,0 +1,29 @@
1
+ export type SyncedStateClient = {
2
+ getState(key: string): Promise<unknown>;
3
+ setState(value: unknown, key: string): Promise<void>;
4
+ subscribe(key: string, handler: (value: unknown) => void): Promise<void>;
5
+ unsubscribe(key: string, handler: (value: unknown) => void): Promise<void>;
6
+ };
7
+ /**
8
+ * Returns a cached client for the provided endpoint, creating it when necessary.
9
+ * The client is wrapped to track subscriptions for cleanup on page reload.
10
+ * @param endpoint Endpoint to connect to.
11
+ * @returns RPC client instance.
12
+ */
13
+ export declare const getSyncedStateClient: (endpoint?: string) => SyncedStateClient;
14
+ /**
15
+ * Initializes and caches an RPC client instance for the sync state endpoint.
16
+ * The client is wrapped to track subscriptions for cleanup on page reload.
17
+ * @param options Optional endpoint override.
18
+ * @returns Cached client instance or `null` when running without `window`.
19
+ */
20
+ export declare const initSyncedStateClient: (options?: {
21
+ endpoint?: string;
22
+ }) => SyncedStateClient | null;
23
+ /**
24
+ * Injects a client instance for tests and updates the cached endpoint.
25
+ * Also clears the subscription registry for test isolation.
26
+ * @param client Stub client instance or `null` to clear the cache.
27
+ * @param endpoint Endpoint associated with the injected client.
28
+ */
29
+ export declare const setSyncedStateClientForTesting: (client: SyncedStateClient | null, endpoint?: string) => void;
@@ -0,0 +1,103 @@
1
+ import { newWebSocketRpcSession } from "capnweb";
2
+ import { DEFAULT_SYNCED_STATE_PATH } from "./constants.mjs";
3
+ // Map of endpoint URLs to their respective clients
4
+ const clientCache = new Map();
5
+ const activeSubscriptions = new Set();
6
+ // Set up beforeunload handler to unsubscribe all active subscriptions
7
+ if (typeof window !== "undefined") {
8
+ const handleBeforeUnload = () => {
9
+ if (activeSubscriptions.size === 0) {
10
+ return;
11
+ }
12
+ // Unsubscribe all active subscriptions
13
+ // Use a synchronous approach where possible, but don't block page unload
14
+ const subscriptions = Array.from(activeSubscriptions);
15
+ activeSubscriptions.clear();
16
+ // Fire-and-forget unsubscribe calls - we can't await during beforeunload
17
+ for (const { key, handler, client } of subscriptions) {
18
+ void client.unsubscribe(key, handler).catch(() => {
19
+ // Ignore errors during page unload - the connection will be closed anyway
20
+ });
21
+ }
22
+ };
23
+ window.addEventListener("beforeunload", handleBeforeUnload);
24
+ }
25
+ /**
26
+ * Returns a cached client for the provided endpoint, creating it when necessary.
27
+ * The client is wrapped to track subscriptions for cleanup on page reload.
28
+ * @param endpoint Endpoint to connect to.
29
+ * @returns RPC client instance.
30
+ */
31
+ export const getSyncedStateClient = (endpoint = DEFAULT_SYNCED_STATE_PATH) => {
32
+ // Return existing client if already cached for this endpoint
33
+ const existingClient = clientCache.get(endpoint);
34
+ if (existingClient) {
35
+ return existingClient;
36
+ }
37
+ const baseClient = newWebSocketRpcSession(endpoint);
38
+ // Wrap the client using a Proxy to track subscriptions
39
+ // The RPC client uses dynamic property access, so we can't use .bind()
40
+ const wrappedClient = new Proxy(baseClient, {
41
+ get(target, prop) {
42
+ if (prop === "subscribe") {
43
+ return async (key, handler) => {
44
+ const subscription = {
45
+ key,
46
+ handler,
47
+ client: wrappedClient,
48
+ };
49
+ activeSubscriptions.add(subscription);
50
+ return target[prop](key, handler);
51
+ };
52
+ }
53
+ if (prop === "unsubscribe") {
54
+ return async (key, handler) => {
55
+ // Find and remove the subscription
56
+ for (const sub of activeSubscriptions) {
57
+ if (sub.key === key &&
58
+ sub.handler === handler &&
59
+ sub.client === wrappedClient) {
60
+ activeSubscriptions.delete(sub);
61
+ break;
62
+ }
63
+ }
64
+ return target[prop](key, handler);
65
+ };
66
+ }
67
+ // Pass through all other properties/methods
68
+ return target[prop];
69
+ },
70
+ });
71
+ // Cache the client for this endpoint
72
+ clientCache.set(endpoint, wrappedClient);
73
+ return wrappedClient;
74
+ };
75
+ /**
76
+ * Initializes and caches an RPC client instance for the sync state endpoint.
77
+ * The client is wrapped to track subscriptions for cleanup on page reload.
78
+ * @param options Optional endpoint override.
79
+ * @returns Cached client instance or `null` when running without `window`.
80
+ */
81
+ export const initSyncedStateClient = (options = {}) => {
82
+ const endpoint = options.endpoint ?? DEFAULT_SYNCED_STATE_PATH;
83
+ if (typeof window === "undefined") {
84
+ return null;
85
+ }
86
+ // Use getSyncedStateClient which now handles caching via Map
87
+ return getSyncedStateClient(endpoint);
88
+ };
89
+ /**
90
+ * Injects a client instance for tests and updates the cached endpoint.
91
+ * Also clears the subscription registry for test isolation.
92
+ * @param client Stub client instance or `null` to clear the cache.
93
+ * @param endpoint Endpoint associated with the injected client.
94
+ */
95
+ export const setSyncedStateClientForTesting = (client, endpoint = DEFAULT_SYNCED_STATE_PATH) => {
96
+ if (client) {
97
+ clientCache.set(endpoint, client);
98
+ }
99
+ else {
100
+ clientCache.delete(endpoint);
101
+ }
102
+ activeSubscriptions.clear();
103
+ };
@@ -0,0 +1,3 @@
1
+ export { getSyncedStateClient, initSyncedStateClient, setSyncedStateClientForTesting, } from "./client-core.js";
2
+ export type { SyncedStateClient } from "./client-core.js";
3
+ export { useSyncedState } from "./useSyncedState.js";
@@ -0,0 +1,4 @@
1
+ // Re-export everything from client-core to maintain the public API
2
+ export { getSyncedStateClient, initSyncedStateClient, setSyncedStateClientForTesting, } from "./client-core.js";
3
+ // Re-export useSyncedState (no circular dependency since useSyncedState imports from client-core, not client)
4
+ export { useSyncedState } from "./useSyncedState.js";
@@ -0,0 +1 @@
1
+ export declare const DEFAULT_SYNCED_STATE_PATH = "/__synced-state";
@@ -0,0 +1 @@
1
+ export const DEFAULT_SYNCED_STATE_PATH = "/__synced-state";
@@ -0,0 +1,21 @@
1
+ import React from "react";
2
+ type HookDeps = {
3
+ useState: typeof React.useState;
4
+ useEffect: typeof React.useEffect;
5
+ useRef: typeof React.useRef;
6
+ useCallback: typeof React.useCallback;
7
+ };
8
+ type Setter<T> = (value: T | ((previous: T) => T)) => void;
9
+ export type CreateSyncedStateHookOptions = {
10
+ url?: string;
11
+ roomId?: string;
12
+ hooks?: HookDeps;
13
+ };
14
+ /**
15
+ * Builds a `useSyncedState` hook configured with optional endpoint and hook overrides.
16
+ * @param options Optional overrides for endpoint and React primitives.
17
+ * @returns Hook that syncs state through the sync state service.
18
+ */
19
+ export declare const createSyncedStateHook: (options?: CreateSyncedStateHookOptions) => <T>(initialValue: T, key: string, roomId?: string | undefined) => [T, Setter<T>];
20
+ export declare const useSyncedState: <T>(initialValue: T, key: string, roomId?: string | undefined) => [T, Setter<T>];
21
+ export {};
@@ -0,0 +1,64 @@
1
+ import React from "react";
2
+ import { getSyncedStateClient } from "./client-core.js";
3
+ import { DEFAULT_SYNCED_STATE_PATH } from "./constants.mjs";
4
+ const defaultDeps = {
5
+ useState: React.useState,
6
+ useEffect: React.useEffect,
7
+ useRef: React.useRef,
8
+ useCallback: React.useCallback,
9
+ };
10
+ /**
11
+ * Builds a `useSyncedState` hook configured with optional endpoint and hook overrides.
12
+ * @param options Optional overrides for endpoint and React primitives.
13
+ * @returns Hook that syncs state through the sync state service.
14
+ */
15
+ export const createSyncedStateHook = (options = {}) => {
16
+ const basePath = options.url ?? DEFAULT_SYNCED_STATE_PATH;
17
+ const deps = options.hooks ?? defaultDeps;
18
+ const { useState, useEffect, useRef, useCallback } = deps;
19
+ return function useSyncedState(initialValue, key, roomId = options.roomId) {
20
+ if (typeof window === "undefined" && !options.hooks) {
21
+ return [initialValue, () => { }];
22
+ }
23
+ const resolvedUrl = roomId ? `${basePath}/${roomId}` : basePath;
24
+ const client = getSyncedStateClient(resolvedUrl);
25
+ const [value, setValue] = useState(initialValue);
26
+ const valueRef = useRef(value);
27
+ valueRef.current = value;
28
+ const setSyncValue = useCallback((nextValue) => {
29
+ const resolved = typeof nextValue === "function"
30
+ ? nextValue(valueRef.current)
31
+ : nextValue;
32
+ setValue(resolved);
33
+ valueRef.current = resolved;
34
+ void client.setState(resolved, key);
35
+ }, [client, key, setValue, valueRef]);
36
+ useEffect(() => {
37
+ let isActive = true;
38
+ const handleUpdate = (next) => {
39
+ if (isActive) {
40
+ setValue(next);
41
+ valueRef.current = next;
42
+ }
43
+ };
44
+ void client.getState(key).then((existing) => {
45
+ if (existing !== undefined && isActive) {
46
+ setValue(existing);
47
+ valueRef.current = existing;
48
+ }
49
+ });
50
+ void client.subscribe(key, handleUpdate);
51
+ return () => {
52
+ isActive = false;
53
+ // Call unsubscribe when component unmounts
54
+ // Page reloads are handled by the beforeunload event listener in client-core.ts
55
+ void client.unsubscribe(key, handleUpdate).catch((error) => {
56
+ // Log but don't throw - cleanup should not prevent unmounting
57
+ console.error("[useSyncedState] Error during unsubscribe:", error);
58
+ });
59
+ };
60
+ }, [client, key, setValue, valueRef]);
61
+ return [value, setSyncValue];
62
+ };
63
+ };
64
+ export const useSyncedState = createSyncedStateHook();
@@ -0,0 +1,14 @@
1
+ import type { RequestInfo } from "../runtime/requestInfo/types";
2
+ import { SyncedStateServer } from "./SyncedStateServer.mjs";
3
+ export { SyncedStateServer };
4
+ export type SyncedStateRouteOptions = {
5
+ basePath?: string;
6
+ durableObjectName?: string;
7
+ };
8
+ /**
9
+ * Registers routes that forward sync state requests to the configured Durable Object namespace.
10
+ * @param getNamespace Function that returns the Durable Object namespace from the Worker env.
11
+ * @param options Optional overrides for base path and object name.
12
+ * @returns Router entries for the sync state API.
13
+ */
14
+ export declare const syncedStateRoutes: (getNamespace: (env: Cloudflare.Env) => DurableObjectNamespace<SyncedStateServer>, options?: SyncedStateRouteOptions) => import("../runtime/lib/router.js").RouteDefinition<`/${string}`, RequestInfo<any, import("../runtime/requestInfo/types").DefaultAppContext>>[];
@@ -0,0 +1,135 @@
1
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
+ if (kind === "m") throw new TypeError("Private method is not writable");
3
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
+ };
7
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
+ };
12
+ var _SyncedStateProxy_instances, _SyncedStateProxy_stub, _SyncedStateProxy_keyHandler, _SyncedStateProxy_requestInfo, _SyncedStateProxy_transformKey, _SyncedStateProxy_callHandler;
13
+ import { RpcTarget, newWorkersRpcResponse } from "capnweb";
14
+ import { env } from "cloudflare:workers";
15
+ import { route } from "../runtime/entries/router";
16
+ import { runWithRequestInfo } from "../runtime/requestInfo/worker";
17
+ import { SyncedStateServer, } from "./SyncedStateServer.mjs";
18
+ import { DEFAULT_SYNCED_STATE_PATH } from "./constants.mjs";
19
+ export { SyncedStateServer };
20
+ const DEFAULT_SYNC_STATE_NAME = "syncedState";
21
+ class SyncedStateProxy extends RpcTarget {
22
+ constructor(stub, keyHandler, requestInfo) {
23
+ super();
24
+ _SyncedStateProxy_instances.add(this);
25
+ _SyncedStateProxy_stub.set(this, void 0);
26
+ _SyncedStateProxy_keyHandler.set(this, void 0);
27
+ _SyncedStateProxy_requestInfo.set(this, void 0);
28
+ __classPrivateFieldSet(this, _SyncedStateProxy_stub, stub, "f");
29
+ __classPrivateFieldSet(this, _SyncedStateProxy_keyHandler, keyHandler, "f");
30
+ __classPrivateFieldSet(this, _SyncedStateProxy_requestInfo, requestInfo, "f");
31
+ // Set stub in DO instance so handlers can access it
32
+ if (stub && typeof stub._setStub === "function") {
33
+ void stub._setStub(stub);
34
+ }
35
+ }
36
+ async getState(key) {
37
+ const transformedKey = await __classPrivateFieldGet(this, _SyncedStateProxy_instances, "m", _SyncedStateProxy_transformKey).call(this, key);
38
+ return __classPrivateFieldGet(this, _SyncedStateProxy_stub, "f").getState(transformedKey);
39
+ }
40
+ async setState(value, key) {
41
+ const transformedKey = await __classPrivateFieldGet(this, _SyncedStateProxy_instances, "m", _SyncedStateProxy_transformKey).call(this, key);
42
+ return __classPrivateFieldGet(this, _SyncedStateProxy_stub, "f").setState(value, transformedKey);
43
+ }
44
+ async subscribe(key, client) {
45
+ const transformedKey = await __classPrivateFieldGet(this, _SyncedStateProxy_instances, "m", _SyncedStateProxy_transformKey).call(this, key);
46
+ const subscribeHandler = SyncedStateServer.getSubscribeHandler();
47
+ if (subscribeHandler) {
48
+ __classPrivateFieldGet(this, _SyncedStateProxy_instances, "m", _SyncedStateProxy_callHandler).call(this, subscribeHandler, transformedKey, __classPrivateFieldGet(this, _SyncedStateProxy_stub, "f"));
49
+ }
50
+ // dup the client if it is a function; otherwise, pass it as is;
51
+ // this is because the client is a WebSocketRpcSession, and we need to pass a new instance of the client to the DO;
52
+ const clientToPass = typeof client.dup === "function" ? client.dup() : client;
53
+ return __classPrivateFieldGet(this, _SyncedStateProxy_stub, "f").subscribe(transformedKey, clientToPass);
54
+ }
55
+ async unsubscribe(key, client) {
56
+ const transformedKey = await __classPrivateFieldGet(this, _SyncedStateProxy_instances, "m", _SyncedStateProxy_transformKey).call(this, key);
57
+ // Call unsubscribe handler before unsubscribe, similar to subscribe handler
58
+ // This ensures the handler is called even if the unsubscribe doesn't find a match
59
+ // or if the RPC call fails
60
+ const unsubscribeHandler = SyncedStateServer.getUnsubscribeHandler();
61
+ if (unsubscribeHandler) {
62
+ __classPrivateFieldGet(this, _SyncedStateProxy_instances, "m", _SyncedStateProxy_callHandler).call(this, unsubscribeHandler, transformedKey, __classPrivateFieldGet(this, _SyncedStateProxy_stub, "f"));
63
+ }
64
+ try {
65
+ await __classPrivateFieldGet(this, _SyncedStateProxy_stub, "f").unsubscribe(transformedKey, client);
66
+ }
67
+ catch (error) {
68
+ // Ignore errors during unsubscribe - handler has already been called
69
+ // This prevents RPC stub disposal errors from propagating
70
+ }
71
+ }
72
+ }
73
+ _SyncedStateProxy_stub = new WeakMap(), _SyncedStateProxy_keyHandler = new WeakMap(), _SyncedStateProxy_requestInfo = new WeakMap(), _SyncedStateProxy_instances = new WeakSet(), _SyncedStateProxy_transformKey =
74
+ /**
75
+ * Transforms a key using the keyHandler, preserving async context so requestInfo.ctx is available.
76
+ */
77
+ async function _SyncedStateProxy_transformKey(key) {
78
+ if (!__classPrivateFieldGet(this, _SyncedStateProxy_keyHandler, "f")) {
79
+ return key;
80
+ }
81
+ if (__classPrivateFieldGet(this, _SyncedStateProxy_requestInfo, "f")) {
82
+ // Preserve async context when calling keyHandler so requestInfo.ctx is available
83
+ return await runWithRequestInfo(__classPrivateFieldGet(this, _SyncedStateProxy_requestInfo, "f"), async () => await __classPrivateFieldGet(this, _SyncedStateProxy_keyHandler, "f")(key, __classPrivateFieldGet(this, _SyncedStateProxy_stub, "f")));
84
+ }
85
+ return await __classPrivateFieldGet(this, _SyncedStateProxy_keyHandler, "f").call(this, key, __classPrivateFieldGet(this, _SyncedStateProxy_stub, "f"));
86
+ }, _SyncedStateProxy_callHandler = function _SyncedStateProxy_callHandler(handler, key, stub) {
87
+ if (__classPrivateFieldGet(this, _SyncedStateProxy_requestInfo, "f")) {
88
+ // Preserve async context when calling handler so requestInfo.ctx is available
89
+ runWithRequestInfo(__classPrivateFieldGet(this, _SyncedStateProxy_requestInfo, "f"), () => {
90
+ handler(key, stub);
91
+ });
92
+ }
93
+ else {
94
+ handler(key, stub);
95
+ }
96
+ };
97
+ /**
98
+ * Registers routes that forward sync state requests to the configured Durable Object namespace.
99
+ * @param getNamespace Function that returns the Durable Object namespace from the Worker env.
100
+ * @param options Optional overrides for base path and object name.
101
+ * @returns Router entries for the sync state API.
102
+ */
103
+ export const syncedStateRoutes = (getNamespace, options = {}) => {
104
+ const basePath = options.basePath ?? DEFAULT_SYNCED_STATE_PATH;
105
+ const durableObjectName = options.durableObjectName ?? DEFAULT_SYNC_STATE_NAME;
106
+ const forwardRequest = async (request, requestInfo) => {
107
+ const namespace = getNamespace(env);
108
+ // Register the namespace and DO name so handlers can access it
109
+ SyncedStateServer.registerNamespace(namespace, durableObjectName);
110
+ const keyHandler = SyncedStateServer.getKeyHandler();
111
+ const roomHandler = SyncedStateServer.getRoomHandler();
112
+ // Get the room ID from the URL parameter, or undefined if not present
113
+ const idParam = requestInfo.params?.id;
114
+ // Resolve the room name using the roomHandler if present, otherwise use the param or default
115
+ let resolvedRoomName;
116
+ if (roomHandler) {
117
+ resolvedRoomName = await runWithRequestInfo(requestInfo, async () => await roomHandler(idParam, requestInfo));
118
+ }
119
+ else {
120
+ resolvedRoomName = idParam ?? durableObjectName;
121
+ }
122
+ if (!keyHandler) {
123
+ const id = namespace.idFromName(resolvedRoomName);
124
+ return namespace.get(id).fetch(request);
125
+ }
126
+ const id = namespace.idFromName(resolvedRoomName);
127
+ const coordinator = namespace.get(id);
128
+ const proxy = new SyncedStateProxy(coordinator, keyHandler, requestInfo);
129
+ return newWorkersRpcResponse(request, proxy);
130
+ };
131
+ return [
132
+ route(basePath, (requestInfo) => forwardRequest(requestInfo.request, requestInfo)),
133
+ route(basePath + "/:id", (requestInfo) => forwardRequest(requestInfo.request, requestInfo)),
134
+ ];
135
+ };
@@ -1,5 +1,8 @@
1
1
  import debug from "debug";
2
- import { resolve } from "node:path";
2
+ import { existsSync } from "node:fs";
3
+ import { mkdir, rm, writeFile } from "node:fs/promises";
4
+ import { dirname, resolve } from "node:path";
5
+ import { INTERMEDIATES_OUTPUT_DIR } from "../lib/constants.mjs";
3
6
  import { runDirectivesScan } from "./runDirectivesScan.mjs";
4
7
  const log = debug("rwsdk:vite:build-app");
5
8
  /**
@@ -10,7 +13,36 @@ const log = debug("rwsdk:vite:build-app");
10
13
  * @see docs/architecture/productionBuildProcess.md
11
14
  */
12
15
  export async function buildApp({ builder, clientEntryPoints, clientFiles, serverFiles, projectRootDir, workerEntryPathname, }) {
16
+ await rm(resolve(projectRootDir, "dist"), { recursive: true, force: true });
13
17
  const workerEnv = builder.environments.worker;
18
+ // Run a pre-scan build pass to allow plugins to set up and generate code
19
+ // before scanning.
20
+ console.log("Running plugin setup pass...");
21
+ process.env.RWSDK_BUILD_PASS = "plugin-setup";
22
+ const tempEntryPath = resolve(INTERMEDIATES_OUTPUT_DIR, "temp-entry.js");
23
+ try {
24
+ if (!existsSync(dirname(tempEntryPath))) {
25
+ await mkdir(dirname(tempEntryPath), { recursive: true });
26
+ }
27
+ await writeFile(tempEntryPath, "");
28
+ const originalWorkerBuildConfig = workerEnv.config.build;
29
+ workerEnv.config.build = {
30
+ ...originalWorkerBuildConfig,
31
+ write: false,
32
+ rollupOptions: {
33
+ ...originalWorkerBuildConfig?.rollupOptions,
34
+ input: {
35
+ index: tempEntryPath,
36
+ },
37
+ },
38
+ };
39
+ await builder.build(workerEnv);
40
+ // Restore the original config
41
+ workerEnv.config.build = originalWorkerBuildConfig;
42
+ }
43
+ finally {
44
+ await rm(tempEntryPath, { force: true });
45
+ }
14
46
  await runDirectivesScan({
15
47
  rootConfig: builder.config,
16
48
  environments: builder.environments,
@@ -18,7 +50,7 @@ export async function buildApp({ builder, clientEntryPoints, clientFiles, server
18
50
  serverFiles,
19
51
  entries: [workerEntryPathname],
20
52
  });
21
- console.log("Building worker to discover used client components...");
53
+ console.log("Building worker...");
22
54
  process.env.RWSDK_BUILD_PASS = "worker";
23
55
  await builder.build(workerEnv);
24
56
  log("Used client files after worker build & filtering: %O", Array.from(clientFiles));
@@ -0,0 +1,11 @@
1
+ import type { Plugin } from "vite";
2
+ /**
3
+ * Plugin that performs initialization workarounds that must run before
4
+ * the Cloudflare plugin's `configureServer` hook executes.
5
+ *
6
+ * Cloudflare plugin v1.15.0 executes the worker entry file during
7
+ * `configureServer` to detect exports, which triggers SSR code evaluation
8
+ * before normal server initialization completes. This plugin ensures
9
+ * required systems are initialized beforehand.
10
+ */
11
+ export declare const cloudflarePreInitPlugin: () => Plugin;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Plugin that performs initialization workarounds that must run before
3
+ * the Cloudflare plugin's `configureServer` hook executes.
4
+ *
5
+ * Cloudflare plugin v1.15.0 executes the worker entry file during
6
+ * `configureServer` to detect exports, which triggers SSR code evaluation
7
+ * before normal server initialization completes. This plugin ensures
8
+ * required systems are initialized beforehand.
9
+ */
10
+ export const cloudflarePreInitPlugin = () => {
11
+ return {
12
+ name: "rwsdk:cloudflare-pre-init",
13
+ // context(justinvdm, 20 Jan 2025): This plugin must run before the
14
+ // Cloudflare plugin's `configureServer` hook. The Cloudflare plugin
15
+ // executes the worker entry file during `configureServer` to detect
16
+ // exports, and blocks the rest of the configureServer plugins until the
17
+ // request is complete. We must initialize required systems (SSR dependency
18
+ // optimizer + our own plugins to them, as well as CSS plugin's moduleCache)
19
+ // before this happens to prevent errors.
20
+ enforce: "pre",
21
+ async configureServer(server) {
22
+ // context(justinvdm, 20 Jan 2025): Initialize SSR dependency optimizer before
23
+ // Cloudflare plugin triggers SSR code evaluation. This ensures dependencies
24
+ // in `optimizeDeps.include` (like `react-dom/server.edge`) are correctly
25
+ // registered before they are discovered lazily.
26
+ if (server.environments.ssr?.depsOptimizer) {
27
+ await server.environments.ssr.depsOptimizer.init();
28
+ }
29
+ // context(justinvdm, 20 Jan 2025): Initialize CSS plugin's shared moduleCache
30
+ // before CSS modules are processed. The Cloudflare plugin's export detection
31
+ // can trigger CSS module processing in the SSR environment during `configureServer`,
32
+ // which happens before `initServer` runs. Vite's CSS plugin uses a shared
33
+ // `moduleCache` that is initialized in the client environment's `buildStart` hook.
34
+ // By calling `buildStart` here (which is idempotent), we ensure the CSS plugin's
35
+ // cache is initialized before CSS modules are processed, preventing "Cannot read
36
+ // properties of undefined (reading 'set')" errors.
37
+ await server.environments.client.pluginContainer.buildStart();
38
+ },
39
+ };
40
+ };
@@ -3,6 +3,7 @@ import path, { resolve } from "node:path";
3
3
  import { INTERMEDIATE_SSR_BRIDGE_PATH } from "../lib/constants.mjs";
4
4
  import { buildApp } from "./buildApp.mjs";
5
5
  import { externalModules } from "./constants.mjs";
6
+ import { ssrBridgeWrapPlugin } from "./ssrBridgeWrapPlugin.mjs";
6
7
  export const configPlugin = ({ silent, projectRootDir, workerEntryPathname, clientFiles, serverFiles, clientEntryPoints, }) => ({
7
8
  name: "rwsdk:config",
8
9
  config: async (_, { command }) => {
@@ -55,6 +56,7 @@ export const configPlugin = ({ silent, projectRootDir, workerEntryPathname, clie
55
56
  build: {
56
57
  outDir: resolve(projectRootDir, "dist", "worker"),
57
58
  emitAssets: true,
59
+ ssrManifest: true,
58
60
  emptyOutDir: false,
59
61
  ssr: true,
60
62
  },
@@ -93,6 +95,7 @@ export const configPlugin = ({ silent, projectRootDir, workerEntryPathname, clie
93
95
  "rwsdk/constants",
94
96
  "rwsdk/debug",
95
97
  "rwsdk/realtime/client",
98
+ "rwsdk/router",
96
99
  "rwsdk/turnstile",
97
100
  ],
98
101
  entries: [],
@@ -161,22 +164,14 @@ export const configPlugin = ({ silent, projectRootDir, workerEntryPathname, clie
161
164
  // causes a redeclaration error. To solve this, we wrap the SSR
162
165
  // bundle in an exporting IIFE. This creates a scope boundary,
163
166
  // preventing symbol collisions while producing a valid,
164
- // tree-shakeable ES module. The inline plugin below removes the
165
- // original `export` statement from the bundle to prevent syntax
166
- // errors.
167
+ // tree-shakeable ES module.
168
+ //
169
+ // context(justinvdm, 19 Nov 2025): We use a custom plugin
170
+ // (ssrBridgeWrapPlugin) to intelligently inject the IIFE *after*
171
+ // any top-level external imports, ensuring they remain valid.
167
172
  inlineDynamicImports: true,
168
- banner: `export const { renderHtmlStream, ssrLoadModule, ssrWebpackRequire, ssrGetModuleExport, createThenableFromReadableStream } = (function() {`,
169
- footer: `return { renderHtmlStream, ssrLoadModule, ssrWebpackRequire, ssrGetModuleExport, createThenableFromReadableStream };\n})();`,
170
173
  },
171
- plugins: [
172
- {
173
- name: "rwsdk:ssr-bridge-exports",
174
- renderChunk(code) {
175
- // Remove the original export statement as it's now handled by the banner/footer
176
- return code.replace(/export\s*{[^}]+};?/, "");
177
- },
178
- },
179
- ],
174
+ plugins: [ssrBridgeWrapPlugin()],
180
175
  },
181
176
  },
182
177
  },
@@ -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);
@@ -42,6 +42,10 @@ export const createDirectiveLookupPlugin = async ({ projectRootDir, files, confi
42
42
  log("Development mode: %s", isDev);
43
43
  },
44
44
  configureServer(server) {
45
+ // context(justinvdm, 19 Nov 2025): This hook simply saves a reference
46
+ // to the dev server instance for use in other hooks. Unlike plugins that
47
+ // must run before the Cloudflare plugin to prevent startup deadlocks,
48
+ // its execution order is not critical, so `enforce: 'pre'` is not needed.
45
49
  devServer = server;
46
50
  },
47
51
  configEnvironment(env, viteConfig) {
@@ -97,9 +101,12 @@ export const createDirectiveLookupPlugin = async ({ projectRootDir, files, confi
97
101
  log("Skipping optimizeDeps and aliasing for environment: %s", env);
98
102
  }
99
103
  },
100
- resolveId(source) {
101
- // Skip during directive scanning to avoid performance issues
102
- if (process.env.RWSDK_DIRECTIVE_SCAN_ACTIVE) {
104
+ resolveId(source, _importer, options) {
105
+ // Skip during our directive scanning to avoid performance issues
106
+ // context(justinvdm, 20 Jan 2025): We check options.custom?.rwsdk?.directiveScan to distinguish
107
+ // between our directive scan (which should skip) and external calls like Cloudflare's early
108
+ // dispatch (which should be handled normally).
109
+ if (options?.custom?.rwsdk?.directiveScan === true) {
103
110
  return;
104
111
  }
105
112
  if (source !== `${config.virtualModuleName}.js`) {
@@ -140,10 +147,6 @@ export const createDirectiveLookupPlugin = async ({ projectRootDir, files, confi
140
147
  return source;
141
148
  },
142
149
  async load(id) {
143
- // Skip during directive scanning to avoid performance issues
144
- if (process.env.RWSDK_DIRECTIVE_SCAN_ACTIVE) {
145
- return;
146
- }
147
150
  if (id === config.virtualModuleName + ".js") {
148
151
  log("Loading %s module with %d files", config.virtualModuleName, files.size);
149
152
  const environment = this.environment?.name || "client";
@@ -6,6 +6,10 @@ export const devServerTimingPlugin = () => {
6
6
  return {
7
7
  name: "rwsdk:dev-server-timing",
8
8
  configureServer(server) {
9
+ // context(justinvdm, 19 Nov 2025): This hook adds a middleware for
10
+ // logging the time to first response. Unlike other plugins that must
11
+ // run before the Cloudflare plugin to prevent startup deadlocks, its
12
+ // execution order is not critical, so `enforce: 'pre'` is not needed.
9
13
  server.middlewares.use((_req, res, next) => {
10
14
  if (!hasLoggedFirstResponse) {
11
15
  res.on("finish", () => {
@@ -0,0 +1,4 @@
1
+ import { Plugin } from "vite";
2
+ export declare const diagnosticAssetGraphPlugin: ({ rootDir, }: {
3
+ rootDir: string;
4
+ }) => Plugin;