@thednp/rpc 0.0.1 → 0.0.4

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 +193 -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 +66 -1
  9. package/dist/fastify/fastify.d.mts.map +1 -1
  10. package/dist/fastify/fastify.mjs +89 -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 +78 -18
  15. package/dist/fastify/plugin/fastify/plugin.mjs.map +1 -1
  16. package/dist/helpers/helpers.d.mts +64 -3
  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 +66 -5
  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 +182 -18
  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 +104 -4
  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 -30
  37. package/wiki/adapters.md +31 -2
  38. package/wiki/best-practices.md +172 -9
  39. package/wiki/client-usage.md +7 -3
  40. package/wiki/configuration.md +5 -6
  41. package/wiki/getting-started.md +35 -6
  42. package/wiki/index.md +8 -20
  43. package/wiki/security.md +27 -5
  44. package/wiki/server-functions.md +49 -5
  45. package/wiki/setup.md +8 -4
package/dist/index.d.mts CHANGED
@@ -1,53 +1,138 @@
1
1
  import { Connect, Plugin } 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";
8
9
  import { Context, Next } from "koa";
9
10
  //#region src/express/types.d.ts
11
+ /**
12
+ * Express-specific middleware options, constrained to the `"express"` adapter.
13
+ */
10
14
  type ExpressMiddlewareOptions = MiddlewareOptions$1<"express">;
15
+ /**
16
+ * Express middleware factory: takes optional initial options and returns
17
+ * the Express/Connect-compatible handler.
18
+ */
11
19
  type ExpressMiddlewareFn = <A extends RpcPluginOptions$1["adapter"] = "express">(initialOptions?: Partial<ExpressMiddlewareOptions>) => ExpressMiddlewareHooks["handler"];
20
+ /**
21
+ * Express/Connect middleware handler signature used by the RPC middleware.
22
+ */
12
23
  interface ExpressMiddlewareHooks {
13
- handler: (req: IncomingMessage | Request, res: ServerResponse | Response$1, next: Connect.NextFunction | NextFunction) => Promise<void>;
24
+ /**
25
+ * The handler invoked for each matched request.
26
+ * @param req - Node or Express request object
27
+ * @param res - Node or Express response object
28
+ * @param next - Connect or Express next function
29
+ */
30
+ handler: (req: IncomingMessage | Request, res: ServerResponse | Response, next: Connect.NextFunction | NextFunction) => Promise<void>;
14
31
  }
15
32
  //#endregion
16
33
  //#region src/hono/types.d.ts
34
+ /**
35
+ * Hono middleware handler signature used by the RPC middleware.
36
+ */
17
37
  interface HonoMiddlewareHooks {
38
+ /** Hono middleware handler */
18
39
  handler: MiddlewareHandler;
19
40
  }
41
+ /**
42
+ * Hono middleware factory: takes optional initial options and returns
43
+ * the Hono-compatible handler.
44
+ */
20
45
  type HonoMiddlewareFn = <A extends RpcPluginOptions$1["adapter"] = "hono">(initialOptions?: Partial<MiddlewareOptions$1<A>>) => HonoMiddlewareHooks["handler"];
21
46
  //#endregion
22
47
  //#region src/fastify/types.d.ts
48
+ /**
49
+ * Fastify-specific middleware options, constrained to the `"fastify"` adapter.
50
+ */
23
51
  type FastifyMiddlewareOptions = MiddlewareOptions$1<"fastify">;
52
+ /**
53
+ * Fastify middleware factory: takes optional initial options and returns
54
+ * the Fastify-compatible handler.
55
+ */
24
56
  type FastifyMiddlewareFn = <A extends RpcPluginOptions$1["adapter"] = "fastify">(initialOptions?: Partial<FastifyMiddlewareOptions>) => FastifyMiddlewareHooks["handler"];
57
+ /**
58
+ * Fastify middleware handler signature used by the RPC middleware.
59
+ */
25
60
  interface FastifyMiddlewareHooks {
61
+ /**
62
+ * The handler invoked for each matched request.
63
+ * @param req - Fastify request object
64
+ * @param res - Fastify reply object
65
+ * @param done - Fastify hook completion callback
66
+ */
26
67
  handler: (req: FastifyRequest, res: FastifyReply, done: HookHandlerDoneFunction) => Promise<void>;
27
68
  }
