@thednp/rpc 0.0.1 → 0.0.5
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/AGENTS.md +5 -5
- package/CLAUDE.md +1 -0
- package/README.md +195 -47
- package/dist/express/express.d.mts +110 -16
- package/dist/express/express.d.mts.map +1 -1
- package/dist/express/express.mjs +113 -20
- package/dist/express/express.mjs.map +1 -1
- package/dist/fastify/fastify.d.mts +83 -5
- package/dist/fastify/fastify.d.mts.map +1 -1
- package/dist/fastify/fastify.mjs +84 -18
- package/dist/fastify/fastify.mjs.map +1 -1
- package/dist/fastify/plugin/fastify/plugin.d.mts +64 -9
- package/dist/fastify/plugin/fastify/plugin.d.mts.map +1 -1
- package/dist/fastify/plugin/fastify/plugin.mjs +73 -18
- package/dist/fastify/plugin/fastify/plugin.mjs.map +1 -1
- package/dist/helpers/helpers.d.mts +66 -4
- package/dist/helpers/helpers.d.mts.map +1 -1
- package/dist/helpers/helpers.mjs +34 -7
- package/dist/helpers/helpers.mjs.map +1 -1
- package/dist/hono/hono.d.mts +75 -6
- package/dist/hono/hono.d.mts.map +1 -1
- package/dist/hono/hono.mjs +84 -24
- package/dist/hono/hono.mjs.map +1 -1
- package/dist/index.d.mts +210 -19
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +137 -31
- package/dist/index.mjs.map +1 -1
- package/dist/koa/koa.d.mts +52 -0
- package/dist/koa/koa.d.mts.map +1 -1
- package/dist/koa/koa.mjs +86 -16
- package/dist/koa/koa.mjs.map +1 -1
- package/dist/server/server.d.mts +111 -10
- package/dist/server/server.d.mts.map +1 -1
- package/dist/server/server.mjs +117 -17
- package/dist/server/server.mjs.map +1 -1
- package/package.json +48 -31
- package/wiki/adapters.md +0 -143
- package/wiki/best-practices.md +0 -201
- package/wiki/client-usage.md +0 -62
- package/wiki/configuration.md +0 -77
- package/wiki/getting-started.md +0 -76
- package/wiki/index.md +0 -26
- package/wiki/security.md +0 -54
- package/wiki/server-functions.md +0 -93
- package/wiki/setup.md +0 -77
package/dist/index.d.mts
CHANGED
|
@@ -1,53 +1,139 @@
|
|
|
1
|
-
import { Connect, Plugin } from "vite";
|
|
1
|
+
import { Connect, Plugin, ResolvedConfig } from "vite";
|
|
2
2
|
import { MiddlewareOptions as MiddlewareOptions$1, RpcPluginOptions as RpcPluginOptions$1 } from "@thednp/rpc";
|
|
3
3
|
import { IncomingMessage, ServerResponse } from "node:http";
|
|
4
|
-
import { NextFunction, Request, Response
|
|
4
|
+
import { NextFunction, Request, Response } from "express";
|
|
5
5
|
import { MiddlewareHandler } from "hono";
|
|
6
6
|
import "@hono/node-server";
|
|
7
|
+
import "hono/factory";
|
|
7
8
|
import { FastifyReply, FastifyRequest, HookHandlerDoneFunction } from "fastify";
|
|
9
|
+
import "fastify-plugin";
|
|
8
10
|
import { Context, Next } from "koa";
|
|
9
11
|
//#region src/express/types.d.ts
|
|
12
|
+
/**
|
|
13
|
+
* Express-specific middleware options, constrained to the `"express"` adapter.
|
|
14
|
+
*/
|
|
10
15
|
type ExpressMiddlewareOptions = MiddlewareOptions$1<"express">;
|
|
16
|
+
/**
|
|
17
|
+
* Express middleware factory: takes optional initial options and returns
|
|
18
|
+
* the Express/Connect-compatible handler.
|
|
19
|
+
*/
|
|
11
20
|
type ExpressMiddlewareFn = <A extends RpcPluginOptions$1["adapter"] = "express">(initialOptions?: Partial<ExpressMiddlewareOptions>) => ExpressMiddlewareHooks["handler"];
|
|
21
|
+
/**
|
|
22
|
+
* Express/Connect middleware handler signature used by the RPC middleware.
|
|
23
|
+
*/
|
|
12
24
|
interface ExpressMiddlewareHooks {
|
|
13
|
-
|
|
25
|
+
/**
|
|
26
|
+
* The handler invoked for each matched request.
|
|
27
|
+
* @param req - Node or Express request object
|
|
28
|
+
* @param res - Node or Express response object
|
|
29
|
+
* @param next - Connect or Express next function
|
|
30
|
+
*/
|
|
31
|
+
handler: (req: IncomingMessage | Request, res: ServerResponse | Response, next: Connect.NextFunction | NextFunction) => Promise<void>;
|
|
14
32
|
}
|
|
15
33
|
//#endregion
|
|
16
34
|
//#region src/hono/types.d.ts
|
|
35
|
+
/**
|
|
36
|
+
* Hono middleware handler signature used by the RPC middleware.
|
|
37
|
+
*/
|
|
17
38
|
interface HonoMiddlewareHooks {
|
|
39
|
+
/** Hono middleware handler */
|
|
18
40
|
handler: MiddlewareHandler;
|
|
19
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Hono middleware factory: takes optional initial options and returns
|
|
44
|
+
* the Hono-compatible handler.
|
|
45
|
+
*/
|
|
20
46
|
type HonoMiddlewareFn = <A extends RpcPluginOptions$1["adapter"] = "hono">(initialOptions?: Partial<MiddlewareOptions$1<A>>) => HonoMiddlewareHooks["handler"];
|
|
21
47
|
//#endregion
|
|
22
48
|
//#region src/fastify/types.d.ts
|
|
49
|
+
/**
|
|
50
|
+
* Fastify-specific middleware options, constrained to the `"fastify"` adapter.
|
|
51
|
+
*/
|
|
23
52
|
type FastifyMiddlewareOptions = MiddlewareOptions$1<"fastify">;
|
|
53
|
+
/**
|
|
54
|
+
* Fastify middleware factory: takes optional initial options and returns
|
|
55
|
+
* the Fastify-compatible handler.
|
|
56
|
+
*/
|
|
24
57
|
type FastifyMiddlewareFn = <A extends RpcPluginOptions$1["adapter"] = "fastify">(initialOptions?: Partial<FastifyMiddlewareOptions>) => FastifyMiddlewareHooks["handler"];
|
|
58
|
+
/**
|
|
59
|
+
* Fastify middleware handler signature used by the RPC middleware.
|
|
60
|
+
*/
|
|
25
61
|
interface FastifyMiddlewareHooks {
|
|
62
|
+
/**
|
|
63
|
+
* The handler invoked for each matched request.
|
|
64
|
+
* @param req - Fastify request object
|
|
65
|
+
* @param res - Fastify reply object
|
|
66
|
+
* @param done - Fastify hook completion callback
|
|
67
|
+
*/
|
|
26
68
|
handler: (req: FastifyRequest, res: FastifyReply, done: HookHandlerDoneFunction) => Promise<void>;
|
|
27
69
|
}
|
|
28
70
|
//#endregion
|
|
29
71
|
//#region src/koa/types.d.ts
|
|
72
|
+
/**
|
|
73
|
+
* Koa-specific middleware options, constrained to the `"koa"` adapter.
|
|
74
|
+
*/
|
|
30
75
|
type KoaMiddlewareOptions = MiddlewareOptions$1<"koa">;
|
|
76
|
+
/**
|
|
77
|
+
* Koa middleware handler signature used by the RPC middleware.
|
|
78
|
+
*/
|
|
31
79
|
interface KoaMiddlewareHooks {
|
|
80
|
+
/**
|
|
81
|
+
* The handler invoked for each matched request.
|
|
82
|
+
* @param ctx - Koa context object
|
|
83
|
+
* @param next - Koa next function
|
|
84
|
+
*/
|
|
32
85
|
handler: (ctx: Context, next: Next) => Promise<void>;
|
|
33
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Koa middleware factory: takes optional initial options and returns
|
|
89
|
+
* the Koa-compatible handler.
|
|
90
|
+
*/
|
|
34
91
|
type KoaMiddlewareFn = <A extends RpcPluginOptions$1["adapter"] = "koa">(initialOptions?: Partial<KoaMiddlewareOptions>) => KoaMiddlewareHooks["handler"];
|
|
35
92
|
//#endregion
|
|
36
93
|
//#region src/types.d.ts
|
|
94
|
+
/**
|
|
95
|
+
* Maps each supported framework adapter to its middleware hooks (handler signatures).
|
|
96
|
+
* Used to keep the middleware options type-safe per adapter.
|
|
97
|
+
*/
|
|
37
98
|
interface FrameworkHooks {
|
|
99
|
+
/** Express/Connect middleware handler signature */
|
|
38
100
|
express: ExpressMiddlewareHooks;
|
|
101
|
+
/** Hono middleware handler signature */
|
|
39
102
|
hono: HonoMiddlewareHooks;
|
|
103
|
+
/** Fastify middleware handler signature */
|
|
40
104
|
fastify: FastifyMiddlewareHooks;
|
|
105
|
+
/** Koa middleware handler signature */
|
|
41
106
|
koa: KoaMiddlewareHooks;
|
|
42
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Maps each supported framework adapter to its middleware factory function type.
|
|
110
|
+
*/
|
|
43
111
|
interface FrameworkMiddlewareFn {
|
|
112
|
+
/** Express/Connect middleware factory */
|
|
44
113
|
express: ExpressMiddlewareFn;
|
|
114
|
+
/** Hono middleware factory */
|
|
45
115
|
hono: HonoMiddlewareFn;
|
|
116
|
+
/** Fastify middleware factory */
|
|
46
117
|
fastify: FastifyMiddlewareFn;
|
|
118
|
+
/** Koa middleware factory */
|
|
47
119
|
koa: KoaMiddlewareFn;
|
|
48
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* Content types the RPC middleware accepts when reading request bodies.
|
|
123
|
+
* Only `application/json` and `text/plain` are currently supported.
|
|
124
|
+
*/
|
|
49
125
|
type SupportableContentType = "multipart/form-data" | "application/json" | "text/plain" | "application/octet-stream";
|
|
126
|
+
/**
|
|
127
|
+
* Content types the RPC client modules send with each request.
|
|
128
|
+
*/
|
|
50
129
|
type ContentType = "application/json" | "text/plain";
|
|
130
|
+
/**
|
|
131
|
+
* Fetch `credentials` policy used by the generated client modules.
|
|
132
|
+
*/
|
|
133
|
+
type Credentials = "same-origin" | "include" | "omit";
|
|
134
|
+
/**
|
|
135
|
+
* Parsed request body result discriminated by content type.
|
|
136
|
+
*/
|
|
51
137
|
type BodyResult = {
|
|
52
138
|
contentType: "application/json";
|
|
53
139
|
data: JsonValue;
|
|
@@ -55,15 +141,47 @@ type BodyResult = {
|
|
|
55
141
|
contentType: "text/plain";
|
|
56
142
|
data: string;
|
|
57
143
|
};
|
|
144
|
+
/**
|
|
145
|
+
* Options for a single server function, controlling how the generated
|
|
146
|
+
* client module serializes the request body and sends credentials.
|
|
147
|
+
*/
|
|
58
148
|
interface ServerFunctionOptions {
|
|
149
|
+
/**
|
|
150
|
+
* Content type used for the request body.
|
|
151
|
+
* @default "application/json"
|
|
152
|
+
*/
|
|
59
153
|
contentType: ContentType;
|
|
154
|
+
/**
|
|
155
|
+
* Fetch credentials policy.
|
|
156
|
+
* @default "same-origin"
|
|
157
|
+
*/
|
|
158
|
+
credentials?: Credentials;
|
|
159
|
+
/**
|
|
160
|
+
* HTTP method used for the RPC request.
|
|
161
|
+
* GET requests send arguments as an `?args=` JSON query parameter
|
|
162
|
+
* (a fetch request body is not allowed on GET).
|
|
163
|
+
* @default "POST"
|
|
164
|
+
*/
|
|
165
|
+
method?: "GET" | "POST";
|
|
60
166
|
}
|
|
61
167
|
// primitives and their compositions
|
|
168
|
+
/**
|
|
169
|
+
* Primitive JSON values, including `undefined` for optional parameters.
|
|
170
|
+
*/
|
|
62
171
|
type JsonPrimitive = string | number | boolean | null | undefined;
|
|
172
|
+
/**
|
|
173
|
+
* A JSON object whose values are JSON values or arrays.
|
|
174
|
+
*/
|
|
63
175
|
type JsonObject = {
|
|
64
176
|
[key: string]: JsonValue | JsonArray;
|
|
65
177
|
};
|
|
178
|
+
/**
|
|
179
|
+
* A JSON array of JSON values.
|
|
180
|
+
*/
|
|
66
181
|
type JsonArray = JsonValue[];
|
|
182
|
+
/**
|
|
183
|
+
* Any JSON-serializable value: primitive, array, or object.
|
|
184
|
+
*/
|
|
67
185
|
type JsonValue = JsonPrimitive | JsonArray | JsonObject;
|
|
68
186
|
// Keep these as a refference
|
|
69
187
|
// Date strings are common in APIs
|
|
@@ -80,28 +198,71 @@ type JsonValue = JsonPrimitive | JsonArray | JsonObject;
|
|
|
80
198
|
// | Blob // for binary data
|
|
81
199
|
// | URLSearchParams; // for query parameters
|
|
82
200
|
// export type ServerFnArgs = [JsonObject | JsonPrimitive, ...JsonArray];
|
|
201
|
+
/**
|
|
202
|
+
* Arguments passed to a server function, spread as a JSON array.
|
|
203
|
+
*/
|
|
83
204
|
type ServerFnArgs = [...JsonArray];
|
|
205
|
+
/**
|
|
206
|
+
* Server-side handler signature: receives the `AbortSignal` first,
|
|
207
|
+
* followed by any serializable arguments.
|
|
208
|
+
*/
|
|
84
209
|
type ServerFunction<TArgs extends JsonArray = JsonArray, TResult extends JsonValue = JsonValue> = (signal: AbortSignal, ...args: TArgs) => Promise<TResult>;
|
|
210
|
+
/**
|
|
211
|
+
* Server function initialization signature, identical to `ServerFunction`.
|
|
212
|
+
* Used when registering a function with `createServerFunction`.
|
|
213
|
+
*/
|
|
85
214
|
type ServerFunctionInit<TArgs extends JsonArray = JsonArray, TResult extends JsonValue = JsonValue> = (signal: AbortSignal, ...args: TArgs) => Promise<TResult>;
|
|
215
|
+
/**
|
|
216
|
+
* Client-side stub signature generated for each server function.
|
|
217
|
+
* Returns a promise-backed `data` handle plus a `cancel` function
|
|
218
|
+
* that aborts the underlying fetch request.
|
|
219
|
+
*/
|
|
86
220
|
type ClientFunction<TArgs extends JsonArray = JsonArray, TResult extends JsonValue = JsonValue> = (...args: TArgs) => {
|
|
221
|
+
/** Promise resolving to the server response data */
|
|
87
222
|
data: Promise<TResult>;
|
|
223
|
+
/** Aborts the in-flight request with the given reason */
|
|
88
224
|
cancel: (reason: string) => void;
|
|
89
225
|
};
|
|
226
|
+
/**
|
|
227
|
+
* A client function augmented with its registered export name and
|
|
228
|
+
* per-function options (content type, credentials).
|
|
229
|
+
*/
|
|
90
230
|
type ClientFunctionWithOptions = ClientFunction & {
|
|
231
|
+
/** Registered export name of the server function */
|
|
91
232
|
name: string;
|
|
233
|
+
/** Per-function content type and credentials options */
|
|
92
234
|
options?: ServerFunctionOptions;
|
|
93
235
|
};
|
|
236
|
+
/**
|
|
237
|
+
* Internal plugin options accepted by `getClientModules`.
|
|
238
|
+
*/
|
|
239
|
+
interface RpcPluginOptionsInternal {
|
|
240
|
+
/** RPC endpoint prefix (e.g. "__rpc") */
|
|
241
|
+
rpcPrefix: string;
|
|
242
|
+
/** Framework adapter name */
|
|
243
|
+
adapter?: string | undefined;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Partial Vite config used when scanning server files outside a running dev server.
|
|
247
|
+
*/
|
|
248
|
+
type ScanConfig = Pick<ResolvedConfig, "root" | "base"> & {
|
|
249
|
+
/** Vite server options override (e.g. `middlewareMode`) */
|
|
250
|
+
server?: Partial<ResolvedConfig["server"]>;
|
|
251
|
+
};
|
|
252
|
+
/**
|
|
253
|
+
* Entry in the server functions map: registered name, client handler,
|
|
254
|
+
* optional per-function options, and the original export name.
|
|
255
|
+
*/
|
|
94
256
|
interface ServerFnEntry {
|
|
257
|
+
/** Registered RPC function name (used in the URL path) */
|
|
95
258
|
name: string;
|
|
259
|
+
/** Client-side handler stub for this function */
|
|
96
260
|
handler: ClientFunctionWithOptions;
|
|
261
|
+
/** Per-function content type and credentials options */
|
|
97
262
|
options?: ServerFunctionOptions;
|
|
263
|
+
/** Original export name from the server module */
|
|
98
264
|
exportName?: string;
|
|
99
265
|
}
|
|
100
|
-
interface CacheEntry<T> {
|
|
101
|
-
data?: T;
|
|
102
|
-
timestamp: number;
|
|
103
|
-
promise?: Promise<T>;
|
|
104
|
-
}
|
|
105
266
|
/**
|
|
106
267
|
* ### @thednp/rpc
|
|
107
268
|
* The plugin configuration allows for granular control of your
|
|
@@ -117,9 +278,9 @@ interface RpcPluginOptions {
|
|
|
117
278
|
* @default "__rpc"
|
|
118
279
|
* @example
|
|
119
280
|
* // Results in endpoints like: /api/rpc/myFunction
|
|
120
|
-
*
|
|
281
|
+
* rpcPrefix: "api/rpc"
|
|
121
282
|
*/
|
|
122
|
-
|
|
283
|
+
rpcPrefix: "__rpc" | string;
|
|
123
284
|
/**
|
|
124
285
|
* Option to set an adapter for the middleware connection. The default is _express_,
|
|
125
286
|
* which is the most popular and battle tested server app. The _express_ adapter is
|
|
@@ -152,9 +313,17 @@ interface MiddlewareOptions<A extends RpcPluginOptions["adapter"] = "express"> {
|
|
|
152
313
|
* @default string
|
|
153
314
|
* @example
|
|
154
315
|
* // Results in endpoints like: /api/rpc/myFunction
|
|
155
|
-
*
|
|
316
|
+
* rpcPrefix: "api/rpc"
|
|
156
317
|
*/
|
|
157
|
-
|
|
318
|
+
rpcPrefix?: string | false;
|
|
319
|
+
/**
|
|
320
|
+
* Allowed request origin (e.g. "https://example.com").
|
|
321
|
+
* When set, any request carrying an `Origin` header that does not match
|
|
322
|
+
* is rejected with a 403 Forbidden response. Requests without an `Origin`
|
|
323
|
+
* header (curl, native clients) pass through unchecked.
|
|
324
|
+
* When unset (default), no origin validation is performed.
|
|
325
|
+
*/
|
|
326
|
+
origin?: string;
|
|
158
327
|
/**
|
|
159
328
|
* Async handler for request processing.
|
|
160
329
|
* Core middleware function that processes incoming requests.
|
|
@@ -174,19 +343,41 @@ interface MiddlewareOptions<A extends RpcPluginOptions["adapter"] = "express"> {
|
|
|
174
343
|
*/
|
|
175
344
|
handler?: FrameworkHooks[A]["handler"];
|
|
176
345
|
}
|
|
346
|
+
/**
|
|
347
|
+
* Return shape of `innerModule`: a promise of the response data plus
|
|
348
|
+
* a `cancel` function to abort the underlying fetch request.
|
|
349
|
+
*/
|
|
350
|
+
type InnerModReturn = {
|
|
351
|
+
/** Promise resolving to the server response data */
|
|
352
|
+
data: Promise<JsonValue | void>;
|
|
353
|
+
/** Aborts the in-flight request with the given reason */
|
|
354
|
+
cancel: (reason: string) => void;
|
|
355
|
+
};
|
|
177
356
|
//#endregion
|
|
178
357
|
//#region src/index.d.ts
|
|
179
358
|
/**
|
|
180
|
-
*
|
|
181
|
-
*
|
|
359
|
+
* Type-safe helper to create an RPC configuration object.
|
|
360
|
+
* Merges the provided partial config with built-in defaults.
|
|
361
|
+
* @param uniConfig - System-wide RPC configuration overrides
|
|
362
|
+
* @returns Complete RPC plugin options with defaults applied
|
|
363
|
+
*/
|
|
364
|
+
declare const defineConfig: (c: Partial<RpcPluginOptions>) => RpcPluginOptions;
|
|
365
|
+
/**
|
|
366
|
+
* Loads the RPC configuration by searching for config files in the project root.
|
|
367
|
+
* Searches in order: `rpc.config.ts`, `rpc.config.js`, `rpc.config.mjs`, `rpc.config.mts`,
|
|
368
|
+
* `.rpcrc.ts`, `.rpcrc.js`. Falls back to defaults if none found.
|
|
369
|
+
* @param configFile - Optional explicit config file path; skips file search when provided
|
|
370
|
+
* @returns Resolved RPC plugin options
|
|
182
371
|
*/
|
|
183
|
-
declare const
|
|
372
|
+
declare const loadRPCConfig: (f?: string) => Promise<RpcPluginOptions>;
|
|
184
373
|
/**
|
|
185
|
-
*
|
|
186
|
-
*
|
|
374
|
+
* Vite plugin that enables automatic RPC generation.
|
|
375
|
+
* Transforms server function imports into fetch-based client stubs during development and production builds.
|
|
376
|
+
* In dev mode, attaches the RPC middleware to Vite's Connect server.
|
|
377
|
+
* @param devOptions - Development-only overrides (merged on top of config file values)
|
|
378
|
+
* @returns A Vite plugin object
|
|
187
379
|
*/
|
|
188
|
-
declare function loadRPCConfig(configFile?: string): Promise<RpcPluginOptions>;
|
|
189
380
|
declare function rpcPlugin(devOptions?: Partial<RpcPluginOptions>): Plugin<unknown>;
|
|
190
381
|
//#endregion
|
|
191
|
-
export { type BodyResult, type
|
|
382
|
+
export { type BodyResult, type ClientFunction, type ClientFunctionWithOptions, type ContentType, type Credentials, type FrameworkHooks, type FrameworkMiddlewareFn, type InnerModReturn, type JsonArray, type JsonObject, type JsonPrimitive, type JsonValue, type MiddlewareOptions, type RpcPluginOptions, type RpcPluginOptionsInternal, type ScanConfig, type ServerFnArgs, type ServerFnEntry, type ServerFunction, type ServerFunctionInit, type ServerFunctionOptions, type SupportableContentType, rpcPlugin as default, defineConfig, loadRPCConfig };
|
|
192
383
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/express/types.d.ts","../src/hono/types.d.ts","../src/fastify/types.d.ts","../src/koa/types.d.ts","../src/types.d.ts","../src/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/express/types.d.ts","../src/hono/types.d.ts","../src/fastify/types.d.ts","../src/koa/types.d.ts","../src/types.d.ts","../src/index.ts"],"mappings":";;;;;;;;;;;;;;KAgBY,2BAA2B;;;;;KAM3B,uBACV,UAAU,2CAEV,iBAAiB,QAAQ,8BACtB;;;;UAKY;;;;;;;EAOf,UACE,KAAK,kBAAkB,SACvB,KAAK,iBAAiB,UACtB,MAAM,QAAQ,eAAe,iBAC1B;;;;;;;UCzBU;;EAEf,SAAS;;;;;;KAOC,oBAAoB,UAAU,wCACxC,iBAAiB,QAAQ,oBAAkB,QACxC;;;;;;KCWO,2BAA2B;;;;;KAM3B,uBACV,UAAU,2CAEV,iBAAiB,QAAQ,8BACtB;;;;UAKY;;;;;;;EAOf,UACE,KAAK,gBACL,KAAK,cACL,MAAM,4BACH;;;;;;;KCtDK,uBAAuB;;;;UAalB;;;;;;EAMf,UAAU,KAAK,SAAS,MAAM,SAAS;;;;;;KAO7B,mBAAmB,UAAU,uCACvC,iBAAiB,QAAQ,0BACtB;;;;;;;UClBY;;EAEf,SAAS;;EAET,MAAM;;EAEN,SAAS;;EAET,KAAK;;;;;UAMU;;EAEf,SAAS;;EAET,MAAM;;EAEN,SAAS;;EAET,KAAK;;;;;;KAOK;;;;KASA;;;;KAKA;;;;KAKA;EACN;EAAiC,MAAM;;EACvC;EAA2B;;;;;;UAMhB;;;;;EAKf,aAAa;;;;;EAKb,cAAc;;;;;;;EAOd;;;;;;KAOU;;;;KAIA;GAAgB,cAAc,YAAY;;;;;KAI1C,YAAY;;;;KAIZ,YAAY,gBAAgB,YAAY;;;;;;;;;;;;;;;;;;;KAuBxC,mBAAmB;;;;;KAMnB,eACV,cAAc,YAAY,WAC1B,gBAAgB,YAAY,cACzB,QAAQ,gBAAgB,MAAM,UAAU,QAAQ;;;;;KAMzC,mBACV,cAAc,YAAY,WAC1B,gBAAgB,YAAY,cACzB,QAAQ,gBAAgB,MAAM,UAAU,QAAQ;;;;;;KAOzC,eACV,cAAc,YAAY,WAC1B,gBAAgB,YAAY,iBACtB,MAAM;;EAEZ,MAAM,QAAQ;;EAEd,SAAS;;;;;;KAOC,4BAA4B;;EAEtC;;EAEA,UAAU;;;;;UAMK;;EAEf;;EAEA;;;;;KAMU,aAAa,KAAK;;EAE5B,SAAS,QAAQ;;;;;;UAOF;;EAEf;;EAEA,SAAS;;EAET,UAAU;;EAEV;;;;;;;;UASe;;;;;;;;;;;EAWf;;;;;;;EAQA;;UAGe,kBACf,UAAU;;;;EAKV;;;;;;;;;;;;EAaA,gBAAgB;;;;;;;;;;EAWhB;;;;;;;;EASA;;;;;;;;;;;;;;;;;;EAmBA,UAAU,eAAe;;;;;;KAOf;;EAEV,MAAM,QAAQ;;EAEd,SAAS;;;;;;;;;;cC5QL,eAAe,GAAG,QAAQ,sBAAsB;;;;;;;;cAehD,gBAAgB,eAAe,QAAQ;;;;;;;;iBA4FpC,UACP,aAAY,QAAQ,oBACnB"}
|
package/dist/index.mjs
CHANGED
|
@@ -5,15 +5,23 @@ import { existsSync } from "node:fs";
|
|
|
5
5
|
import { getClientModules, scanForServerFiles, serverFunctionsMap } from "@thednp/rpc/server";
|
|
6
6
|
//#region src/options.ts
|
|
7
7
|
const defaultRPCOptions = {
|
|
8
|
-
|
|
8
|
+
rpcPrefix: "__rpc",
|
|
9
9
|
adapter: "express"
|
|
10
10
|
};
|
|
11
11
|
const defaultMiddlewareOptions = {
|
|
12
|
-
|
|
13
|
-
path: void 0
|
|
12
|
+
rpcPrefix: void 0,
|
|
13
|
+
path: void 0,
|
|
14
|
+
origin: void 0
|
|
14
15
|
};
|
|
15
16
|
//#endregion
|
|
16
17
|
//#region src/express/helpers.ts
|
|
18
|
+
/**
|
|
19
|
+
* Reads and parses the HTTP request body from an Express or Node IncomingMessage.
|
|
20
|
+
* If a body parser middleware (e.g. express.json()) already consumed the stream,
|
|
21
|
+
* uses the pre-parsed body from `req.body`.
|
|
22
|
+
* @param req - Express or Node.js IncomingMessage
|
|
23
|
+
* @returns A promise resolving to the parsed body with its content type
|
|
24
|
+
*/
|
|
17
25
|
const readBody = (req) => {
|
|
18
26
|
return new Promise((resolve, reject) => {
|
|
19
27
|
if (hasPreParsedBody(req) && req.body !== void 0) {
|
|
@@ -57,15 +65,37 @@ const readBody = (req) => {
|
|
|
57
65
|
toggleListeners(true);
|
|
58
66
|
});
|
|
59
67
|
};
|
|
68
|
+
/**
|
|
69
|
+
* Type guard that checks whether a request is an Express Request (has `originalUrl`).
|
|
70
|
+
* @param req - A Node IncomingMessage or Express Request
|
|
71
|
+
* @returns True if the request is an Express Request
|
|
72
|
+
*/
|
|
60
73
|
const isExpressRequest = (req) => {
|
|
61
74
|
return "originalUrl" in req;
|
|
62
75
|
};
|
|
76
|
+
/**
|
|
77
|
+
* Type guard that checks whether a response is an Express Response (has `json` and `send` methods).
|
|
78
|
+
* @param res - A Node ServerResponse or Express Response
|
|
79
|
+
* @returns True if the response is an Express Response
|
|
80
|
+
*/
|
|
63
81
|
const isExpressResponse = (res) => {
|
|
64
82
|
return "json" in res && "send" in res;
|
|
65
83
|
};
|
|
84
|
+
/**
|
|
85
|
+
* Type guard that checks whether a request has a pre-parsed body (`body` property).
|
|
86
|
+
* Used to detect if a body-parser middleware already consumed the stream.
|
|
87
|
+
* @param req - A Node IncomingMessage or Express Request
|
|
88
|
+
* @returns True if the request has a body property
|
|
89
|
+
*/
|
|
66
90
|
const hasPreParsedBody = (req) => {
|
|
67
91
|
return "body" in req;
|
|
68
92
|
};
|
|
93
|
+
/**
|
|
94
|
+
* Extracts normalized request details from an Express or Node IncomingMessage.
|
|
95
|
+
* Parses the URL to extract pathname, search string, and search params.
|
|
96
|
+
* @param request - Express or Node.js request object
|
|
97
|
+
* @returns Normalized request details including URL, headers, and method
|
|
98
|
+
*/
|
|
69
99
|
const getRequestDetails = (request) => {
|
|
70
100
|
const rawUrl = isExpressRequest(request) ? request.originalUrl : request.url;
|
|
71
101
|
const url = new URL(rawUrl, "http://localhost");
|
|
@@ -77,6 +107,12 @@ const getRequestDetails = (request) => {
|
|
|
77
107
|
method: request.method
|
|
78
108
|
};
|
|
79
109
|
};
|
|
110
|
+
/**
|
|
111
|
+
* Wraps an Express or Node ServerResponse with a uniform API for setting headers,
|
|
112
|
+
* status codes, and sending JSON responses. Handles the Express vs raw Node API differences.
|
|
113
|
+
* @param response - Express or Node.js server response object
|
|
114
|
+
* @returns A ResponseDetails object with setHeader, setStatusCode, and sendResponse helpers
|
|
115
|
+
*/
|
|
80
116
|
const getResponseDetails = (response) => {
|
|
81
117
|
const isResponseSent = response.headersSent || response.writableEnded;
|
|
82
118
|
const setHeader = (name, value) => {
|
|
@@ -103,17 +139,44 @@ const getResponseDetails = (response) => {
|
|
|
103
139
|
};
|
|
104
140
|
//#endregion
|
|
105
141
|
//#region src/tools.ts
|
|
142
|
+
/**
|
|
143
|
+
* Escapes special regex metacharacters in a string.
|
|
144
|
+
* Used to safely embed user-configurable values (like rpcPrefix) into regular expressions,
|
|
145
|
+
* preventing ReDoS and regex injection attacks.
|
|
146
|
+
* @param s - The raw string to escape
|
|
147
|
+
* @returns The escaped string safe for use in new RegExp()
|
|
148
|
+
*/
|
|
106
149
|
function escapeRegExp(s) {
|
|
107
150
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
108
151
|
}
|
|
109
152
|
//#endregion
|
|
153
|
+
//#region src/constants.ts
|
|
154
|
+
const FUNCTION_NOT_FOUND = "Function not found";
|
|
155
|
+
const METHOD_NOT_ALLOWED = "Method Not Allowed";
|
|
156
|
+
const REQUEST_FORBIDDEN = "Forbidden";
|
|
157
|
+
const INTERNAL_SERVER_ERROR = "Internal Server Error";
|
|
158
|
+
const CLIENT_DISCONNECTED = "client disconnected";
|
|
159
|
+
/** Returns a warning when a middleware name is reused, preventing registration conflicts. @param name - The duplicate middleware name */
|
|
160
|
+
const MIDDLEWARE_NAME_USED = (name) => `The middleware name "${name}" is already used.`;
|
|
161
|
+
/** Warning message when a specified RPC config file cannot be resolved on disk. @param configFile - The requested config filename. @param configFilePath - The resolved absolute path */
|
|
162
|
+
const CONFIG_FILE_NOT_FOUND = (configFile, configFilePath) => ` ⚠︎ The specified RPC config file ${configFile} cannot be found at ${configFilePath}, loading the defaults..`;
|
|
163
|
+
const NO_CONFIG_FOUND = ` ⚡︎ No RPC config found, loading the defaults..`;
|
|
164
|
+
const FAILED_LOAD_CONFIG = ` ⚠︎ Failed to load RPC config:`;
|
|
165
|
+
//#endregion
|
|
110
166
|
//#region src/express/createMiddleware.ts
|
|
111
167
|
let middlewareCount = 0;
|
|
112
168
|
const middlewareStack = /* @__PURE__ */ new Set();
|
|
169
|
+
/**
|
|
170
|
+
* Creates an Express middleware with optional path and rpcPrefix filtering.
|
|
171
|
+
* Middleware names are deduplicated — reusing a name throws an error.
|
|
172
|
+
* Prefix and path regexes are compiled once at creation time (hoisted) for performance.
|
|
173
|
+
* @param initialOptions - Options for rpcPrefix, path matching, and the handler function
|
|
174
|
+
* @returns An Express middleware function
|
|
175
|
+
*/
|
|
113
176
|
const createMiddleware = (initialOptions = {}) => {
|
|
114
177
|
const options = Object.assign({}, defaultMiddlewareOptions, initialOptions);
|
|
115
178
|
const middlewareName = options.name;
|
|
116
|
-
const
|
|
179
|
+
const rpcPrefix = options.rpcPrefix;
|
|
117
180
|
const path = options.path;
|
|
118
181
|
const handler = options.handler;
|
|
119
182
|
let name = middlewareName;
|
|
@@ -121,56 +184,86 @@ const createMiddleware = (initialOptions = {}) => {
|
|
|
121
184
|
name = "viteRPCMiddleware-" + middlewareCount;
|
|
122
185
|
middlewareCount += 1;
|
|
123
186
|
}
|
|
124
|
-
if (middlewareStack.has(name)) throw new Error(
|
|
187
|
+
if (middlewareStack.has(name)) throw new Error(MIDDLEWARE_NAME_USED(name));
|
|
125
188
|
middlewareStack.add(name);
|
|
126
|
-
const prefixRegex =
|
|
189
|
+
const prefixRegex = rpcPrefix ? new RegExp(`^/${escapeRegExp(rpcPrefix)}/`) : null;
|
|
127
190
|
const pathMatcher = path ? typeof path === "string" ? new RegExp(path) : path : null;
|
|
128
|
-
const middlewareHandler = async (req,
|
|
191
|
+
const middlewareHandler = async (req, res, next) => {
|
|
129
192
|
const { url } = getRequestDetails(req);
|
|
130
193
|
if (serverFunctionsMap.size === 0) await scanForServerFiles();
|
|
131
194
|
if (!handler) return next?.();
|
|
132
195
|
if (pathMatcher && !pathMatcher.test(url)) return next?.();
|
|
133
196
|
if (prefixRegex && !prefixRegex.test(url)) return next?.();
|
|
134
|
-
await handler(req,
|
|
197
|
+
await handler(req, res, next);
|
|
135
198
|
};
|
|
136
199
|
Object.defineProperty(middlewareHandler, "name", { value: name });
|
|
137
200
|
return middlewareHandler;
|
|
138
201
|
};
|
|
202
|
+
/**
|
|
203
|
+
* Creates the Express RPC middleware that routes incoming requests to registered server functions.
|
|
204
|
+
* Reads the request body, dispatches to the matching function via serverFunctionsMap,
|
|
205
|
+
* and sends the JSON-serialized result. Handles client disconnection via abort signals.
|
|
206
|
+
* @param initialOptions - Options including rpcPrefix for URL routing
|
|
207
|
+
* @returns An Express middleware function
|
|
208
|
+
*/
|
|
139
209
|
const createRPCMiddleware = (initialOptions = {}) => {
|
|
140
|
-
const options = Object.assign({}, defaultMiddlewareOptions, {
|
|
141
|
-
const
|
|
142
|
-
const prefixRegex =
|
|
143
|
-
const prefixReplace = `/${
|
|
210
|
+
const options = Object.assign({}, defaultMiddlewareOptions, { rpcPrefix: defaultRPCOptions.rpcPrefix }, initialOptions);
|
|
211
|
+
const rpcPrefix = options.rpcPrefix;
|
|
212
|
+
const prefixRegex = rpcPrefix ? new RegExp(`^/${escapeRegExp(rpcPrefix)}/`) : null;
|
|
213
|
+
const prefixReplace = `/${rpcPrefix}/`;
|
|
144
214
|
return createMiddleware({
|
|
145
215
|
...options,
|
|
146
216
|
handler: async (req, res, _next) => {
|
|
147
|
-
const { url } = getRequestDetails(req);
|
|
217
|
+
const { url: path, searchParams } = getRequestDetails(req);
|
|
148
218
|
const { sendResponse } = getResponseDetails(res);
|
|
149
|
-
if (prefixRegex && !prefixRegex.test(
|
|
150
|
-
const
|
|
219
|
+
if (prefixRegex && !prefixRegex.test(path)) return;
|
|
220
|
+
const origin = options.origin;
|
|
221
|
+
const requestOrigin = req.headers.origin;
|
|
222
|
+
if (origin && requestOrigin && requestOrigin !== origin) {
|
|
223
|
+
sendResponse(403, { error: REQUEST_FORBIDDEN });
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const functionName = path.replace(prefixReplace, "");
|
|
151
227
|
const serverFunction = serverFunctionsMap.get(functionName);
|
|
152
228
|
if (!serverFunction) {
|
|
153
|
-
sendResponse(404, { error:
|
|
229
|
+
sendResponse(404, { error: FUNCTION_NOT_FOUND });
|
|
154
230
|
return;
|
|
155
231
|
}
|
|
156
232
|
try {
|
|
157
|
-
const
|
|
158
|
-
|
|
233
|
+
const method = serverFunction.options?.method || "POST";
|
|
234
|
+
if (req.method?.toUpperCase() !== method) {
|
|
235
|
+
sendResponse(405, { error: METHOD_NOT_ALLOWED });
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
let args = [];
|
|
239
|
+
if (method === "GET") {
|
|
240
|
+
const raw = searchParams.get("args");
|
|
241
|
+
if (raw) args = JSON.parse(raw);
|
|
242
|
+
} else {
|
|
243
|
+
const body = await readBody(req);
|
|
244
|
+
args = Array.isArray(body.data) ? body.data : [body.data];
|
|
245
|
+
}
|
|
159
246
|
const { data, cancel } = serverFunction.handler(...args);
|
|
160
|
-
const onClose = () => cancel(
|
|
247
|
+
const onClose = () => cancel(CLIENT_DISCONNECTED);
|
|
161
248
|
req.on("close", onClose);
|
|
162
249
|
const result = await data;
|
|
163
250
|
req.off("close", onClose);
|
|
164
251
|
if (!res.headersSent) sendResponse(200, { data: result });
|
|
165
252
|
} catch (err) {
|
|
166
253
|
console.error(String(err));
|
|
167
|
-
sendResponse(500, { error:
|
|
254
|
+
sendResponse(500, { error: INTERNAL_SERVER_ERROR });
|
|
168
255
|
}
|
|
169
256
|
}
|
|
170
257
|
});
|
|
171
258
|
};
|
|
172
259
|
//#endregion
|
|
173
260
|
//#region src/index.ts
|
|
261
|
+
/**
|
|
262
|
+
* Loads and transforms a single RPC config file using Vite's config loader.
|
|
263
|
+
* @param env - Vite config environment
|
|
264
|
+
* @param file - Config file path (e.g. "rpc.config.ts")
|
|
265
|
+
* @returns The loaded config augmented with the configFile path, or null on failure
|
|
266
|
+
*/
|
|
174
267
|
const loadConfigFile = async (env, file) => {
|
|
175
268
|
const result = await loadConfigFromFile(env, file);
|
|
176
269
|
return result ? {
|
|
@@ -182,18 +275,23 @@ const loadConfigFile = async (env, file) => {
|
|
|
182
275
|
} : null;
|
|
183
276
|
};
|
|
184
277
|
/**
|
|
185
|
-
*
|
|
186
|
-
*
|
|
278
|
+
* Type-safe helper to create an RPC configuration object.
|
|
279
|
+
* Merges the provided partial config with built-in defaults.
|
|
280
|
+
* @param uniConfig - System-wide RPC configuration overrides
|
|
281
|
+
* @returns Complete RPC plugin options with defaults applied
|
|
187
282
|
*/
|
|
188
283
|
const defineConfig = (uniConfig) => {
|
|
189
284
|
return mergeConfig(defaultRPCOptions, uniConfig);
|
|
190
285
|
};
|
|
191
286
|
let RPCConfig;
|
|
192
287
|
/**
|
|
193
|
-
*
|
|
194
|
-
*
|
|
288
|
+
* Loads the RPC configuration by searching for config files in the project root.
|
|
289
|
+
* Searches in order: `rpc.config.ts`, `rpc.config.js`, `rpc.config.mjs`, `rpc.config.mts`,
|
|
290
|
+
* `.rpcrc.ts`, `.rpcrc.js`. Falls back to defaults if none found.
|
|
291
|
+
* @param configFile - Optional explicit config file path; skips file search when provided
|
|
292
|
+
* @returns Resolved RPC plugin options
|
|
195
293
|
*/
|
|
196
|
-
async
|
|
294
|
+
const loadRPCConfig = async (configFile) => {
|
|
197
295
|
try {
|
|
198
296
|
const env = {
|
|
199
297
|
command: "serve",
|
|
@@ -211,7 +309,7 @@ async function loadRPCConfig(configFile) {
|
|
|
211
309
|
if (configFile) {
|
|
212
310
|
const configFilePath = resolve(env.root, configFile);
|
|
213
311
|
if (!existsSync(configFilePath)) {
|
|
214
|
-
console.warn(
|
|
312
|
+
console.warn(CONFIG_FILE_NOT_FOUND(configFile, configFilePath));
|
|
215
313
|
RPCConfig = defaultRPCOptions;
|
|
216
314
|
return defaultRPCOptions;
|
|
217
315
|
}
|
|
@@ -239,13 +337,20 @@ async function loadRPCConfig(configFile) {
|
|
|
239
337
|
}
|
|
240
338
|
}
|
|
241
339
|
RPCConfig = defaultRPCOptions;
|
|
242
|
-
console.warn(
|
|
340
|
+
console.warn(NO_CONFIG_FOUND);
|
|
243
341
|
} catch (error) {
|
|
244
342
|
RPCConfig = defaultRPCOptions;
|
|
245
|
-
console.warn(
|
|
343
|
+
console.warn(FAILED_LOAD_CONFIG, error);
|
|
246
344
|
}
|
|
247
345
|
return RPCConfig;
|
|
248
|
-
}
|
|
346
|
+
};
|
|
347
|
+
/**
|
|
348
|
+
* Vite plugin that enables automatic RPC generation.
|
|
349
|
+
* Transforms server function imports into fetch-based client stubs during development and production builds.
|
|
350
|
+
* In dev mode, attaches the RPC middleware to Vite's Connect server.
|
|
351
|
+
* @param devOptions - Development-only overrides (merged on top of config file values)
|
|
352
|
+
* @returns A Vite plugin object
|
|
353
|
+
*/
|
|
249
354
|
function rpcPlugin(devOptions = {}) {
|
|
250
355
|
let options;
|
|
251
356
|
let config;
|
|
@@ -255,7 +360,8 @@ function rpcPlugin(devOptions = {}) {
|
|
|
255
360
|
name: "vite-plugin-universal-rpc",
|
|
256
361
|
enforce: "pre",
|
|
257
362
|
async configResolved(resolvedConfig) {
|
|
258
|
-
|
|
363
|
+
const uniConfig = await loadRPCConfig();
|
|
364
|
+
options = mergeConfig(uniConfig, devOptions);
|
|
259
365
|
config = resolvedConfig;
|
|
260
366
|
},
|
|
261
367
|
async configureServer(server) {
|
|
@@ -276,7 +382,7 @@ function rpcPlugin(devOptions = {}) {
|
|
|
276
382
|
const transformer = isOxc ? "transformWithOxc" : "transformWithEsbuild";
|
|
277
383
|
const langProp = isOxc ? "lang" : "loader";
|
|
278
384
|
const source = getClientModules({
|
|
279
|
-
|
|
385
|
+
rpcPrefix: options.rpcPrefix,
|
|
280
386
|
adapter: options.adapter
|
|
281
387
|
});
|
|
282
388
|
const result = await vite[transformer](source, id, {
|