@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.
Files changed (45) hide show
  1. package/AGENTS.md +5 -5
  2. package/CLAUDE.md +1 -0
  3. package/README.md +195 -47
  4. package/dist/express/express.d.mts +110 -16
  5. package/dist/express/express.d.mts.map +1 -1
  6. package/dist/express/express.mjs +113 -20
  7. package/dist/express/express.mjs.map +1 -1
  8. package/dist/fastify/fastify.d.mts +83 -5
  9. package/dist/fastify/fastify.d.mts.map +1 -1
  10. package/dist/fastify/fastify.mjs +84 -18
  11. package/dist/fastify/fastify.mjs.map +1 -1
  12. package/dist/fastify/plugin/fastify/plugin.d.mts +64 -9
  13. package/dist/fastify/plugin/fastify/plugin.d.mts.map +1 -1
  14. package/dist/fastify/plugin/fastify/plugin.mjs +73 -18
  15. package/dist/fastify/plugin/fastify/plugin.mjs.map +1 -1
  16. package/dist/helpers/helpers.d.mts +66 -4
  17. package/dist/helpers/helpers.d.mts.map +1 -1
  18. package/dist/helpers/helpers.mjs +34 -7
  19. package/dist/helpers/helpers.mjs.map +1 -1
  20. package/dist/hono/hono.d.mts +75 -6
  21. package/dist/hono/hono.d.mts.map +1 -1
  22. package/dist/hono/hono.mjs +84 -24
  23. package/dist/hono/hono.mjs.map +1 -1
  24. package/dist/index.d.mts +210 -19
  25. package/dist/index.d.mts.map +1 -1
  26. package/dist/index.mjs +137 -31
  27. package/dist/index.mjs.map +1 -1
  28. package/dist/koa/koa.d.mts +52 -0
  29. package/dist/koa/koa.d.mts.map +1 -1
  30. package/dist/koa/koa.mjs +86 -16
  31. package/dist/koa/koa.mjs.map +1 -1
  32. package/dist/server/server.d.mts +111 -10
  33. package/dist/server/server.d.mts.map +1 -1
  34. package/dist/server/server.mjs +117 -17
  35. package/dist/server/server.mjs.map +1 -1
  36. package/package.json +48 -31
  37. package/wiki/adapters.md +0 -143
  38. package/wiki/best-practices.md +0 -201
  39. package/wiki/client-usage.md +0 -62
  40. package/wiki/configuration.md +0 -77
  41. package/wiki/getting-started.md +0 -76
  42. package/wiki/index.md +0 -26
  43. package/wiki/security.md +0 -54
  44. package/wiki/server-functions.md +0 -93
  45. 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 as Response$1 } from "express";
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
- handler: (req: IncomingMessage | Request, res: ServerResponse | Response$1, next: Connect.NextFunction | NextFunction) => Promise<void>;
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
- * rpcPreffix: "api/rpc"
281
+ * rpcPrefix: "api/rpc"
121
282
  */
122
- rpcPreffix: "__rpc" | string;
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
- * rpcPreffix: "api/rpc"
316
+ * rpcPrefix: "api/rpc"
156
317
  */
