rwsdk 1.0.0-beta.5 → 1.0.0-beta.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/rw-scripts.mjs +13 -13
- 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 +49 -57
- package/dist/lib/e2e/environment.d.mts +2 -0
- package/dist/lib/e2e/environment.mjs +201 -64
- package/dist/lib/e2e/index.d.mts +2 -0
- package/dist/lib/e2e/index.mjs +2 -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 +57 -52
- package/dist/lib/e2e/tarball.mjs +2 -34
- package/dist/lib/e2e/testHarness.d.mts +39 -3
- package/dist/lib/e2e/testHarness.mjs +239 -92
- package/dist/lib/e2e/utils.d.mts +1 -0
- package/dist/lib/e2e/utils.mjs +15 -0
- package/dist/lib/normalizeModulePath.mjs +1 -1
- package/dist/runtime/client/client.d.ts +64 -2
- package/dist/runtime/client/client.js +156 -15
- package/dist/runtime/client/navigation.d.ts +45 -0
- package/dist/runtime/client/navigation.js +68 -14
- package/dist/runtime/client/navigationCache.d.ts +68 -0
- package/dist/runtime/client/navigationCache.js +294 -0
- package/dist/runtime/client/navigationCache.test.js +469 -0
- package/dist/runtime/client/types.d.ts +26 -5
- package/dist/runtime/client/types.js +8 -1
- 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/no-react-server.js +3 -1
- package/dist/runtime/entries/react-server-only.js +1 -1
- 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 +4 -0
- package/dist/runtime/entries/worker.js +4 -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 +84 -26
- package/dist/runtime/lib/links.test.d.ts +1 -0
- 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 +28 -6
- package/dist/runtime/lib/realtime/worker.d.ts +1 -1
- package/dist/runtime/lib/router.d.ts +154 -35
- package/dist/runtime/lib/router.js +491 -105
- package/dist/runtime/lib/router.test.js +611 -1
- 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/register/client.d.ts +1 -1
- package/dist/runtime/register/client.js +10 -3
- package/dist/runtime/register/worker.js +13 -4
- package/dist/runtime/render/normalizeActionResult.js +8 -1
- 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 +5 -1
- package/dist/runtime/requestInfo/utils.d.ts +9 -0
- package/dist/runtime/requestInfo/utils.js +45 -0
- package/dist/runtime/requestInfo/worker.d.ts +0 -1
- package/dist/runtime/requestInfo/worker.js +5 -11
- package/dist/runtime/script.d.ts +1 -3
- package/dist/runtime/script.js +1 -10
- package/dist/runtime/server.d.ts +52 -0
- package/dist/runtime/server.js +88 -0
- 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 +45 -2
- 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 +36 -0
- package/dist/use-synced-state/SyncedStateServer.mjs +196 -0
- package/dist/use-synced-state/__tests__/SyncStateServer.test.d.mts +1 -0
- package/dist/use-synced-state/__tests__/SyncStateServer.test.mjs +116 -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 +70 -0
- package/dist/use-synced-state/client-core.d.ts +29 -0
- package/dist/use-synced-state/client-core.js +103 -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 +21 -0
- package/dist/use-synced-state/useSyncedState.js +64 -0
- package/dist/use-synced-state/worker.d.mts +14 -0
- package/dist/use-synced-state/worker.mjs +135 -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 +9 -11
- package/dist/vite/redwoodPlugin.test.mjs +4 -4
- package/dist/vite/runDirectivesScan.mjs +75 -19
- 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/transformClientComponents.test.mjs +32 -0
- package/dist/vite/transformJsxScriptTagsPlugin.mjs +0 -5
- package/dist/vite/transformServerFunctions.mjs +66 -4
- package/dist/vite/transformServerFunctions.test.mjs +35 -0
- package/dist/vite/virtualPlugin.mjs +6 -7
- package/package.json +41 -19
- package/dist/vite/manifestPlugin.d.mts +0 -4
- package/dist/vite/manifestPlugin.mjs +0 -63
- /package/dist/runtime/{lib/rwContext.js → client/navigationCache.test.d.ts} +0 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { generateNonce } from "../lib/utils";
|
|
3
|
+
export const DefaultRequestInfoDocument = ({ children }) => (_jsx(_Fragment, { children: children }));
|
|
4
|
+
/**
|
|
5
|
+
* Constructs a generic requestInfo that can be used as defaults.
|
|
6
|
+
* Allows for passing in overrides to initialize with defaults.
|
|
7
|
+
*/
|
|
8
|
+
export const constructWithDefaultRequestInfo = (overrides = {}) => {
|
|
9
|
+
const { rw: rwOverrides, ...otherRequestInfoOverrides } = overrides;
|
|
10
|
+
const defaultRequestInfo = {
|
|
11
|
+
request: new Request("http://localhost/"),
|
|
12
|
+
path: "/",
|
|
13
|
+
params: {},
|
|
14
|
+
ctx: {},
|
|
15
|
+
cf: {
|
|
16
|
+
waitUntil: () => { },
|
|
17
|
+
passThroughOnException: () => { },
|
|
18
|
+
props: {},
|
|
19
|
+
},
|
|
20
|
+
response: {
|
|
21
|
+
status: 200,
|
|
22
|
+
headers: new Headers(),
|
|
23
|
+
},
|
|
24
|
+
isAction: false,
|
|
25
|
+
rw: {
|
|
26
|
+
Document: DefaultRequestInfoDocument,
|
|
27
|
+
nonce: generateNonce(),
|
|
28
|
+
rscPayload: true,
|
|
29
|
+
ssr: true,
|
|
30
|
+
databases: new Map(),
|
|
31
|
+
scriptsToBeLoaded: new Set(),
|
|
32
|
+
entryScripts: new Set(),
|
|
33
|
+
inlineScripts: new Set(),
|
|
34
|
+
pageRouteResolved: undefined,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
return {
|
|
38
|
+
...defaultRequestInfo,
|
|
39
|
+
...otherRequestInfoOverrides,
|
|
40
|
+
rw: {
|
|
41
|
+
...defaultRequestInfo.rw,
|
|
42
|
+
...rwOverrides,
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
};
|
|
@@ -2,7 +2,6 @@ import { DefaultAppContext, RequestInfo } from "./types";
|
|
|
2
2
|
type DefaultRequestInfo = RequestInfo<DefaultAppContext>;
|
|
3
3
|
export declare const requestInfo: DefaultRequestInfo;
|
|
4
4
|
export declare function getRequestInfo(): RequestInfo;
|
|
5
|
-
export declare function waitForRequestInfo(): Promise<DefaultRequestInfo>;
|
|
6
5
|
export declare function runWithRequestInfo<Result>(nextRequestInfo: DefaultRequestInfo, fn: () => Result): Result;
|
|
7
6
|
export declare function runWithRequestInfoOverrides<Result>(overrides: Partial<DefaultRequestInfo>, fn: () => Result): Result;
|
|
8
7
|
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "async_hooks";
|
|
2
|
-
|
|
3
|
-
const requestInfoStore = new AsyncLocalStorage();
|
|
2
|
+
import { defineRwState } from "rwsdk/__state";
|
|
3
|
+
const requestInfoStore = defineRwState("requestInfoStore", () => new AsyncLocalStorage());
|
|
4
4
|
const requestInfoBase = {};
|
|
5
5
|
const REQUEST_INFO_KEYS = ["request", "params", "ctx", "rw", "cf", "response"];
|
|
6
6
|
REQUEST_INFO_KEYS.forEach((key) => {
|
|
@@ -17,19 +17,13 @@ export const requestInfo = Object.freeze(requestInfoBase);
|
|
|
17
17
|
export function getRequestInfo() {
|
|
18
18
|
const store = requestInfoStore.getStore();
|
|
19
19
|
if (!store) {
|
|
20
|
-
throw new Error("Request context not found"
|
|
20
|
+
throw new Error("RedwoodSDK: Request context not found. getRequestInfo() can only be called within the request lifecycle (e.g., in a route handler, middleware, or server action).\n\n" +
|
|
21
|
+
"For detailed troubleshooting steps, see: https://docs.rwsdk.com/guides/troubleshooting#request-context-errors");
|
|
21
22
|
}
|
|
22
23
|
return store;
|
|
23
24
|
}
|
|
24
|
-
export function waitForRequestInfo() {
|
|
25
|
-
return requestInfoDeferred.promise;
|
|
26
|
-
}
|
|
27
25
|
export function runWithRequestInfo(nextRequestInfo, fn) {
|
|
28
|
-
|
|
29
|
-
requestInfoDeferred.resolve(nextRequestInfo);
|
|
30
|
-
return fn();
|
|
31
|
-
};
|
|
32
|
-
return requestInfoStore.run(nextRequestInfo, runWithRequestInfoFn);
|
|
26
|
+
return requestInfoStore.run(nextRequestInfo, fn);
|
|
33
27
|
}
|
|
34
28
|
export function runWithRequestInfoOverrides(overrides, fn) {
|
|
35
29
|
const requestInfo = requestInfoStore.getStore();
|
package/dist/runtime/script.d.ts
CHANGED
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,52 @@
|
|
|
1
|
+
type Interruptor<TArgs extends any[] = any[], TResult = any> = (context: {
|
|
2
|
+
request: Request;
|
|
3
|
+
ctx: Record<string, any>;
|
|
4
|
+
args: TArgs;
|
|
5
|
+
}) => Promise<Response | void | TResult> | Response | void | TResult;
|
|
6
|
+
type ServerFunction<TArgs extends any[] = any[], TResult = any> = (...args: TArgs) => Promise<TResult>;
|
|
7
|
+
type ServerFunctionOptions = {
|
|
8
|
+
method?: "GET" | "POST";
|
|
9
|
+
};
|
|
10
|
+
type WrappedServerFunction<TArgs extends any[] = any[], TResult = any> = {
|
|
11
|
+
(...args: TArgs): Promise<TResult>;
|
|
12
|
+
method?: "GET" | "POST";
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Wrap a function to be used as a server query.
|
|
16
|
+
*
|
|
17
|
+
* - **Method**: Defaults to `GET`. can be changed via `options`.
|
|
18
|
+
* - **Behavior**: When called from the client, it returns data-only and does **not** rehydrate or re-render the React page.
|
|
19
|
+
* - **Location**: Must be defined in a file with `"use server"`. We recommend `queries.ts` colocated with components.
|
|
20
|
+
* - **Middleware**: You can pass an array of functions as the first argument to act as interruptors (e.g. for auth).
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* // getters.ts
|
|
25
|
+
* "use server"
|
|
26
|
+
*
|
|
27
|
+
* export const getUser = serverQuery(async (id: string) => {
|
|
28
|
+
* return db.user.findUnique({ where: { id } })
|
|
29
|
+
* })
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare function serverQuery<TArgs extends any[] = any[], TResult = any>(fnsOrFn: ServerFunction<TArgs, TResult> | [...Interruptor<TArgs, TResult>[], ServerFunction<TArgs, TResult>], options?: ServerFunctionOptions): WrappedServerFunction<TArgs, TResult>;
|
|
33
|
+
/**
|
|
34
|
+
* Wrap a function to be used as a server action.
|
|
35
|
+
*
|
|
36
|
+
* - **Method**: Defaults to `POST`. can be changed via `options`.
|
|
37
|
+
* - **Behavior**: When called from the client, it **will** rehydrate and re-render the React page with the new server state.
|
|
38
|
+
* - **Location**: Must be defined in a file with `"use server"`. We recommend `actions.ts` colocated with components.
|
|
39
|
+
* - **Middleware**: You can pass an array of functions as the first argument to act as interruptors (e.g. for auth).
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* // actions.ts
|
|
44
|
+
* "use server"
|
|
45
|
+
*
|
|
46
|
+
* export const updateUser = serverAction(async (id: string, data: any) => {
|
|
47
|
+
* return db.user.update({ where: { id }, data })
|
|
48
|
+
* })
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export declare function serverAction<TArgs extends any[] = any[], TResult = any>(fnsOrFn: ServerFunction<TArgs, TResult> | [...Interruptor<TArgs, TResult>[], ServerFunction<TArgs, TResult>], options?: ServerFunctionOptions): WrappedServerFunction<TArgs, TResult>;
|
|
52
|
+
export {};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { requestInfo } from "./requestInfo/worker";
|
|
2
|
+
function createServerFunction(fns, mainFn, options) {
|
|
3
|
+
const wrapped = async (...args) => {
|
|
4
|
+
const { request, ctx } = requestInfo;
|
|
5
|
+
// Execute interruptors
|
|
6
|
+
for (const fn of fns) {
|
|
7
|
+
const result = await fn({ request, ctx, args });
|
|
8
|
+
if (result instanceof Response) {
|
|
9
|
+
// We can't easily return a Response from a server action function
|
|
10
|
+
// because the return type is expected to be TResult.
|
|
11
|
+
// However, if the interruptor returns a Response, it usually means "stop and return this HTTP response".
|
|
12
|
+
// In the RSC context, throwing a Response is a common pattern to short-circuit.
|
|
13
|
+
throw result;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return mainFn(...args);
|
|
17
|
+
};
|
|
18
|
+
wrapped.method = options?.method ?? "POST"; // Default to POST if not specified, though user said serverQuery defaults to GET?
|
|
19
|
+
// User said: "export const getProject = serverQuery(...) // Defaults to GET"
|
|
20
|
+
// So serverQuery defaults to GET, serverAction defaults to POST?
|
|
21
|
+
return wrapped;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Wrap a function to be used as a server query.
|
|
25
|
+
*
|
|
26
|
+
* - **Method**: Defaults to `GET`. can be changed via `options`.
|
|
27
|
+
* - **Behavior**: When called from the client, it returns data-only and does **not** rehydrate or re-render the React page.
|
|
28
|
+
* - **Location**: Must be defined in a file with `"use server"`. We recommend `queries.ts` colocated with components.
|
|
29
|
+
* - **Middleware**: You can pass an array of functions as the first argument to act as interruptors (e.g. for auth).
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* // getters.ts
|
|
34
|
+
* "use server"
|
|
35
|
+
*
|
|
36
|
+
* export const getUser = serverQuery(async (id: string) => {
|
|
37
|
+
* return db.user.findUnique({ where: { id } })
|
|
38
|
+
* })
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export function serverQuery(fnsOrFn, options) {
|
|
42
|
+
let fns = [];
|
|
43
|
+
let mainFn;
|
|
44
|
+
if (Array.isArray(fnsOrFn)) {
|
|
45
|
+
fns = fnsOrFn.slice(0, -1);
|
|
46
|
+
mainFn = fnsOrFn[fnsOrFn.length - 1];
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
mainFn = fnsOrFn;
|
|
50
|
+
}
|
|
51
|
+
const method = options?.method ?? "GET"; // Default to GET for query
|
|
52
|
+
const wrapped = createServerFunction(fns, mainFn, { ...options, method });
|
|
53
|
+
wrapped.method = method;
|
|
54
|
+
return wrapped;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Wrap a function to be used as a server action.
|
|
58
|
+
*
|
|
59
|
+
* - **Method**: Defaults to `POST`. can be changed via `options`.
|
|
60
|
+
* - **Behavior**: When called from the client, it **will** rehydrate and re-render the React page with the new server state.
|
|
61
|
+
* - **Location**: Must be defined in a file with `"use server"`. We recommend `actions.ts` colocated with components.
|
|
62
|
+
* - **Middleware**: You can pass an array of functions as the first argument to act as interruptors (e.g. for auth).
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```ts
|
|
66
|
+
* // actions.ts
|
|
67
|
+
* "use server"
|
|
68
|
+
*
|
|
69
|
+
* export const updateUser = serverAction(async (id: string, data: any) => {
|
|
70
|
+
* return db.user.update({ where: { id }, data })
|
|
71
|
+
* })
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export function serverAction(fnsOrFn, options) {
|
|
75
|
+
let fns = [];
|
|
76
|
+
let mainFn;
|
|
77
|
+
if (Array.isArray(fnsOrFn)) {
|
|
78
|
+
fns = fnsOrFn.slice(0, -1);
|
|
79
|
+
mainFn = fnsOrFn[fnsOrFn.length - 1];
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
mainFn = fnsOrFn;
|
|
83
|
+
}
|
|
84
|
+
const method = options?.method ?? "POST"; // Default to POST for action
|
|
85
|
+
const wrapped = createServerFunction(fns, mainFn, { ...options, method });
|
|
86
|
+
wrapped.method = method;
|
|
87
|
+
return wrapped;
|
|
88
|
+
}
|
|
@@ -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
|
@@ -11,10 +11,11 @@ import { defineRoutes } from "./lib/router";
|
|
|
11
11
|
import { generateNonce } from "./lib/utils";
|
|
12
12
|
export * from "./requestInfo/types";
|
|
13
13
|
export const defineApp = (routes) => {
|
|
14
|
+
const router = defineRoutes(routes);
|
|
14
15
|
return {
|
|
16
|
+
__rwRoutes: routes,
|
|
15
17
|
fetch: async (request, env, cf) => {
|
|
16
18
|
globalThis.__webpack_require__ = ssrWebpackRequire;
|
|
17
|
-
const router = defineRoutes(routes);
|
|
18
19
|
// context(justinvdm, 5 Feb 2025): Serve assets requests using the assets service binding
|
|
19
20
|
// todo(justinvdm, 5 Feb 2025): Find a way to avoid this so asset requests are served directly
|
|
20
21
|
// rather than first needing to go through the worker
|
|
@@ -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;', {
|
|
@@ -33,6 +63,10 @@ export const defineApp = (routes) => {
|
|
|
33
63
|
}
|
|
34
64
|
try {
|
|
35
65
|
const url = new URL(request.url);
|
|
66
|
+
let path = url.pathname;
|
|
67
|
+
if (path !== "/" && !path.endsWith("/")) {
|
|
68
|
+
path = path + "/";
|
|
69
|
+
}
|
|
36
70
|
const isRSCRequest = url.searchParams.has("__rsc") ||
|
|
37
71
|
request.headers.get("accept")?.includes("text/x-component");
|
|
38
72
|
const isAction = url.searchParams.has("__rsc_action_id");
|
|
@@ -43,6 +77,8 @@ export const defineApp = (routes) => {
|
|
|
43
77
|
ssr: true,
|
|
44
78
|
databases: new Map(),
|
|
45
79
|
scriptsToBeLoaded: new Set(),
|
|
80
|
+
entryScripts: new Set(),
|
|
81
|
+
inlineScripts: new Set(),
|
|
46
82
|
pageRouteResolved: undefined,
|
|
47
83
|
};
|
|
48
84
|
const userResponseInit = {
|
|
@@ -51,6 +87,7 @@ export const defineApp = (routes) => {
|
|
|
51
87
|
};
|
|
52
88
|
const outerRequestInfo = {
|
|
53
89
|
request,
|
|
90
|
+
path,
|
|
54
91
|
cf,
|
|
55
92
|
params: {},
|
|
56
93
|
ctx: {},
|
|
@@ -80,7 +117,13 @@ export const defineApp = (routes) => {
|
|
|
80
117
|
});
|
|
81
118
|
}
|
|
82
119
|
const actionResult = normalizeActionResult(requestInfo.rw.actionResult);
|
|
83
|
-
const
|
|
120
|
+
const isDataOnly = request.headers.get("x-rsc-data-only") === "true";
|
|
121
|
+
const pageElement = isDataOnly && actionResult !== undefined
|
|
122
|
+
? null
|
|
123
|
+
: createPageElement(requestInfo, Page);
|
|
124
|
+
if (pageElement === null) {
|
|
125
|
+
rw.pageRouteResolved?.resolve();
|
|
126
|
+
}
|
|
84
127
|
const { rscPayload: shouldInjectRSCPayload } = rw;
|
|
85
128
|
let rscPayloadStream = renderToRscStream({
|
|
86
129
|
input: {
|
|
@@ -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();
|