@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.
Files changed (41) hide show
  1. package/AGENTS.md +34 -19
  2. package/CHANGELOG.md +74 -0
  3. package/README.md +23 -10
  4. package/dist/config/config.d.mts +65 -0
  5. package/dist/config/config.d.mts.map +1 -0
  6. package/dist/config/config.mjs +24 -0
  7. package/dist/config/config.mjs.map +1 -0
  8. package/dist/express/express.d.mts +4 -2
  9. package/dist/express/express.d.mts.map +1 -1
  10. package/dist/express/express.mjs +41 -8
  11. package/dist/express/express.mjs.map +1 -1
  12. package/dist/fastify/fastify.d.mts.map +1 -1
  13. package/dist/fastify/fastify.mjs +37 -6
  14. package/dist/fastify/fastify.mjs.map +1 -1
  15. package/dist/fastify/plugin/fastify/plugin.d.mts +13 -0
  16. package/dist/fastify/plugin/fastify/plugin.d.mts.map +1 -1
  17. package/dist/fastify/plugin/fastify/plugin.mjs +37 -6
  18. package/dist/fastify/plugin/fastify/plugin.mjs.map +1 -1
  19. package/dist/h3/h3.d.mts.map +1 -1
  20. package/dist/h3/h3.mjs +47 -16
  21. package/dist/h3/h3.mjs.map +1 -1
  22. package/dist/helpers/helpers.d.mts +63 -5
  23. package/dist/helpers/helpers.d.mts.map +1 -1
  24. package/dist/helpers/helpers.mjs +58 -1
  25. package/dist/helpers/helpers.mjs.map +1 -1
  26. package/dist/hono/hono.d.mts.map +1 -1
  27. package/dist/hono/hono.mjs +37 -6
  28. package/dist/hono/hono.mjs.map +1 -1
  29. package/dist/index.d.mts +48 -12
  30. package/dist/index.d.mts.map +1 -1
  31. package/dist/index.mjs +92 -61
  32. package/dist/index.mjs.map +1 -1
  33. package/dist/koa/koa.d.mts.map +1 -1
  34. package/dist/koa/koa.mjs +47 -16
  35. package/dist/koa/koa.mjs.map +1 -1
  36. package/dist/server/server.d.mts +219 -26
  37. package/dist/server/server.d.mts.map +1 -1
  38. package/dist/server/server.mjs +108 -69
  39. package/dist/server/server.mjs.map +1 -1
  40. package/llms.txt +81 -0
  41. package/package.json +23 -19
@@ -1,15 +1,95 @@
1
- import { ResolvedConfig, ViteDevServer } from "vite";
1
+ import { Connect, ResolvedConfig, ViteDevServer } from "vite";
2
2
  import "@thednp/rpc";
3
- import "express";
4
- import "hono";
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
- type ScanConfig = Pick<ResolvedConfig, "base"> & {
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 global `serverFunctionsMap` with their exported functions.
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 a client-compatible
185
- * wrapper that exposes `data` (Promise) and `cancel` (function) for request lifecycle control.
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 credentials settings
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?: Partial<ServerFunctionOptions>): ClientFunction<TArgs, TResult>;
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 (or Oxc) during
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
- contentType: "application/json";
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":";;;;;;;;;;;;;;;KAkEY;;;;KASA;;;;;UAkBK;;;;;EAKf,aAAa;;;;;EAKb,cAAc;;;;;;;EAOd;;;;;;KAOU;;;;KAIA;GAAgB,cAAc,YAAY;;;;;KAI1C,aAAa,WAAW;;;;KAIxB,YAAY,gBAAgB,YAAY;;;;;KAsCxC,mBACV,cAAc,WAAW,YAAY,WACrC,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;EAC5B;EACA,SAAS,QAAQ;EACjB;EACA;;;;;;UAOe;;EAEf;;EAEA,SAAS;;EAET,UAAU;;EAEV;;;;;;;;UASe;;;;;;;;;;;EAWf;;;;;;;EAQA;;;;;;;EAQA;;;;;;;EAQA;;;;cChRW,oBAAoB,YAAY;;;;cCehC,oBAAoB;;;;;;;;;;;;cAepB,qBAAkB,aAChB,YAAU,YACX,kBACX;;;;;;;;;;;;iBCda,qBACd,cAAc,YAAY,WAC1B,gBAAgB,YAAY,WAE5B,cACA,SAAS,mBAAmB,OAAO,UACnC,YAAW,QAAQ,yBAClB,eAAe,OAAO;;;;;;;;;;cCgEZ,mBAAgB,gBACX;;;;;;;cChFL,gBAAa,gBAAwB;;;;;;cA4BrC,iBAAiB;;EAE5B;;EAEA,OAAO;EACP,YAAY,iBAAiB,eAAmB,OAAO;;;;;;;;;;cAgB5C,cAAW,cACV,0BAEX;;;;;;;cAqBU,oBAAiB;;;;;;;;;;;cAcjB,yBAAsB,UACvB,aAAW;;;;;;;;iBAqBP,aAAa;;;;;;;;;;;;;;cAmBhB,UAAO,gBAAkB,kBAAyB;;;;;;;;;;;;;;;;;;UC/G9C;;EAEf;;EAEA;;EAEA;;;;;;;EAOA,WAAW,kBAAkB;;;;;EAK7B;IAAe;IAAkB;;;;;;;;;;;EAUjC,OACE,gBACA,MAAM,WACN,UAAU;;;;;EAMZ;IAAS;IAAgB,MAAM;IAAW,UAAU;;;;;;EAKpD;;EAEA,QAAQ;GACP;;;;;;;;;cAiBU,wBAAyB,GAAC,MAC/B,cAAY,UACR,MACT;;;;;;cAOU,yBAAwB;;;;;;;;;cAgBxB,WAAQ,kBAAoB;;;;;;;;;;;;cAe5B,eAAY,gBACT,MACR,WAAS,UACL;;;;;;;UAWK;;EAEf;;EAEA;;EAEA;;EAEA,cAAc;;EAEd,SAAS;;EAET;;EAEA;;EAEA;;;;;;;;;;cAsCW,iBAAc,OAAW,iBAAe;;;cCnMxC;;;;;cAMA,mBAAmB;cAOnB"}
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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"}
@@ -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
- const serverFunctionsMap = /* @__PURE__ */ new Map();
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_$][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 global `serverFunctionsMap` with their exported functions.
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
- const { createServer, normalizePath } = await import("vite");
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
- if (seenNames.has(registeredName)) {
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(registeredName);
213
- serverFunctionsMap.set(registeredName, {
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?.options,
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 a client-compatible
249
- * wrapper that exposes `data` (Promise) and `cancel` (function) for request lifecycle control.
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 credentials settings
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
- serverFunctionsMap.set(name, {
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_$@][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, hyphens, and forward slashes.
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
- let body = "";
362
- let headers = "{}";
363
- switch (options.contentType) {
364
- case "text/plain":
365
- body = `args[0]`;
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} = (...args) => {
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 (or Oxc) during
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 { innerModule } from "@thednp/rpc/helpers";
407
- ${Array.from(serverFunctionsMap.entries()).filter(([, entry]) => entry.exportName).map(([registeredName, entry]) => getModule(registeredName, entry.exportName, {
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