rwsdk 1.0.0-beta.48 → 1.0.0-beta.49-test.20260121220325
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/runtime/client/client.js +33 -10
- package/dist/runtime/client/types.d.ts +2 -3
- package/dist/runtime/client/types.js +1 -0
- package/dist/runtime/entries/worker.d.ts +1 -0
- package/dist/runtime/entries/worker.js +1 -0
- package/dist/runtime/lib/realtime/client.js +4 -4
- package/dist/runtime/lib/router.js +1 -1
- 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/server.d.ts +52 -0
- package/dist/runtime/server.js +88 -0
- package/dist/runtime/worker.js +7 -1
- package/dist/vite/transformServerFunctions.mjs +66 -4
- package/dist/vite/transformServerFunctions.test.mjs +35 -0
- package/package.json +3 -3
|
@@ -16,12 +16,16 @@ export { initClientNavigation, navigate } from "./navigation.js";
|
|
|
16
16
|
import { getCachedNavigationResponse } from "./navigationCache.js";
|
|
17
17
|
import { isActionResponse } from "./types";
|
|
18
18
|
export const fetchTransport = (transportContext) => {
|
|
19
|
-
const fetchCallServer = async (id, args, source = "action") => {
|
|
19
|
+
const fetchCallServer = async (id, args, source = "action", method = "POST") => {
|
|
20
20
|
const url = new URL(window.location.href);
|
|
21
21
|
url.searchParams.set("__rsc", "");
|
|
22
22
|
const isAction = id != null;
|
|
23
23
|
if (isAction) {
|
|
24
24
|
url.searchParams.set("__rsc_action_id", id);
|
|
25
|
+
// If args are provided and method is GET, serialize them into the query string
|
|
26
|
+
if (args != null && method === "GET") {
|
|
27
|
+
url.searchParams.set("args", JSON.stringify(args));
|
|
28
|
+
}
|
|
25
29
|
}
|
|
26
30
|
let fetchPromise;
|
|
27
31
|
if (!isAction && source === "navigation") {
|
|
@@ -39,11 +43,26 @@ export const fetchTransport = (transportContext) => {
|
|
|
39
43
|
}
|
|
40
44
|
}
|
|
41
45
|
else {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
46
|
+
const headers = new Headers();
|
|
47
|
+
// Add x-rsc-data-only header if we want to skip the React tree render on the server
|
|
48
|
+
if (source === "query") {
|
|
49
|
+
headers.set("x-rsc-data-only", "true");
|
|
50
|
+
}
|
|
51
|
+
if (method === "GET") {
|
|
52
|
+
fetchPromise = fetch(url, {
|
|
53
|
+
method: "GET",
|
|
54
|
+
headers,
|
|
55
|
+
redirect: "manual",
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
fetchPromise = fetch(url, {
|
|
60
|
+
method: "POST",
|
|
61
|
+
headers,
|
|
62
|
+
redirect: "manual",
|
|
63
|
+
body: args != null ? await encodeReply(args) : null,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
47
66
|
}
|
|
48
67
|
// If there's a response handler, check the response first
|
|
49
68
|
if (transportContext.handleResponse) {
|
|
@@ -56,7 +75,9 @@ export const fetchTransport = (transportContext) => {
|
|
|
56
75
|
const streamData = createFromFetch(Promise.resolve(response), {
|
|
57
76
|
callServer: fetchCallServer,
|
|
58
77
|
});
|
|
59
|
-
|
|
78
|
+
if (source === "navigation" || source === "action") {
|
|
79
|
+
transportContext.setRscPayload(streamData);
|
|
80
|
+
}
|
|
60
81
|
const result = await streamData;
|
|
61
82
|
const rawActionResult = result.actionResult;
|
|
62
83
|
if (isActionResponse(rawActionResult)) {
|
|
@@ -78,7 +99,9 @@ export const fetchTransport = (transportContext) => {
|
|
|
78
99
|
const streamData = createFromFetch(fetchPromise, {
|
|
79
100
|
callServer: fetchCallServer,
|
|
80
101
|
});
|
|
81
|
-
|
|
102
|
+
if (source === "navigation" || source === "action") {
|
|
103
|
+
transportContext.setRscPayload(streamData);
|
|
104
|
+
}
|
|
82
105
|
const result = await streamData;
|
|
83
106
|
const rawActionResult = result.actionResult;
|
|
84
107
|
if (isActionResponse(rawActionResult)) {
|
|
@@ -164,8 +187,8 @@ export const initClient = async ({ transport = fetchTransport, hydrateRootOption
|
|
|
164
187
|
onActionResponse,
|
|
165
188
|
};
|
|
166
189
|
let transportCallServer = transport(transportContext);
|
|
167
|
-
const callServer = (id, args, source) => {
|
|
168
|
-
return transportCallServer(id, args, source);
|
|
190
|
+
const callServer = (id, args, source, method) => {
|
|
191
|
+
return transportCallServer(id, args, source, method);
|
|
169
192
|
};
|
|
170
193
|
const upgradeToRealtime = async ({ key } = {}) => {
|
|
171
194
|
const { realtimeTransport } = await import("../lib/realtime/client");
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import type { CallServerCallback } from "react-server-dom-webpack/client.browser";
|
|
2
1
|
export type { HydrationOptions } from "react-dom/client";
|
|
3
|
-
export type
|
|
2
|
+
export type CallServerCallback = <Result>(id: null | string, args: null | unknown[], source?: "action" | "navigation" | "query", method?: "GET" | "POST") => Promise<Result | undefined>;
|
|
4
3
|
export type RscActionResponse<Result> = {
|
|
5
4
|
node: React.ReactNode;
|
|
6
5
|
actionResult: Result;
|
|
@@ -32,4 +31,4 @@ export type TransportContext = {
|
|
|
32
31
|
onActionResponse?: (actionResponse: ActionResponseData) => boolean | void;
|
|
33
32
|
};
|
|
34
33
|
export type Transport = (context: TransportContext) => CallServerCallback;
|
|
35
|
-
export type CreateCallServer = (context: TransportContext) => <Result>(id: null | string, args: null | unknown[], source?: "action" | "navigation") => Promise<Result>;
|
|
34
|
+
export type CreateCallServer = (context: TransportContext) => <Result>(id: null | string, args: null | unknown[], source?: "action" | "navigation" | "query", method?: "GET" | "POST") => Promise<Result | undefined>;
|
|
@@ -58,7 +58,7 @@ export const realtimeTransport = ({ key = DEFAULT_KEY, handleResponse, }) => (tr
|
|
|
58
58
|
}
|
|
59
59
|
return ws;
|
|
60
60
|
};
|
|
61
|
-
const realtimeCallServer = async (id, args) => {
|
|
61
|
+
const realtimeCallServer = async (id, args, _source, _method) => {
|
|
62
62
|
try {
|
|
63
63
|
const socket = ensureWs();
|
|
64
64
|
const { encodeReply } = await import("react-server-dom-webpack/client.browser");
|
|
@@ -82,7 +82,7 @@ export const realtimeTransport = ({ key = DEFAULT_KEY, handleResponse, }) => (tr
|
|
|
82
82
|
}
|
|
83
83
|
catch (e) {
|
|
84
84
|
console.error("[Realtime] Error calling server", e);
|
|
85
|
-
return
|
|
85
|
+
return undefined;
|
|
86
86
|
}
|
|
87
87
|
};
|
|
88
88
|
const processResponse = async (response) => {
|
|
@@ -99,7 +99,7 @@ export const realtimeTransport = ({ key = DEFAULT_KEY, handleResponse, }) => (tr
|
|
|
99
99
|
streamForRsc = response.body;
|
|
100
100
|
}
|
|
101
101
|
if (!shouldContinue) {
|
|
102
|
-
return
|
|
102
|
+
return undefined;
|
|
103
103
|
}
|
|
104
104
|
const rscPayload = createFromReadableStream(streamForRsc, {
|
|
105
105
|
callServer: realtimeCallServer,
|
|
@@ -114,7 +114,7 @@ export const realtimeTransport = ({ key = DEFAULT_KEY, handleResponse, }) => (tr
|
|
|
114
114
|
const isRedirect = actionResponse.status >= 300 && actionResponse.status < 400;
|
|
115
115
|
if (location && isRedirect) {
|
|
116
116
|
window.location.href = location;
|
|
117
|
-
return
|
|
117
|
+
return undefined;
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
return rawActionResult;
|
|
@@ -314,7 +314,7 @@ export function defineRoutes(routes) {
|
|
|
314
314
|
if (isRouteComponent(componentHandler)) {
|
|
315
315
|
const requestInfo = getRequestInfo();
|
|
316
316
|
const WrappedComponent = wrapWithLayouts(wrapHandlerToThrowResponses(componentHandler), route.layouts || [], requestInfo);
|
|
317
|
-
if (!isClientReference(componentHandler)) {
|
|
317
|
+
if (!isClientReference(componentHandler) && !requestInfo.rw.pageRouteResolved) {
|
|
318
318
|
requestInfo.rw.pageRouteResolved = Promise.withResolvers();
|
|
319
319
|
}
|
|
320
320
|
return await renderPage(requestInfo, WrappedComponent, onError);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const createServerReference: (id: string, name: string) => Function;
|
|
1
|
+
export declare const createServerReference: (id: string, name: string, method?: "GET" | "POST", source?: "action" | "query") => Function;
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { createServerReference as baseCreateServerReference } from "react-server-dom-webpack/client.browser";
|
|
2
|
-
export const createServerReference = (id, name) => {
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
export const createServerReference = (id, name, method, source = "action") => {
|
|
3
|
+
const fullId = id + "#" + name;
|
|
4
|
+
const proxy = baseCreateServerReference(fullId, (id, args) => {
|
|
5
|
+
return globalThis.__rsc_callServer(id, args, source, method);
|
|
6
|
+
});
|
|
7
|
+
// Attach metadata that hooks like useQuery can use
|
|
8
|
+
proxy.id = fullId;
|
|
9
|
+
proxy.method = method;
|
|
10
|
+
proxy.source = source;
|
|
11
|
+
return proxy;
|
|
5
12
|
};
|
|
@@ -50,10 +50,19 @@ export async function __smokeTestActionHandler(timestamp) {
|
|
|
50
50
|
export async function rscActionHandler(req) {
|
|
51
51
|
const url = new URL(req.url);
|
|
52
52
|
const contentType = req.headers.get("content-type");
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
let args = [];
|
|
54
|
+
if (req.method === "GET") {
|
|
55
|
+
const argsParam = url.searchParams.get("args");
|
|
56
|
+
if (argsParam) {
|
|
57
|
+
args = JSON.parse(argsParam);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
const data = contentType?.startsWith("multipart/form-data")
|
|
62
|
+
? await req.formData()
|
|
63
|
+
: await req.text();
|
|
64
|
+
args = (await decodeReply(data, null));
|
|
65
|
+
}
|
|
57
66
|
const actionId = url.searchParams.get("__rsc_action_id");
|
|
58
67
|
if (import.meta.env.VITE_IS_DEV_SERVER && actionId === "__rsc_hot_update") {
|
|
59
68
|
return null;
|
|
@@ -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
|
+
}
|
package/dist/runtime/worker.js
CHANGED
|
@@ -117,7 +117,13 @@ export const defineApp = (routes) => {
|
|
|
117
117
|
});
|
|
118
118
|
}
|
|
119
119
|
const actionResult = normalizeActionResult(requestInfo.rw.actionResult);
|
|
120
|
-
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
|
+
}
|
|
121
127
|
const { rscPayload: shouldInjectRSCPayload } = rw;
|
|
122
128
|
let rscPayloadStream = renderToRscStream({
|
|
123
129
|
input: {
|
|
@@ -111,16 +111,78 @@ export const transformServerFunctions = (code, normalizedId, environment, server
|
|
|
111
111
|
else {
|
|
112
112
|
s.append('import { createServerReference } from "rwsdk/client";\n\n');
|
|
113
113
|
}
|
|
114
|
+
const ext = path.extname(normalizedId).toLowerCase();
|
|
115
|
+
const lang = ext === ".tsx" || ext === ".jsx" ? Lang.Tsx : SgLang.TypeScript;
|
|
116
|
+
const root = sgParse(lang, code);
|
|
114
117
|
for (const name of allExports) {
|
|
115
118
|
if (name !== "default" && name !== defaultFunctionName) {
|
|
116
|
-
|
|
117
|
-
|
|
119
|
+
let method;
|
|
120
|
+
let source = "action";
|
|
121
|
+
// Try to find if this export is a serverQuery or serverAction call to extract the method
|
|
122
|
+
const patterns = [
|
|
123
|
+
`export const ${name} = serverQuery($$$, { method: "$METHOD" })`,
|
|
124
|
+
`export const ${name} = serverQuery($$$)`,
|
|
125
|
+
`export const ${name} = serverAction($$$)`,
|
|
126
|
+
];
|
|
127
|
+
for (const pattern of patterns) {
|
|
128
|
+
const matches = root.root().findAll(pattern);
|
|
129
|
+
if (matches.length > 0) {
|
|
130
|
+
if (pattern.includes("serverQuery")) {
|
|
131
|
+
const methodMatch = matches[0].getMatch("METHOD");
|
|
132
|
+
method = methodMatch ? methodMatch.text() : "GET";
|
|
133
|
+
source = "query";
|
|
134
|
+
}
|
|
135
|
+
else if (pattern.includes("serverAction")) {
|
|
136
|
+
method = "POST";
|
|
137
|
+
source = "action";
|
|
138
|
+
}
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
let extraArgs = "";
|
|
143
|
+
if (source === "query") {
|
|
144
|
+
extraArgs = `, ${JSON.stringify(method ?? "GET")}, "query"`;
|
|
145
|
+
}
|
|
146
|
+
else if (method !== undefined) {
|
|
147
|
+
extraArgs = `, ${JSON.stringify(method)}`;
|
|
148
|
+
}
|
|
149
|
+
s.append(`export let ${name} = createServerReference(${JSON.stringify(normalizedId)}, ${JSON.stringify(name)}${extraArgs});\n`);
|
|
150
|
+
log(`Added ${environment} server reference for function: %s (method: %s, source: %s) in normalizedId=%s`, name, method || "default", source, normalizedId);
|
|
118
151
|
}
|
|
119
152
|
}
|
|
120
153
|
// Check for default export in the actual module (not re-exports)
|
|
121
154
|
if (hasDefaultExport(code, normalizedId)) {
|
|
122
|
-
|
|
123
|
-
|
|
155
|
+
let method;
|
|
156
|
+
let source = "action";
|
|
157
|
+
const patterns = [
|
|
158
|
+
`export default serverQuery($$$, { method: "$METHOD" })`,
|
|
159
|
+
`export default serverQuery($$$)`,
|
|
160
|
+
`export default serverAction($$$)`,
|
|
161
|
+
];
|
|
162
|
+
for (const pattern of patterns) {
|
|
163
|
+
const matches = root.root().findAll(pattern);
|
|
164
|
+
if (matches.length > 0) {
|
|
165
|
+
if (pattern.includes("serverQuery")) {
|
|
166
|
+
const methodMatch = matches[0].getMatch("METHOD");
|
|
167
|
+
method = methodMatch ? methodMatch.text() : "GET";
|
|
168
|
+
source = "query";
|
|
169
|
+
}
|
|
170
|
+
else if (pattern.includes("serverAction")) {
|
|
171
|
+
method = "POST";
|
|
172
|
+
source = "action";
|
|
173
|
+
}
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
let extraArgs = "";
|
|
178
|
+
if (source === "query") {
|
|
179
|
+
extraArgs = `, ${JSON.stringify(method ?? "GET")}, "query"`;
|
|
180
|
+
}
|
|
181
|
+
else if (method !== undefined) {
|
|
182
|
+
extraArgs = `, ${JSON.stringify(method)}`;
|
|
183
|
+
}
|
|
184
|
+
s.append(`\nexport default createServerReference(${JSON.stringify(normalizedId)}, "default"${extraArgs});\n`);
|
|
185
|
+
log(`Added ${environment} server reference for default export (method: %s, source: %s) in normalizedId=%s`, method || "default", source, normalizedId);
|
|
124
186
|
}
|
|
125
187
|
process.env.VERBOSE &&
|
|
126
188
|
log(`${environment} transformation complete for normalizedId=%s`, normalizedId);
|
|
@@ -89,6 +89,36 @@ export async function sum() {
|
|
|
89
89
|
export { sum } from './math';
|
|
90
90
|
export { default as multiply } from './multiply';
|
|
91
91
|
export * from './utils';
|
|
92
|
+
`;
|
|
93
|
+
let SERVER_QUERY_GET_CODE = `
|
|
94
|
+
"use server";
|
|
95
|
+
export const getProject = serverQuery(async (id) => {
|
|
96
|
+
return { id, name: "Project X" };
|
|
97
|
+
});
|
|
98
|
+
`;
|
|
99
|
+
let SERVER_QUERY_POST_CODE = `
|
|
100
|
+
"use server";
|
|
101
|
+
export const getProject = serverQuery(async (id) => {
|
|
102
|
+
return { id, name: "Project X" };
|
|
103
|
+
}, { method: "POST" });
|
|
104
|
+
`;
|
|
105
|
+
let SERVER_ACTION_CODE = `
|
|
106
|
+
"use server";
|
|
107
|
+
export const upvote = serverAction(async (id) => {
|
|
108
|
+
return { id, count: 1 };
|
|
109
|
+
});
|
|
110
|
+
`;
|
|
111
|
+
let SERVER_QUERY_DEFAULT_CODE = `
|
|
112
|
+
"use server";
|
|
113
|
+
export default serverQuery(async (id) => {
|
|
114
|
+
return { id, name: "Project X" };
|
|
115
|
+
});
|
|
116
|
+
`;
|
|
117
|
+
let SERVER_ACTION_DEFAULT_CODE = `
|
|
118
|
+
"use server";
|
|
119
|
+
export default serverAction(async (id) => {
|
|
120
|
+
return { id, name: "Project X" };
|
|
121
|
+
});
|
|
92
122
|
`;
|
|
93
123
|
const TEST_CASES = {
|
|
94
124
|
COMMENT_CODE,
|
|
@@ -102,6 +132,11 @@ export * from './utils';
|
|
|
102
132
|
RE_EXPORT_CODE,
|
|
103
133
|
PREDEFINED_DEFAULT_EXPORT_CODE,
|
|
104
134
|
EXPORT_DEFAULT_FUNCTION_CODE,
|
|
135
|
+
SERVER_QUERY_GET_CODE,
|
|
136
|
+
SERVER_QUERY_POST_CODE,
|
|
137
|
+
SERVER_ACTION_CODE,
|
|
138
|
+
SERVER_QUERY_DEFAULT_CODE,
|
|
139
|
+
SERVER_ACTION_DEFAULT_CODE,
|
|
105
140
|
};
|
|
106
141
|
describe("TRANSFORMS", () => {
|
|
107
142
|
for (const [key, CODE] of Object.entries(TEST_CASES)) {
|
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.49-test.20260121220325",
|
|
4
4
|
"description": "Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -151,7 +151,7 @@
|
|
|
151
151
|
"license": "MIT",
|
|
152
152
|
"dependencies": {
|
|
153
153
|
"@ast-grep/napi": "~0.39.0",
|
|
154
|
-
"@cloudflare/workers-types": "~4.
|
|
154
|
+
"@cloudflare/workers-types": "~4.20260120.0",
|
|
155
155
|
"@mdx-js/mdx": "~3.1.1",
|
|
156
156
|
"@puppeteer/browsers": "~2.10.0",
|
|
157
157
|
"@types/decompress": "~4.2.7",
|
|
@@ -200,7 +200,7 @@
|
|
|
200
200
|
},
|
|
201
201
|
"packageManager": "pnpm@10.0.0+sha512.b8fef5494bd3fe4cbd4edabd0745df2ee5be3e4b0b8b08fa643aa3e4c6702ccc0f00d68fa8a8c9858a735a0032485a44990ed2810526c875e416f001b17df12b",
|
|
202
202
|
"devDependencies": {
|
|
203
|
-
"@cloudflare/vite-plugin": "1.
|
|
203
|
+
"@cloudflare/vite-plugin": "1.21.1",
|
|
204
204
|
"capnweb": "~0.2.0",
|
|
205
205
|
"@types/debug": "~4.1.12",
|
|
206
206
|
"@types/js-beautify": "~1.14.3",
|