rwsdk 1.0.0-beta.28 → 1.0.0-beta.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/e2e/environment.mjs +6 -1
- package/dist/lib/e2e/release.d.mts +1 -0
- package/dist/lib/e2e/release.mjs +16 -3
- package/dist/lib/e2e/tarball.mjs +3 -10
- package/dist/lib/e2e/testHarness.mjs +6 -3
- package/dist/runtime/lib/links.d.ts +18 -25
- package/dist/runtime/lib/links.js +70 -42
- package/dist/runtime/lib/links.test.d.ts +1 -0
- package/dist/runtime/lib/links.test.js +20 -0
- package/dist/runtime/lib/realtime/worker.d.ts +1 -1
- package/dist/runtime/lib/router.d.ts +23 -9
- package/dist/runtime/lib/router.js +14 -36
- package/dist/runtime/worker.d.ts +3 -1
- package/dist/runtime/worker.js +1 -0
- package/dist/use-synced-state/worker.d.mts +1 -1
- package/dist/vite/constants.d.mts +1 -0
- package/dist/vite/constants.mjs +1 -0
- package/dist/vite/ssrBridgePlugin.mjs +9 -0
- package/package.json +2 -2
|
@@ -144,8 +144,13 @@ export async function copyProjectToTempDir(projectDir, resourceUniqueKey, packag
|
|
|
144
144
|
// Determine the source directory to copy from
|
|
145
145
|
const sourceDir = monorepoRoot || projectDir;
|
|
146
146
|
// Create unique project directory name
|
|
147
|
+
// Format: {projectName}-t-{hash} (kept under 54 chars for Cloudflare limit)
|
|
147
148
|
const originalDirName = basename(sourceDir);
|
|
148
|
-
const
|
|
149
|
+
const slugified = slugify(originalDirName);
|
|
150
|
+
// Truncate project name to leave room for "-t-" (3 chars) + hash (8 chars) = 11 chars
|
|
151
|
+
// Max project name: 54 - 11 = 43 chars
|
|
152
|
+
const truncatedProjectName = slugified.substring(0, 43);
|
|
153
|
+
const workerName = `${truncatedProjectName}-t-${resourceUniqueKey}`;
|
|
149
154
|
const tempCopyRoot = resolve(tempDir.path, workerName);
|
|
150
155
|
// If it's a monorepo, the targetDir for commands is a subdirectory
|
|
151
156
|
const targetDir = monorepoRoot
|
|
@@ -39,6 +39,7 @@ export declare function runRelease(cwd: string, projectDir: string, resourceUniq
|
|
|
39
39
|
/**
|
|
40
40
|
* Check if a resource name includes a specific resource unique key
|
|
41
41
|
* This is used to identify resources created during our tests
|
|
42
|
+
* Handles both full format (adjective-animal-hash) and hash-only format
|
|
42
43
|
*/
|
|
43
44
|
export declare function isRelatedToTest(resourceName: string, resourceUniqueKey: string): boolean;
|
|
44
45
|
/**
|
package/dist/lib/e2e/release.mjs
CHANGED
|
@@ -245,9 +245,15 @@ export async function runRelease(cwd, projectDir, resourceUniqueKey) {
|
|
|
245
245
|
await ensureCloudflareAccountId(cwd, projectDir);
|
|
246
246
|
// Extract worker name from directory name to ensure consistency
|
|
247
247
|
const dirName = cwd ? basename(cwd) : "unknown-worker";
|
|
248
|
+
// Extract hash part from resourceUniqueKey for matching
|
|
249
|
+
// resourceUniqueKey format is typically "adjective-animal-hash" or just "hash"
|
|
250
|
+
const hashPart = resourceUniqueKey.includes("-")
|
|
251
|
+
? resourceUniqueKey.split("-").pop() || resourceUniqueKey.substring(0, 8)
|
|
252
|
+
: resourceUniqueKey.substring(0, 8);
|
|
253
|
+
const uniqueKeyForMatching = hashPart.substring(0, 8);
|
|
248
254
|
// Ensure resource unique key is included in worker name for tracking
|
|
249
|
-
if (resourceUniqueKey && !dirName.includes(
|
|
250
|
-
log(`Worker name doesn't contain our unique key, this is unexpected: ${dirName}, key: ${
|
|
255
|
+
if (resourceUniqueKey && !dirName.includes(uniqueKeyForMatching)) {
|
|
256
|
+
log(`Worker name doesn't contain our unique key, this is unexpected: ${dirName}, key: ${uniqueKeyForMatching}`);
|
|
251
257
|
console.log(`⚠️ Worker name doesn't contain our unique key. This might cause cleanup issues.`);
|
|
252
258
|
}
|
|
253
259
|
// Ensure the worker name in wrangler.jsonc matches our unique name
|
|
@@ -376,9 +382,16 @@ export async function runRelease(cwd, projectDir, resourceUniqueKey) {
|
|
|
376
382
|
/**
|
|
377
383
|
* Check if a resource name includes a specific resource unique key
|
|
378
384
|
* This is used to identify resources created during our tests
|
|
385
|
+
* Handles both full format (adjective-animal-hash) and hash-only format
|
|
379
386
|
*/
|
|
380
387
|
export function isRelatedToTest(resourceName, resourceUniqueKey) {
|
|
381
|
-
|
|
388
|
+
// Extract hash part if resourceUniqueKey contains dashes (full format)
|
|
389
|
+
// Otherwise use as-is (hash-only format)
|
|
390
|
+
const hashPart = resourceUniqueKey.includes("-")
|
|
391
|
+
? resourceUniqueKey.split("-").pop() || resourceUniqueKey.substring(0, 8)
|
|
392
|
+
: resourceUniqueKey;
|
|
393
|
+
const uniqueKeyForMatching = hashPart.substring(0, 8);
|
|
394
|
+
return resourceName.includes(uniqueKeyForMatching);
|
|
382
395
|
}
|
|
383
396
|
/**
|
|
384
397
|
* Delete the worker using wrangler
|
package/dist/lib/e2e/tarball.mjs
CHANGED
|
@@ -2,9 +2,8 @@ import { createHash } from "crypto";
|
|
|
2
2
|
import { $ } from "execa";
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import { adjectives, animals, uniqueNamesGenerator, } from "unique-names-generator";
|
|
6
5
|
import { ROOT_DIR } from "../constants.mjs";
|
|
7
|
-
import { copyProjectToTempDir
|
|
6
|
+
import { copyProjectToTempDir } from "./environment.mjs";
|
|
8
7
|
const log = (message) => console.log(message);
|
|
9
8
|
/**
|
|
10
9
|
* Copies wrangler cache from monorepo to temp directory for deployment tests
|
|
@@ -66,18 +65,12 @@ async function copyWranglerCache(targetDir, sdkRoot) {
|
|
|
66
65
|
export async function setupTarballEnvironment({ projectDir, monorepoRoot, packageManager = "pnpm", }) {
|
|
67
66
|
log(`🚀 Setting up tarball environment for ${projectDir}`);
|
|
68
67
|
// Generate a resource unique key for this test run
|
|
69
|
-
|
|
70
|
-
dictionaries: [adjectives, animals],
|
|
71
|
-
separator: "-",
|
|
72
|
-
length: 2,
|
|
73
|
-
style: "lowerCase",
|
|
74
|
-
});
|
|
75
|
-
// Create a short unique hash based on the timestamp
|
|
68
|
+
// Use just the hash to keep worker names short (under Cloudflare's 54 char limit)
|
|
76
69
|
const hash = createHash("md5")
|
|
77
70
|
.update(Date.now().toString())
|
|
78
71
|
.digest("hex")
|
|
79
72
|
.substring(0, 8);
|
|
80
|
-
const resourceUniqueKey =
|
|
73
|
+
const resourceUniqueKey = hash;
|
|
81
74
|
try {
|
|
82
75
|
const { tempDir, targetDir } = await copyProjectToTempDir(projectDir, resourceUniqueKey, packageManager, monorepoRoot);
|
|
83
76
|
// Copy wrangler cache to improve deployment performance
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import fs from "fs-extra";
|
|
2
2
|
import path, { basename, dirname, join as pathJoin } from "path";
|
|
3
3
|
import puppeteer from "puppeteer-core";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
4
5
|
import { afterAll, afterEach, beforeAll, beforeEach, describe, test, } from "vitest";
|
|
5
6
|
import { launchBrowser } from "./browser.mjs";
|
|
6
|
-
import { ensureTmpDir } from "./utils.mjs";
|
|
7
7
|
import { DEPLOYMENT_CHECK_TIMEOUT, DEPLOYMENT_MIN_TRIES, DEPLOYMENT_TIMEOUT, DEV_SERVER_MIN_TRIES, DEV_SERVER_TIMEOUT, HYDRATION_TIMEOUT, INSTALL_DEPENDENCIES_RETRIES, PUPPETEER_TIMEOUT, SETUP_PLAYGROUND_ENV_TIMEOUT, SETUP_WAIT_TIMEOUT, TEST_MAX_RETRIES, TEST_MAX_RETRIES_PER_CODE, } from "./constants.mjs";
|
|
8
8
|
import { runDevServer } from "./dev.mjs";
|
|
9
9
|
import { poll, pollValue } from "./poll.mjs";
|
|
10
10
|
import { deleteD1Database, deleteWorker, isRelatedToTest, runRelease, } from "./release.mjs";
|
|
11
11
|
import { setupTarballEnvironment } from "./tarball.mjs";
|
|
12
|
-
import {
|
|
12
|
+
import { ensureTmpDir } from "./utils.mjs";
|
|
13
13
|
export { DEPLOYMENT_CHECK_TIMEOUT, DEPLOYMENT_MIN_TRIES, DEPLOYMENT_TIMEOUT, DEV_SERVER_MIN_TRIES, DEV_SERVER_TIMEOUT, HYDRATION_TIMEOUT, INSTALL_DEPENDENCIES_RETRIES, PUPPETEER_TIMEOUT, SETUP_PLAYGROUND_ENV_TIMEOUT, SETUP_WAIT_TIMEOUT, TEST_MAX_RETRIES, TEST_MAX_RETRIES_PER_CODE, };
|
|
14
14
|
// Environment variable flags for skipping tests
|
|
15
15
|
const SKIP_DEV_SERVER_TESTS = process.env.RWSDK_SKIP_DEV === "1";
|
|
@@ -223,7 +223,10 @@ export function createDeployment() {
|
|
|
223
223
|
}
|
|
224
224
|
const newInstance = await pollValue(async () => {
|
|
225
225
|
const dirName = basename(projectDir);
|
|
226
|
-
|
|
226
|
+
// Match formats: {projectName}-t-{hash}, {projectName}-test-{hash}, or {projectName}-e2e-test-{hash}
|
|
227
|
+
const match = dirName.match(/-t-([a-f0-9]+)$/) ||
|
|
228
|
+
dirName.match(/-test-([a-f0-9]+)$/) ||
|
|
229
|
+
dirName.match(/-e2e-test-([a-f0-9]+)$/);
|
|
227
230
|
const resourceUniqueKey = match
|
|
228
231
|
? match[1]
|
|
229
232
|
: Math.random().toString(36).substring(2, 15);
|
|
@@ -1,32 +1,25 @@
|
|
|
1
|
-
type
|
|
1
|
+
import type { RouteDefinition, RouteMiddleware } from "./router";
|
|
2
|
+
type PathParams<Path extends string> = Path extends `${string}:${infer Param}/${infer Rest}` ? {
|
|
2
3
|
[K in Param]: string;
|
|
3
|
-
} &
|
|
4
|
+
} & PathParams<Rest> : Path extends `${string}:${infer Param}` ? {
|
|
4
5
|
[K in Param]: string;
|
|
5
|
-
} :
|
|
6
|
+
} : Path extends `${string}*${infer Rest}` ? {
|
|
6
7
|
$0: string;
|
|
7
|
-
} &
|
|
8
|
+
} & PathParams<Rest> : Path extends `${string}*` ? {
|
|
8
9
|
$0: string;
|
|
9
10
|
} : {};
|
|
10
|
-
type
|
|
11
|
-
|
|
11
|
+
type ParamsForPath<Path extends string> = PathParams<Path> extends Record<string, never> ? undefined : PathParams<Path>;
|
|
12
|
+
export type LinkFunction<Paths extends string> = {
|
|
13
|
+
<Path extends Paths>(path: Path, params?: ParamsForPath<Path>): string;
|
|
12
14
|
};
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
* ] as const)
|
|
24
|
-
*
|
|
25
|
-
* // Generate links with type checking
|
|
26
|
-
* link("/") // "/"
|
|
27
|
-
* link("/about") // "/about"
|
|
28
|
-
* link("/users/:id", { id: "123" }) // "/users/123"
|
|
29
|
-
* link("/files/*", { $0: "docs/guide.pdf" }) // "/files/docs/guide.pdf"
|
|
30
|
-
*/
|
|
31
|
-
export declare function defineLinks<const T extends readonly string[]>(routes: T): LinkFunction<T>;
|
|
15
|
+
type RoutePaths<Value> = Value extends RouteDefinition<infer Path, any> ? Path : Value extends readonly (infer Item)[] ? RouteArrayPaths<Value> : Value extends RouteMiddleware<any> ? never : never;
|
|
16
|
+
type RouteArrayPaths<Routes extends readonly any[]> = number extends Routes["length"] ? RoutePaths<Routes[number]> : Routes extends readonly [infer Head, ...infer Tail] ? RoutePaths<Head> | RouteArrayPaths<Tail> : never;
|
|
17
|
+
type AppRoutes<App> = App extends {
|
|
18
|
+
__rwRoutes: infer Routes;
|
|
19
|
+
} ? Routes : never;
|
|
20
|
+
export type AppRoutePaths<App> = RoutePaths<AppRoutes<App>>;
|
|
21
|
+
export type AppLink<App> = LinkFunction<AppRoutePaths<App>>;
|
|
22
|
+
export declare function linkFor<App>(): AppLink<App>;
|
|
23
|
+
export declare function createLinks<App>(_app?: App): AppLink<App>;
|
|
24
|
+
export declare function defineLinks<const T extends readonly string[]>(routes: T): LinkFunction<T[number]>;
|
|
32
25
|
export {};
|
|
@@ -1,56 +1,84 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
* "/",
|
|
8
|
-
* "/about",
|
|
9
|
-
* "/users/:id",
|
|
10
|
-
* "/files/*",
|
|
11
|
-
* ] as const)
|
|
12
|
-
*
|
|
13
|
-
* // Generate links with type checking
|
|
14
|
-
* link("/") // "/"
|
|
15
|
-
* link("/about") // "/about"
|
|
16
|
-
* link("/users/:id", { id: "123" }) // "/users/123"
|
|
17
|
-
* link("/files/*", { $0: "docs/guide.pdf" }) // "/files/docs/guide.pdf"
|
|
18
|
-
*/
|
|
1
|
+
export function linkFor() {
|
|
2
|
+
return createLinkFunction();
|
|
3
|
+
}
|
|
4
|
+
export function createLinks(_app) {
|
|
5
|
+
return linkFor();
|
|
6
|
+
}
|
|
19
7
|
export function defineLinks(routes) {
|
|
20
|
-
// Validate routes at runtime
|
|
21
8
|
routes.forEach((route) => {
|
|
22
9
|
if (typeof route !== "string") {
|
|
23
10
|
throw new Error(`Invalid route: ${route}. Routes must be strings.`);
|
|
24
11
|
}
|
|
25
12
|
});
|
|
26
|
-
|
|
13
|
+
const link = createLinkFunction();
|
|
14
|
+
return ((path, params) => {
|
|
27
15
|
if (!routes.includes(path)) {
|
|
28
16
|
throw new Error(`Invalid route: ${path}`);
|
|
29
17
|
}
|
|
30
|
-
|
|
18
|
+
return link(path, params);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
const TOKEN_REGEX = /:([a-zA-Z0-9_]+)|\*/g;
|
|
22
|
+
function createLinkFunction() {
|
|
23
|
+
return ((path, params) => {
|
|
24
|
+
const expectsParams = hasRouteParameters(path);
|
|
25
|
+
if (!params || Object.keys(params).length === 0) {
|
|
26
|
+
if (expectsParams) {
|
|
27
|
+
throw new Error(`Route ${path} requires an object of parameters`);
|
|
28
|
+
}
|
|
31
29
|
return path;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
30
|
+
}
|
|
31
|
+
return interpolate(path, params);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function hasRouteParameters(path) {
|
|
35
|
+
TOKEN_REGEX.lastIndex = 0;
|
|
36
|
+
const result = TOKEN_REGEX.test(path);
|
|
37
|
+
TOKEN_REGEX.lastIndex = 0;
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
function interpolate(template, params) {
|
|
41
|
+
let result = "";
|
|
42
|
+
let lastIndex = 0;
|
|
43
|
+
let wildcardIndex = 0;
|
|
44
|
+
const consumed = new Set();
|
|
45
|
+
TOKEN_REGEX.lastIndex = 0;
|
|
46
|
+
let match;
|
|
47
|
+
while ((match = TOKEN_REGEX.exec(template)) !== null) {
|
|
48
|
+
result += template.slice(lastIndex, match.index);
|
|
49
|
+
if (match[1]) {
|
|
50
|
+
const name = match[1];
|
|
51
|
+
const value = params[name];
|
|
52
|
+
if (value === undefined) {
|
|
53
|
+
throw new Error(`Missing parameter "${name}" for route ${template}`);
|
|
45
54
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
55
|
+
result += encodeURIComponent(value);
|
|
56
|
+
consumed.add(name);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
const key = `$${wildcardIndex}`;
|
|
60
|
+
const value = params[key];
|
|
61
|
+
if (value === undefined) {
|
|
62
|
+
throw new Error(`Missing parameter "${key}" for route ${template}`);
|
|
52
63
|
}
|
|
64
|
+
result += encodeWildcardValue(value);
|
|
65
|
+
consumed.add(key);
|
|
66
|
+
wildcardIndex += 1;
|
|
67
|
+
}
|
|
68
|
+
lastIndex = TOKEN_REGEX.lastIndex;
|
|
69
|
+
}
|
|
70
|
+
result += template.slice(lastIndex);
|
|
71
|
+
for (const key of Object.keys(params)) {
|
|
72
|
+
if (!consumed.has(key)) {
|
|
73
|
+
throw new Error(`Parameter "${key}" is not used by route ${template}`);
|
|
53
74
|
}
|
|
54
|
-
|
|
55
|
-
|
|
75
|
+
}
|
|
76
|
+
TOKEN_REGEX.lastIndex = 0;
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
function encodeWildcardValue(value) {
|
|
80
|
+
return value
|
|
81
|
+
.split("/")
|
|
82
|
+
.map((segment) => encodeURIComponent(segment))
|
|
83
|
+
.join("/");
|
|
56
84
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { defineLinks } from "./links";
|
|
3
|
+
const link = defineLinks(["/", "/users/:id", "/files/*"]);
|
|
4
|
+
describe("link helpers", () => {
|
|
5
|
+
it("returns static routes without parameters", () => {
|
|
6
|
+
expect(link("/")).toBe("/");
|
|
7
|
+
});
|
|
8
|
+
it("replaces named parameters with encoded values", () => {
|
|
9
|
+
expect(link("/users/:id", { id: "user id" })).toBe("/users/user%20id");
|
|
10
|
+
});
|
|
11
|
+
it("replaces wildcard parameters preserving path segments", () => {
|
|
12
|
+
expect(link("/files/*", { $0: "docs/Guide Document.md" })).toBe("/files/docs/Guide%20Document.md");
|
|
13
|
+
});
|
|
14
|
+
it("throws when parameters are missing", () => {
|
|
15
|
+
expect(() => link("/users/:id")).toThrowError(/requires an object of parameters/i);
|
|
16
|
+
});
|
|
17
|
+
it("throws when extra parameters are supplied", () => {
|
|
18
|
+
expect(() => link("/users/:id", { id: "123", extra: "value" })).toThrowError(/is not used by route/i);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { RealtimeDurableObject } from "./durableObject";
|
|
2
2
|
export { renderRealtimeClients } from "./renderRealtimeClients";
|
|
3
|
-
export declare const realtimeRoute: (getDurableObjectNamespace: (env: Cloudflare.Env) => DurableObjectNamespace<RealtimeDurableObject>) => import("../router").RouteDefinition<import("../../worker").RequestInfo<any, import("../../worker").DefaultAppContext>>;
|
|
3
|
+
export declare const realtimeRoute: (getDurableObjectNamespace: (env: Cloudflare.Env) => DurableObjectNamespace<RealtimeDurableObject>) => import("../router").RouteDefinition<"/__realtime", import("../../worker").RequestInfo<any, import("../../worker").DefaultAppContext>>;
|
|
@@ -19,14 +19,27 @@ export type MethodHandlers<T extends RequestInfo = RequestInfo> = {
|
|
|
19
19
|
[method: string]: RouteHandler<T>;
|
|
20
20
|
};
|
|
21
21
|
};
|
|
22
|
-
export type Route<T extends RequestInfo = RequestInfo> = RouteMiddleware<T> | RouteDefinition<T> |
|
|
23
|
-
|
|
22
|
+
export type Route<T extends RequestInfo = RequestInfo> = RouteMiddleware<T> | RouteDefinition<string, T> | readonly Route<T>[];
|
|
23
|
+
type NormalizedRouteDefinition<T extends RequestInfo = RequestInfo> = {
|
|
24
24
|
path: string;
|
|
25
25
|
handler: RouteHandler<T> | MethodHandlers<T>;
|
|
26
26
|
layouts?: React.FC<LayoutProps<T>>[];
|
|
27
27
|
};
|
|
28
|
+
export type RouteDefinition<Path extends string = string, T extends RequestInfo = RequestInfo> = NormalizedRouteDefinition<T> & {
|
|
29
|
+
readonly __rwPath?: Path;
|
|
30
|
+
};
|
|
31
|
+
type TrimTrailingSlash<S extends string> = S extends `${infer Head}/` ? TrimTrailingSlash<Head> : S;
|
|
32
|
+
type TrimLeadingSlash<S extends string> = S extends `/${infer Rest}` ? TrimLeadingSlash<Rest> : S;
|
|
33
|
+
type NormalizePrefix<Prefix extends string> = TrimTrailingSlash<TrimLeadingSlash<Prefix>> extends "" ? "" : `/${TrimTrailingSlash<TrimLeadingSlash<Prefix>>}`;
|
|
34
|
+
type NormalizePath<Path extends string> = TrimTrailingSlash<Path> extends "/" ? "/" : `/${TrimTrailingSlash<TrimLeadingSlash<Path>>}`;
|
|
35
|
+
type JoinPaths<Prefix extends string, Path extends string> = NormalizePrefix<Prefix> extends "" ? NormalizePath<Path> : Path extends "/" ? NormalizePrefix<Prefix> : `${NormalizePrefix<Prefix>}${NormalizePath<Path>}`;
|
|
36
|
+
type PrefixedRouteValue<Prefix extends string, Value> = Value extends RouteDefinition<infer Path, infer Req> ? RouteDefinition<JoinPaths<Prefix, Path>, Req> : Value extends readonly Route<any>[] ? PrefixedRouteArray<Prefix, Value> : Value;
|
|
37
|
+
type PrefixedRouteArray<Prefix extends string, Routes extends readonly Route<any>[]> = Routes extends readonly [] ? [] : Routes extends readonly [infer Head, ...infer Tail] ? readonly [
|
|
38
|
+
PrefixedRouteValue<Prefix, Head>,
|
|
39
|
+
...PrefixedRouteArray<Prefix, Tail extends readonly Route<any>[] ? Tail : []>
|
|
40
|
+
] : ReadonlyArray<PrefixedRouteValue<Prefix, Routes[number]>>;
|
|
28
41
|
export declare function matchPath<T extends RequestInfo = RequestInfo>(routePath: string, requestPath: string): T["params"] | null;
|
|
29
|
-
export declare function defineRoutes<T extends RequestInfo = RequestInfo>(routes: Route<T>[]): {
|
|
42
|
+
export declare function defineRoutes<T extends RequestInfo = RequestInfo>(routes: readonly Route<T>[]): {
|
|
30
43
|
routes: Route<T>[];
|
|
31
44
|
handle: ({ request, renderPage, getRequestInfo, onError, runWithRequestInfoOverrides, rscActionHandler, }: {
|
|
32
45
|
request: Request;
|
|
@@ -74,7 +87,7 @@ export declare function defineRoutes<T extends RequestInfo = RequestInfo>(routes
|
|
|
74
87
|
* // Route with middleware array
|
|
75
88
|
* route("/admin", [isAuthenticated, isAdmin, () => <AdminDashboard />])
|
|
76
89
|
*/
|
|
77
|
-
export declare function route<T extends RequestInfo = RequestInfo>(path:
|
|
90
|
+
export declare function route<Path extends string, T extends RequestInfo = RequestInfo>(path: Path, handler: RouteHandler<T> | MethodHandlers<T>): RouteDefinition<NormalizePath<Path>, T>;
|
|
78
91
|
/**
|
|
79
92
|
* Defines a route handler for the root path "/".
|
|
80
93
|
*
|
|
@@ -86,7 +99,7 @@ export declare function route<T extends RequestInfo = RequestInfo>(path: string,
|
|
|
86
99
|
* // With middleware
|
|
87
100
|
* index([logRequest, () => <HomePage />])
|
|
88
101
|
*/
|
|
89
|
-
export declare function index<T extends RequestInfo = RequestInfo>(handler: RouteHandler<T>): RouteDefinition<T>;
|
|
102
|
+
export declare function index<T extends RequestInfo = RequestInfo>(handler: RouteHandler<T>): RouteDefinition<"/", T>;
|
|
90
103
|
/**
|
|
91
104
|
* Prefixes a group of routes with a path.
|
|
92
105
|
*
|
|
@@ -106,7 +119,7 @@ export declare function index<T extends RequestInfo = RequestInfo>(handler: Rout
|
|
|
106
119
|
* ]),
|
|
107
120
|
* ])
|
|
108
121
|
*/
|
|
109
|
-
export declare function prefix<T extends RequestInfo = RequestInfo
|
|
122
|
+
export declare function prefix<Prefix extends string, T extends RequestInfo = RequestInfo, Routes extends readonly Route<T>[] = readonly Route<T>[]>(prefixPath: Prefix, routes: Routes): PrefixedRouteArray<Prefix, Routes>;
|
|
110
123
|
export declare const wrapHandlerToThrowResponses: <T extends RequestInfo = RequestInfo>(handler: RouteFunction<T> | RouteComponent<T>) => RouteHandler<T>;
|
|
111
124
|
/**
|
|
112
125
|
* Wraps routes with a layout component.
|
|
@@ -128,7 +141,7 @@ export declare const wrapHandlerToThrowResponses: <T extends RequestInfo = Reque
|
|
|
128
141
|
* route("/post/:id", ({ params }) => <BlogPost id={params.id} />),
|
|
129
142
|
* ])
|
|
130
143
|
*/
|
|
131
|
-
export declare function layout<T extends RequestInfo = RequestInfo>(LayoutComponent: React.FC<LayoutProps<T>>, routes:
|
|
144
|
+
export declare function layout<T extends RequestInfo = RequestInfo, Routes extends readonly Route<T>[] = readonly Route<T>[]>(LayoutComponent: React.FC<LayoutProps<T>>, routes: Routes): Routes;
|
|
132
145
|
/**
|
|
133
146
|
* Wraps routes with a Document component and configures rendering options.
|
|
134
147
|
*
|
|
@@ -153,9 +166,10 @@ export declare function layout<T extends RequestInfo = RequestInfo>(LayoutCompon
|
|
|
153
166
|
* ssr: true,
|
|
154
167
|
* })
|
|
155
168
|
*/
|
|
156
|
-
|
|
169
|
+
type RenderedRoutes<T extends RequestInfo, Routes extends readonly Route<T>[]> = readonly [RouteMiddleware<T>, ...Routes];
|
|
170
|
+
export declare function render<T extends RequestInfo = RequestInfo, Routes extends readonly Route<T>[] = readonly Route<T>[]>(Document: React.FC<DocumentProps<T>>, routes: Routes, options?: {
|
|
157
171
|
rscPayload?: boolean;
|
|
158
172
|
ssr?: boolean;
|
|
159
|
-
}):
|
|
173
|
+
}): RenderedRoutes<T, Routes>;
|
|
160
174
|
export declare const isClientReference: (value: any) => boolean;
|
|
161
175
|
export {};
|
|
@@ -259,8 +259,9 @@ export function defineRoutes(routes) {
|
|
|
259
259
|
* route("/admin", [isAuthenticated, isAdmin, () => <AdminDashboard />])
|
|
260
260
|
*/
|
|
261
261
|
export function route(path, handler) {
|
|
262
|
-
|
|
263
|
-
|
|
262
|
+
let normalizedPath = path;
|
|
263
|
+
if (!normalizedPath.endsWith("/")) {
|
|
264
|
+
normalizedPath = normalizedPath + "/";
|
|
264
265
|
}
|
|
265
266
|
// Normalize custom method keys to lowercase
|
|
266
267
|
if (isMethodHandlers(handler) && handler.custom) {
|
|
@@ -273,8 +274,9 @@ export function route(path, handler) {
|
|
|
273
274
|
};
|
|
274
275
|
}
|
|
275
276
|
return {
|
|
276
|
-
path,
|
|
277
|
+
path: normalizedPath,
|
|
277
278
|
handler,
|
|
279
|
+
__rwPath: normalizedPath,
|
|
278
280
|
};
|
|
279
281
|
}
|
|
280
282
|
/**
|
|
@@ -311,7 +313,7 @@ export function index(handler) {
|
|
|
311
313
|
* ])
|
|
312
314
|
*/
|
|
313
315
|
export function prefix(prefixPath, routes) {
|
|
314
|
-
|
|
316
|
+
const prefixed = routes.map((r) => {
|
|
315
317
|
if (typeof r === "function") {
|
|
316
318
|
const middleware = (requestInfo) => {
|
|
317
319
|
const url = new URL(requestInfo.request.url);
|
|
@@ -326,13 +328,14 @@ export function prefix(prefixPath, routes) {
|
|
|
326
328
|
// Recursively process nested route arrays
|
|
327
329
|
return prefix(prefixPath, r);
|
|
328
330
|
}
|
|
329
|
-
|
|
331
|
+
const routeDef = r;
|
|
330
332
|
return {
|
|
331
|
-
path: prefixPath +
|
|
332
|
-
handler:
|
|
333
|
-
...(
|
|
333
|
+
path: prefixPath + routeDef.path,
|
|
334
|
+
handler: routeDef.handler,
|
|
335
|
+
...(routeDef.layouts && { layouts: routeDef.layouts }),
|
|
334
336
|
};
|
|
335
337
|
});
|
|
338
|
+
return prefixed;
|
|
336
339
|
}
|
|
337
340
|
function wrapWithLayouts(Component, layouts = [], requestInfo) {
|
|
338
341
|
if (layouts.length === 0) {
|
|
@@ -396,7 +399,6 @@ export const wrapHandlerToThrowResponses = (handler) => {
|
|
|
396
399
|
* ])
|
|
397
400
|
*/
|
|
398
401
|
export function layout(LayoutComponent, routes) {
|
|
399
|
-
// Attach layouts directly to route definitions
|
|
400
402
|
return routes.map((route) => {
|
|
401
403
|
if (typeof route === "function") {
|
|
402
404
|
// Pass through middleware as-is
|
|
@@ -406,37 +408,13 @@ export function layout(LayoutComponent, routes) {
|
|
|
406
408
|
// Recursively process nested route arrays
|
|
407
409
|
return layout(LayoutComponent, route);
|
|
408
410
|
}
|
|
409
|
-
|
|
411
|
+
const routeDef = route;
|
|
410
412
|
return {
|
|
411
|
-
...
|
|
412
|
-
layouts: [LayoutComponent, ...(
|
|
413
|
+
...routeDef,
|
|
414
|
+
layouts: [LayoutComponent, ...(routeDef.layouts || [])],
|
|
413
415
|
};
|
|
414
416
|
});
|
|
415
417
|
}
|
|
416
|
-
/**
|
|
417
|
-
* Wraps routes with a Document component and configures rendering options.
|
|
418
|
-
*
|
|
419
|
-
* @param options.rscPayload - Toggle the RSC payload that's appended to the Document. Disabling this will mean that interactivity no longer works.
|
|
420
|
-
* @param options.ssr - Disable sever side rendering for all these routes. This only allow client side rendering, which requires `rscPayload` to be enabled.
|
|
421
|
-
*
|
|
422
|
-
* @example
|
|
423
|
-
* // Basic usage
|
|
424
|
-
* defineApp([
|
|
425
|
-
* render(Document, [
|
|
426
|
-
* route("/", () => <HomePage />),
|
|
427
|
-
* route("/about", () => <AboutPage />),
|
|
428
|
-
* ]),
|
|
429
|
-
* ])
|
|
430
|
-
*
|
|
431
|
-
* @example
|
|
432
|
-
* // With custom rendering options
|
|
433
|
-
* render(Document, [
|
|
434
|
-
* route("/", () => <HomePage />),
|
|
435
|
-
* ], {
|
|
436
|
-
* rscPayload: true,
|
|
437
|
-
* ssr: true,
|
|
438
|
-
* })
|
|
439
|
-
*/
|
|
440
418
|
export function render(Document, routes, options = {}) {
|
|
441
419
|
options = {
|
|
442
420
|
rscPayload: true,
|
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);
|
|
@@ -11,4 +11,4 @@ export type SyncStateRouteOptions = {
|
|
|
11
11
|
* @param options Optional overrides for base path, reset path, and object name.
|
|
12
12
|
* @returns Router entries for the sync state API and reset endpoint.
|
|
13
13
|
*/
|
|
14
|
-
export declare const syncStateRoutes: (getNamespace: (env: Cloudflare.Env) => DurableObjectNamespace<SyncStateServer>, options?: SyncStateRouteOptions) => import("../runtime/lib/router.js").RouteDefinition
|
|
14
|
+
export declare const syncStateRoutes: (getNamespace: (env: Cloudflare.Env) => DurableObjectNamespace<SyncStateServer>, options?: SyncStateRouteOptions) => import("../runtime/lib/router.js").RouteDefinition<`/${string}`, import("../runtime/worker.js").RequestInfo<any, import("../runtime/worker.js").DefaultAppContext>>[];
|
package/dist/vite/constants.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import debug from "debug";
|
|
2
2
|
import MagicString from "magic-string";
|
|
3
3
|
import { INTERMEDIATE_SSR_BRIDGE_PATH } from "../lib/constants.mjs";
|
|
4
|
+
import { externalModulesSet } from "./constants.mjs";
|
|
4
5
|
import { findSsrImportCallSites } from "./findSsrSpecifiers.mjs";
|
|
5
6
|
const log = debug("rwsdk:vite:ssr-bridge-plugin");
|
|
6
7
|
export const VIRTUAL_SSR_PREFIX = "virtual:rwsdk:ssr:";
|
|
@@ -184,6 +185,14 @@ export const ssrBridgePlugin = ({ clientFiles, serverFiles, }) => {
|
|
|
184
185
|
const normalized = site.specifier.startsWith("/@id/")
|
|
185
186
|
? site.specifier.slice("/@id/".length)
|
|
186
187
|
: site.specifier;
|
|
188
|
+
// If the import is for a known external module, we must leave it
|
|
189
|
+
// as a bare specifier. Rewriting it with any prefix (`/@id/` or
|
|
190
|
+
// our virtual one) will break Vite's default externalization.
|
|
191
|
+
if (externalModulesSet.has(normalized)) {
|
|
192
|
+
const replacement = `import("${normalized}")`;
|
|
193
|
+
s.overwrite(site.start, site.end, replacement);
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
187
196
|
// context(justinvdm, 11 Aug 2025):
|
|
188
197
|
// - We replace __vite_ssr_import__ and __vite_ssr_dynamic_import__
|
|
189
198
|
// with import() calls so that the module graph can be built
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rwsdk",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.29",
|
|
4
4
|
"description": "Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -167,7 +167,7 @@
|
|
|
167
167
|
"find-up": "~8.0.0",
|
|
168
168
|
"fs-extra": "~11.3.0",
|
|
169
169
|
"get-port": "^7.1.0",
|
|
170
|
-
"glob": "~11.0
|
|
170
|
+
"glob": "~11.1.0",
|
|
171
171
|
"ignore": "~7.0.4",
|
|
172
172
|
"jsonc-parser": "~3.3.1",
|
|
173
173
|
"kysely": "~0.28.2",
|