rwsdk 0.1.6-test.20250702140551 → 0.1.6
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/smokeTests/components.d.mts +8 -0
- package/dist/lib/smokeTests/components.mjs +194 -0
- package/dist/lib/smokeTests/templates/SmokeTestInfo.template.d.ts +1 -0
- package/dist/lib/smokeTests/templates/SmokeTestInfo.template.js +82 -0
- package/dist/runtime/client.js +1 -6
- package/dist/runtime/clientNavigation.d.ts +0 -1
- package/dist/runtime/clientNavigation.js +24 -34
- package/dist/runtime/components/HealthCheck.d.ts +13 -0
- package/dist/runtime/components/HealthCheck.js +56 -0
- package/dist/runtime/components/HealthCheckClient.d.ts +2 -0
- package/dist/runtime/components/HealthCheckClient.js +78 -0
- package/dist/runtime/imports/NoSSRStub.d.ts +1 -0
- package/dist/runtime/imports/NoSSRStub.js +4 -0
- package/dist/runtime/lib/db/create.d.ts +3 -0
- package/dist/runtime/lib/db/create.js +36 -0
- package/dist/runtime/lib/db/logger.d.ts +2 -0
- package/dist/runtime/lib/db/logger.js +41 -0
- package/dist/runtime/lib/db/types.d.ts +0 -0
- package/dist/runtime/lib/db/types.js +1 -0
- package/dist/runtime/lib/realtime/client.js +0 -6
- package/dist/runtime/lib/realtime/durableObject.js +6 -8
- package/dist/runtime/lib/realtime/worker.js +3 -1
- package/dist/runtime/render/__rwsdk_ssr_bridge.d.ts +10 -0
- package/dist/runtime/render/__rwsdk_ssr_bridge.js +9 -0
- package/dist/runtime/render/__rwsdkssr_render.d.ts +9 -0
- package/dist/runtime/render/__rwsdkssr_render.js +13 -0
- package/dist/runtime/render/injectRSCPayload.d.ts +3 -0
- package/dist/runtime/render/injectRSCPayload.js +79 -0
- package/dist/runtime/render/renderRscThenableToHtmlStream.d.ts +1 -2
- package/dist/runtime/render/renderRscThenableToHtmlStream.js +1 -22
- package/dist/runtime/render/renderToStream.js +1 -2
- package/dist/runtime/render/ssrBridge.d.ts +2 -0
- package/dist/runtime/render/ssrBridge.js +2 -0
- package/dist/runtime/render/ssrRenderToReadableStream.d.ts +2 -0
- package/dist/runtime/render/ssrRenderToReadableStream.js +2 -0
- package/dist/runtime/render/transformRscToHtmlStream.d.ts +1 -2
- package/dist/runtime/render/transformRscToHtmlStream.js +1 -2
- package/dist/runtime/requestInfo/__rwsdknossr_worker.d.ts +5 -0
- package/dist/runtime/requestInfo/__rwsdknossr_worker.js +33 -0
- package/dist/runtime/worker.js +0 -1
- package/dist/scripts/build-vendor-bundles.d.mts +1 -0
- package/dist/scripts/build-vendor-bundles.mjs +92 -0
- package/dist/scripts/debug-sync.d.mts +0 -1
- package/dist/scripts/debug-sync.mjs +3 -9
- package/dist/vite/aliasedModuleResolver.d.mts +9 -0
- package/dist/vite/aliasedModuleResolver.mjs +62 -0
- package/dist/vite/aliasedSSRResolver.d.mts +5 -0
- package/dist/vite/aliasedSSRResolver.mjs +74 -0
- package/dist/vite/copyPrismaWasmPlugin.d.mts +4 -0
- package/dist/vite/copyPrismaWasmPlugin.mjs +32 -0
- package/dist/vite/ensureConfigArrays.d.mts +1 -0
- package/dist/vite/ensureConfigArrays.mjs +12 -0
- package/dist/vite/findImportSpecifiers.d.mts +30 -0
- package/dist/vite/findImportSpecifiers.mjs +228 -0
- package/dist/vite/findImportSpecifiers.test.mjs +73 -0
- package/dist/vite/isBareImport.d.mts +1 -0
- package/dist/vite/isBareImport.mjs +5 -0
- package/dist/vite/miniflarePlugin.d.mts +9 -0
- package/dist/vite/miniflarePlugin.mjs +135 -0
- package/dist/vite/moduleResolver.d.mts +10 -0
- package/dist/vite/moduleResolver.mjs +74 -0
- package/dist/vite/resolveModuleId.d.mts +6 -0
- package/dist/vite/resolveModuleId.mjs +14 -0
- package/dist/vite/rscDirectivesPlugin.d.mts +6 -0
- package/dist/vite/rscDirectivesPlugin.mjs +80 -0
- package/dist/vite/transformServerReferences.d.mts +11 -0
- package/dist/vite/transformServerReferences.mjs +74 -0
- package/dist/vite/useClientPlugin.d.mts +8 -0
- package/dist/vite/useClientPlugin.mjs +299 -0
- package/dist/vite/useClientPlugin.test.d.mts +1 -0
- package/dist/vite/useClientPlugin.test.mjs +1294 -0
- package/dist/vite/useServerPlugin.test.d.mts +1 -0
- package/dist/vite/useServerPlugin.test.mjs +99 -0
- package/dist/vite/virtualizedSSRPlugin.d.mts +56 -0
- package/dist/vite/virtualizedSSRPlugin.mjs +464 -0
- package/dist/vite/wasmPlugin.d.mts +2 -0
- package/dist/vite/wasmPlugin.mjs +14 -0
- package/package.json +1 -1
- package/dist/runtime/clientNavigation.test.js +0 -55
- /package/dist/{runtime/clientNavigation.test.d.ts → vite/findImportSpecifiers.test.d.mts} +0 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates the smoke test components in the target project directory
|
|
3
|
+
*/
|
|
4
|
+
export declare function createSmokeTestComponents(targetDir: string, skipClient?: boolean): Promise<void>;
|
|
5
|
+
/**
|
|
6
|
+
* Modifies the worker.tsx and wrangler.jsonc files to add realtime support
|
|
7
|
+
*/
|
|
8
|
+
export declare function modifyAppForRealtime(targetDir: string): Promise<void>;
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { join } from "path";
|
|
2
|
+
import * as fs from "fs/promises";
|
|
3
|
+
import { log } from "./constants.mjs";
|
|
4
|
+
import { getSmokeTestFunctionsTemplate } from "./templates/smokeTestFunctions.template";
|
|
5
|
+
import { getSmokeTestTemplate } from "./templates/SmokeTest.template";
|
|
6
|
+
import { getSmokeTestClientTemplate } from "./templates/SmokeTestClient.template";
|
|
7
|
+
import MagicString from "magic-string";
|
|
8
|
+
import { parse as parseJsonc } from "jsonc-parser";
|
|
9
|
+
/**
|
|
10
|
+
* Creates the smoke test components in the target project directory
|
|
11
|
+
*/
|
|
12
|
+
export async function createSmokeTestComponents(targetDir, skipClient = false) {
|
|
13
|
+
console.log("Creating smoke test components in project...");
|
|
14
|
+
// Create directories if they don't exist
|
|
15
|
+
const componentsDir = join(targetDir, "src", "app", "components");
|
|
16
|
+
log("Creating components directory: %s", componentsDir);
|
|
17
|
+
await fs.mkdir(componentsDir, { recursive: true });
|
|
18
|
+
// Create __smokeTestFunctions.ts
|
|
19
|
+
const smokeTestFunctionsPath = join(componentsDir, "__smokeTestFunctions.ts");
|
|
20
|
+
log("Creating __smokeTestFunctions.ts at: %s", smokeTestFunctionsPath);
|
|
21
|
+
const smokeTestFunctionsContent = getSmokeTestFunctionsTemplate();
|
|
22
|
+
// Create SmokeTest.tsx with conditional client component import
|
|
23
|
+
const smokeTestPath = join(componentsDir, "__SmokeTest.tsx");
|
|
24
|
+
log("Creating __SmokeTest.tsx at: %s", smokeTestPath);
|
|
25
|
+
const smokeTestContent = getSmokeTestTemplate(skipClient);
|
|
26
|
+
// Write the server files
|
|
27
|
+
log("Writing SmokeTestFunctions file");
|
|
28
|
+
await fs.writeFile(smokeTestFunctionsPath, smokeTestFunctionsContent);
|
|
29
|
+
log("Writing SmokeTest component file");
|
|
30
|
+
await fs.writeFile(smokeTestPath, smokeTestContent);
|
|
31
|
+
// Only create client component if not skipping client-side tests
|
|
32
|
+
if (!skipClient) {
|
|
33
|
+
// Create SmokeTestClient.tsx
|
|
34
|
+
const smokeTestClientPath = join(componentsDir, "__SmokeTestClient.tsx");
|
|
35
|
+
log("Creating __SmokeTestClient.tsx at: %s", smokeTestClientPath);
|
|
36
|
+
const smokeTestClientContent = getSmokeTestClientTemplate();
|
|
37
|
+
log("Writing SmokeTestClient component file");
|
|
38
|
+
await fs.writeFile(smokeTestClientPath, smokeTestClientContent);
|
|
39
|
+
log("Created client-side smoke test component");
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
log("Skipping client-side smoke test component creation");
|
|
43
|
+
}
|
|
44
|
+
// Modify worker.tsx and wrangler.jsonc for realtime support
|
|
45
|
+
await modifyAppForRealtime(targetDir);
|
|
46
|
+
log("Smoke test components created successfully");
|
|
47
|
+
console.log("Created smoke test components:");
|
|
48
|
+
console.log(`- ${smokeTestFunctionsPath}`);
|
|
49
|
+
console.log(`- ${smokeTestPath}`);
|
|
50
|
+
if (!skipClient) {
|
|
51
|
+
console.log(`- ${join(componentsDir, "__SmokeTestClient.tsx")}`);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
console.log("- Client component skipped (--skip-client was specified)");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Modifies the worker.tsx and wrangler.jsonc files to add realtime support
|
|
59
|
+
*/
|
|
60
|
+
export async function modifyAppForRealtime(targetDir) {
|
|
61
|
+
log("Modifying worker.tsx and wrangler.jsonc for realtime support");
|
|
62
|
+
// Modify worker.tsx
|
|
63
|
+
const workerPath = join(targetDir, "src", "worker.tsx");
|
|
64
|
+
if (await fs
|
|
65
|
+
.access(workerPath)
|
|
66
|
+
.then(() => true)
|
|
67
|
+
.catch(() => false)) {
|
|
68
|
+
log("Found worker.tsx, checking for realtime code");
|
|
69
|
+
const workerContent = await fs.readFile(workerPath, "utf-8");
|
|
70
|
+
// Check if the realtime export line already exists
|
|
71
|
+
const hasRealtimeExport = workerContent.includes('export { RealtimeDurableObject } from "rwsdk/realtime/durableObject"');
|
|
72
|
+
const hasRealtimeRoute = workerContent.includes("realtimeRoute(");
|
|
73
|
+
if (!hasRealtimeExport || !hasRealtimeRoute) {
|
|
74
|
+
log("Need to modify worker.tsx for realtime support");
|
|
75
|
+
const s = new MagicString(workerContent);
|
|
76
|
+
// Add the export line if it doesn't exist
|
|
77
|
+
if (!hasRealtimeExport) {
|
|
78
|
+
const importRegex = /import.*?from.*?;\n/g;
|
|
79
|
+
let lastImportMatch;
|
|
80
|
+
let lastImportPosition = 0;
|
|
81
|
+
// Find the position after the last import statement
|
|
82
|
+
while ((lastImportMatch = importRegex.exec(workerContent)) !== null) {
|
|
83
|
+
lastImportPosition =
|
|
84
|
+
lastImportMatch.index + lastImportMatch[0].length;
|
|
85
|
+
}
|
|
86
|
+
if (lastImportPosition > 0) {
|
|
87
|
+
s.appendRight(lastImportPosition, 'export { RealtimeDurableObject } from "rwsdk/realtime/durableObject";\n');
|
|
88
|
+
log("Added RealtimeDurableObject export");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Add the realtimeRoute line if it doesn't exist
|
|
92
|
+
if (!hasRealtimeRoute) {
|
|
93
|
+
const defineAppMatch = workerContent.match(/export default defineApp\(\[/);
|
|
94
|
+
if (defineAppMatch && defineAppMatch.index !== undefined) {
|
|
95
|
+
const insertPosition = defineAppMatch.index + defineAppMatch[0].length;
|
|
96
|
+
s.appendRight(insertPosition, "\n realtimeRoute(() => env.REALTIME_DURABLE_OBJECT),");
|
|
97
|
+
log("Added realtimeRoute to defineApp");
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Import realtimeRoute if it's not already imported
|
|
101
|
+
if (!workerContent.includes("realtimeRoute")) {
|
|
102
|
+
// Find the router import to append to it
|
|
103
|
+
const routerImportMatch = workerContent.match(/import \{(.*?)\} from "rwsdk\/router";/);
|
|
104
|
+
if (routerImportMatch) {
|
|
105
|
+
const importList = routerImportMatch[1];
|
|
106
|
+
if (!importList.includes("realtimeRoute")) {
|
|
107
|
+
s.replace(routerImportMatch[0], routerImportMatch[0].replace(/import \{(.*?)\} from "rwsdk\/router";/, (match, imports) => `import { ${imports}, realtimeRoute } from "rwsdk/router";`));
|
|
108
|
+
log("Added realtimeRoute to router imports");
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Write the modified file
|
|
113
|
+
await fs.writeFile(workerPath, s.toString(), "utf-8");
|
|
114
|
+
log("Successfully modified worker.tsx");
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
log("worker.tsx already has realtime support, no changes needed");
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
log("worker.tsx not found, skipping modification");
|
|
122
|
+
}
|
|
123
|
+
// Modify wrangler.jsonc
|
|
124
|
+
const wranglerPath = join(targetDir, "wrangler.jsonc");
|
|
125
|
+
if (await fs
|
|
126
|
+
.access(wranglerPath)
|
|
127
|
+
.then(() => true)
|
|
128
|
+
.catch(() => false)) {
|
|
129
|
+
log("Found wrangler.jsonc, checking for realtime durable objects");
|
|
130
|
+
const wranglerContent = await fs.readFile(wranglerPath, "utf-8");
|
|
131
|
+
const wranglerConfig = parseJsonc(wranglerContent);
|
|
132
|
+
let modified = false;
|
|
133
|
+
// Check if REALTIME_DURABLE_OBJECT already exists in durable_objects bindings
|
|
134
|
+
const hasDurableObjectBinding = wranglerConfig.durable_objects?.bindings?.some((binding) => binding.name === "REALTIME_DURABLE_OBJECT");
|
|
135
|
+
// Check if RealtimeDurableObject is already in migrations
|
|
136
|
+
const hasMigration = wranglerConfig.migrations?.some((migration) => migration.new_sqlite_classes?.includes("RealtimeDurableObject"));
|
|
137
|
+
if (!hasDurableObjectBinding || !hasMigration) {
|
|
138
|
+
log("Need to modify wrangler.jsonc for realtime support");
|
|
139
|
+
// Create a deep copy of the config to make modifications
|
|
140
|
+
const newConfig = JSON.parse(JSON.stringify(wranglerConfig));
|
|
141
|
+
// Add durable objects binding if needed
|
|
142
|
+
if (!hasDurableObjectBinding) {
|
|
143
|
+
if (!newConfig.durable_objects) {
|
|
144
|
+
newConfig.durable_objects = {};
|
|
145
|
+
}
|
|
146
|
+
if (!newConfig.durable_objects.bindings) {
|
|
147
|
+
newConfig.durable_objects.bindings = [];
|
|
148
|
+
}
|
|
149
|
+
newConfig.durable_objects.bindings.push({
|
|
150
|
+
name: "REALTIME_DURABLE_OBJECT",
|
|
151
|
+
class_name: "RealtimeDurableObject",
|
|
152
|
+
});
|
|
153
|
+
modified = true;
|
|
154
|
+
log("Added REALTIME_DURABLE_OBJECT to durable_objects bindings");
|
|
155
|
+
}
|
|
156
|
+
// Add migration if needed
|
|
157
|
+
if (!hasMigration) {
|
|
158
|
+
if (!newConfig.migrations) {
|
|
159
|
+
newConfig.migrations = [
|
|
160
|
+
{
|
|
161
|
+
tag: "v1",
|
|
162
|
+
new_sqlite_classes: ["RealtimeDurableObject"],
|
|
163
|
+
},
|
|
164
|
+
];
|
|
165
|
+
modified = true;
|
|
166
|
+
log("Added new migrations with RealtimeDurableObject");
|
|
167
|
+
}
|
|
168
|
+
else if (newConfig.migrations.length > 0) {
|
|
169
|
+
// Add RealtimeDurableObject to the first migration's sqlite classes
|
|
170
|
+
const firstMigration = newConfig.migrations[0];
|
|
171
|
+
if (!firstMigration.new_sqlite_classes) {
|
|
172
|
+
firstMigration.new_sqlite_classes = ["RealtimeDurableObject"];
|
|
173
|
+
}
|
|
174
|
+
else if (!firstMigration.new_sqlite_classes.includes("RealtimeDurableObject")) {
|
|
175
|
+
firstMigration.new_sqlite_classes.push("RealtimeDurableObject");
|
|
176
|
+
}
|
|
177
|
+
modified = true;
|
|
178
|
+
log("Added RealtimeDurableObject to existing migration");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (modified) {
|
|
182
|
+
// Write the modified config back to the file
|
|
183
|
+
await fs.writeFile(wranglerPath, JSON.stringify(newConfig, null, 2), "utf-8");
|
|
184
|
+
log("Successfully modified wrangler.jsonc");
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
log("wrangler.jsonc already has realtime support, no changes needed");
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
log("wrangler.jsonc not found, skipping modification");
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getSmokeTestTemplate(skipClient?: boolean): string;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export function getSmokeTestTemplate(skipClient = false) {
|
|
2
|
+
return `
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { RequestInfo } from "rwsdk/worker";
|
|
5
|
+
${skipClient ? "" : 'import { SmokeTestClient } from "./__SmokeTestClient";'}
|
|
6
|
+
import { smokeTestAction } from "./__smokeTestFunctions";
|
|
7
|
+
|
|
8
|
+
export const SmokeTestInfo: React.FC = async () => {
|
|
9
|
+
const timestamp = Date.now();
|
|
10
|
+
let status = "error";
|
|
11
|
+
let verificationPassed = false;
|
|
12
|
+
let serverStoredTimestamp = 0;
|
|
13
|
+
let result: any = null;
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
// Call the smoke test action to verify basic server functionality
|
|
17
|
+
result = await smokeTestAction(timestamp);
|
|
18
|
+
status = result.status || "error";
|
|
19
|
+
verificationPassed = result.timestamp === timestamp;
|
|
20
|
+
serverStoredTimestamp = result.serverStoredTimestamp;
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error("Smoke test failed:", error);
|
|
23
|
+
status = "error";
|
|
24
|
+
result = { error: error instanceof Error ? error.message : String(error) };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div
|
|
29
|
+
id="smoke-test-container"
|
|
30
|
+
data-testid="health-status"
|
|
31
|
+
data-status={status}
|
|
32
|
+
data-timestamp={timestamp}
|
|
33
|
+
data-server-timestamp={Date.now()}
|
|
34
|
+
data-server-stored-timestamp={serverStoredTimestamp}
|
|
35
|
+
data-verified={verificationPassed ? "true" : "false"}
|
|
36
|
+
style={{
|
|
37
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
38
|
+
margin: "20px",
|
|
39
|
+
padding: "15px",
|
|
40
|
+
border: "1px solid #ddd",
|
|
41
|
+
borderRadius: "4px",
|
|
42
|
+
background: "#f9f9f9",
|
|
43
|
+
}}
|
|
44
|
+
>
|
|
45
|
+
<h2
|
|
46
|
+
style={{
|
|
47
|
+
color: status === "ok" ? "#0c9" : "#f44",
|
|
48
|
+
margin: "0 0 10px 0",
|
|
49
|
+
}}
|
|
50
|
+
>
|
|
51
|
+
Smoke Test: {status}
|
|
52
|
+
</h2>
|
|
53
|
+
<div
|
|
54
|
+
id="smoke-test-result"
|
|
55
|
+
>
|
|
56
|
+
{verificationPassed
|
|
57
|
+
? "Timestamp verification passed ✅"
|
|
58
|
+
: "Timestamp verification failed ⚠️"}
|
|
59
|
+
</div>
|
|
60
|
+
<div id="server-stored-timestamp">
|
|
61
|
+
Server Stored Timestamp: {serverStoredTimestamp}
|
|
62
|
+
</div>
|
|
63
|
+
<details style={{ marginTop: "10px" }}>
|
|
64
|
+
<summary>Details</summary>
|
|
65
|
+
<pre
|
|
66
|
+
style={{
|
|
67
|
+
background: "#f5f5f5",
|
|
68
|
+
padding: "10px",
|
|
69
|
+
borderRadius: "4px",
|
|
70
|
+
fontSize: "12px",
|
|
71
|
+
overflow: "auto",
|
|
72
|
+
}}
|
|
73
|
+
>
|
|
74
|
+
{JSON.stringify({ timestamp, serverStoredTimestamp, result, verificationPassed }, null, 2)}
|
|
75
|
+
</pre>
|
|
76
|
+
</details>
|
|
77
|
+
|
|
78
|
+
${!skipClient ? "<SmokeTestClient/>" : ""}
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
};`;
|
|
82
|
+
}
|
package/dist/runtime/client.js
CHANGED
|
@@ -59,12 +59,7 @@ export const initClient = async ({ transport = fetchTransport, hydrateRootOption
|
|
|
59
59
|
transportContext.setRscPayload = (v) => startTransition(() => setStreamData(v));
|
|
60
60
|
return _jsx(_Fragment, { children: React.use(streamData).node });
|
|
61
61
|
}
|
|
62
|
-
hydrateRoot(rootEl, _jsx(Content, {}),
|
|
63
|
-
onUncaughtError: (error, { componentStack }) => {
|
|
64
|
-
console.error("Uncaught error: %O\n\nComponent stack:%s", error, componentStack);
|
|
65
|
-
},
|
|
66
|
-
...hydrateRootOptions,
|
|
67
|
-
});
|
|
62
|
+
hydrateRoot(rootEl, _jsx(Content, {}), hydrateRootOptions);
|
|
68
63
|
if (import.meta.hot) {
|
|
69
64
|
import.meta.hot.on("rsc:update", (e) => {
|
|
70
65
|
console.log("[rwsdk] hot update", e.file);
|
|
@@ -1,32 +1,3 @@
|
|
|
1
|
-
export function validateClickEvent(event, target) {
|
|
2
|
-
// should this only work for left click?
|
|
3
|
-
if (event.button !== 0) {
|
|
4
|
-
return false;
|
|
5
|
-
}
|
|
6
|
-
if (event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) {
|
|
7
|
-
return false;
|
|
8
|
-
}
|
|
9
|
-
const link = target.closest("a");
|
|
10
|
-
if (!link) {
|
|
11
|
-
return false;
|
|
12
|
-
}
|
|
13
|
-
const href = link.getAttribute("href");
|
|
14
|
-
if (!href) {
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
// Skip if target="_blank" or similar
|
|
18
|
-
if (link.target && link.target !== "_self") {
|
|
19
|
-
return false;
|
|
20
|
-
}
|
|
21
|
-
if (href.startsWith("http")) {
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
// Skip if download attribute
|
|
25
|
-
if (link.hasAttribute("download")) {
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
28
|
-
return true;
|
|
29
|
-
}
|
|
30
1
|
export function initClientNavigation(opts = {
|
|
31
2
|
onNavigate: async function onNavigate() {
|
|
32
3
|
// @ts-expect-error
|
|
@@ -35,14 +6,33 @@ export function initClientNavigation(opts = {
|
|
|
35
6
|
}) {
|
|
36
7
|
// Intercept all anchor tag clicks
|
|
37
8
|
document.addEventListener("click", async function handleClickEvent(event) {
|
|
38
|
-
//
|
|
39
|
-
if (
|
|
9
|
+
// should this only work for left click?
|
|
10
|
+
if (event.button !== 0) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
if (event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) {
|
|
40
14
|
return;
|
|
41
15
|
}
|
|
16
|
+
const target = event.target;
|
|
17
|
+
const link = target.closest("a");
|
|
18
|
+
if (!link) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const href = link.getAttribute("href");
|
|
22
|
+
if (!href) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
// Skip if target="_blank" or similar
|
|
26
|
+
if (link.target && link.target !== "_self") {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
// Skip if download attribute
|
|
30
|
+
if (link.hasAttribute("download")) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
// Prevent default navigation
|
|
42
34
|
event.preventDefault();
|
|
43
|
-
|
|
44
|
-
const a = el.closest("a");
|
|
45
|
-
const href = a?.getAttribute("href");
|
|
35
|
+
// push this to the history stack.
|
|
46
36
|
window.history.pushState({ path: href }, "", window.location.origin + href);
|
|
47
37
|
await opts.onNavigate();
|
|
48
38
|
}, true);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { RequestInfo } from "../requestInfo/types";
|
|
3
|
+
export declare const HealthCheckInfo: React.FC;
|
|
4
|
+
/**
|
|
5
|
+
* Wrapper component that displays health check info above the original page content
|
|
6
|
+
*/
|
|
7
|
+
export declare const HealthCheckWrapper: React.FC<{
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
}>;
|
|
10
|
+
/**
|
|
11
|
+
* Standalone health check page that conforms to the RouteComponent type
|
|
12
|
+
*/
|
|
13
|
+
export declare const HealthCheckPage: (requestInfo: RequestInfo) => React.JSX.Element;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { HealthCheckClient } from "./HealthCheckClient";
|
|
3
|
+
export const HealthCheckInfo = async () => {
|
|
4
|
+
const timestamp = Date.now();
|
|
5
|
+
let status = "error";
|
|
6
|
+
let verificationPassed = false;
|
|
7
|
+
let result = null;
|
|
8
|
+
try {
|
|
9
|
+
result = await globalThis.__rw.callServer("__health", [timestamp]);
|
|
10
|
+
// Check the result
|
|
11
|
+
if (typeof result === "object" && result !== null) {
|
|
12
|
+
status = result.status || "error";
|
|
13
|
+
verificationPassed = result.timestamp === timestamp;
|
|
14
|
+
}
|
|
15
|
+
else if (result === "ok") {
|
|
16
|
+
status = "ok";
|
|
17
|
+
verificationPassed = true;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
console.error("Health check failed:", error);
|
|
22
|
+
status = "error";
|
|
23
|
+
result = { error: error instanceof Error ? error.message : String(error) };
|
|
24
|
+
}
|
|
25
|
+
return (_jsxs("div", { id: "health-check-container", style: {
|
|
26
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
27
|
+
margin: "20px",
|
|
28
|
+
padding: "15px",
|
|
29
|
+
border: "1px solid #ddd",
|
|
30
|
+
borderRadius: "4px",
|
|
31
|
+
background: "#f9f9f9",
|
|
32
|
+
}, children: [_jsxs("h2", { style: {
|
|
33
|
+
color: status === "ok" ? "#0c9" : "#f44",
|
|
34
|
+
margin: "0 0 10px 0",
|
|
35
|
+
}, children: ["Health Check: ", status] }), _jsx("div", { id: "health-check-result", "data-result": status, "data-timestamp": timestamp, "data-verified": verificationPassed ? "true" : "false", children: verificationPassed
|
|
36
|
+
? "Timestamp verification passed ✅"
|
|
37
|
+
: "Timestamp verification failed ⚠️" }), _jsxs("details", { style: { marginTop: "10px" }, children: [_jsx("summary", { children: "Details" }), _jsx("pre", { style: {
|
|
38
|
+
background: "#f5f5f5",
|
|
39
|
+
padding: "10px",
|
|
40
|
+
borderRadius: "4px",
|
|
41
|
+
fontSize: "12px",
|
|
42
|
+
overflow: "auto",
|
|
43
|
+
}, children: JSON.stringify({ timestamp, result, verificationPassed }, null, 2) })] }), _jsx(HealthCheckClient, {})] }));
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Wrapper component that displays health check info above the original page content
|
|
47
|
+
*/
|
|
48
|
+
export const HealthCheckWrapper = ({ children }) => {
|
|
49
|
+
return (_jsxs(_Fragment, { children: [_jsx(HealthCheckInfo, {}), children] }));
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Standalone health check page that conforms to the RouteComponent type
|
|
53
|
+
*/
|
|
54
|
+
export const HealthCheckPage = (requestInfo) => {
|
|
55
|
+
return (_jsxs("div", { style: { maxWidth: "800px", margin: "0 auto", padding: "40px 20px" }, children: [_jsx("h1", { children: "RedwoodJS SDK Health Check" }), _jsx(HealthCheckInfo, {}), _jsx("p", { style: { marginTop: "20px" }, children: "This is a dedicated health check page to verify that your RedwoodJS SDK application is functioning correctly. It tests that server-side rendering, client-side hydration, and RSC (React Server Components) actions are all working properly." }), _jsx("p", { children: "Use the button below to manually trigger a new health check at any time." })] }));
|
|
56
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
export const HealthCheckClient = () => {
|
|
5
|
+
const [loading, setLoading] = useState(false);
|
|
6
|
+
const [lastCheck, setLastCheck] = useState(null);
|
|
7
|
+
const runHealthCheck = async () => {
|
|
8
|
+
setLoading(true);
|
|
9
|
+
try {
|
|
10
|
+
// Get current timestamp to verify round-trip
|
|
11
|
+
const timestamp = Date.now();
|
|
12
|
+
const result = await globalThis.__rw.callServer("__health", [timestamp]);
|
|
13
|
+
// Process the result
|
|
14
|
+
let status = "error";
|
|
15
|
+
let verificationPassed = false;
|
|
16
|
+
if (typeof result === "object" && result !== null) {
|
|
17
|
+
const typedResult = result;
|
|
18
|
+
status = typedResult.status || "error";
|
|
19
|
+
verificationPassed = typedResult.timestamp === timestamp;
|
|
20
|
+
}
|
|
21
|
+
else if (result === "ok") {
|
|
22
|
+
status = "ok";
|
|
23
|
+
verificationPassed = true;
|
|
24
|
+
}
|
|
25
|
+
setLastCheck({
|
|
26
|
+
status,
|
|
27
|
+
verificationPassed,
|
|
28
|
+
timestamp,
|
|
29
|
+
rawResult: result,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
setLastCheck({
|
|
34
|
+
status: "error",
|
|
35
|
+
verificationPassed: false,
|
|
36
|
+
timestamp: Date.now(),
|
|
37
|
+
error: error instanceof Error ? error.message : String(error),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
setLoading(false);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
return (_jsxs("div", { className: "health-check-client", style: {
|
|
45
|
+
margin: "20px 0",
|
|
46
|
+
padding: "15px",
|
|
47
|
+
border: "1px solid #ddd",
|
|
48
|
+
borderRadius: "4px",
|
|
49
|
+
background: "#f9f9f9",
|
|
50
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
51
|
+
}, children: [_jsx("h3", { children: "Manual Health Check" }), _jsx("button", { onClick: runHealthCheck, disabled: loading, style: {
|
|
52
|
+
padding: "8px 16px",
|
|
53
|
+
background: loading ? "#ccc" : "#0070f3",
|
|
54
|
+
color: "white",
|
|
55
|
+
border: "none",
|
|
56
|
+
borderRadius: "4px",
|
|
57
|
+
cursor: loading ? "not-allowed" : "pointer",
|
|
58
|
+
fontWeight: "bold",
|
|
59
|
+
}, children: loading ? "Checking..." : "Run Health Check" }), lastCheck && (_jsx("div", { style: { marginTop: "15px" }, children: _jsxs("div", { style: {
|
|
60
|
+
padding: "10px",
|
|
61
|
+
borderRadius: "4px",
|
|
62
|
+
background: lastCheck.status === "ok" ? "#e6f7ee" : "#ffeded",
|
|
63
|
+
border: `1px solid ${lastCheck.status === "ok" ? "#0c9" : "#f44"}`,
|
|
64
|
+
}, children: [_jsxs("h4", { style: {
|
|
65
|
+
margin: "0 0 10px 0",
|
|
66
|
+
color: lastCheck.status === "ok" ? "#0c9" : "#f44",
|
|
67
|
+
}, children: ["Status: ", lastCheck.status] }), _jsxs("p", { children: ["Timestamp verification:", " ", lastCheck.verificationPassed ? "Passed ✅" : "Failed ⚠️"] }), lastCheck.error && (_jsxs("p", { style: { color: "#f44" }, children: ["Error: ", lastCheck.error] })), _jsxs("details", { style: { marginTop: "10px" }, children: [_jsx("summary", { children: "Raw Result" }), _jsx("pre", { style: {
|
|
68
|
+
background: "#f5f5f5",
|
|
69
|
+
padding: "10px",
|
|
70
|
+
borderRadius: "4px",
|
|
71
|
+
fontSize: "12px",
|
|
72
|
+
overflow: "auto",
|
|
73
|
+
}, children: JSON.stringify({
|
|
74
|
+
timestamp: lastCheck.timestamp,
|
|
75
|
+
result: lastCheck.rawResult,
|
|
76
|
+
verificationPassed: lastCheck.verificationPassed,
|
|
77
|
+
}, null, 2) })] })] }) }))] }));
|
|
78
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const NoSSRStub: () => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Kysely } from "kysely";
|
|
2
|
+
import { requestInfo } from "../../requestInfo/worker.js";
|
|
3
|
+
import { DOWorkerDialect } from "./SqliteDurableObject.js";
|
|
4
|
+
export function createDurableObjectDb(durableObjectBinding, name = "main") {
|
|
5
|
+
const durableObjectId = durableObjectBinding.idFromName(name);
|
|
6
|
+
const stub = durableObjectBinding.get(durableObjectId);
|
|
7
|
+
return new Kysely({
|
|
8
|
+
dialect: new DOWorkerDialect({ stub }),
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
export function createDb(durableObjectBinding, name = "main") {
|
|
12
|
+
// Create a cache key from the binding and name
|
|
13
|
+
const cacheKey = `${durableObjectBinding}_${name}`;
|
|
14
|
+
// Return a proxy that lazily creates and caches the database instance
|
|
15
|
+
return new Proxy({}, {
|
|
16
|
+
get(target, prop, receiver) {
|
|
17
|
+
if (!requestInfo.rw.databases) {
|
|
18
|
+
requestInfo.rw.databases = new Map();
|
|
19
|
+
}
|
|
20
|
+
// Check if we have a cached instance
|
|
21
|
+
let db = requestInfo.rw.databases.get(cacheKey);
|
|
22
|
+
if (!db) {
|
|
23
|
+
// Create the database instance and cache it
|
|
24
|
+
db = createDurableObjectDb(durableObjectBinding, name);
|
|
25
|
+
requestInfo.rw.databases.set(cacheKey, db);
|
|
26
|
+
}
|
|
27
|
+
// Forward the property access to the actual database instance
|
|
28
|
+
const value = db[prop];
|
|
29
|
+
// If it's a function, bind it to the database instance
|
|
30
|
+
if (typeof value === "function") {
|
|
31
|
+
return value.bind(db);
|
|
32
|
+
}
|
|
33
|
+
return value;
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Simple debug function with DEBUG environment variable filtering
|
|
2
|
+
// Supports patterns like: DEBUG=passkey:* or DEBUG=passkey:database,passkey:functions
|
|
3
|
+
function isEnabled(namespace) {
|
|
4
|
+
const debug = process.env.DEBUG;
|
|
5
|
+
if (!debug)
|
|
6
|
+
return false;
|
|
7
|
+
// Split by comma and check each pattern
|
|
8
|
+
const patterns = debug.split(",").map((p) => p.trim());
|
|
9
|
+
for (const pattern of patterns) {
|
|
10
|
+
// Handle exclusions (patterns starting with -)
|
|
11
|
+
if (pattern.startsWith("-")) {
|
|
12
|
+
const excludePattern = pattern.slice(1);
|
|
13
|
+
if (matchesPattern(namespace, excludePattern)) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
// Check if namespace matches this pattern
|
|
19
|
+
if (matchesPattern(namespace, pattern)) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
function matchesPattern(namespace, pattern) {
|
|
26
|
+
// Convert pattern to regex (handle * wildcards)
|
|
27
|
+
const regex = pattern
|
|
28
|
+
.replace(/\*/g, ".*") // * becomes .*
|
|
29
|
+
.replace(/:/g, "\\:"); // escape colons
|
|
30
|
+
return new RegExp(`^${regex}$`).test(namespace);
|
|
31
|
+
}
|
|
32
|
+
// Factory function that creates a debugger for a given namespace
|
|
33
|
+
const debug = (namespace) => {
|
|
34
|
+
return (...args) => {
|
|
35
|
+
if (isEnabled(namespace)) {
|
|
36
|
+
console.log(`[${namespace}]`, ...args);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
// Export the factory function as default (like the real debug package)
|
|
41
|
+
export default debug;
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
|
@@ -82,18 +82,12 @@ export const realtimeTransport = ({ key = DEFAULT_KEY }) => (transportContext) =
|
|
|
82
82
|
try {
|
|
83
83
|
const socket = ensureWs();
|
|
84
84
|
const { encodeReply } = await import("react-server-dom-webpack/client.browser");
|
|
85
|
-
// Note(peterp, 2025-07-02): We need to send the "current URL" per message,
|
|
86
|
-
// in case the user has enabled client side navigation.
|
|
87
|
-
const clientUrl = new URL(window.location.href);
|
|
88
|
-
clientUrl.protocol = "";
|
|
89
|
-
clientUrl.host = "";
|
|
90
85
|
const encodedArgs = args != null ? await encodeReply(args) : null;
|
|
91
86
|
const requestId = crypto.randomUUID();
|
|
92
87
|
const messageData = JSON.stringify({
|
|
93
88
|
id,
|
|
94
89
|
args: encodedArgs,
|
|
95
90
|
requestId,
|
|
96
|
-
clientUrl,
|
|
97
91
|
});
|
|
98
92
|
const encoder = new TextEncoder();
|
|
99
93
|
const messageBytes = encoder.encode(messageData);
|