@lovrozagar/flare 0.6.0 → 0.7.0
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/package.json +1 -1
- package/src/plugins/server-fn.ts +1 -1
- package/src/server-fn/index.ts +13 -0
- package/src/server-fn/rpc.ts +41 -0
- package/src/server-fn-query.ts +4 -42
package/package.json
CHANGED
package/src/plugins/server-fn.ts
CHANGED
|
@@ -605,7 +605,7 @@ export function createServerFnPlugin(_config?: { ignorePrefix?: string }): ViteP
|
|
|
605
605
|
if (hasServerFnCalls(transformed)) {
|
|
606
606
|
transformed = replaceServerFnConfigs(transformed, (content) => {
|
|
607
607
|
const fnId = computeFnId(id, content);
|
|
608
|
-
return ` __id: "${fnId}",${content}`;
|
|
608
|
+
return ` __client: true, __id: "${fnId}",${content}`;
|
|
609
609
|
});
|
|
610
610
|
}
|
|
611
611
|
|
package/src/server-fn/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FORM_FN_FIELD, parseServerFnPathname, serverFnPath } from "../protocol.ts";
|
|
2
|
+
import { callServerFnOverHttp } from "./rpc.ts";
|
|
2
3
|
import {
|
|
3
4
|
isNotFoundError,
|
|
4
5
|
isRedirectResponse,
|
|
@@ -26,6 +27,8 @@ import { runValidator } from "../validation/index.ts";
|
|
|
26
27
|
export type { Validator } from "../validation/index.ts";
|
|
27
28
|
|
|
28
29
|
export interface ServerFnConfig {
|
|
30
|
+
/** Set by the client build transform: calls go over HTTP, the handler body is gone. */
|
|
31
|
+
__client?: boolean;
|
|
29
32
|
__id?: string;
|
|
30
33
|
method?: "get" | "post";
|
|
31
34
|
name: string;
|
|
@@ -87,6 +90,7 @@ export interface ServerFnStreamRegistration extends ServerFnRegistrationBase {
|
|
|
87
90
|
export type ServerFnRegistration = ServerFnHandlerRegistration | ServerFnStreamRegistration;
|
|
88
91
|
|
|
89
92
|
interface BuilderState {
|
|
93
|
+
__client?: boolean;
|
|
90
94
|
__id?: string;
|
|
91
95
|
authenticate: boolean;
|
|
92
96
|
authorizeFn?: (ctx: { auth: unknown; input: unknown }) => boolean | Promise<boolean>;
|
|
@@ -139,6 +143,14 @@ function createBuilderTerminal<TAuth, TInput, TOutput>(
|
|
|
139
143
|
name: state.name,
|
|
140
144
|
};
|
|
141
145
|
|
|
146
|
+
if (state.__client) {
|
|
147
|
+
/* browser: the server validates, authenticates, and runs the handler */
|
|
148
|
+
const clientFn: ServerFn<TInput, TOutput> = async (input: TInput): Promise<TOutput> =>
|
|
149
|
+
(await callServerFnOverHttp<TOutput>(registration, input)).data;
|
|
150
|
+
clientFn._registration = registration;
|
|
151
|
+
return clientFn;
|
|
152
|
+
}
|
|
153
|
+
|
|
142
154
|
const serverFn: ServerFn<TInput, TOutput> = async (_input: TInput): Promise<TOutput> => {
|
|
143
155
|
if (state.authenticate) {
|
|
144
156
|
throw new Error(
|
|
@@ -271,6 +283,7 @@ function createBuilder<TAuth, TInput, TOutput>(state: BuilderState): ServerFnBui
|
|
|
271
283
|
|
|
272
284
|
export function createServerFn(config: ServerFnConfig): ServerFnBuilder<null, void, unknown> {
|
|
273
285
|
return createBuilder<null, void, unknown>({
|
|
286
|
+
__client: config.__client,
|
|
274
287
|
__id: config.__id,
|
|
275
288
|
authenticate: false,
|
|
276
289
|
method: config.method ?? "post",
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser side of a server fn call: one HTTP request to `/_flare/server-fn/{id}/{name}`.
|
|
3
|
+
* Client-safe — must not import server-context or anything that reaches node:*.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { serverFnPath } from "../protocol.ts";
|
|
7
|
+
import { serverFnGetUrl } from "./get-input.ts";
|
|
8
|
+
import { throwServerFnHttpError } from "./http-error.ts";
|
|
9
|
+
|
|
10
|
+
export interface ServerFnTarget {
|
|
11
|
+
id?: string;
|
|
12
|
+
method?: string;
|
|
13
|
+
name: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ServerFnEnvelope<TOutput> {
|
|
17
|
+
data: TOutput;
|
|
18
|
+
queries?: Array<{ data: unknown; key: unknown[] }>;
|
|
19
|
+
revalidatedTags?: string[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function callServerFnOverHttp<TOutput>(
|
|
23
|
+
target: ServerFnTarget,
|
|
24
|
+
input: unknown,
|
|
25
|
+
): Promise<ServerFnEnvelope<TOutput>> {
|
|
26
|
+
const url = serverFnPath(target.id ?? target.name, target.name);
|
|
27
|
+
const res =
|
|
28
|
+
target.method === "get"
|
|
29
|
+
? await fetch(serverFnGetUrl(url, input))
|
|
30
|
+
: await fetch(url, {
|
|
31
|
+
body: input !== undefined ? JSON.stringify(input) : undefined,
|
|
32
|
+
headers: { "content-type": "application/json" },
|
|
33
|
+
method: "POST",
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
if (!res.ok) {
|
|
37
|
+
const body: unknown = await res.json().catch(() => null);
|
|
38
|
+
throwServerFnHttpError(body, res.status, target.name);
|
|
39
|
+
}
|
|
40
|
+
return (await res.json()) as ServerFnEnvelope<TOutput>;
|
|
41
|
+
}
|
package/src/server-fn-query.ts
CHANGED
|
@@ -4,9 +4,7 @@
|
|
|
4
4
|
* to avoid pulling in node:async_hooks on the client.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
import { throwServerFnHttpError } from "./server-fn/http-error.ts";
|
|
9
|
-
import { serverFnPath } from "./protocol.ts";
|
|
7
|
+
import { callServerFnOverHttp } from "./server-fn/rpc.ts";
|
|
10
8
|
|
|
11
9
|
export interface PiggybackedQuery {
|
|
12
10
|
data: unknown;
|
|
@@ -52,28 +50,7 @@ export function serverFnQueryOptions<TInput, TOutput>(
|
|
|
52
50
|
return serverFn(config?.input as TInput);
|
|
53
51
|
}
|
|
54
52
|
|
|
55
|
-
const
|
|
56
|
-
const method = reg?.method ?? "post";
|
|
57
|
-
|
|
58
|
-
const res =
|
|
59
|
-
method === "get"
|
|
60
|
-
? await fetch(serverFnGetUrl(url, config?.input))
|
|
61
|
-
: await fetch(url, {
|
|
62
|
-
body: config?.input !== undefined ? JSON.stringify(config.input) : undefined,
|
|
63
|
-
headers: { "content-type": "application/json" },
|
|
64
|
-
method: "POST",
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
if (!res.ok) {
|
|
68
|
-
const body: unknown = await res.json().catch(() => null);
|
|
69
|
-
throwServerFnHttpError(body, res.status, name);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const json = (await res.json()) as {
|
|
73
|
-
data: TOutput;
|
|
74
|
-
queries?: Array<{ data: unknown; key: unknown[] }>;
|
|
75
|
-
revalidatedTags?: string[];
|
|
76
|
-
};
|
|
53
|
+
const json = await callServerFnOverHttp<TOutput>({ id, method: reg?.method ?? "post", name }, config?.input);
|
|
77
54
|
|
|
78
55
|
if (json.queries && config?.queryClient) {
|
|
79
56
|
for (const q of json.queries) {
|
|
@@ -117,23 +94,8 @@ export function serverFnMutationOptions<TInput, TOutput>(
|
|
|
117
94
|
return serverFn(input);
|
|
118
95
|
}
|
|
119
96
|
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
body: input !== undefined ? JSON.stringify(input) : undefined,
|
|
123
|
-
headers: { "content-type": "application/json" },
|
|
124
|
-
method: "POST",
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
if (!res.ok) {
|
|
128
|
-
const body: unknown = await res.json().catch(() => null);
|
|
129
|
-
throwServerFnHttpError(body, res.status, name);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const json = (await res.json()) as {
|
|
133
|
-
data: TOutput;
|
|
134
|
-
queries?: Array<{ data: unknown; key: unknown[] }>;
|
|
135
|
-
revalidatedTags?: string[];
|
|
136
|
-
};
|
|
97
|
+
/* mutations always POST, whatever the fn's own method */
|
|
98
|
+
const json = await callServerFnOverHttp<TOutput>({ id, method: "post", name }, input);
|
|
137
99
|
|
|
138
100
|
if (json.queries && config?.queryClient) {
|
|
139
101
|
for (const q of json.queries) {
|