157
- rpcPreffix?: string | false;
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
- * Utility to define `@thednp/rpc` configuration file similar to vite.
181
- * @param uniConfig a system wide RPC configuration
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 defineConfig: (uniConfig: Partial<RpcPluginOptions>) => RpcPluginOptions;
372
+ declare const loadRPCConfig: (f?: string) => Promise<RpcPluginOptions>;
184
373
  /**
185
- * Utility to load `@thednp/rpc` configuration file system wide.
186
- * @param configFile an optional parameter to specify a file within your project scope
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 CacheEntry, type ClientFunction, type ClientFunctionWithOptions, type ContentType, type FrameworkHooks, type FrameworkMiddlewareFn, type JsonArray, type JsonObject, type JsonPrimitive, type JsonValue, type MiddlewareOptions, type RpcPluginOptions, type ServerFnArgs, type ServerFnEntry, type ServerFunction, type ServerFunctionInit, type ServerFunctionOptions, type SupportableContentType, rpcPlugin as default, defineConfig, loadRPCConfig };
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
@@ -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":";;;;;;;;;KAKY,2BAA2B;KAE3B,uBACV,UAAU,2CAEV,iBAAiB,QAAQ,8BACtB;UAEY;EACf,UACE,KAAK,kBAAkB,SACvB,KAAK,iBAAiB,YACtB,MAAM,QAAQ,eAAe,iBAC1B;;;;UCbU;EACf,SAAS;;KAGC,oBAAoB,UAAU,wCACxC,iBAAiB,QAAQ,oBAAkB,QACxC;;;KCJO,2BAA2B;KAE3B,uBACV,UAAU,2CAEV,iBAAiB,QAAQ,8BACtB;UAEY;EACf,UACE,KAAK,gBACL,KAAK,cACL,MAAM,4BACH;;;;KCZK,uBAAuB;UAMlB;EACf,UAAU,KAAK,SAAS,MAAM,SAAS;;KAG7B,mBAAmB,UAAU,uCACvC,iBAAiB,QAAQ,0BACtB;;;UCLY;EACf,SAAS;EACT,MAAM;EACN,SAAS;EACT,KAAK;;UAGU;EACf,SAAS;EACT,MAAM;EACN,SAAS;EACT,KAAK;;KAGK;KAMA;KAEA;EACN;EAAiC,MAAM;;EACvC;EAA2B;;UAEhB;EACf,aAAa;;;KAIH;KACA;GAAgB,cAAc,YAAY;;KAC1C,YAAY;KACZ,YAAY,gBAAgB,YAAY;;;;;;;;;;;;;;;;KAoBxC,mBAAmB;KAEnB,eACV,cAAc,YAAY,WAC1B,gBAAgB,YAAY,cACzB,QAAQ,gBAAgB,MAAM,UAAU,QAAQ;KAEzC,mBACV,cAAc,YAAY,WAC1B,gBAAgB,YAAY,cACzB,QAAQ,gBAAgB,MAAM,UAAU,QAAQ;KAEzC,eACV,cAAc,YAAY,WAC1B,gBAAgB,YAAY,iBACtB,MAAM;EACZ,MAAM,QAAQ;EACd,SAAS;;KAGC,4BAA4B;EACtC;EACA,UAAU;;UAGK;EACf;EACA,SAAS;EACT,UAAU;EACV;;UAGe,WAAW;EAC1B,OAAO;EACP;EACA,UAAU,QAAQ;;;;;;;;UASH;;;;;;;;;;;EAWf;;;;;;;EAQA;;UAGe,kBACf,UAAU;;;;EAKV;;;;;;;;;;;;EAaA,gBAAgB;;;;;;;;;;EAWhB;;;;;;;;;;;;;;;;;;EAmBA,UAAU,eAAe;;;;;;;;cC1JrB,eAAY,WAAe,QAAQ,sBACa;;;;;iBASvC,cAAc,sBAAmB,QAAA;iBA0FvC,UACP,aAAY,QAAQ,oBACnB"}
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
- rpcPreffix: "__rpc",
8
+ rpcPrefix: "__rpc",
9
9
  adapter: "express"
10
10
  };
11
11
  const defaultMiddlewareOptions = {
12
- rpcPreffix: void 0,
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 rpcPreffix = options.rpcPreffix;
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(`The middleware name "${name}" is already used.`);
187
+ if (middlewareStack.has(name)) throw new Error(MIDDLEWARE_NAME_USED(name));
125
188
  middlewareStack.add(name);
126
- const prefixRegex = rpcPreffix ? new RegExp(`^/${escapeRegExp(rpcPreffix)}/`) : null;
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, _res, next) => {
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, _res, next);
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, { rpcPreffix: defaultRPCOptions.rpcPreffix }, initialOptions);
141
- const rpcPreffix = options.rpcPreffix;
142
- const prefixRegex = rpcPreffix ? new RegExp(`^/${escapeRegExp(rpcPreffix)}/`) : null;
143
- const prefixReplace = `/${rpcPreffix}/`;
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(url)) return;
150
- const functionName = url.replace(prefixReplace, "");
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: "Function not found" });
229
+ sendResponse(404, { error: FUNCTION_NOT_FOUND });
154
230
  return;
155
231
  }
156
232
  try {
157
- const body = await readBody(req);
158
- const args = Array.isArray(body.data) ? body.data : [body.data];
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("client disconnected");
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: "Internal Server 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
- * Utility to define `@thednp/rpc` configuration file similar to vite.
186
- * @param uniConfig a system wide RPC configuration
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
- * Utility to load `@thednp/rpc` configuration file system wide.
194
- * @param configFile an optional parameter to specify a file within your project scope
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 function loadRPCConfig(configFile) {
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(` ⚠︎ The specified RPC config file ${configFile} cannot be found at ${configFilePath}, loading the defaults..`);
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(` ⚡︎ No RPC config found, loading the defaults..`);
340
+ console.warn(NO_CONFIG_FOUND);
243
341
  } catch (error) {
244
342
  RPCConfig = defaultRPCOptions;
245
- console.warn(` ⚠︎ Failed to load RPC config:`, error);
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
- options = mergeConfig(await loadRPCConfig(), devOptions);
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
- rpcPreffix: options.rpcPreffix,
385
+ rpcPrefix: options.rpcPrefix,
280
386
  adapter: options.adapter
281
387
  });
282
388
  const result = await vite[transformer](source, id, {