rwsdk 1.0.0-beta.4 → 1.0.0-beta.40
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.
- package/dist/lib/constants.d.mts +1 -0
- package/dist/lib/constants.mjs +7 -4
- package/dist/lib/e2e/browser.mjs +6 -2
- package/dist/lib/e2e/constants.d.mts +4 -0
- package/dist/lib/e2e/constants.mjs +49 -12
- package/dist/lib/e2e/dev.mjs +37 -49
- package/dist/lib/e2e/environment.d.mts +2 -0
- package/dist/lib/e2e/environment.mjs +201 -64
- package/dist/lib/e2e/index.d.mts +1 -0
- package/dist/lib/e2e/index.mjs +1 -0
- package/dist/lib/e2e/poll.d.mts +1 -1
- package/dist/lib/e2e/release.d.mts +1 -0
- package/dist/lib/e2e/release.mjs +16 -32
- package/dist/lib/e2e/tarball.mjs +2 -34
- package/dist/lib/e2e/testHarness.d.mts +34 -3
- package/dist/lib/e2e/testHarness.mjs +219 -90
- package/dist/lib/e2e/utils.d.mts +1 -0
- package/dist/lib/e2e/utils.mjs +15 -0
- package/dist/runtime/client/client.d.ts +35 -0
- package/dist/runtime/client/client.js +35 -0
- package/dist/runtime/client/navigation.d.ts +49 -0
- package/dist/runtime/client/navigation.js +80 -31
- package/dist/runtime/entries/clientSSR.d.ts +1 -0
- package/dist/runtime/entries/clientSSR.js +3 -0
- package/dist/runtime/entries/no-react-server-ssr-bridge.d.ts +0 -0
- package/dist/runtime/entries/no-react-server-ssr-bridge.js +2 -0
- package/dist/runtime/entries/router.d.ts +1 -0
- package/dist/runtime/entries/routerClient.d.ts +1 -0
- package/dist/runtime/entries/routerClient.js +1 -0
- package/dist/runtime/entries/worker.d.ts +2 -0
- package/dist/runtime/entries/worker.js +2 -0
- package/dist/runtime/imports/__mocks__/use-client-lookup.d.ts +6 -0
- package/dist/runtime/imports/__mocks__/use-client-lookup.js +6 -0
- package/dist/runtime/lib/db/SqliteDurableObject.d.ts +2 -2
- package/dist/runtime/lib/db/SqliteDurableObject.js +2 -2
- package/dist/runtime/lib/db/createDb.d.ts +1 -2
- package/dist/runtime/lib/db/createDb.js +4 -0
- package/dist/runtime/lib/db/typeInference/builders/alterTable.d.ts +13 -3
- package/dist/runtime/lib/db/typeInference/builders/columnDefinition.d.ts +35 -21
- package/dist/runtime/lib/db/typeInference/builders/createTable.d.ts +9 -2
- package/dist/runtime/lib/db/typeInference/database.d.ts +16 -2
- package/dist/runtime/lib/db/typeInference/typetests/alterTable.typetest.js +80 -5
- package/dist/runtime/lib/db/typeInference/typetests/createTable.typetest.js +104 -2
- package/dist/runtime/lib/db/typeInference/typetests/testUtils.d.ts +1 -0
- package/dist/runtime/lib/db/typeInference/utils.d.ts +59 -9
- package/dist/runtime/lib/links.d.ts +21 -7
- package/dist/runtime/lib/links.js +82 -24
- package/dist/runtime/lib/links.test.js +20 -0
- package/dist/runtime/lib/manifest.d.ts +1 -1
- package/dist/runtime/lib/manifest.js +7 -4
- package/dist/runtime/lib/realtime/client.js +8 -2
- package/dist/runtime/lib/realtime/worker.d.ts +1 -1
- package/dist/runtime/lib/router.d.ts +153 -36
- package/dist/runtime/lib/router.js +169 -20
- package/dist/runtime/lib/router.test.js +241 -0
- package/dist/runtime/lib/stitchDocumentAndAppStreams.d.ts +66 -0
- package/dist/runtime/lib/stitchDocumentAndAppStreams.js +302 -35
- package/dist/runtime/lib/stitchDocumentAndAppStreams.test.d.ts +1 -0
- package/dist/runtime/lib/stitchDocumentAndAppStreams.test.js +418 -0
- package/dist/runtime/lib/{rwContext.d.ts → types.d.ts} +1 -0
- package/dist/runtime/lib/types.js +1 -0
- package/dist/runtime/render/renderDocumentHtmlStream.d.ts +1 -1
- package/dist/runtime/render/renderToStream.d.ts +4 -2
- package/dist/runtime/render/renderToStream.js +53 -24
- package/dist/runtime/render/renderToString.d.ts +3 -6
- package/dist/runtime/requestInfo/types.d.ts +4 -1
- package/dist/runtime/requestInfo/utils.d.ts +9 -0
- package/dist/runtime/requestInfo/utils.js +44 -0
- package/dist/runtime/requestInfo/worker.d.ts +0 -1
- package/dist/runtime/requestInfo/worker.js +3 -10
- package/dist/runtime/script.d.ts +1 -3
- package/dist/runtime/script.js +1 -10
- package/dist/runtime/state.d.ts +3 -0
- package/dist/runtime/state.js +13 -0
- package/dist/runtime/worker.d.ts +3 -1
- package/dist/runtime/worker.js +32 -0
- package/dist/scripts/debug-sync.mjs +18 -20
- package/dist/scripts/worker-run.d.mts +1 -1
- package/dist/scripts/worker-run.mjs +59 -113
- package/dist/use-synced-state/SyncedStateServer.d.mts +21 -0
- package/dist/use-synced-state/SyncedStateServer.mjs +128 -0
- package/dist/use-synced-state/__tests__/SyncStateServer.test.d.mts +1 -0
- package/dist/use-synced-state/__tests__/SyncStateServer.test.mjs +109 -0
- package/dist/use-synced-state/__tests__/useSyncState.test.d.ts +1 -0
- package/dist/use-synced-state/__tests__/useSyncState.test.js +115 -0
- package/dist/use-synced-state/__tests__/useSyncedState.test.d.ts +1 -0
- package/dist/use-synced-state/__tests__/useSyncedState.test.js +115 -0
- package/dist/use-synced-state/__tests__/worker.test.d.mts +1 -0
- package/dist/use-synced-state/__tests__/worker.test.mjs +69 -0
- package/dist/use-synced-state/client-core.d.ts +26 -0
- package/dist/use-synced-state/client-core.js +39 -0
- package/dist/use-synced-state/client.d.ts +3 -0
- package/dist/use-synced-state/client.js +4 -0
- package/dist/use-synced-state/constants.d.mts +1 -0
- package/dist/use-synced-state/constants.mjs +1 -0
- package/dist/use-synced-state/useSyncedState.d.ts +20 -0
- package/dist/use-synced-state/useSyncedState.js +58 -0
- package/dist/use-synced-state/worker.d.mts +13 -0
- package/dist/use-synced-state/worker.mjs +69 -0
- package/dist/vite/buildApp.mjs +34 -2
- package/dist/vite/cloudflarePreInitPlugin.d.mts +11 -0
- package/dist/vite/cloudflarePreInitPlugin.mjs +40 -0
- package/dist/vite/configPlugin.mjs +9 -14
- package/dist/vite/constants.d.mts +1 -0
- package/dist/vite/constants.mjs +1 -0
- package/dist/vite/createDirectiveLookupPlugin.mjs +10 -7
- package/dist/vite/devServerTimingPlugin.mjs +4 -0
- package/dist/vite/diagnosticAssetGraphPlugin.d.mts +4 -0
- package/dist/vite/diagnosticAssetGraphPlugin.mjs +41 -0
- package/dist/vite/directiveModulesDevPlugin.mjs +9 -1
- package/dist/vite/directivesPlugin.mjs +4 -4
- package/dist/vite/envResolvers.d.mts +11 -0
- package/dist/vite/envResolvers.mjs +20 -0
- package/dist/vite/getViteEsbuild.mjs +2 -1
- package/dist/vite/hmrStabilityPlugin.d.mts +2 -0
- package/dist/vite/hmrStabilityPlugin.mjs +73 -0
- package/dist/vite/injectVitePreamblePlugin.mjs +0 -4
- package/dist/vite/knownDepsResolverPlugin.d.mts +0 -6
- package/dist/vite/knownDepsResolverPlugin.mjs +25 -17
- package/dist/vite/linkerPlugin.d.mts +2 -1
- package/dist/vite/linkerPlugin.mjs +11 -3
- package/dist/vite/linkerPlugin.test.mjs +15 -0
- package/dist/vite/miniflareHMRPlugin.mjs +6 -38
- package/dist/vite/moveStaticAssetsPlugin.mjs +35 -4
- package/dist/vite/redwoodPlugin.mjs +8 -10
- package/dist/vite/runDirectivesScan.mjs +72 -18
- package/dist/vite/ssrBridgePlugin.mjs +132 -40
- package/dist/vite/ssrBridgeWrapPlugin.d.mts +2 -0
- package/dist/vite/ssrBridgeWrapPlugin.mjs +85 -0
- package/dist/vite/staleDepRetryPlugin.d.mts +2 -0
- package/dist/vite/staleDepRetryPlugin.mjs +74 -0
- package/dist/vite/statePlugin.d.mts +4 -0
- package/dist/vite/statePlugin.mjs +62 -0
- package/dist/vite/transformJsxScriptTagsPlugin.mjs +0 -5
- package/dist/vite/virtualPlugin.mjs +6 -7
- package/package.json +27 -10
- package/dist/vite/manifestPlugin.d.mts +0 -4
- package/dist/vite/manifestPlugin.mjs +0 -63
- /package/dist/runtime/lib/{rwContext.js → links.test.d.ts} +0 -0
package/dist/runtime/script.js
CHANGED
|
@@ -1,11 +1,2 @@
|
|
|
1
1
|
import { env } from "cloudflare:workers";
|
|
2
|
-
|
|
3
|
-
export const defineScript = (fn) => {
|
|
4
|
-
const app = defineApp([
|
|
5
|
-
async () => {
|
|
6
|
-
await fn({ env: env });
|
|
7
|
-
return new Response("Done!");
|
|
8
|
-
},
|
|
9
|
-
]);
|
|
10
|
-
return app;
|
|
11
|
-
};
|
|
2
|
+
export const defineScript = (fn) => () => fn({ env: env });
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const state = {};
|
|
2
|
+
export function defineRwState(key, initializer) {
|
|
3
|
+
if (!(key in state)) {
|
|
4
|
+
state[key] = initializer();
|
|
5
|
+
}
|
|
6
|
+
return state[key];
|
|
7
|
+
}
|
|
8
|
+
export function getRwState(key) {
|
|
9
|
+
return state[key];
|
|
10
|
+
}
|
|
11
|
+
export function setRwState(key, value) {
|
|
12
|
+
state[key] = value;
|
|
13
|
+
}
|
package/dist/runtime/worker.d.ts
CHANGED
|
@@ -8,9 +8,11 @@ declare global {
|
|
|
8
8
|
DB: D1Database;
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
|
-
export
|
|
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
|
}>;
|
package/dist/runtime/worker.js
CHANGED
|
@@ -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);
|
|
@@ -23,6 +24,35 @@ export const defineApp = (routes) => {
|
|
|
23
24
|
url.pathname = url.pathname.slice("/assets/".length);
|
|
24
25
|
return env.ASSETS.fetch(new Request(url.toString(), request));
|
|
25
26
|
}
|
|
27
|
+
else if (import.meta.env.VITE_IS_DEV_SERVER &&
|
|
28
|
+
new URL(request.url).pathname === "/__worker-run") {
|
|
29
|
+
const expectedToken = import.meta.env
|
|
30
|
+
.VITE_RWSDK_WORKER_RUN_TOKEN;
|
|
31
|
+
const requestToken = request.headers.get("x-rwsdk-worker-run-token");
|
|
32
|
+
if (!expectedToken || expectedToken !== requestToken) {
|
|
33
|
+
return new Response("Forbidden", { status: 403 });
|
|
34
|
+
}
|
|
35
|
+
const url = new URL(request.url);
|
|
36
|
+
const scriptPath = url.searchParams.get("script");
|
|
37
|
+
if (!scriptPath) {
|
|
38
|
+
return new Response("Missing 'script' query parameter", {
|
|
39
|
+
status: 400,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const scriptModule = await import(/* @vite-ignore */ scriptPath);
|
|
44
|
+
if (scriptModule.default) {
|
|
45
|
+
await scriptModule.default(request, env, cf);
|
|
46
|
+
}
|
|
47
|
+
return new Response("Script executed successfully");
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
console.error(`Error executing script: ${scriptPath}\n\n${e.stack}`);
|
|
51
|
+
return new Response(`Error executing script: ${e.message}`, {
|
|
52
|
+
status: 500,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
26
56
|
else if (import.meta.env.VITE_IS_DEV_SERVER &&
|
|
27
57
|
request.url.includes("/__vite_preamble__")) {
|
|
28
58
|
return new Response('import RefreshRuntime from "/@react-refresh"; RefreshRuntime.injectIntoGlobalHook(window); window.$RefreshReg$ = () => {}; window.$RefreshSig$ = () => (type) => type; window.__vite_plugin_react_preamble_installed__ = true;', {
|
|
@@ -43,6 +73,8 @@ export const defineApp = (routes) => {
|
|
|
43
73
|
ssr: true,
|
|
44
74
|
databases: new Map(),
|
|
45
75
|
scriptsToBeLoaded: new Set(),
|
|
76
|
+
entryScripts: new Set(),
|
|
77
|
+
inlineScripts: new Set(),
|
|
46
78
|
pageRouteResolved: undefined,
|
|
47
79
|
};
|
|
48
80
|
const userResponseInit = {
|
|
@@ -99,28 +99,20 @@ const findUp = async (names, startDir) => {
|
|
|
99
99
|
return undefined;
|
|
100
100
|
};
|
|
101
101
|
const getMonorepoRoot = async (startDir) => {
|
|
102
|
-
|
|
103
|
-
// `pnpm root` is the most reliable way to find the workspace root node_modules
|
|
104
|
-
const { stdout } = await $({
|
|
105
|
-
cwd: startDir,
|
|
106
|
-
}) `pnpm root`;
|
|
107
|
-
// pnpm root returns the node_modules path, so we go up one level
|
|
108
|
-
return path.resolve(stdout, "..");
|
|
109
|
-
}
|
|
110
|
-
catch (e) {
|
|
111
|
-
console.warn(`Could not determine pnpm root from ${startDir}. Falling back to file search.`);
|
|
112
|
-
const root = await findUp(["pnpm-workspace.yaml"], startDir);
|
|
113
|
-
if (root) {
|
|
114
|
-
return root;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
console.warn("Could not find pnpm monorepo root. Using parent directory of target as fallback.");
|
|
118
|
-
return path.resolve(startDir, "..");
|
|
102
|
+
return await findUp(["pnpm-workspace.yaml"], startDir);
|
|
119
103
|
};
|
|
120
104
|
const areDependenciesEqual = (deps1, deps2) => {
|
|
121
105
|
// Simple string comparison for this use case is sufficient
|
|
122
106
|
return JSON.stringify(deps1 ?? {}) === JSON.stringify(deps2 ?? {});
|
|
123
107
|
};
|
|
108
|
+
const isPlaygroundExample = async (targetDir, monorepoRoot) => {
|
|
109
|
+
const pkgJson = JSON.parse(await fs.readFile(path.join(monorepoRoot, "package.json"), "utf-8"));
|
|
110
|
+
if (pkgJson.name === "rw-sdk-monorepo") {
|
|
111
|
+
const playgroundDir = path.join(monorepoRoot, "playground");
|
|
112
|
+
return targetDir.startsWith(playgroundDir);
|
|
113
|
+
}
|
|
114
|
+
return false;
|
|
115
|
+
};
|
|
124
116
|
const performFullSync = async (sdkDir, targetDir, monorepoRoot) => {
|
|
125
117
|
console.log("📦 Performing full sync with tarball...");
|
|
126
118
|
let tarballPath = "";
|
|
@@ -178,8 +170,14 @@ const performSync = async (sdkDir, targetDir) => {
|
|
|
178
170
|
// Clean up vite cache in the target project
|
|
179
171
|
await cleanupViteEntries(targetDir);
|
|
180
172
|
const monorepoRoot = await getMonorepoRoot(targetDir);
|
|
173
|
+
const rootDir = monorepoRoot ?? targetDir;
|
|
181
174
|
const projectName = path.basename(targetDir);
|
|
182
|
-
|
|
175
|
+
if (monorepoRoot && (await isPlaygroundExample(targetDir, monorepoRoot))) {
|
|
176
|
+
console.log("Playground example detected. Skipping file sync; workspace linking will be used.");
|
|
177
|
+
console.log("✅ Done syncing");
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const installedSdkPackageJsonPath = path.join(rootDir, "node_modules", `.rwsync_${projectName}`, "node_modules", "rwsdk", "package.json");
|
|
183
181
|
let needsFullSync = false;
|
|
184
182
|
if (!existsSync(installedSdkPackageJsonPath)) {
|
|
185
183
|
console.log("No previous sync found, performing full sync.");
|
|
@@ -195,10 +193,10 @@ const performSync = async (sdkDir, targetDir) => {
|
|
|
195
193
|
}
|
|
196
194
|
}
|
|
197
195
|
if (needsFullSync) {
|
|
198
|
-
await performFullSync(sdkDir, targetDir,
|
|
196
|
+
await performFullSync(sdkDir, targetDir, rootDir);
|
|
199
197
|
}
|
|
200
198
|
else {
|
|
201
|
-
await performFastSync(sdkDir, targetDir,
|
|
199
|
+
await performFastSync(sdkDir, targetDir, rootDir);
|
|
202
200
|
}
|
|
203
201
|
console.log("✅ Done syncing");
|
|
204
202
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export {};
|
|
@@ -1,131 +1,77 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
import dbg from "debug";
|
|
3
|
+
import getPort from "get-port";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { pathToFileURL } from "url";
|
|
6
|
+
import * as vite from "vite";
|
|
7
|
+
import { createLogger } from "vite";
|
|
8
|
+
const debug = dbg("rwsdk:worker-run");
|
|
9
|
+
const main = async () => {
|
|
10
|
+
process.env.RWSDK_WORKER_RUN = "1";
|
|
11
|
+
const token = crypto.randomBytes(32).toString("hex");
|
|
12
|
+
process.env.VITE_RWSDK_WORKER_RUN_TOKEN = token;
|
|
13
|
+
const relativeScriptPath = process.argv[2];
|
|
13
14
|
if (!relativeScriptPath) {
|
|
14
15
|
console.error("Error: Script path is required");
|
|
15
16
|
console.log("\nUsage:");
|
|
16
|
-
console.log("
|
|
17
|
-
console.log("\nOptions:");
|
|
18
|
-
console.log(" RWSDK_WRANGLER_CONFIG Environment variable for config path");
|
|
17
|
+
console.log(" rwsdk worker-run <script-path>");
|
|
19
18
|
console.log("\nExamples:");
|
|
20
|
-
console.log("
|
|
21
|
-
console.log(" RWSDK_WRANGLER_CONFIG=custom.toml npm run worker:run src/scripts/seed.ts\n");
|
|
19
|
+
console.log(" rwsdk worker-run src/scripts/seed.ts\n");
|
|
22
20
|
process.exit(1);
|
|
23
21
|
}
|
|
24
|
-
const scriptPath = resolve(process.cwd(), relativeScriptPath);
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const workerConfig = unstable_readConfig({
|
|
31
|
-
config: workerConfigPath,
|
|
32
|
-
env: "dev",
|
|
33
|
-
});
|
|
34
|
-
const durableObjectsToExport = workerConfig.durable_objects?.bindings
|
|
35
|
-
.filter((binding) => !binding.script_name)
|
|
36
|
-
.map((binding) => binding.class_name) ?? [];
|
|
37
|
-
const workerEntryRelativePath = workerConfig.main;
|
|
38
|
-
const workerEntryPath = workerEntryRelativePath ?? path.join(process.cwd(), "src/worker.tsx");
|
|
39
|
-
const durableObjectExports = [];
|
|
40
|
-
if (durableObjectsToExport.length > 0) {
|
|
41
|
-
const resolver = enhancedResolve.create.sync({
|
|
42
|
-
extensions: [".mts", ".ts", ".tsx", ".mjs", ".js", ".jsx", ".json"],
|
|
43
|
-
});
|
|
44
|
-
const workerEntryContents = await readFile(workerEntryPath, "utf-8");
|
|
45
|
-
const workerEntryAst = parse(Lang.Tsx, workerEntryContents);
|
|
46
|
-
const exportDeclarations = [
|
|
47
|
-
...workerEntryAst.root().findAll('export { $$$EXPORTS } from "$MODULE"'),
|
|
48
|
-
...workerEntryAst.root().findAll("export { $$$EXPORTS } from '$MODULE'"),
|
|
49
|
-
...workerEntryAst.root().findAll("export { $$$EXPORTS } from '$MODULE'"),
|
|
50
|
-
];
|
|
51
|
-
for (const exportDeclaration of exportDeclarations) {
|
|
52
|
-
const moduleMatch = exportDeclaration.getMatch("MODULE");
|
|
53
|
-
const exportsMatch = exportDeclaration.getMultipleMatches("EXPORTS");
|
|
54
|
-
if (!moduleMatch || exportsMatch.length === 0) {
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
const modulePath = moduleMatch.text();
|
|
58
|
-
const specifiers = exportsMatch.map((m) => m.text().trim());
|
|
59
|
-
for (const specifier of specifiers) {
|
|
60
|
-
if (durableObjectsToExport.includes(specifier)) {
|
|
61
|
-
const resolvedPath = resolver(path.dirname(workerEntryPath), modulePath);
|
|
62
|
-
durableObjectExports.push(`export { ${specifier} } from "${resolvedPath}";`);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
22
|
+
const scriptPath = path.resolve(process.cwd(), relativeScriptPath);
|
|
23
|
+
const port = await getPort();
|
|
24
|
+
let server;
|
|
25
|
+
const cleanup = async () => {
|
|
26
|
+
if (server) {
|
|
27
|
+
await server.close();
|
|
65
28
|
}
|
|
66
|
-
|
|
67
|
-
const tmpDir = await tmp.dir({
|
|
68
|
-
prefix: "rw-worker-run-",
|
|
69
|
-
unsafeCleanup: true,
|
|
70
|
-
});
|
|
71
|
-
const relativeTmpWorkerEntryPath = "worker.tsx";
|
|
72
|
-
const tmpWorkerPath = path.join(tmpDir.path, "wrangler.json");
|
|
73
|
-
const tmpWorkerEntryPath = path.join(tmpDir.path, relativeTmpWorkerEntryPath);
|
|
74
|
-
const scriptWorkerConfig = {
|
|
75
|
-
...workerConfig,
|
|
76
|
-
configPath: tmpWorkerPath,
|
|
77
|
-
userConfigPath: tmpWorkerPath,
|
|
78
|
-
main: relativeTmpWorkerEntryPath,
|
|
29
|
+
process.exit();
|
|
79
30
|
};
|
|
31
|
+
process.on("SIGINT", cleanup);
|
|
32
|
+
process.on("SIGTERM", cleanup);
|
|
80
33
|
try {
|
|
81
|
-
await
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
configFile: false,
|
|
91
|
-
plugins: [
|
|
92
|
-
redwood({
|
|
93
|
-
configPath: tmpWorkerPath,
|
|
94
|
-
includeCloudflarePlugin: true,
|
|
95
|
-
entry: {
|
|
96
|
-
worker: tmpWorkerEntryPath,
|
|
97
|
-
},
|
|
98
|
-
}),
|
|
99
|
-
],
|
|
34
|
+
server = await vite.createServer({
|
|
35
|
+
logLevel: "silent",
|
|
36
|
+
build: {
|
|
37
|
+
outDir: ".rwsdk",
|
|
38
|
+
},
|
|
39
|
+
customLogger: createLogger("info", {
|
|
40
|
+
prefix: "[rwsdk]",
|
|
41
|
+
allowClearScreen: true,
|
|
42
|
+
}),
|
|
100
43
|
server: {
|
|
101
|
-
port
|
|
44
|
+
port,
|
|
45
|
+
host: "localhost",
|
|
102
46
|
},
|
|
103
47
|
});
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
48
|
+
await server.listen();
|
|
49
|
+
const fileUrl = pathToFileURL(scriptPath).href;
|
|
50
|
+
const url = `http://localhost:${port}/__worker-run?script=${encodeURIComponent(fileUrl)}`;
|
|
51
|
+
debug("Fetching %s", url);
|
|
52
|
+
const response = await fetch(url, {
|
|
53
|
+
headers: {
|
|
54
|
+
"x-rwsdk-worker-run-token": token,
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
debug("Response from worker: %s", response);
|
|
58
|
+
if (!response.ok) {
|
|
59
|
+
const errorText = await response.text();
|
|
60
|
+
console.error(`Error: worker-run script failed with status ${response.status}.`);
|
|
61
|
+
if (errorText) {
|
|
62
|
+
console.error("Response:", errorText);
|
|
111
63
|
}
|
|
112
|
-
|
|
113
|
-
await fetch(`http://localhost:${address.port}/`);
|
|
114
|
-
debug("Worker fetched successfully");
|
|
115
|
-
}
|
|
116
|
-
finally {
|
|
117
|
-
debug("Closing server...");
|
|
118
|
-
server.close();
|
|
119
|
-
debug("Server closed");
|
|
64
|
+
process.exit(1);
|
|
120
65
|
}
|
|
66
|
+
const responseText = await response.text();
|
|
67
|
+
debug("Response from worker: %s", responseText);
|
|
68
|
+
}
|
|
69
|
+
catch (e) {
|
|
70
|
+
console.error("rwsdk: Error running script:\n\n%s", e.message);
|
|
71
|
+
process.exit(1);
|
|
121
72
|
}
|
|
122
73
|
finally {
|
|
123
|
-
|
|
124
|
-
debug("Temporary files cleaned up");
|
|
74
|
+
await cleanup();
|
|
125
75
|
}
|
|
126
|
-
// todo(justinvdm, 01 Apr 2025): Investigate what handles are remaining open
|
|
127
|
-
process.exit(0);
|
|
128
76
|
};
|
|
129
|
-
|
|
130
|
-
runWorkerScript(process.argv[2]);
|
|
131
|
-
}
|
|
77
|
+
main();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { RpcStub } from "capnweb";
|
|
2
|
+
import { DurableObject } from "cloudflare:workers";
|
|
3
|
+
export type SyncedStateValue = unknown;
|
|
4
|
+
type OnSetHandler = (key: string, value: SyncedStateValue) => void;
|
|
5
|
+
type OnGetHandler = (key: string, value: SyncedStateValue | undefined) => void;
|
|
6
|
+
/**
|
|
7
|
+
* Durable Object that keeps shared state for multiple clients and notifies subscribers.
|
|
8
|
+
*/
|
|
9
|
+
export declare class SyncedStateServer extends DurableObject {
|
|
10
|
+
#private;
|
|
11
|
+
static registerKeyHandler(handler: (key: string) => Promise<string>): void;
|
|
12
|
+
static getKeyHandler(): ((key: string) => Promise<string>) | null;
|
|
13
|
+
static registerSetStateHandler(handler: OnSetHandler | null): void;
|
|
14
|
+
static registerGetStateHandler(handler: OnGetHandler | null): void;
|
|
15
|
+
getState(key: string): SyncedStateValue;
|
|
16
|
+
setState(value: SyncedStateValue, key: string): void;
|
|
17
|
+
subscribe(key: string, client: RpcStub<(value: SyncedStateValue) => void>): void;
|
|
18
|
+
unsubscribe(key: string, client: RpcStub<(value: SyncedStateValue) => void>): void;
|
|
19
|
+
fetch(request: Request): Promise<Response>;
|
|
20
|
+
}
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,128 @@
|
|
|
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 _a, _SyncedStateServer_keyHandler, _SyncedStateServer_setStateHandler, _SyncedStateServer_getStateHandler, _SyncedStateServer_stateStore, _SyncedStateServer_subscriptions, _SyncedStateServer_subscriptionRefs, _CoordinatorApi_coordinator;
|
|
13
|
+
import { RpcTarget, newWorkersRpcResponse } from "capnweb";
|
|
14
|
+
import { DurableObject } from "cloudflare:workers";
|
|
15
|
+
/**
|
|
16
|
+
* Durable Object that keeps shared state for multiple clients and notifies subscribers.
|
|
17
|
+
*/
|
|
18
|
+
export class SyncedStateServer extends DurableObject {
|
|
19
|
+
constructor() {
|
|
20
|
+
super(...arguments);
|
|
21
|
+
_SyncedStateServer_stateStore.set(this, new Map());
|
|
22
|
+
_SyncedStateServer_subscriptions.set(this, new Map());
|
|
23
|
+
_SyncedStateServer_subscriptionRefs.set(this, new Map());
|
|
24
|
+
}
|
|
25
|
+
static registerKeyHandler(handler) {
|
|
26
|
+
__classPrivateFieldSet(_a, _a, handler, "f", _SyncedStateServer_keyHandler);
|
|
27
|
+
}
|
|
28
|
+
static getKeyHandler() {
|
|
29
|
+
return __classPrivateFieldGet(_a, _a, "f", _SyncedStateServer_keyHandler);
|
|
30
|
+
}
|
|
31
|
+
static registerSetStateHandler(handler) {
|
|
32
|
+
__classPrivateFieldSet(_a, _a, handler, "f", _SyncedStateServer_setStateHandler);
|
|
33
|
+
}
|
|
34
|
+
static registerGetStateHandler(handler) {
|
|
35
|
+
__classPrivateFieldSet(_a, _a, handler, "f", _SyncedStateServer_getStateHandler);
|
|
36
|
+
}
|
|
37
|
+
getState(key) {
|
|
38
|
+
const value = __classPrivateFieldGet(this, _SyncedStateServer_stateStore, "f").get(key);
|
|
39
|
+
if (__classPrivateFieldGet(_a, _a, "f", _SyncedStateServer_getStateHandler)) {
|
|
40
|
+
__classPrivateFieldGet(_a, _a, "f", _SyncedStateServer_getStateHandler).call(_a, key, value);
|
|
41
|
+
}
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
setState(value, key) {
|
|
45
|
+
__classPrivateFieldGet(this, _SyncedStateServer_stateStore, "f").set(key, value);
|
|
46
|
+
if (__classPrivateFieldGet(_a, _a, "f", _SyncedStateServer_setStateHandler)) {
|
|
47
|
+
__classPrivateFieldGet(_a, _a, "f", _SyncedStateServer_setStateHandler).call(_a, key, value);
|
|
48
|
+
}
|
|
49
|
+
const subscribers = __classPrivateFieldGet(this, _SyncedStateServer_subscriptions, "f").get(key);
|
|
50
|
+
if (!subscribers) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
for (const subscriber of subscribers) {
|
|
54
|
+
subscriber(value).catch(() => {
|
|
55
|
+
subscribers.delete(subscriber);
|
|
56
|
+
const refs = __classPrivateFieldGet(this, _SyncedStateServer_subscriptionRefs, "f").get(key);
|
|
57
|
+
if (refs) {
|
|
58
|
+
for (const [original, duplicate] of refs) {
|
|
59
|
+
if (duplicate === subscriber) {
|
|
60
|
+
refs.delete(original);
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (refs.size === 0) {
|
|
65
|
+
__classPrivateFieldGet(this, _SyncedStateServer_subscriptionRefs, "f").delete(key);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
if (subscribers.size === 0) {
|
|
71
|
+
__classPrivateFieldGet(this, _SyncedStateServer_subscriptions, "f").delete(key);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
subscribe(key, client) {
|
|
75
|
+
if (!__classPrivateFieldGet(this, _SyncedStateServer_subscriptions, "f").has(key)) {
|
|
76
|
+
__classPrivateFieldGet(this, _SyncedStateServer_subscriptions, "f").set(key, new Set());
|
|
77
|
+
}
|
|
78
|
+
if (!__classPrivateFieldGet(this, _SyncedStateServer_subscriptionRefs, "f").has(key)) {
|
|
79
|
+
__classPrivateFieldGet(this, _SyncedStateServer_subscriptionRefs, "f").set(key, new Map());
|
|
80
|
+
}
|
|
81
|
+
const duplicate = client.dup();
|
|
82
|
+
__classPrivateFieldGet(this, _SyncedStateServer_subscriptions, "f").get(key).add(duplicate);
|
|
83
|
+
__classPrivateFieldGet(this, _SyncedStateServer_subscriptionRefs, "f").get(key).set(client, duplicate);
|
|
84
|
+
}
|
|
85
|
+
unsubscribe(key, client) {
|
|
86
|
+
const duplicates = __classPrivateFieldGet(this, _SyncedStateServer_subscriptionRefs, "f").get(key);
|
|
87
|
+
const duplicate = duplicates?.get(client);
|
|
88
|
+
const subscribers = __classPrivateFieldGet(this, _SyncedStateServer_subscriptions, "f").get(key);
|
|
89
|
+
if (duplicate && subscribers) {
|
|
90
|
+
subscribers.delete(duplicate);
|
|
91
|
+
duplicates.delete(client);
|
|
92
|
+
if (subscribers.size === 0) {
|
|
93
|
+
__classPrivateFieldGet(this, _SyncedStateServer_subscriptions, "f").delete(key);
|
|
94
|
+
}
|
|
95
|
+
if (duplicates.size === 0) {
|
|
96
|
+
__classPrivateFieldGet(this, _SyncedStateServer_subscriptionRefs, "f").delete(key);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async fetch(request) {
|
|
101
|
+
const api = new CoordinatorApi(this);
|
|
102
|
+
return newWorkersRpcResponse(request, api);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
_a = SyncedStateServer, _SyncedStateServer_stateStore = new WeakMap(), _SyncedStateServer_subscriptions = new WeakMap(), _SyncedStateServer_subscriptionRefs = new WeakMap();
|
|
106
|
+
_SyncedStateServer_keyHandler = { value: null };
|
|
107
|
+
_SyncedStateServer_setStateHandler = { value: null };
|
|
108
|
+
_SyncedStateServer_getStateHandler = { value: null };
|
|
109
|
+
class CoordinatorApi extends RpcTarget {
|
|
110
|
+
constructor(coordinator) {
|
|
111
|
+
super();
|
|
112
|
+
_CoordinatorApi_coordinator.set(this, void 0);
|
|
113
|
+
__classPrivateFieldSet(this, _CoordinatorApi_coordinator, coordinator, "f");
|
|
114
|
+
}
|
|
115
|
+
getState(key) {
|
|
116
|
+
return __classPrivateFieldGet(this, _CoordinatorApi_coordinator, "f").getState(key);
|
|
117
|
+
}
|
|
118
|
+
setState(value, key) {
|
|
119
|
+
__classPrivateFieldGet(this, _CoordinatorApi_coordinator, "f").setState(value, key);
|
|
120
|
+
}
|
|
121
|
+
subscribe(key, client) {
|
|
122
|
+
__classPrivateFieldGet(this, _CoordinatorApi_coordinator, "f").subscribe(key, client);
|
|
123
|
+
}
|
|
124
|
+
unsubscribe(key, client) {
|
|
125
|
+
__classPrivateFieldGet(this, _CoordinatorApi_coordinator, "f").unsubscribe(key, client);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
_CoordinatorApi_coordinator = new WeakMap();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
vi.mock("cloudflare:workers", () => {
|
|
3
|
+
class DurableObject {
|
|
4
|
+
}
|
|
5
|
+
return { DurableObject };
|
|
6
|
+
});
|
|
7
|
+
import { SyncedStateServer } from "../SyncedStateServer.mjs";
|
|
8
|
+
const createStub = (onInvoke) => {
|
|
9
|
+
const fn = Object.assign(async (value) => {
|
|
10
|
+
await onInvoke(value);
|
|
11
|
+
}, {
|
|
12
|
+
dup: () => fn,
|
|
13
|
+
});
|
|
14
|
+
return fn;
|
|
15
|
+
};
|
|
16
|
+
describe("SyncedStateServer", () => {
|
|
17
|
+
it("notifies subscribers when state changes", async () => {
|
|
18
|
+
const coordinator = new SyncedStateServer({}, {});
|
|
19
|
+
const received = [];
|
|
20
|
+
const stub = createStub((value) => {
|
|
21
|
+
received.push(value);
|
|
22
|
+
});
|
|
23
|
+
coordinator.subscribe("counter", stub);
|
|
24
|
+
coordinator.setState(5, "counter");
|
|
25
|
+
expect(coordinator.getState("counter")).toBe(5);
|
|
26
|
+
expect(received).toEqual([5]);
|
|
27
|
+
});
|
|
28
|
+
it("removes subscriptions on unsubscribe", () => {
|
|
29
|
+
const coordinator = new SyncedStateServer({}, {});
|
|
30
|
+
const stub = createStub(() => { });
|
|
31
|
+
coordinator.subscribe("counter", stub);
|
|
32
|
+
coordinator.unsubscribe("counter", stub);
|
|
33
|
+
coordinator.setState(1, "counter");
|
|
34
|
+
expect(coordinator.getState("counter")).toBe(1);
|
|
35
|
+
});
|
|
36
|
+
it("drops failing subscribers", async () => {
|
|
37
|
+
const coordinator = new SyncedStateServer({}, {});
|
|
38
|
+
const stub = createStub(async () => {
|
|
39
|
+
throw new Error("fail");
|
|
40
|
+
});
|
|
41
|
+
coordinator.subscribe("counter", stub);
|
|
42
|
+
coordinator.setState(3, "counter");
|
|
43
|
+
await Promise.resolve();
|
|
44
|
+
coordinator.setState(4, "counter");
|
|
45
|
+
expect(coordinator.getState("counter")).toBe(4);
|
|
46
|
+
});
|
|
47
|
+
it("invokes registered onSet handler", () => {
|
|
48
|
+
const coordinator = new SyncedStateServer({}, {});
|
|
49
|
+
const calls = [];
|
|
50
|
+
SyncedStateServer.registerSetStateHandler((key, value) => {
|
|
51
|
+
calls.push({ key, value });
|
|
52
|
+
});
|
|
53
|
+
coordinator.setState(2, "counter");
|
|
54
|
+
expect(calls).toEqual([{ key: "counter", value: 2 }]);
|
|
55
|
+
SyncedStateServer.registerSetStateHandler(null);
|
|
56
|
+
});
|
|
57
|
+
it("invokes registered onGet handler", () => {
|
|
58
|
+
const coordinator = new SyncedStateServer({}, {});
|
|
59
|
+
const calls = [];
|
|
60
|
+
SyncedStateServer.registerGetStateHandler((key, value) => {
|
|
61
|
+
calls.push({ key, value });
|
|
62
|
+
});
|
|
63
|
+
coordinator.setState(4, "counter");
|
|
64
|
+
expect(coordinator.getState("counter")).toBe(4);
|
|
65
|
+
expect(calls).toEqual([{ key: "counter", value: 4 }]);
|
|
66
|
+
SyncedStateServer.registerGetStateHandler(null);
|
|
67
|
+
});
|
|
68
|
+
describe("registerKeyHandler", () => {
|
|
69
|
+
afterEach(() => {
|
|
70
|
+
SyncedStateServer.registerKeyHandler(async (key) => key);
|
|
71
|
+
});
|
|
72
|
+
it("stores and retrieves the registered handler", async () => {
|
|
73
|
+
const handler = async (key) => `transformed:${key}`;
|
|
74
|
+
SyncedStateServer.registerKeyHandler(handler);
|
|
75
|
+
const retrievedHandler = SyncedStateServer.getKeyHandler();
|
|
76
|
+
expect(retrievedHandler).toBe(handler);
|
|
77
|
+
});
|
|
78
|
+
it("transforms keys using the registered handler", async () => {
|
|
79
|
+
const handler = async (key) => `user:123:${key}`;
|
|
80
|
+
SyncedStateServer.registerKeyHandler(handler);
|
|
81
|
+
const result = await handler("counter");
|
|
82
|
+
expect(result).toBe("user:123:counter");
|
|
83
|
+
});
|
|
84
|
+
it("returns null when no handler is registered", () => {
|
|
85
|
+
SyncedStateServer.registerKeyHandler(async (key) => key);
|
|
86
|
+
const handler = SyncedStateServer.getKeyHandler();
|
|
87
|
+
expect(handler).not.toBeNull();
|
|
88
|
+
});
|
|
89
|
+
it("allows handler to be async", async () => {
|
|
90
|
+
const handler = async (key) => {
|
|
91
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
92
|
+
return `async:${key}`;
|
|
93
|
+
};
|
|
94
|
+
SyncedStateServer.registerKeyHandler(handler);
|
|
95
|
+
const result = await handler("test");
|
|
96
|
+
expect(result).toBe("async:test");
|
|
97
|
+
});
|
|
98
|
+
it("handler receives the correct key parameter", async () => {
|
|
99
|
+
let receivedKey = "";
|
|
100
|
+
const handler = async (key) => {
|
|
101
|
+
receivedKey = key;
|
|
102
|
+
return key;
|
|
103
|
+
};
|
|
104
|
+
SyncedStateServer.registerKeyHandler(handler);
|
|
105
|
+
await handler("myKey");
|
|
106
|
+
expect(receivedKey).toBe("myKey");
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|