@thednp/rpc 0.2.1 → 0.3.1
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 +34 -19
- package/CHANGELOG.md +74 -0
- package/README.md +23 -10
- package/dist/config/config.d.mts +65 -0
- package/dist/config/config.d.mts.map +1 -0
- package/dist/config/config.mjs +24 -0
- package/dist/config/config.mjs.map +1 -0
- package/dist/express/express.d.mts +4 -2
- package/dist/express/express.d.mts.map +1 -1
- package/dist/express/express.mjs +41 -8
- package/dist/express/express.mjs.map +1 -1
- package/dist/fastify/fastify.d.mts.map +1 -1
- package/dist/fastify/fastify.mjs +37 -6
- package/dist/fastify/fastify.mjs.map +1 -1
- package/dist/fastify/plugin/fastify/plugin.d.mts +13 -0
- package/dist/fastify/plugin/fastify/plugin.d.mts.map +1 -1
- package/dist/fastify/plugin/fastify/plugin.mjs +37 -6
- package/dist/fastify/plugin/fastify/plugin.mjs.map +1 -1
- package/dist/h3/h3.d.mts.map +1 -1
- package/dist/h3/h3.mjs +47 -16
- package/dist/h3/h3.mjs.map +1 -1
- package/dist/helpers/helpers.d.mts +63 -5
- package/dist/helpers/helpers.d.mts.map +1 -1
- package/dist/helpers/helpers.mjs +58 -1
- package/dist/helpers/helpers.mjs.map +1 -1
- package/dist/hono/hono.d.mts.map +1 -1
- package/dist/hono/hono.mjs +37 -6
- package/dist/hono/hono.mjs.map +1 -1
- package/dist/index.d.mts +48 -12
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +92 -61
- package/dist/index.mjs.map +1 -1
- package/dist/koa/koa.d.mts.map +1 -1
- package/dist/koa/koa.mjs +47 -16
- package/dist/koa/koa.mjs.map +1 -1
- package/dist/server/server.d.mts +219 -26
- package/dist/server/server.d.mts.map +1 -1
- package/dist/server/server.mjs +108 -69
- package/dist/server/server.mjs.map +1 -1
- package/llms.txt +81 -0
- package/package.json +23 -19
package/dist/server/server.d.mts
CHANGED
|
@@ -1,15 +1,95 @@
|
|
|
1
|
-
import { ResolvedConfig, ViteDevServer } from "vite";
|
|
1
|
+
import { Connect, ResolvedConfig, ViteDevServer } from "vite";
|
|
2
2
|
import "@thednp/rpc";
|
|
3
|
-
import "
|
|
4
|
-
import "
|
|
3
|
+
import { IncomingMessage, ServerResponse } from "node:http";
|
|
4
|
+
import { NextFunction, Request, Response as Response$1 } from "express";
|
|
5
|
+
import { MiddlewareHandler } from "hono";
|
|
5
6
|
import "@hono/node-server";
|
|
6
7
|
import "hono/utils/http-status";
|
|
7
8
|
import "hono/factory";
|
|
8
|
-
import "fastify";
|
|
9
|
+
import { FastifyReply, FastifyRequest, HookHandlerDoneFunction } from "fastify";
|
|
9
10
|
import "fastify-plugin";
|
|
10
|
-
import "koa";
|
|
11
|
-
import "h3";
|
|
11
|
+
import { Context, Next } from "koa";
|
|
12
|
+
import { Middleware } from "h3";
|
|
13
|
+
//#region src/express/types.d.ts
|
|
14
|
+
/**
|
|
15
|
+
* Express/Connect middleware handler signature used by the RPC middleware.
|
|
16
|
+
*/
|
|
17
|
+
interface ExpressMiddlewareHooks {
|
|
18
|
+
/**
|
|
19
|
+
* The handler invoked for each matched request.
|
|
20
|
+
* @param req - Node or Express request object
|
|
21
|
+
* @param res - Node or Express response object
|
|
22
|
+
* @param next - Connect or Express next function
|
|
23
|
+
*/
|
|
24
|
+
handler: (req: IncomingMessage | Request, res: ServerResponse | Response$1, next: Connect.NextFunction | NextFunction) => Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/hono/types.d.ts
|
|
28
|
+
/**
|
|
29
|
+
* Hono middleware handler signature used by the RPC middleware.
|
|
30
|
+
*/
|
|
31
|
+
interface HonoMiddlewareHooks {
|
|
32
|
+
/** Hono middleware handler */
|
|
33
|
+
handler: MiddlewareHandler;
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/fastify/types.d.ts
|
|
37
|
+
/**
|
|
38
|
+
* Fastify middleware handler signature used by the RPC middleware.
|
|
39
|
+
*/
|
|
40
|
+
interface FastifyMiddlewareHooks {
|
|
41
|
+
/**
|
|
42
|
+
* The handler invoked for each matched request.
|
|
43
|
+
* @param req - Fastify request object
|
|
44
|
+
* @param res - Fastify reply object
|
|
45
|
+
* @param done - Fastify hook completion callback
|
|
46
|
+
*/
|
|
47
|
+
handler: (req: FastifyRequest, res: FastifyReply, done: HookHandlerDoneFunction) => Promise<void>;
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/koa/types.d.ts
|
|
51
|
+
/**
|
|
52
|
+
* Koa middleware handler signature used by the RPC middleware.
|
|
53
|
+
*/
|
|
54
|
+
interface KoaMiddlewareHooks {
|
|
55
|
+
/**
|
|
56
|
+
* The handler invoked for each matched request.
|
|
57
|
+
* @param ctx - Koa context object
|
|
58
|
+
* @param next - Koa next function
|
|
59
|
+
*/
|
|
60
|
+
handler: (ctx: Context, next: Next) => Promise<void>;
|
|
61
|
+
}
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/h3/types.d.ts
|
|
64
|
+
/**
|
|
65
|
+
* h3 middleware handler signature used by the RPC middleware.
|
|
66
|
+
*/
|
|
67
|
+
interface H3MiddlewareHooks {
|
|
68
|
+
/**
|
|
69
|
+
* The handler invoked for each matched request.
|
|
70
|
+
* @param event - h3 event object
|
|
71
|
+
* @param next - h3 next function
|
|
72
|
+
*/
|
|
73
|
+
handler: Middleware;
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
12
76
|
//#region src/types.d.ts
|
|
77
|
+
/**
|
|
78
|
+
* Maps each supported framework adapter to its middleware hooks (handler signatures).
|
|
79
|
+
* Used to keep the middleware options type-safe per adapter.
|
|
80
|
+
*/
|
|
81
|
+
interface FrameworkHooks {
|
|
82
|
+
/** Express/Connect middleware handler signature */
|
|
83
|
+
express: ExpressMiddlewareHooks;
|
|
84
|
+
/** Hono middleware handler signature */
|
|
85
|
+
hono: HonoMiddlewareHooks;
|
|
86
|
+
/** Fastify middleware handler signature */
|
|
87
|
+
fastify: FastifyMiddlewareHooks;
|
|
88
|
+
/** Koa middleware handler signature */
|
|
89
|
+
koa: KoaMiddlewareHooks;
|
|
90
|
+
/** h3 middleware handler signature */
|
|
91
|
+
h3: H3MiddlewareHooks;
|
|
92
|
+
}
|
|
13
93
|
/**
|
|
14
94
|
* Content types the RPC client modules send with each request.
|
|
15
95
|
*/
|
|
@@ -40,6 +120,11 @@ interface ServerFunctionOptions {
|
|
|
40
120
|
* @default "POST"
|
|
41
121
|
*/
|
|
42
122
|
method?: "GET" | "POST";
|
|
123
|
+
/**
|
|
124
|
+
* RPC endpoint prefix
|
|
125
|
+
* @default "__rpc"
|
|
126
|
+
*/
|
|
127
|
+
rpcPrefix?: string;
|
|
43
128
|
}
|
|
44
129
|
// primitives and their compositions
|
|
45
130
|
/**
|
|
@@ -98,12 +183,14 @@ interface RpcPluginOptionsInternal {
|
|
|
98
183
|
/**
|
|
99
184
|
* Partial Vite config used when scanning server files outside a running dev server.
|
|
100
185
|
*/
|
|
101
|
-
|
|
186
|
+
interface ScanConfig extends Pick<ResolvedConfig, "base"> {
|
|
102
187
|
root?: string;
|
|
103
188
|
server?: Partial<ResolvedConfig["server"]>;
|
|
104
189
|
serverFiles?: "exact" | "glob";
|
|
105
190
|
scanRoot?: string;
|
|
106
|
-
|
|
191
|
+
/** Default rpcPrefix to register scanned functions under when a function does not declare its own. Defaults to `__rpc` for backward compatibility. */
|
|
192
|
+
rpcPrefix?: string;
|
|
193
|
+
}
|
|
107
194
|
/**
|
|
108
195
|
* Entry in the server functions map: registered name, client handler,
|
|
109
196
|
* optional per-function options, and the original export name.
|
|
@@ -158,8 +245,91 @@ interface RpcPluginOptions {
|
|
|
158
245
|
*/
|
|
159
246
|
serverFiles?: "exact" | "glob";
|
|
160
247
|
}
|
|
248
|
+
interface MiddlewareOptions<A extends RpcPluginOptions["adapter"] = "express"> {
|
|
249
|
+
/**
|
|
250
|
+
* Name for the middleware (used for identification in Express stack)
|
|
251
|
+
*/
|
|
252
|
+
name?: string;
|
|
253
|
+
/**
|
|
254
|
+
* Path pattern to match for middleware execution.
|
|
255
|
+
* Accepts string or RegExp to filter requests based on URL path.
|
|
256
|
+
*
|
|
257
|
+
* @example
|
|
258
|
+
* // String path
|
|
259
|
+
* path: "/api/v1"
|
|
260
|
+
*
|
|
261
|
+
* // RegExp pattern
|
|
262
|
+
* path: /^\/api\/v[0-9]+/
|
|
263
|
+
*/
|
|
264
|
+
path?: string | RegExp;
|
|
265
|
+
/**
|
|
266
|
+
* RPC prefix without leading slash (e.g. "__rpc")
|
|
267
|
+
* Leading slash will be added automatically by the middleware.
|
|
268
|
+
* This prefix defines the base path for all RPC endpoints.
|
|
269
|
+
* @default string
|
|
270
|
+
* @example
|
|
271
|
+
* // Results in endpoints like: /api/rpc/myFunction
|
|
272
|
+
* rpcPrefix: "api/rpc"
|
|
273
|
+
*/
|
|
274
|
+
rpcPrefix?: string | false;
|
|
275
|
+
/**
|
|
276
|
+
* Allowed request origin (e.g. "https://example.com").
|
|
277
|
+
* When set, any request carrying an `Origin` header that does not match
|
|
278
|
+
* is rejected with a 403 Forbidden response. Requests without an `Origin`
|
|
279
|
+
* header (curl, native clients) pass through unchecked.
|
|
280
|
+
* When unset (default), no origin validation is performed.
|
|
281
|
+
*/
|
|
282
|
+
origin?: string;
|
|
283
|
+
/**
|
|
284
|
+
* Server file matching mode. Use `"exact"` for `server.ts|js|mjs|mts`
|
|
285
|
+
* names, or `"glob"` to match `**\/*.server.{ts,js,mjs,mts}` inside the
|
|
286
|
+
* scan root. Only used for the lazy production scan when the middleware
|
|
287
|
+
* populates its prefix map on first request.
|
|
288
|
+
* @default "exact"
|
|
289
|
+
*/
|
|
290
|
+
serverFiles?: "exact" | "glob";
|
|
291
|
+
/**
|
|
292
|
+
* Root directory for scanning server files. Defaults to `<root>/src/api`.
|
|
293
|
+
* Only used for the lazy production scan.
|
|
294
|
+
*/
|
|
295
|
+
scanRoot?: string;
|
|
296
|
+
/**
|
|
297
|
+
* Async handler for request processing.
|
|
298
|
+
* Core middleware function that processes incoming requests.
|
|
299
|
+
*
|
|
300
|
+
* @param req - The incoming request object
|
|
301
|
+
* @param res - The server response object
|
|
302
|
+
* @param next - Function to pass control to the next middleware
|
|
303
|
+
*
|
|
304
|
+
* @example
|
|
305
|
+
* handler: async (req, res, next) => {
|
|
306
|
+
* // Process request
|
|
307
|
+
* const data = await processRequest(req);
|
|
308
|
+
*
|
|
309
|
+
* // Send response
|
|
310
|
+
* sendResponse(res, { data }, 200);
|
|
311
|
+
* }
|
|
312
|
+
*/
|
|
313
|
+
handler?: FrameworkHooks[A]["handler"];
|
|
314
|
+
}
|
|
161
315
|
//#endregion
|
|
162
316
|
//#region src/functionsMap.d.ts
|
|
317
|
+
/**
|
|
318
|
+
* Map of rpcPrefix -> Map of function names -> ServerFnEntry
|
|
319
|
+
* Enables multiple RPC instances with different prefixes to coexist
|
|
320
|
+
* without name collisions.
|
|
321
|
+
*/
|
|
322
|
+
declare const serverFunctionsByPrefix: Map<string, Map<string, ServerFnEntry>>;
|
|
323
|
+
/**
|
|
324
|
+
* Gets or creates the function map for a specific prefix.
|
|
325
|
+
* @param prefix - The RPC prefix (e.g., "__rpc", "v1:rpc", "admin:rpc")
|
|
326
|
+
* @returns Map of function names to ServerFnEntry for that prefix
|
|
327
|
+
*/
|
|
328
|
+
declare const getFunctionsForPrefix: (prefix: string) => Map<string, ServerFnEntry>;
|
|
329
|
+
/**
|
|
330
|
+
* Backward compatibility: default map for the default prefix.
|
|
331
|
+
* Legacy code can still use serverFunctionsMap.set(name, entry).
|
|
332
|
+
*/
|
|
163
333
|
declare const serverFunctionsMap: Map<string, ServerFnEntry>;
|
|
164
334
|
//#endregion
|
|
165
335
|
//#region src/scanForServerFiles.d.ts
|
|
@@ -167,7 +337,7 @@ declare const serverFunctionsMap: Map<string, ServerFnEntry>;
|
|
|
167
337
|
declare const scannedServerFiles: Set<string>;
|
|
168
338
|
/**
|
|
169
339
|
* Scans `src/api/` (or an explicit `scanRoot`) for server function files
|
|
170
|
-
* and populates the
|
|
340
|
+
* and populates the server functions map (scoped by rpcPrefix) with their exported functions.
|
|
171
341
|
* Uses Vite's SSR module loading to resolve and execute each file.
|
|
172
342
|
*
|
|
173
343
|
* Supports two matching modes via `config.serverFiles`:
|
|
@@ -179,22 +349,49 @@ declare const scannedServerFiles: Set<string>;
|
|
|
179
349
|
declare const scanForServerFiles: (initialCfg?: ScanConfig, devServer?: ViteDevServer) => Promise<void>;
|
|
180
350
|
//#endregion
|
|
181
351
|
//#region src/createFunction.d.ts
|
|
352
|
+
/**
|
|
353
|
+
* Extended options for createServerFunction, including rpcPrefix for multi-instance support.
|
|
354
|
+
*/
|
|
355
|
+
interface CreateServerFunctionOptions extends Partial<ServerFunctionOptions> {
|
|
356
|
+
/**
|
|
357
|
+
* RPC prefix for this function. Enables multiple RPC instances with different prefixes.
|
|
358
|
+
* When using multi-prefix setup, functions with the same name but different prefixes
|
|
359
|
+
* can coexist without collision.
|
|
360
|
+
* @default "__rpc"
|
|
361
|
+
* @example
|
|
362
|
+
* // v1 API
|
|
363
|
+
* export const login = createServerFunction(
|
|
364
|
+
* "login",
|
|
365
|
+
* async (signal, email, password) => ({...}),
|
|
366
|
+
* { rpcPrefix: "v1:rpc" },
|
|
367
|
+
* );
|
|
368
|
+
*
|
|
369
|
+
* // v2 API - same function name, different prefix
|
|
370
|
+
* export const login = createServerFunction(
|
|
371
|
+
* "login",
|
|
372
|
+
* async (signal, credentials) => ({...}),
|
|
373
|
+
* { rpcPrefix: "v2:rpc" },
|
|
374
|
+
* );
|
|
375
|
+
*/
|
|
376
|
+
rpcPrefix?: string;
|
|
377
|
+
}
|
|
182
378
|
/**
|
|
183
379
|
* Creates a server-side RPC function.
|
|
184
|
-
* Registers the function in the server functions map and returns
|
|
185
|
-
* wrapper that exposes `data` (Promise) and `cancel` (function)
|
|
380
|
+
* Registers the function in the server functions map (scoped by rpcPrefix) and returns
|
|
381
|
+
* a client-compatible wrapper that exposes `data` (Promise) and `cancel` (function)
|
|
382
|
+
* for request lifecycle control.
|
|
186
383
|
* @param name - Unique identifier used by the RPC router to dispatch requests
|
|
187
384
|
* @param handler - The actual implementation receiving an AbortSignal followed by JSON-serializable arguments
|
|
188
|
-
* @param fnOptions - Optional contentType and
|
|
385
|
+
* @param fnOptions - Optional contentType, credentials, and rpcPrefix settings
|
|
189
386
|
* @returns A client stub with `data` promise and `cancel` method, auto-registered in the server map
|
|
190
387
|
*/
|
|
191
|
-
declare function createServerFunction<TArgs extends JsonArray = JsonArray, TResult extends JsonValue = JsonValue>(name: string, handler: ServerFunctionInit<TArgs, TResult>, fnOptions?:
|
|
388
|
+
declare function createServerFunction<TArgs extends JsonArray = JsonArray, TResult extends JsonValue = JsonValue>(name: string, handler: ServerFunctionInit<TArgs, TResult>, fnOptions?: CreateServerFunctionOptions): ClientFunction<TArgs, TResult>;
|
|
192
389
|
//#endregion
|
|
193
390
|
//#region src/getClientModules.d.ts
|
|
194
391
|
/**
|
|
195
392
|
* Generates the complete client-side module bundle by iterating all registered server functions
|
|
196
|
-
* and producing fetch-based stubs for each. The result is transformed by Vite
|
|
197
|
-
* the dev server or production build.
|
|
393
|
+
* for a specific prefix and producing fetch-based stubs for each. The result is transformed by Vite
|
|
394
|
+
* (or Oxc) during the dev server or production build.
|
|
198
395
|
* @param initialOptions - Plugin options containing rpcPrefix and optional adapter
|
|
199
396
|
* @returns A string of JavaScript code with all client RPC modules and their import dependencies
|
|
200
397
|
*/
|
|
@@ -267,6 +464,9 @@ declare function escapeRegExp(s: string): string;
|
|
|
267
464
|
* @returns A URL object; never throws
|
|
268
465
|
*/
|
|
269
466
|
declare const safeURL: (rawUrl: string, base?: string) => URL;
|
|
467
|
+
/** Global rpcPrefix from the last loaded config / middleware — fallback for functions without explicit prefix. */
|
|
468
|
+
declare const getGlobalPrefix: () => string | undefined;
|
|
469
|
+
declare const setGlobalPrefix: (prefix: string | undefined) => void;
|
|
270
470
|
//#endregion
|
|
271
471
|
//#region src/context.d.ts
|
|
272
472
|
/**
|
|
@@ -404,17 +604,10 @@ interface RequestMeta {
|
|
|
404
604
|
declare const getRequestMeta: (event: RequestEvent) => RequestMeta;
|
|
405
605
|
//#endregion
|
|
406
606
|
//#region src/options.d.ts
|
|
407
|
-
declare const defaultServerFnOptions:
|
|
408
|
-
|
|
409
|
-
credentials: "same-origin";
|
|
410
|
-
method: "POST";
|
|
411
|
-
};
|
|
607
|
+
declare const defaultServerFnOptions: ServerFunctionOptions;
|
|
608
|
+
declare const defaultPrefix = "__rpc";
|
|
412
609
|
declare const defaultRPCOptions: RpcPluginOptions;
|
|
413
|
-
declare const defaultMiddlewareOptions:
|
|
414
|
-
rpcPrefix: undefined;
|
|
415
|
-
path: undefined;
|
|
416
|
-
origin: undefined;
|
|
417
|
-
};
|
|
610
|
+
declare const defaultMiddlewareOptions: MiddlewareOptions;
|
|
418
611
|
//#endregion
|
|
419
|
-
export { RPCError, RequestEvent, RequestMeta, createServerFunction, defaultMiddlewareOptions, defaultRPCOptions, defaultServerFnOptions, escapeRegExp, formatError, getClientModules, getRequestContext, getRequestMeta, hasContentTypeMismatch, isFormContentType, provideRequestContext, redirect, safeURL, scanForServerFiles, scannedServerFiles, sendResponse, serverFunctionsMap, walkGlobFiles };
|
|
612
|
+
export { CreateServerFunctionOptions, RPCError, RequestEvent, RequestMeta, createServerFunction, defaultMiddlewareOptions, defaultPrefix, defaultRPCOptions, defaultServerFnOptions, escapeRegExp, formatError, getClientModules, getFunctionsForPrefix, getGlobalPrefix, getRequestContext, getRequestMeta, hasContentTypeMismatch, isFormContentType, provideRequestContext, redirect, safeURL, scanForServerFiles, scannedServerFiles, sendResponse, serverFunctionsByPrefix, serverFunctionsMap, setGlobalPrefix, walkGlobFiles };
|
|
420
613
|
//# sourceMappingURL=server.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.mts","names":[],"sources":["../../src/types.d.ts","../../src/functionsMap.ts","../../src/scanForServerFiles.ts","../../src/createFunction.ts","../../src/getClientModules.ts","../../src/server-helpers.ts","../../src/context.ts","../../src/options.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"server.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/h3/types.d.ts","../../src/types.d.ts","../../src/functionsMap.ts","../../src/scanForServerFiles.ts","../../src/createFunction.ts","../../src/getClientModules.ts","../../src/server-helpers.ts","../../src/context.ts","../../src/options.ts"],"mappings":"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"}
|
package/dist/server/server.mjs
CHANGED
|
@@ -2,8 +2,69 @@ import { readdir } from "node:fs/promises";
|
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
3
|
import process from "node:process";
|
|
4
4
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
5
|
+
//#region src/options.ts
|
|
6
|
+
const defaultServerFnOptions = {
|
|
7
|
+
contentType: "application/json",
|
|
8
|
+
credentials: "same-origin",
|
|
9
|
+
method: "POST"
|
|
10
|
+
};
|
|
11
|
+
const defaultPrefix = "__rpc";
|
|
12
|
+
const defaultRPCOptions = {
|
|
13
|
+
rpcPrefix: defaultPrefix,
|
|
14
|
+
adapter: "express",
|
|
15
|
+
serverFiles: "exact",
|
|
16
|
+
scanRoot: void 0
|
|
17
|
+
};
|
|
18
|
+
const defaultMiddlewareOptions = {
|
|
19
|
+
rpcPrefix: void 0,
|
|
20
|
+
path: void 0,
|
|
21
|
+
origin: void 0
|
|
22
|
+
};
|
|
23
|
+
//#endregion
|
|
5
24
|
//#region src/functionsMap.ts
|
|
6
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Global symbol under which the shared `serverFunctionsByPrefix` map is stored
|
|
27
|
+
* on `globalThis`. Keeping it on a `Symbol.for` key makes it instance-stable
|
|
28
|
+
* across the bundled entry copies (`index.mjs`, `server.mjs`, `express.mjs`,
|
|
29
|
+
* ...) and dev-server hot reloads, exactly like the request-context storage in
|
|
30
|
+
* `context.ts`. Without this, `scanForServerFiles` (bundled into the plugin)
|
|
31
|
+
* would populate a map copy the adapter middleware could not read.
|
|
32
|
+
*/
|
|
33
|
+
const functionsMapSymbol = Symbol.for("thednp.rpc.functionsMap");
|
|
34
|
+
/**
|
|
35
|
+
* Map of rpcPrefix -> Map of function names -> ServerFnEntry
|
|
36
|
+
* Enables multiple RPC instances with different prefixes to coexist
|
|
37
|
+
* without name collisions.
|
|
38
|
+
*/
|
|
39
|
+
const serverFunctionsByPrefix = globalThis[functionsMapSymbol] ??= /* @__PURE__ */ new Map();
|
|
40
|
+
/**
|
|
41
|
+
* Gets or creates the function map for a specific prefix.
|
|
42
|
+
* @param prefix - The RPC prefix (e.g., "__rpc", "v1:rpc", "admin:rpc")
|
|
43
|
+
* @returns Map of function names to ServerFnEntry for that prefix
|
|
44
|
+
*/
|
|
45
|
+
const getFunctionsForPrefix = (prefix) => {
|
|
46
|
+
if (!serverFunctionsByPrefix.has(prefix)) serverFunctionsByPrefix.set(prefix, /* @__PURE__ */ new Map());
|
|
47
|
+
return serverFunctionsByPrefix.get(prefix);
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Backward compatibility: default map for the default prefix.
|
|
51
|
+
* Legacy code can still use serverFunctionsMap.set(name, entry).
|
|
52
|
+
*/
|
|
53
|
+
const serverFunctionsMap = {
|
|
54
|
+
get: (key) => getFunctionsForPrefix(defaultPrefix).get(key),
|
|
55
|
+
set: (key, value) => getFunctionsForPrefix(defaultPrefix).set(key, value),
|
|
56
|
+
has: (key) => getFunctionsForPrefix(defaultPrefix).has(key),
|
|
57
|
+
delete: (key) => getFunctionsForPrefix(defaultPrefix).delete(key),
|
|
58
|
+
clear: () => getFunctionsForPrefix(defaultPrefix).clear(),
|
|
59
|
+
get size() {
|
|
60
|
+
return getFunctionsForPrefix(defaultPrefix).size;
|
|
61
|
+
},
|
|
62
|
+
entries: () => getFunctionsForPrefix(defaultPrefix).entries(),
|
|
63
|
+
keys: () => getFunctionsForPrefix(defaultPrefix).keys(),
|
|
64
|
+
values: () => getFunctionsForPrefix(defaultPrefix).values(),
|
|
65
|
+
forEach: (callback) => getFunctionsForPrefix(defaultPrefix).forEach(callback),
|
|
66
|
+
[Symbol.iterator]: () => getFunctionsForPrefix(defaultPrefix)[Symbol.iterator]()
|
|
67
|
+
};
|
|
7
68
|
//#endregion
|
|
8
69
|
//#region src/constants.ts
|
|
9
70
|
const OPERATION_ABORTED = "Operation aborted";
|
|
@@ -13,7 +74,7 @@ const INTERNAL_SERVER_ERROR = "Internal Server Error";
|
|
|
13
74
|
/** Error message when a value fails the safe-identifier validation. @param label - What kind of value was being validated. @param name - The rejected value */
|
|
14
75
|
const INVALID_IDENTIFIER = (label, name) => `Invalid ${label}: "${name}" must match /^[A-Za-z_$][A-Za-z0-9_$]*$/`;
|
|
15
76
|
/** Error message when a value fails the safe-path-segment validation. @param label - What kind of value was being validated. @param segment - The rejected value */
|
|
16
|
-
const INVALID_PATH_SEGMENT = (label, segment) => `Invalid ${label}: "${segment}" must match /^[A-Za-z0-9_
|
|
77
|
+
const INVALID_PATH_SEGMENT = (label, segment) => `Invalid ${label}: "${segment}" must match /^[A-Za-z0-9_$@:][A-Za-z0-9_$@:/-]*$/`;
|
|
17
78
|
/** Error template for duplicate server function names across files. @param name - The duplicate registered name */
|
|
18
79
|
const DUPLICATE_FUNCTION_NAME = (name) => `Duplicate server function "${name}" detected. Each server function must have a unique name. Remove or rename the duplicate.`;
|
|
19
80
|
//#endregion
|
|
@@ -133,6 +194,13 @@ const safeURL = (rawUrl, base = SAFE_URL_BASE) => {
|
|
|
133
194
|
return new URL("/", base);
|
|
134
195
|
}
|
|
135
196
|
};
|
|
197
|
+
const globalPrefixSymbol = Symbol.for("thednp.rpc.globalPrefix");
|
|
198
|
+
/** Global rpcPrefix from the last loaded config / middleware — fallback for functions without explicit prefix. */
|
|
199
|
+
const getGlobalPrefix = () => globalThis[globalPrefixSymbol];
|
|
200
|
+
const setGlobalPrefix = (prefix) => {
|
|
201
|
+
if (prefix) globalThis[globalPrefixSymbol] = prefix;
|
|
202
|
+
else delete globalThis[globalPrefixSymbol];
|
|
203
|
+
};
|
|
136
204
|
//#endregion
|
|
137
205
|
//#region src/scanForServerFiles.ts
|
|
138
206
|
let isScanned = false;
|
|
@@ -146,7 +214,7 @@ const EXACT_NAMES = [
|
|
|
146
214
|
];
|
|
147
215
|
/**
|
|
148
216
|
* Scans `src/api/` (or an explicit `scanRoot`) for server function files
|
|
149
|
-
* and populates the
|
|
217
|
+
* and populates the server functions map (scoped by rpcPrefix) with their exported functions.
|
|
150
218
|
* Uses Vite's SSR module loading to resolve and execute each file.
|
|
151
219
|
*
|
|
152
220
|
* Supports two matching modes via `config.serverFiles`:
|
|
@@ -157,7 +225,13 @@ const EXACT_NAMES = [
|
|
|
157
225
|
*/
|
|
158
226
|
const scanForServerFiles = async (initialCfg, devServer) => {
|
|
159
227
|
if (isScanned && !devServer) return;
|
|
160
|
-
|
|
228
|
+
let createServer;
|
|
229
|
+
let normalizePath;
|
|
230
|
+
try {
|
|
231
|
+
({createServer, normalizePath} = await import("vite"));
|
|
232
|
+
} catch {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
161
235
|
const config = !initialCfg && !devServer || !initialCfg ? {
|
|
162
236
|
root: process.cwd(),
|
|
163
237
|
base: process.env.BASE || "/",
|
|
@@ -204,16 +278,21 @@ const scanForServerFiles = async (initialCfg, devServer) => {
|
|
|
204
278
|
}
|
|
205
279
|
for (const [exportName, exportValue] of moduleEntries) {
|
|
206
280
|
const registeredName = exportValue.name;
|
|
207
|
-
|
|
281
|
+
const prefix = exportValue.options?.rpcPrefix || config.rpcPrefix || "__rpc";
|
|
282
|
+
const seenKey = `${prefix}:${registeredName}`;
|
|
283
|
+
if (seenNames.has(seenKey)) {
|
|
208
284
|
if (process.env.NODE_ENV !== "production") throw new Error(DUPLICATE_FUNCTION_NAME(registeredName));
|
|
209
285
|
console.warn(DUPLICATE_FUNCTION_NAME(registeredName));
|
|
210
286
|
continue;
|
|
211
287
|
}
|
|
212
|
-
seenNames.add(
|
|
213
|
-
|
|
288
|
+
seenNames.add(seenKey);
|
|
289
|
+
const prefixMap = getFunctionsForPrefix(prefix);
|
|
290
|
+
const existing = prefixMap.get(registeredName);
|
|
291
|
+
if (existing) existing.exportName = exportName;
|
|
292
|
+
else prefixMap.set(registeredName, {
|
|
214
293
|
name: registeredName,
|
|
215
294
|
handler: exportValue,
|
|
216
|
-
options: exportValue
|
|
295
|
+
options: exportValue.options,
|
|
217
296
|
exportName
|
|
218
297
|
});
|
|
219
298
|
}
|
|
@@ -224,36 +303,20 @@ const scanForServerFiles = async (initialCfg, devServer) => {
|
|
|
224
303
|
}
|
|
225
304
|
};
|
|
226
305
|
//#endregion
|
|
227
|
-
//#region src/options.ts
|
|
228
|
-
const defaultServerFnOptions = {
|
|
229
|
-
contentType: "application/json",
|
|
230
|
-
credentials: "same-origin",
|
|
231
|
-
method: "POST"
|
|
232
|
-
};
|
|
233
|
-
const defaultRPCOptions = {
|
|
234
|
-
rpcPrefix: "__rpc",
|
|
235
|
-
adapter: "express",
|
|
236
|
-
serverFiles: "exact",
|
|
237
|
-
scanRoot: void 0
|
|
238
|
-
};
|
|
239
|
-
const defaultMiddlewareOptions = {
|
|
240
|
-
rpcPrefix: void 0,
|
|
241
|
-
path: void 0,
|
|
242
|
-
origin: void 0
|
|
243
|
-
};
|
|
244
|
-
//#endregion
|
|
245
306
|
//#region src/createFunction.ts
|
|
246
307
|
/**
|
|
247
308
|
* Creates a server-side RPC function.
|
|
248
|
-
* Registers the function in the server functions map and returns
|
|
249
|
-
* wrapper that exposes `data` (Promise) and `cancel` (function)
|
|
309
|
+
* Registers the function in the server functions map (scoped by rpcPrefix) and returns
|
|
310
|
+
* a client-compatible wrapper that exposes `data` (Promise) and `cancel` (function)
|
|
311
|
+
* for request lifecycle control.
|
|
250
312
|
* @param name - Unique identifier used by the RPC router to dispatch requests
|
|
251
313
|
* @param handler - The actual implementation receiving an AbortSignal followed by JSON-serializable arguments
|
|
252
|
-
* @param fnOptions - Optional contentType and
|
|
314
|
+
* @param fnOptions - Optional contentType, credentials, and rpcPrefix settings
|
|
253
315
|
* @returns A client stub with `data` promise and `cancel` method, auto-registered in the server map
|
|
254
316
|
*/
|
|
255
317
|
function createServerFunction(name, handler, fnOptions = {}) {
|
|
256
318
|
const options = Object.assign({}, defaultServerFnOptions, fnOptions);
|
|
319
|
+
const rpcPrefix = fnOptions.rpcPrefix || getGlobalPrefix() || "__rpc";
|
|
257
320
|
const wrappedFunction = (...args) => {
|
|
258
321
|
const controller = new AbortController();
|
|
259
322
|
const cancel = (reason) => controller.abort(reason);
|
|
@@ -278,7 +341,7 @@ function createServerFunction(name, handler, fnOptions = {}) {
|
|
|
278
341
|
configurable: false
|
|
279
342
|
}
|
|
280
343
|
});
|
|
281
|
-
|
|
344
|
+
getFunctionsForPrefix(rpcPrefix).set(name, {
|
|
282
345
|
name,
|
|
283
346
|
handler: wrappedFunction,
|
|
284
347
|
options
|
|
@@ -288,7 +351,7 @@ function createServerFunction(name, handler, fnOptions = {}) {
|
|
|
288
351
|
//#endregion
|
|
289
352
|
//#region src/validate.ts
|
|
290
353
|
const SAFE_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
291
|
-
const SAFE_PATH_SEGMENT = /^[A-Za-z0-9_
|
|
354
|
+
const SAFE_PATH_SEGMENT = /^[A-Za-z0-9_$@:][A-Za-z0-9_$@:/-]*$/;
|
|
292
355
|
const CREDENTIALS_VALUES = [
|
|
293
356
|
"same-origin",
|
|
294
357
|
"include",
|
|
@@ -308,7 +371,8 @@ function validateIdentifier(name, label) {
|
|
|
308
371
|
}
|
|
309
372
|
/**
|
|
310
373
|
* Validates that a string is a safe path segment for RPC routing.
|
|
311
|
-
* Allows alphanumeric characters, underscores, dollar signs, at signs,
|
|
374
|
+
* Allows alphanumeric characters, underscores, dollar signs, at signs,
|
|
375
|
+
* colons, hyphens, and forward slashes.
|
|
312
376
|
* @param segment - The string to validate
|
|
313
377
|
* @param label - Human-readable label for error messages (e.g. "rpcPrefix")
|
|
314
378
|
* @returns The validated segment if it passes
|
|
@@ -358,53 +422,28 @@ const getModule = (fnName, fnEntry, options) => {
|
|
|
358
422
|
const safePrefix = validatePathSegment(options.rpcPrefix, "rpcPrefix");
|
|
359
423
|
const credentials = validateCredentials(options.credentials);
|
|
360
424
|
const method = validateMethod(options.method);
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
headers = `{ 'Content-Type': 'text/plain' }`;
|
|
367
|
-
break;
|
|
368
|
-
case "application/x-www-form-urlencoded":
|
|
369
|
-
body = `new URLSearchParams(args[0]).toString()`;
|
|
370
|
-
headers = `{ 'Content-Type': 'application/x-www-form-urlencoded' }`;
|
|
371
|
-
break;
|
|
372
|
-
case "multipart/form-data":
|
|
373
|
-
body = `args[0]`;
|
|
374
|
-
headers = `{}`;
|
|
375
|
-
break;
|
|
376
|
-
default:
|
|
377
|
-
body = `JSON.stringify(args)`;
|
|
378
|
-
headers = `{ 'Content-Type': 'application/json' }`;
|
|
379
|
-
}
|
|
380
|
-
if (method === "GET") {
|
|
381
|
-
body = `JSON.stringify(args)`;
|
|
382
|
-
headers = `{}`;
|
|
383
|
-
}
|
|
425
|
+
const contentType = options.contentType ?? "application/json";
|
|
426
|
+
const opts = [];
|
|
427
|
+
if (method !== "POST") opts.push(`method: "${method}"`);
|
|
428
|
+
if (credentials !== "same-origin") opts.push(`credentials: "${credentials}"`);
|
|
429
|
+
if (contentType !== "application/json") opts.push(`contentType: "${contentType}"`);
|
|
384
430
|
return `
|
|
385
|
-
export const ${safeFnEntry} = (
|
|
386
|
-
const body = ${body};
|
|
387
|
-
const headers = ${headers};
|
|
388
|
-
const prefix = "${safePrefix}";
|
|
389
|
-
const name = "${safeFnName}";
|
|
390
|
-
const credentials = "${credentials}";
|
|
391
|
-
const method = "${method}";
|
|
392
|
-
return innerModule(body, headers, credentials, prefix, name, method);
|
|
393
|
-
}`.trim();
|
|
431
|
+
export const ${safeFnEntry} = getClientStub("${safePrefix}", "${safeFnName}"${opts.length ? `, { ${opts.join(", ")} }` : ""});`.trim();
|
|
394
432
|
};
|
|
395
433
|
/**
|
|
396
434
|
* Generates the complete client-side module bundle by iterating all registered server functions
|
|
397
|
-
* and producing fetch-based stubs for each. The result is transformed by Vite
|
|
398
|
-
* the dev server or production build.
|
|
435
|
+
* for a specific prefix and producing fetch-based stubs for each. The result is transformed by Vite
|
|
436
|
+
* (or Oxc) during the dev server or production build.
|
|
399
437
|
* @param initialOptions - Plugin options containing rpcPrefix and optional adapter
|
|
400
438
|
* @returns A string of JavaScript code with all client RPC modules and their import dependencies
|
|
401
439
|
*/
|
|
402
440
|
const getClientModules = (initialOptions) => {
|
|
403
441
|
validatePathSegment(initialOptions.rpcPrefix, "rpcPrefix");
|
|
442
|
+
const prefixMap = getFunctionsForPrefix(initialOptions.rpcPrefix);
|
|
404
443
|
return `
|
|
405
444
|
|
|
406
|
-
import {
|
|
407
|
-
${Array.from(
|
|
445
|
+
import { getClientStub } from "@thednp/rpc/helpers";
|
|
446
|
+
${Array.from(prefixMap.entries()).filter(([, entry]) => entry.exportName).map(([registeredName, entry]) => getModule(registeredName, entry.exportName, {
|
|
408
447
|
...initialOptions,
|
|
409
448
|
...entry.options || {}
|
|
410
449
|
})).join("\n")}`.trim();
|
|
@@ -507,6 +546,6 @@ const getRequestMeta = (event) => {
|
|
|
507
546
|
};
|
|
508
547
|
};
|
|
509
548
|
//#endregion
|
|
510
|
-
export { RPCError, createServerFunction, defaultMiddlewareOptions, defaultRPCOptions, defaultServerFnOptions, escapeRegExp, formatError, getClientModules, getRequestContext, getRequestMeta, hasContentTypeMismatch, isFormContentType, provideRequestContext, redirect, safeURL, scanForServerFiles, scannedServerFiles, sendResponse, serverFunctionsMap, walkGlobFiles };
|
|
549
|
+
export { RPCError, createServerFunction, defaultMiddlewareOptions, defaultPrefix, defaultRPCOptions, defaultServerFnOptions, escapeRegExp, formatError, getClientModules, getFunctionsForPrefix, getGlobalPrefix, getRequestContext, getRequestMeta, hasContentTypeMismatch, isFormContentType, provideRequestContext, redirect, safeURL, scanForServerFiles, scannedServerFiles, sendResponse, serverFunctionsByPrefix, serverFunctionsMap, setGlobalPrefix, walkGlobFiles };
|
|
511
550
|
|
|
512
551
|
//# sourceMappingURL=server.mjs.map
|