28
69
  //#endregion
29
70
  //#region src/koa/types.d.ts
71
+ /**
72
+ * Koa-specific middleware options, constrained to the `"koa"` adapter.
73
+ */
30
74
  type KoaMiddlewareOptions = MiddlewareOptions$1<"koa">;
75
+ /**
76
+ * Koa middleware handler signature used by the RPC middleware.
77
+ */
31
78
  interface KoaMiddlewareHooks {
79
+ /**
80
+ * The handler invoked for each matched request.
81
+ * @param ctx - Koa context object
82
+ * @param next - Koa next function
83
+ */
32
84
  handler: (ctx: Context, next: Next) => Promise<void>;
33
85
  }
86
+ /**
87
+ * Koa middleware factory: takes optional initial options and returns
88
+ * the Koa-compatible handler.
89
+ */
34
90
  type KoaMiddlewareFn = <A extends RpcPluginOptions$1["adapter"] = "koa">(initialOptions?: Partial<KoaMiddlewareOptions>) => KoaMiddlewareHooks["handler"];
35
91
  //#endregion
36
92
  //#region src/types.d.ts
93
+ /**
94
+ * Maps each supported framework adapter to its middleware hooks (handler signatures).
95
+ * Used to keep the middleware options type-safe per adapter.
96
+ */
37
97
  interface FrameworkHooks {
98
+ /** Express/Connect middleware handler signature */
38
99
  express: ExpressMiddlewareHooks;
100
+ /** Hono middleware handler signature */
39
101
  hono: HonoMiddlewareHooks;
102
+ /** Fastify middleware handler signature */
40
103
  fastify: FastifyMiddlewareHooks;
104
+ /** Koa middleware handler signature */
41
105
  koa: KoaMiddlewareHooks;
42
106
  }
107
+ /**
108
+ * Maps each supported framework adapter to its middleware factory function type.
109
+ */
43
110
  interface FrameworkMiddlewareFn {
111
+ /** Express/Connect middleware factory */
44
112
  express: ExpressMiddlewareFn;
113
+ /** Hono middleware factory */
45
114
  hono: HonoMiddlewareFn;
115
+ /** Fastify middleware factory */
46
116
  fastify: FastifyMiddlewareFn;
117
+ /** Koa middleware factory */
47
118
  koa: KoaMiddlewareFn;
48
119
  }
120
+ /**
121
+ * Content types the RPC middleware accepts when reading request bodies.
122
+ * Only `application/json` and `text/plain` are currently supported.
123
+ */
49
124
  type SupportableContentType = "multipart/form-data" | "application/json" | "text/plain" | "application/octet-stream";
125
+ /**
126
+ * Content types the RPC client modules send with each request.
127
+ */
50
128
  type ContentType = "application/json" | "text/plain";
129
+ /**
130
+ * Fetch `credentials` policy used by the generated client modules.
131
+ */
132
+ type Credentials = "same-origin" | "include" | "omit";
133
+ /**
134
+ * Parsed request body result discriminated by content type.
135
+ */
51
136
  type BodyResult = {
52
137
  contentType: "application/json";
53
138
  data: JsonValue;
@@ -55,15 +140,47 @@ type BodyResult = {
55
140
  contentType: "text/plain";
56
141
  data: string;
57
142
  };
143
+ /**
144
+ * Options for a single server function, controlling how the generated
145
+ * client module serializes the request body and sends credentials.
146
+ */
58
147
  interface ServerFunctionOptions {
148
+ /**
149
+ * Content type used for the request body.
150
+ * @default "application/json"
151
+ */
59
152
  contentType: ContentType;
153
+ /**
154
+ * Fetch credentials policy.
155
+ * @default "same-origin"
156
+ */
157
+ credentials?: Credentials;
158
+ /**
159
+ * HTTP method used for the RPC request.
160
+ * GET requests send arguments as an `?args=` JSON query parameter
161
+ * (a fetch request body is not allowed on GET).
162
+ * @default "POST"
163
+ */
164
+ method?: "GET" | "POST";
60
165
  }
61
166
  // primitives and their compositions
167
+ /**
168
+ * Primitive JSON values, including `undefined` for optional parameters.
169
+ */
62
170
  type JsonPrimitive = string | number | boolean | null | undefined;
171
+ /**
172
+ * A JSON object whose values are JSON values or arrays.
173
+ */
63
174
  type JsonObject = {
64
175
  [key: string]: JsonValue | JsonArray;
65
176
  };
177
+ /**
178
+ * A JSON array of JSON values.
179
+ */
66
180
  type JsonArray = JsonValue[];
181
+ /**
182
+ * Any JSON-serializable value: primitive, array, or object.
183
+ */
67
184
  type JsonValue = JsonPrimitive | JsonArray | JsonObject;
68
185
  // Keep these as a refference
69
186
  // Date strings are common in APIs
@@ -80,28 +197,55 @@ type JsonValue = JsonPrimitive | JsonArray | JsonObject;
80
197
  // | Blob // for binary data
81
198
  // | URLSearchParams; // for query parameters
82
199
  // export type ServerFnArgs = [JsonObject | JsonPrimitive, ...JsonArray];
200
+ /**
201
+ * Arguments passed to a server function, spread as a JSON array.
202
+ */
83
203
  type ServerFnArgs = [...JsonArray];
204
+ /**
205
+ * Server-side handler signature: receives the `AbortSignal` first,
206
+ * followed by any serializable arguments.
207
+ */
84
208
  type ServerFunction<TArgs extends JsonArray = JsonArray, TResult extends JsonValue = JsonValue> = (signal: AbortSignal, ...args: TArgs) => Promise<TResult>;
209
+ /**
210
+ * Server function initialization signature, identical to `ServerFunction`.
211
+ * Used when registering a function with `createServerFunction`.
212
+ */
85
213
  type ServerFunctionInit<TArgs extends JsonArray = JsonArray, TResult extends JsonValue = JsonValue> = (signal: AbortSignal, ...args: TArgs) => Promise<TResult>;
214
+ /**
215
+ * Client-side stub signature generated for each server function.
216
+ * Returns a promise-backed `data` handle plus a `cancel` function
217
+ * that aborts the underlying fetch request.
218
+ */
86
219
  type ClientFunction<TArgs extends JsonArray = JsonArray, TResult extends JsonValue = JsonValue> = (...args: TArgs) => {
220
+ /** Promise resolving to the server response data */
87
221
  data: Promise<TResult>;
222
+ /** Aborts the in-flight request with the given reason */
88
223
  cancel: (reason: string) => void;
89
224
  };
225
+ /**
226
+ * A client function augmented with its registered export name and
227
+ * per-function options (content type, credentials).
228
+ */
90
229
  type ClientFunctionWithOptions = ClientFunction & {
230
+ /** Registered export name of the server function */
91
231
  name: string;
232
+ /** Per-function content type and credentials options */
92
233
  options?: ServerFunctionOptions;
93
234
  };
235
+ /**
236
+ * Entry in the server functions map: registered name, client handler,
237
+ * optional per-function options, and the original export name.
238
+ */
94
239
  interface ServerFnEntry {
240
+ /** Registered RPC function name (used in the URL path) */
95
241
  name: string;
242
+ /** Client-side handler stub for this function */
96
243
  handler: ClientFunctionWithOptions;
244
+ /** Per-function content type and credentials options */
97
245
  options?: ServerFunctionOptions;
246
+ /** Original export name from the server module */
98
247
  exportName?: string;
99
248
  }
100
- interface CacheEntry<T> {
101
- data?: T;
102
- timestamp: number;
103
- promise?: Promise<T>;
104
- }
105
249
  /**
106
250
  * ### @thednp/rpc
107
251
  * The plugin configuration allows for granular control of your
@@ -117,9 +261,9 @@ interface RpcPluginOptions {
117
261
  * @default "__rpc"
118
262
  * @example
119
263
  * // Results in endpoints like: /api/rpc/myFunction
120
- * rpcPreffix: "api/rpc"
264
+ * rpcPrefix: "api/rpc"
121
265
  */
122
- rpcPreffix: "__rpc" | string;
266
+ rpcPrefix: "__rpc" | string;
123
267
  /**
124
268
  * Option to set an adapter for the middleware connection. The default is _express_,
125
269
  * which is the most popular and battle tested server app. The _express_ adapter is
@@ -152,9 +296,17 @@ interface MiddlewareOptions<A extends RpcPluginOptions["adapter"] = "express"> {
152
296
  * @default string
153
297
  * @example
154
298
  * // Results in endpoints like: /api/rpc/myFunction
155
- * rpcPreffix: "api/rpc"
299
+ * rpcPrefix: "api/rpc"
156
300
  */
157
- rpcPreffix?: string | false;
301
+ rpcPrefix?: string | false;
302
+ /**
303
+ * Allowed request origin (e.g. "https://example.com").
304
+ * When set, any request carrying an `Origin` header that does not match
305
+ * is rejected with a 403 Forbidden response. Requests without an `Origin`
306
+ * header (curl, native clients) pass through unchecked.
307
+ * When unset (default), no origin validation is performed.
308
+ */
309
+ origin?: string;
158
310
  /**
159
311
  * Async handler for request processing.
160
312
  * Core middleware function that processes incoming requests.
@@ -177,16 +329,28 @@ interface MiddlewareOptions<A extends RpcPluginOptions["adapter"] = "express"> {
177
329
  //#endregion
178
330
  //#region src/index.d.ts
179
331
  /**
180
- * Utility to define `@thednp/rpc` configuration file similar to vite.
181
- * @param uniConfig a system wide RPC configuration
332
+ * Type-safe helper to create an RPC configuration object.
333
+ * Merges the provided partial config with built-in defaults.
334
+ * @param uniConfig - System-wide RPC configuration overrides
335
+ * @returns Complete RPC plugin options with defaults applied
336
+ */
337
+ declare const defineConfig: (c: Partial<RpcPluginOptions>) => RpcPluginOptions;
338
+ /**
339
+ * Loads the RPC configuration by searching for config files in the project root.
340
+ * Searches in order: `rpc.config.ts`, `rpc.config.js`, `rpc.config.mjs`, `rpc.config.mts`,
341
+ * `.rpcrc.ts`, `.rpcrc.js`. Falls back to defaults if none found.
342
+ * @param configFile - Optional explicit config file path; skips file search when provided
343
+ * @returns Resolved RPC plugin options
182
344
  */
183
- declare const defineConfig: (uniConfig: Partial<RpcPluginOptions>) => RpcPluginOptions;
345
+ declare const loadRPCConfig: (f?: string) => Promise<RpcPluginOptions>;
184
346
  /**
185
- * Utility to load `@thednp/rpc` configuration file system wide.
186
- * @param configFile an optional parameter to specify a file within your project scope
347
+ * Vite plugin that enables automatic RPC generation.
348
+ * Transforms server function imports into fetch-based client stubs during development and production builds.
349
+ * In dev mode, attaches the RPC middleware to Vite's Connect server.
350
+ * @param devOptions - Development-only overrides (merged on top of config file values)
351
+ * @returns A Vite plugin object
187
352
  */
188
- declare function loadRPCConfig(configFile?: string): Promise<RpcPluginOptions>;
189
353
  declare function rpcPlugin(devOptions?: Partial<RpcPluginOptions>): Plugin<unknown>;
190
354
  //#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 };
355
+ export { type BodyResult, type ClientFunction, type ClientFunctionWithOptions, type ContentType, type Credentials, 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 };
192
356
  //# 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;;;;;;;UC/BU;;EAEf,SAAS;;;;;;KAOC,oBAAoB,UAAU,wCACxC,iBAAiB,QAAQ,oBAAkB,QACxC;;;;;;KCZO,2BAA2B;;;;;KAM3B,uBACV,UAAU,2CAEV,iBAAiB,QAAQ,8BACtB;;;;UAKY;;;;;;;EAOf,UACE,KAAK,gBACL,KAAK,cACL,MAAM,4BACH;;;;;;;KCzBK,uBAAuB;;;;UAalB;;;;;;EAMf,UAAU,KAAK,SAAS,MAAM,SAAS;;;;;;KAO7B,mBAAmB,UAAU,uCACvC,iBAAiB,QAAQ,0BACtB;;;;;;;UCpBY;;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;;;;;;UAOK;;EAEf;;EAEA,SAAS;;EAET,UAAU;;EAEV;;;;;;;;UASe;;;;;;;;;;;EAWf;;;;;;;EAQA;;UAGe,kBACf,UAAU;;;;EAKV;;;;;;;;;;;;EAaA,gBAAgB;;;;;;;;;;EAWhB;;;;;;;;EASA;;;;;;;;;;;;;;;;;;EAmBA,UAAU,eAAe;;;;;;;;;;cC7OrB,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, {