@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
@@ -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/options.ts"],"mappings":";;;;;;;;KAmCY;UAMK;EACf,aAAa;;;KAIH;KACA;GAAgB,cAAc,YAAY;;KAC1C,YAAY;KACZ,YAAY,gBAAgB,YAAY;KA2BxC,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;;;;;;;;UAee;;;;;;;;;;;EAWf;;;;;;;EAQA;;;;cClIW,oBAAkB,YAAA;;;KCO1B,aAAa,KAAK;EACrB,SAAS,QAAQ;;cAIN,qBAAkB,aAChB,YAAU,YACX,kBAAa;;;iBCLX,qBACd,cAAc,YAAY,WAC1B,gBAAgB,YAAY,WAE5B,cACA,SAAS,mBAAmB,OAAO,UACnC,YAAW,QAAQ,yBAA2B,eAAA,OAAA;;;UChBtC;EACR;EACA;;cAwEW,mBAAgB,gBAAoB;;;cCrEpC;EAAA;;cAIA,mBAAmB;cAKnB;EAAA;EAAA"}
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/options.ts"],"mappings":";;;;;;;;;;;;KAyDY;;;;KAKA;;;;;UAaK;;;;;EAKf,aAAa;;;;;EAKb,cAAc;;;;;;;EAOd;;;;;;KAOU;;;;KAIA;GAAgB,cAAc,YAAY;;;;;KAI1C,YAAY;;;;KAIZ,YAAY,gBAAgB,YAAY;;;;;KAsCxC,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;;;;cC1NW,oBAAoB,YAAY;;;;;;KCWxC,aAAa,KAAK;;EAErB,SAAS,QAAQ;;;;;;;;;cAWN,qBAAkB,aAChB,YAAU,YACX,kBACX;;;;;;;;;;;;iBCRa,qBACd,cAAc,YAAY,WAC1B,gBAAgB,YAAY,WAE5B,cACA,SAAS,mBAAmB,OAAO,UACnC,YAAW,QAAQ,yBAClB,eAAe,OAAO;;;;;;UCzBf;;EAER;;EAEA;;;;;;;;;cA2EW,mBAAgB,gBACX;;;cC7EL;;;;;cAMA,mBAAmB;cAKnB"}
@@ -5,8 +5,24 @@ import process from "node:process";
5
5
  //#region src/functionsMap.ts
6
6
  const serverFunctionsMap = /* @__PURE__ */ new Map();
7
7
  //#endregion
8
+ //#region src/constants.ts
9
+ const OPERATION_ABORTED = "Operation aborted";
10
+ const NO_SERVER_FUNCTION_FOUND = "No server function found.";
11
+ const ERROR_LOADING_FILE = "Error loading file:";
12
+ /** Error message when a value fails the safe-identifier validation. @param label - What kind of value was being validated. @param name - The rejected value */
13
+ const INVALID_IDENTIFIER = (label, name) => `Invalid ${label}: "${name}" must match /^[A-Za-z_$][A-Za-z0-9_$]*$/`;
14
+ /** 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 */
15
+ const INVALID_PATH_SEGMENT = (label, segment) => `Invalid ${label}: "${segment}" must match /^[A-Za-z0-9_$][A-Za-z0-9_$/-]*$/`;
16
+ //#endregion
8
17
  //#region src/scanForServerFiles.ts
9
18
  let isScanned = false;
19
+ /**
20
+ * Scans `src/api/` for server function files (`server.ts`, `server.js`, `server.mjs`, `server.mts`)
21
+ * and populates the global `serverFunctionsMap` with their exported functions.
22
+ * Uses Vite's SSR module loading to resolve and execute each file.
23
+ * @param initialCfg - Optional Vite config overrides (root, base, server)
24
+ * @param devServer - Optional running Vite dev server instance; when provided, skips creating a new one
25
+ */
10
26
  const scanForServerFiles = async (initialCfg, devServer) => {
11
27
  if (isScanned && !devServer) return;
12
28
  const config = !initialCfg && !devServer || !initialCfg ? {
@@ -33,7 +49,7 @@ const scanForServerFiles = async (initialCfg, devServer) => {
33
49
  const apiDir = join(config.root, "src", "api");
34
50
  let files;
35
51
  try {
36
- files = (await readdir(apiDir, { withFileTypes: true })).filter((f) => svFiles.some((fn) => f.name.includes(fn))).map((f) => join(apiDir, f.name));
52
+ files = (await readdir(apiDir, { withFileTypes: true })).filter((f) => svFiles.includes(f.name)).map((f) => join(apiDir, f.name));
37
53
  } catch (_e) {
38
54
  files = [];
39
55
  }
@@ -42,7 +58,7 @@ const scanForServerFiles = async (initialCfg, devServer) => {
42
58
  const moduleExports = await server.ssrLoadModule(file);
43
59
  const moduleEntries = Object.entries(moduleExports);
44
60
  if (!moduleEntries.length) {
45
- console.warn("No server function found.");
61
+ console.warn(NO_SERVER_FUNCTION_FOUND);
46
62
  return;
47
63
  }
48
64
  for (const [exportName, exportValue] of moduleEntries) {
@@ -55,7 +71,7 @@ const scanForServerFiles = async (initialCfg, devServer) => {
55
71
  });
56
72
  }
57
73
  } catch (error) {
58
- console.error("Error loading file:", file, error);
74
+ console.error(ERROR_LOADING_FILE, file, error);
59
75
  }
60
76
  } finally {
61
77
  if (!devServer && server) await server.close();
@@ -64,24 +80,38 @@ const scanForServerFiles = async (initialCfg, devServer) => {
64
80
  };
65
81
  //#endregion
66
82
  //#region src/options.ts
67
- const defaultServerFnOptions = { contentType: "application/json" };
83
+ const defaultServerFnOptions = {
84
+ contentType: "application/json",
85
+ credentials: "same-origin",
86
+ method: "POST"
87
+ };
68
88
  const defaultRPCOptions = {
69
- rpcPreffix: "__rpc",
89
+ rpcPrefix: "__rpc",
70
90
  adapter: "express"
71
91
  };
72
92
  const defaultMiddlewareOptions = {
73
- rpcPreffix: void 0,
74
- path: void 0
93
+ rpcPrefix: void 0,
94
+ path: void 0,
95
+ origin: void 0
75
96
  };
76
97
  //#endregion
77
98
  //#region src/createFunction.ts
99
+ /**
100
+ * Creates a server-side RPC function.
101
+ * Registers the function in the server functions map and returns a client-compatible
102
+ * wrapper that exposes `data` (Promise) and `cancel` (function) for request lifecycle control.
103
+ * @param name - Unique identifier used by the RPC router to dispatch requests
104
+ * @param handler - The actual implementation receiving an AbortSignal followed by JSON-serializable arguments
105
+ * @param fnOptions - Optional contentType and credentials settings
106
+ * @returns A client stub with `data` promise and `cancel` method, auto-registered in the server map
107
+ */
78
108
  function createServerFunction(name, handler, fnOptions = {}) {
79
109
  const options = Object.assign({}, defaultServerFnOptions, fnOptions);
80
110
  const wrappedFunction = (...args) => {
81
111
  const controller = new AbortController();
82
112
  const cancel = (reason) => controller.abort(reason);
83
113
  const fetcher = async () => {
84
- if (controller.signal.aborted) throw new Error("Operation aborted");
114
+ if (controller.signal.aborted) throw new Error(OPERATION_ABORTED);
85
115
  return await handler(controller.signal, ...args);
86
116
  };
87
117
  return {
@@ -93,12 +123,12 @@ function createServerFunction(name, handler, fnOptions = {}) {
93
123
  name: {
94
124
  value: name,
95
125
  enumerable: true,
96
- configurable: true
126
+ configurable: false
97
127
  },
98
128
  options: {
99
129
  value: options,
100
130
  enumerable: true,
101
- configurable: true
131
+ configurable: false
102
132
  }
103
133
  });
104
134
  serverFunctionsMap.set(name, {
@@ -109,21 +139,78 @@ function createServerFunction(name, handler, fnOptions = {}) {
109
139
  return wrappedFunction;
110
140
  }
111
141
  //#endregion
112
- //#region src/getClientModules.ts
142
+ //#region src/validate.ts
113
143
  const SAFE_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
114
144
  const SAFE_PATH_SEGMENT = /^[A-Za-z0-9_$][A-Za-z0-9_$/-]*$/;
145
+ const CREDENTIALS_VALUES = [
146
+ "same-origin",
147
+ "include",
148
+ "omit"
149
+ ];
150
+ /**
151
+ * Validates that a string is a safe JavaScript identifier.
152
+ * Used to prevent code injection when interpolating export names into generated client code.
153
+ * @param name - The string to validate
154
+ * @param label - Human-readable label for error messages (e.g. "export name")
155
+ * @returns The validated name if it passes
156
+ * @throws Error if the name contains characters outside /^[A-Za-z_$][A-Za-z0-9_$]*$/
157
+ */
115
158
  function validateIdentifier(name, label) {
116
- if (!SAFE_IDENTIFIER.test(name)) throw new Error(`Invalid ${label}: "${name}" must match /^[A-Za-z_$][A-Za-z0-9_$]*$/`);
159
+ if (!SAFE_IDENTIFIER.test(name)) throw new Error(INVALID_IDENTIFIER(label, name));
117
160
  return name;
118
161
  }
162
+ /**
163
+ * Validates that a string is a safe path segment for RPC routing.
164
+ * Allows alphanumeric characters, underscores, dollar signs, hyphens, and forward slashes.
165
+ * @param segment - The string to validate
166
+ * @param label - Human-readable label for error messages (e.g. "rpcPrefix")
167
+ * @returns The validated segment if it passes
168
+ * @throws Error if the segment contains disallowed characters
169
+ */
119
170
  function validatePathSegment(segment, label) {
120
- if (!SAFE_PATH_SEGMENT.test(segment)) throw new Error(`Invalid ${label}: "${segment}" must match /^[A-Za-z0-9_$][A-Za-z0-9_$/-]*$/`);
171
+ if (!SAFE_PATH_SEGMENT.test(segment)) throw new Error(INVALID_PATH_SEGMENT(label, segment));
121
172
  return segment;
122
173
  }
174
+ /**
175
+ * Validates and normalizes the credentials option.
176
+ * Accepts "same-origin", "include", or "omit"; defaults to "same-origin" when undefined.
177
+ * @param value - Credentials value to validate
178
+ * @returns The validated credentials string
179
+ * @throws Error if the value is not one of the accepted credentials
180
+ */
181
+ function validateCredentials(value) {
182
+ const creds = value || "same-origin";
183
+ if (!CREDENTIALS_VALUES.includes(creds)) throw new Error(`Invalid credentials: "${value}" must be one of ${CREDENTIALS_VALUES.join(", ")}`);
184
+ return creds;
185
+ }
186
+ /**
187
+ * Validates and normalizes the HTTP method option for a server function.
188
+ * Accepts "GET" or "POST" (case-insensitive); defaults to "POST" when undefined.
189
+ * @param value - Method value to validate
190
+ * @returns The validated uppercase method string
191
+ * @throws Error if the value is not "GET" or "POST"
192
+ */
193
+ function validateMethod(value) {
194
+ const method = (value || "POST").toUpperCase();
195
+ if (method !== "GET" && method !== "POST") throw new Error(`Invalid method: "${value}" must be one of GET, POST`);
196
+ return method;
197
+ }
198
+ //#endregion
199
+ //#region src/getClientModules.ts
200
+ /**
201
+ * Generates a JavaScript client module string for a single server function.
202
+ * All interpolated values are validated to prevent code injection.
203
+ * @param fnName - Registered RPC function name (validated as path segment)
204
+ * @param fnEntry - Export name used in the generated module (validated as identifier)
205
+ * @param options - Content type, credentials, and RPC prefix settings
206
+ * @returns A string of JavaScript code exporting the client stub
207
+ */
123
208
  const getModule = (fnName, fnEntry, options) => {
124
209
  const safeFnName = validatePathSegment(fnName, "function name");
125
210
  const safeFnEntry = validateIdentifier(fnEntry, "export name");
126
- const safePrefix = validatePathSegment(options.rpcPreffix, "rpcPreffix");
211
+ const safePrefix = validatePathSegment(options.rpcPrefix, "rpcPrefix");
212
+ const credentials = validateCredentials(options.credentials);
213
+ const method = validateMethod(options.method);
127
214
  let body = "";
128
215
  let headers = "{}";
129
216
  switch (options.contentType) {
@@ -135,17 +222,30 @@ const getModule = (fnName, fnEntry, options) => {
135
222
  body = `JSON.stringify(args)`;
136
223
  headers = `{ 'Content-Type': 'application/json' }`;
137
224
  }
225
+ if (method === "GET") {
226
+ body = `JSON.stringify(args)`;
227
+ headers = `{}`;
228
+ }
138
229
  return `
139
230
  export const ${safeFnEntry} = (...args) => {
140
231
  const body = ${body};
141
232
  const headers = ${headers};
142
- const preffix = "${safePrefix}";
233
+ const prefix = "${safePrefix}";
143
234
  const name = "${safeFnName}";
144
- return innerModule(body, headers, preffix, name);
235
+ const credentials = "${credentials}";
236
+ const method = "${method}";
237
+ return innerModule(body, headers, credentials, prefix, name, method);
145
238
  }`.trim();
146
239
  };
240
+ /**
241
+ * Generates the complete client-side module bundle by iterating all registered server functions
242
+ * and producing fetch-based stubs for each. The result is transformed by Vite (or Oxc) during
243
+ * the dev server or production build.
244
+ * @param initialOptions - Plugin options containing rpcPrefix and optional adapter
245
+ * @returns A string of JavaScript code with all client RPC modules and their import dependencies
246
+ */
147
247
  const getClientModules = (initialOptions) => {
148
- validatePathSegment(initialOptions.rpcPreffix, "rpcPreffix");
248
+ validatePathSegment(initialOptions.rpcPrefix, "rpcPrefix");
149
249
  return `
150
250
 
151
251
  import { innerModule } from "@thednp/rpc/helpers";
@@ -1 +1 @@
1
- {"version":3,"file":"server.mjs","names":[],"sources":["../../src/functionsMap.ts","../../src/scanForServerFiles.ts","../../src/options.ts","../../src/createFunction.ts","../../src/getClientModules.ts"],"sourcesContent":["import type { ServerFnEntry } from \"./types.d.ts\";\n\nexport const serverFunctionsMap = new Map<string, ServerFnEntry>();\n","import type { ResolvedConfig, ViteDevServer } from \"vite\";\nimport type { ClientFunctionWithOptions } from \"./types.d.ts\";\nimport { createServer } from \"vite\";\nimport { readdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport process from \"node:process\";\n\nimport { serverFunctionsMap } from \"./functionsMap.ts\";\n\ntype ScanConfig = Pick<ResolvedConfig, \"root\" | \"base\"> & {\n server?: Partial<ResolvedConfig[\"server\"]>;\n};\n\nlet isScanned = false;\nexport const scanForServerFiles = async (\n initialCfg?: ScanConfig,\n devServer?: ViteDevServer,\n) => {\n if (isScanned && !devServer) {\n return;\n }\n const config = (!initialCfg && !devServer) || !initialCfg\n ? {\n root: process.cwd(),\n base: process.env.BASE || \"/\",\n server: { middlewareMode: true },\n }\n : {\n ...initialCfg,\n root: process.cwd(),\n };\n\n let server = devServer;\n if (!server) {\n server = await createServer({\n server: config.server,\n appType: \"custom\",\n base: config.base,\n root: config.root,\n });\n }\n\n const svFiles = [\"server.ts\", \"server.js\", \"server.mjs\", \"server.mts\"];\n const apiDir = join(config.root, \"src\", \"api\");\n let files: string[];\n try {\n files = (await readdir(apiDir, { withFileTypes: true }))\n .filter((f) => svFiles.some((fn) => f.name.includes(fn)))\n .map((f) => join(apiDir, f.name));\n } catch (_e) {\n files = [];\n }\n\n try {\n for (const file of files) {\n try {\n const moduleExports = (await server.ssrLoadModule(file)) as Record<\n string,\n ClientFunctionWithOptions\n >;\n const moduleEntries = Object.entries(moduleExports);\n if (!moduleEntries.length) {\n console.warn(\"No server function found.\");\n return;\n }\n\n for (const [exportName, exportValue] of moduleEntries) {\n // const registeredName = exportValue?.name ?? exportName;\n const registeredName = exportValue.name;\n serverFunctionsMap.set(registeredName, {\n name: registeredName,\n handler: exportValue,\n options: exportValue?.options,\n exportName,\n });\n }\n } catch (error) {\n console.error(\"Error loading file:\", file, error);\n }\n }\n } finally {\n if (!devServer && server) {\n await server.close();\n }\n isScanned = true;\n }\n};\n","import type {\n MiddlewareOptions,\n RpcPluginOptions,\n ServerFunctionOptions,\n} from \"./types.d.ts\";\n\nexport const defaultServerFnOptions = {\n contentType: \"application/json\",\n} satisfies ServerFunctionOptions;\n\nexport const defaultRPCOptions: RpcPluginOptions = {\n rpcPreffix: \"__rpc\",\n adapter: \"express\",\n};\n\nexport const defaultMiddlewareOptions = {\n rpcPreffix: undefined,\n path: undefined,\n} satisfies MiddlewareOptions;\n","// /@thednp/rpc/src/createFn.ts\nimport type {\n ClientFunction,\n JsonArray,\n JsonValue,\n ServerFunctionInit,\n ServerFunctionOptions,\n} from \"./types.d.ts\";\nimport { serverFunctionsMap } from \"./functionsMap.ts\";\nimport { defaultServerFnOptions } from \"./options.ts\";\n\nexport function createServerFunction<\n TArgs extends JsonArray = JsonArray,\n TResult extends JsonValue = JsonValue,\n>(\n name: string,\n handler: ServerFunctionInit<TArgs, TResult>,\n fnOptions: Partial<ServerFunctionOptions> = {},\n) {\n const options = Object.assign({}, defaultServerFnOptions, fnOptions);\n\n const wrappedFunction: ClientFunction<TArgs, TResult> = (...args: TArgs) => {\n const controller = new AbortController();\n const cancel = (reason: string) => controller.abort(reason);\n\n const fetcher = async () => {\n if (controller.signal.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n return await handler(controller.signal, ...args);\n };\n\n return {\n data: fetcher(),\n cancel,\n };\n };\n\n Object.defineProperties(wrappedFunction, {\n name: { value: name, enumerable: true, configurable: true },\n options: { value: options, enumerable: true, configurable: true },\n });\n\n serverFunctionsMap.set(\n name,\n {\n name,\n handler: wrappedFunction as never,\n options,\n },\n );\n\n return wrappedFunction;\n}\n","// Internal type that accepts all adapters\ninterface RpcPluginOptionsInternal {\n rpcPreffix: string;\n adapter?: string | undefined;\n}\n\nimport type { ServerFunctionOptions } from \"./types.d.ts\";\nimport { serverFunctionsMap } from \"./functionsMap.ts\";\n\n// Safe identifier pattern: must match JS identifier rules (letters, digits, $, _)\n// and must not contain regex metacharacters or template literal interpolation.\nconst SAFE_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\n// Safe URL path segment: identifier characters plus \"/\" for nested prefixes like \"api/rpc\"\nconst SAFE_PATH_SEGMENT = /^[A-Za-z0-9_$][A-Za-z0-9_$/-]*$/;\n\nfunction validateIdentifier(name: string, label: string): string {\n // istanbul ignore if\n if (!SAFE_IDENTIFIER.test(name)) {\n throw new Error(\n `Invalid ${label}: \"${name}\" must match /^[A-Za-z_$][A-Za-z0-9_$]*$/`,\n );\n }\n return name;\n}\n\nfunction validatePathSegment(segment: string, label: string): string {\n // istanbul ignore if\n if (!SAFE_PATH_SEGMENT.test(segment)) {\n throw new Error(\n `Invalid ${label}: \"${segment}\" must match /^[A-Za-z0-9_$][A-Za-z0-9_$/-]*$/`,\n );\n }\n return segment;\n}\n\nconst getModule = (\n fnName: string,\n fnEntry: string,\n options: Partial<ServerFunctionOptions> & {\n contentType: ServerFunctionOptions[\"contentType\"];\n rpcPreffix: string;\n },\n) => {\n // Validate all interpolated strings to prevent code injection\n const safeFnName = validatePathSegment(fnName, \"function name\");\n const safeFnEntry = validateIdentifier(fnEntry, \"export name\");\n const safePrefix = validatePathSegment(options.rpcPreffix, \"rpcPreffix\");\n\n let body = \"\";\n let headers = \"{}\";\n switch (options.contentType) {\n case \"text/plain\":\n {\n body = `args[0]`;\n headers = `{ 'Content-Type': 'text/plain' }`;\n }\n break;\n default: {\n body = `JSON.stringify(args)`;\n headers = `{ 'Content-Type': 'application/json' }`;\n }\n }\n\n const output = `\nexport const ${safeFnEntry} = (...args) => {\n const body = ${body};\n const headers = ${headers};\n const preffix = \"${safePrefix}\";\n const name = \"${safeFnName}\";\n return innerModule(body, headers, preffix, name);\n}`;\n\n return output.trim();\n};\n\nexport const getClientModules = (initialOptions: RpcPluginOptionsInternal) => {\n // Validate prefix once at the top level\n validatePathSegment(initialOptions.rpcPreffix, \"rpcPreffix\");\n\n return `\n// Client-side RPC modules\nimport { innerModule } from \"@thednp/rpc/helpers\";\n${\n Array.from(serverFunctionsMap.entries())\n .filter(([, entry]) => entry.exportName)\n .map(([registeredName, entry]) =>\n getModule(registeredName, entry.exportName!, {\n ...initialOptions,\n ...((entry.options as ServerFunctionOptions) || {}),\n })\n )\n .join(\"\\n\")\n }\n`.trim();\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"server.mjs","names":[],"sources":["../../src/functionsMap.ts","../../src/constants.ts","../../src/scanForServerFiles.ts","../../src/options.ts","../../src/createFunction.ts","../../src/validate.ts","../../src/getClientModules.ts"],"sourcesContent":["import type { ServerFnEntry } from \"./types.d.ts\";\n\nexport const serverFunctionsMap: Map<string, ServerFnEntry> = new Map<\n string,\n ServerFnEntry\n>();\n","export const OPERATION_ABORTED = \"Operation aborted\";\n\nexport const REQUEST_CANCELLED = \"Request was cancelled\";\n\nexport const FETCH_ERROR_PREFIX = \"Fetch error: \";\n\nexport const NO_SERVER_FUNCTION_FOUND = \"No server function found.\";\n\nexport const ERROR_LOADING_FILE = \"Error loading file:\";\n\nexport const FUNCTION_NOT_FOUND = \"Function not found\";\n\nexport const METHOD_NOT_ALLOWED = \"Method Not Allowed\";\n\nexport const REQUEST_FORBIDDEN = \"Forbidden\";\n\nexport const INTERNAL_SERVER_ERROR = \"Internal Server Error\";\n\nexport const CLIENT_DISCONNECTED = \"client disconnected\";\n\n/** Returns a warning when a middleware name is reused, preventing registration conflicts. @param name - The duplicate middleware name */\nexport const MIDDLEWARE_NAME_USED = (name: string) =>\n `The middleware name \"${name}\" is already used.`;\n\n/** Error message when a value fails the safe-identifier validation. @param label - What kind of value was being validated. @param name - The rejected value */\nexport const INVALID_IDENTIFIER = (label: string, name: string) =>\n `Invalid ${label}: \"${name}\" must match /^[A-Za-z_$][A-Za-z0-9_$]*$/`;\n\n/** 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 */\nexport const INVALID_PATH_SEGMENT = (label: string, segment: string) =>\n `Invalid ${label}: \"${segment}\" must match /^[A-Za-z0-9_$][A-Za-z0-9_$/-]*$/`;\n\n/** 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 */\nexport const CONFIG_FILE_NOT_FOUND = (\n configFile: string,\n configFilePath: string,\n) =>\n ` ⚠︎ The specified RPC config file ${configFile} cannot be found at ${configFilePath}, loading the defaults..`;\n\nexport const NO_CONFIG_FOUND = ` ⚡︎ No RPC config found, loading the defaults..`;\n\nexport const FAILED_LOAD_CONFIG = ` ⚠︎ Failed to load RPC config:`;\n","import type { ResolvedConfig, ViteDevServer } from \"vite\";\nimport type { ClientFunctionWithOptions } from \"./types.d.ts\";\nimport { createServer } from \"vite\";\nimport { readdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport process from \"node:process\";\n\nimport { serverFunctionsMap } from \"./functionsMap.ts\";\nimport { ERROR_LOADING_FILE, NO_SERVER_FUNCTION_FOUND } from \"./constants.ts\";\n\n/**\n * Partial Vite config used when scanning server files outside a running dev server.\n */\ntype ScanConfig = Pick<ResolvedConfig, \"root\" | \"base\"> & {\n /** Vite server options override (e.g. `middlewareMode`) */\n server?: Partial<ResolvedConfig[\"server\"]>;\n};\n\nlet isScanned = false;\n/**\n * Scans `src/api/` for server function files (`server.ts`, `server.js`, `server.mjs`, `server.mts`)\n * and populates the global `serverFunctionsMap` with their exported functions.\n * Uses Vite's SSR module loading to resolve and execute each file.\n * @param initialCfg - Optional Vite config overrides (root, base, server)\n * @param devServer - Optional running Vite dev server instance; when provided, skips creating a new one\n */\nexport const scanForServerFiles = async (\n initialCfg?: ScanConfig,\n devServer?: ViteDevServer,\n): Promise<void> => {\n if (isScanned && !devServer) {\n return;\n }\n const config = (!initialCfg && !devServer) || !initialCfg\n ? {\n root: process.cwd(),\n base: process.env.BASE || \"/\",\n server: { middlewareMode: true },\n }\n : {\n ...initialCfg,\n root: process.cwd(),\n };\n\n let server = devServer;\n if (!server) {\n server = await createServer({\n server: config.server,\n appType: \"custom\",\n base: config.base,\n root: config.root,\n });\n }\n\n const svFiles = [\"server.ts\", \"server.js\", \"server.mjs\", \"server.mts\"];\n const apiDir = join(config.root, \"src\", \"api\");\n let files: string[];\n try {\n files = (await readdir(apiDir, { withFileTypes: true }))\n .filter((f) => svFiles.includes(f.name))\n .map((f) => join(apiDir, f.name));\n } catch (_e) {\n files = [];\n }\n\n try {\n for (const file of files) {\n try {\n const moduleExports = (await server.ssrLoadModule(file)) as Record<\n string,\n ClientFunctionWithOptions\n >;\n const moduleEntries = Object.entries(moduleExports);\n if (!moduleEntries.length) {\n console.warn(NO_SERVER_FUNCTION_FOUND);\n return;\n }\n\n for (const [exportName, exportValue] of moduleEntries) {\n // const registeredName = exportValue?.name ?? exportName;\n const registeredName = exportValue.name;\n serverFunctionsMap.set(registeredName, {\n name: registeredName,\n handler: exportValue,\n options: exportValue?.options,\n exportName,\n });\n }\n } catch (error) {\n console.error(ERROR_LOADING_FILE, file, error);\n }\n }\n } finally {\n if (!devServer && server) {\n await server.close();\n }\n isScanned = true;\n }\n};\n","import type {\n MiddlewareOptions,\n RpcPluginOptions,\n ServerFunctionOptions,\n} from \"./types.d.ts\";\n\nexport const defaultServerFnOptions = {\n contentType: \"application/json\",\n credentials: \"same-origin\",\n method: \"POST\",\n} satisfies ServerFunctionOptions;\n\nexport const defaultRPCOptions: RpcPluginOptions = {\n rpcPrefix: \"__rpc\",\n adapter: \"express\",\n};\n\nexport const defaultMiddlewareOptions = {\n rpcPrefix: undefined,\n path: undefined,\n origin: undefined,\n} satisfies MiddlewareOptions;\n","// /@thednp/rpc/src/createFn.ts\nimport type {\n ClientFunction,\n JsonArray,\n JsonValue,\n ServerFunctionInit,\n ServerFunctionOptions,\n} from \"./types.d.ts\";\nimport { serverFunctionsMap } from \"./functionsMap.ts\";\nimport { defaultServerFnOptions } from \"./options.ts\";\nimport { OPERATION_ABORTED } from \"./constants.ts\";\n\n/**\n * Creates a server-side RPC function.\n * Registers the function in the server functions map and returns a client-compatible\n * wrapper that exposes `data` (Promise) and `cancel` (function) for request lifecycle control.\n * @param name - Unique identifier used by the RPC router to dispatch requests\n * @param handler - The actual implementation receiving an AbortSignal followed by JSON-serializable arguments\n * @param fnOptions - Optional contentType and credentials settings\n * @returns A client stub with `data` promise and `cancel` method, auto-registered in the server map\n */\nexport function createServerFunction<\n TArgs extends JsonArray = JsonArray,\n TResult extends JsonValue = JsonValue,\n>(\n name: string,\n handler: ServerFunctionInit<TArgs, TResult>,\n fnOptions: Partial<ServerFunctionOptions> = {},\n): ClientFunction<TArgs, TResult> {\n const options = Object.assign({}, defaultServerFnOptions, fnOptions);\n\n const wrappedFunction: ClientFunction<TArgs, TResult> = (...args: TArgs) => {\n const controller = new AbortController();\n const cancel = (reason: string) => controller.abort(reason);\n\n const fetcher = async () => {\n if (controller.signal.aborted) {\n throw new Error(OPERATION_ABORTED);\n }\n\n return await handler(controller.signal, ...args);\n };\n\n return {\n data: fetcher(),\n cancel,\n };\n };\n\n Object.defineProperties(wrappedFunction, {\n name: { value: name, enumerable: true, configurable: false },\n options: { value: options, enumerable: true, configurable: false },\n });\n\n serverFunctionsMap.set(\n name,\n {\n name,\n handler: wrappedFunction as never,\n options,\n },\n );\n\n return wrappedFunction;\n}\n","import type { Credentials } from \"./types.d.ts\";\nimport { INVALID_IDENTIFIER, INVALID_PATH_SEGMENT } from \"./constants.ts\";\n\nconst SAFE_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\nconst SAFE_PATH_SEGMENT = /^[A-Za-z0-9_$][A-Za-z0-9_$/-]*$/;\nconst CREDENTIALS_VALUES: readonly Credentials[] = [\n \"same-origin\",\n \"include\",\n \"omit\",\n];\n\n/**\n * Validates that a string is a safe JavaScript identifier.\n * Used to prevent code injection when interpolating export names into generated client code.\n * @param name - The string to validate\n * @param label - Human-readable label for error messages (e.g. \"export name\")\n * @returns The validated name if it passes\n * @throws Error if the name contains characters outside /^[A-Za-z_$][A-Za-z0-9_$]*$/\n */\nexport function validateIdentifier(name: string, label: string): string {\n if (!SAFE_IDENTIFIER.test(name)) {\n throw new Error(INVALID_IDENTIFIER(label, name));\n }\n return name;\n}\n\n/**\n * Validates that a string is a safe path segment for RPC routing.\n * Allows alphanumeric characters, underscores, dollar signs, hyphens, and forward slashes.\n * @param segment - The string to validate\n * @param label - Human-readable label for error messages (e.g. \"rpcPrefix\")\n * @returns The validated segment if it passes\n * @throws Error if the segment contains disallowed characters\n */\nexport function validatePathSegment(segment: string, label: string): string {\n if (!SAFE_PATH_SEGMENT.test(segment)) {\n throw new Error(INVALID_PATH_SEGMENT(label, segment));\n }\n return segment;\n}\n\n/**\n * Validates and normalizes the credentials option.\n * Accepts \"same-origin\", \"include\", or \"omit\"; defaults to \"same-origin\" when undefined.\n * @param value - Credentials value to validate\n * @returns The validated credentials string\n * @throws Error if the value is not one of the accepted credentials\n */\nexport function validateCredentials(value?: string): Credentials {\n const creds = value || \"same-origin\";\n if (!CREDENTIALS_VALUES.includes(creds as Credentials)) {\n throw new Error(\n `Invalid credentials: \"${value}\" must be one of ${\n CREDENTIALS_VALUES.join(\", \")\n }`,\n );\n }\n return creds as Credentials;\n}\n\n/**\n * Validates and normalizes the HTTP method option for a server function.\n * Accepts \"GET\" or \"POST\" (case-insensitive); defaults to \"POST\" when undefined.\n * @param value - Method value to validate\n * @returns The validated uppercase method string\n * @throws Error if the value is not \"GET\" or \"POST\"\n */\nexport function validateMethod(value?: string): \"GET\" | \"POST\" {\n const method = (value || \"POST\").toUpperCase();\n if (method !== \"GET\" && method !== \"POST\") {\n throw new Error(`Invalid method: \"${value}\" must be one of GET, POST`);\n }\n return method;\n}\n","/**\n * Internal plugin options accepted by `getClientModules`.\n */\ninterface RpcPluginOptionsInternal {\n /** RPC endpoint prefix (e.g. \"__rpc\") */\n rpcPrefix: string;\n /** Framework adapter name */\n adapter?: string | undefined;\n}\n\nimport type { ServerFunctionOptions } from \"./types.d.ts\";\nimport { serverFunctionsMap } from \"./functionsMap.ts\";\nimport {\n validateCredentials,\n validateIdentifier,\n validateMethod,\n validatePathSegment,\n} from \"./validate.ts\";\n\n/**\n * Generates a JavaScript client module string for a single server function.\n * All interpolated values are validated to prevent code injection.\n * @param fnName - Registered RPC function name (validated as path segment)\n * @param fnEntry - Export name used in the generated module (validated as identifier)\n * @param options - Content type, credentials, and RPC prefix settings\n * @returns A string of JavaScript code exporting the client stub\n */\nconst getModule = (\n fnName: string,\n fnEntry: string,\n options: Partial<ServerFunctionOptions> & {\n contentType: ServerFunctionOptions[\"contentType\"];\n rpcPrefix: string;\n },\n): string => {\n // Validate all interpolated strings to prevent code injection\n const safeFnName = validatePathSegment(fnName, \"function name\");\n const safeFnEntry = validateIdentifier(fnEntry, \"export name\");\n const safePrefix = validatePathSegment(options.rpcPrefix, \"rpcPrefix\");\n const credentials = validateCredentials(options.credentials);\n const method = validateMethod(options.method);\n let body = \"\";\n let headers = \"{}\";\n switch (options.contentType) {\n case \"text/plain\":\n {\n body = `args[0]`;\n headers = `{ 'Content-Type': 'text/plain' }`;\n }\n break;\n default: {\n body = `JSON.stringify(args)`;\n headers = `{ 'Content-Type': 'application/json' }`;\n }\n }\n // GET requests cannot carry a body: args travel as a JSON query parameter\n if (method === \"GET\") {\n body = `JSON.stringify(args)`;\n headers = `{}`;\n }\n\n const output = `\nexport const ${safeFnEntry} = (...args) => {\n const body = ${body};\n const headers = ${headers};\n const prefix = \"${safePrefix}\";\n const name = \"${safeFnName}\";\n const credentials = \"${credentials}\";\n const method = \"${method}\";\n return innerModule(body, headers, credentials, prefix, name, method);\n}`;\n\n return output.trim();\n};\n\n/**\n * Generates the complete client-side module bundle by iterating all registered server functions\n * and producing fetch-based stubs for each. The result is transformed by Vite (or Oxc) during\n * the dev server or production build.\n * @param initialOptions - Plugin options containing rpcPrefix and optional adapter\n * @returns A string of JavaScript code with all client RPC modules and their import dependencies\n */\nexport const getClientModules = (\n initialOptions: RpcPluginOptionsInternal,\n): string => {\n // Validate prefix once at the top level\n validatePathSegment(initialOptions.rpcPrefix, \"rpcPrefix\");\n\n return `\n// Client-side RPC modules\nimport { innerModule } from \"@thednp/rpc/helpers\";\n${\n Array.from(serverFunctionsMap.entries())\n .filter(([, entry]) => entry.exportName)\n .map(([registeredName, entry]) =>\n getModule(registeredName, entry.exportName!, {\n ...initialOptions,\n ...((entry.options as ServerFunctionOptions) || {}),\n })\n )\n .join(\"\\n\")\n }\n`.trim();\n};\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,24 +1,39 @@
1
1
  {
2
2
  "name": "@thednp/rpc",
3
- "version": "0.0.1",
3
+ "version": "0.0.4",
4
+ "license": "MIT",
5
+ "author": "thednp",
4
6
  "description": "⚡ A Vite plugin for creating server functions with automatic Remote Procedure Calls (RPC)",
5
7
  "homepage": "https://github.com/thednp/rpc#readme",
8
+ "keywords": [
9
+ "rpc",
10
+ "vite",
11
+ "vite-plugin",
12
+ "express",
13
+ "fastify",
14
+ "hono",
15
+ "koa"
16
+ ],
6
17
  "bugs": {
7
18
  "url": "https://github.com/thednp/rpc/issues"
8
19
  },
9
20
  "repository": {
10
21
  "type": "git",
11
- "url": "git+https://github.com/thednp/rpc.git"
22
+ "url": "https://github.com/thednp/rpc.git"
23
+ },
24
+ "publishConfig": {
25
+ "access": "public",
26
+ "provenance": true
12
27
  },
13
- "license": "MIT",
14
- "author": "thednp",
15
28
  "type": "module",
16
29
  "types": "./dist/index.d.mts",
30
+ "sideEffects": false,
17
31
  "files": [
18
32
  "dist",
19
33
  "wiki",
20
34
  "README.md",
21
35
  "AGENTS.md",
36
+ "CLAUDE.md",
22
37
  "LICENSE"
23
38
  ],
24
39
  "exports": {
@@ -32,31 +47,11 @@
32
47
  "./server": "./dist/server/server.mjs",
33
48
  "./package.json": "./package.json"
34
49
  },
35
- "dependencies": {
36
- "vite": "^8.1.5"
37
- },
38
- "devDependencies": {
39
- "@hono/node-server": "^2.0.12",
40
- "@types/express": "^5.0.6",
41
- "@types/koa": "^3.0.3",
42
- "@types/node": "^26.1.2",
43
- "@vitest/coverage-istanbul": "^4.1.10",
44
- "@vitest/ui": "^4.1.10",
45
- "fastify": "^5.10.0",
46
- "fastify-plugin": "^6.0.0",
47
- "hono": "^4.12.32",
48
- "koa": "^3.2.1",
49
- "picocolors": "^1.1.1",
50
- "tsdown": "^0.22.14",
51
- "typescript": "^7.0.2",
52
- "vite-plugin-strip-comments": "^0.0.10",
53
- "vitest": "^4.1.10"
54
- },
55
50
  "scripts": {
56
51
  "test": "vitest",
57
52
  "test-ui": "vitest --ui",
58
- "test-dev": "node dev-test",
59
- "test-prod": "node dev-test --mode=preview",
53
+ "test-dev": "node scripts/dev-test",
54
+ "test-prod": "node scripts/dev-test --mode=preview",
60
55
  "format": "deno fmt src tests examples/**/src",
61
56
  "clean": "rm -rf examples/**/dist examples/**/node_modules examples/**/package-lock.json examples/**/pnpm-lock.yaml examples/**/bun.lockb examples/**/deno.lock",
62
57
  "dev": "cd examples/spa && pnpm up && pnpm dev",
@@ -69,10 +64,33 @@
69
64
  "lint:ts": "deno lint src",
70
65
  "fix:ts": "deno lint src --fix",
71
66
  "check:ts": "tsc -noEmit",
72
- "up:examples": "pnpm up -r --latest --filter \"./examples/*\"",
73
- "up:examples_1": "for d in examples/*/; do (cd \"$d\" && pnpm up --latest); done",
67
+ "up:examples": "pnpm up -r --latest --filter \"./examples/*\" && pnpm ci -r --filter \"./examples/*\"",
74
68
  "up:root": "pnpm up --latest",
69
+ "up:deno": "deno update && deno -A scripts/update-deno.js",
75
70
  "upd": "pnpm up:examples && pnpm up:root",
71
+ "audit:src": "node scripts/audit-src.js",
72
+ "prepublishOnly": "pnpm upd && pnpm lint && pnpm format && pnpm audit:src && pnpm build",
76
73
  "build": "tsdown"
77
- }
78
- }
74
+ },
75
+ "dependencies": {
76
+ "express": "^5.2.1",
77
+ "fastify": "^5.11.0",
78
+ "fastify-plugin": "^6.0.0",
79
+ "hono": "^4.12.33",
80
+ "koa": "^3.2.1",
81
+ "vite": "^8.2.0"
82
+ },
83
+ "devDependencies": {
84
+ "@hono/node-server": "^2.0.12",
85
+ "@types/express": "^5.0.6",
86
+ "@types/koa": "^3.0.3",
87
+ "@types/node": "^26.1.2",
88
+ "@vitest/coverage-istanbul": "^4.1.10",
89
+ "@vitest/ui": "^4.1.10",
90
+ "tsdown": "^0.22.14",
91
+ "typescript": "^7.0.2",
92
+ "vite-plugin-strip-comments": "^0.0.10",
93
+ "vitest": "^4.1.10"
94
+ },
95
+ "packageManager": "pnpm@11.18.0"
96
+ }
package/wiki/adapters.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  `@thednp/rpc` provides adapters for ExpressJS, Fastify, Hono, and Koa. Each adapter exports `attachRPC` for production and `attachVite` for development.
4
4
 
5
+ All adapters have an example each, feel free to explore [examples](../examples).
6
+
5
7
  ## Common Pattern
6
8
 
7
9
  All adapters share the same two-function API:
@@ -21,6 +23,12 @@ attachVite(app, vite);
21
23
  ### Installation
22
24
 
23
25
  ```bash
26
+ // jsr registry
27
+ pnpm add jsr:@thednp/rpc express
28
+ ```
29
+
30
+ ```bash
31
+ // npmjs registry
24
32
  pnpm add @thednp/rpc express
25
33
  ```
26
34
 
@@ -51,6 +59,12 @@ app.listen(3000);
51
59
  ### Installation
52
60
 
53
61
  ```bash
62
+ // jsr registry
63
+ pnpm add jsr:@thednp/rpc fastify
64
+ ```
65
+
66
+ ```bash
67
+ // npmjs registry
54
68
  pnpm add @thednp/rpc fastify
55
69
  ```
56
70
 
@@ -59,6 +73,7 @@ pnpm add @thednp/rpc fastify
59
73
  ```ts
60
74
  // server.ts
61
75
  import Fastify from 'fastify';
76
+ import fastifyStatic from '@fastify/static';
62
77
  import { attachRPC, attachVite } from '@thednp/rpc/fastify';
63
78
  import { createServer } from 'vite';
64
79
 
@@ -70,7 +85,7 @@ if (isDev) {
70
85
  attachVite(fastify, vite);
71
86
  } else {
72
87
  await attachRPC(fastify);
73
- fastify.register(require('@fastify/static'), { root: 'dist' });
88
+ fastify.register(fastifyStatic, { root: 'dist' });
74
89
  }
75
90
 
76
91
  fastify.listen({ port: 3000 });
@@ -81,6 +96,12 @@ fastify.listen({ port: 3000 });
81
96
  ### Installation
82
97
 
83
98
  ```bash
99
+ // jsr registry
100
+ pnpm add jsr:@thednp/rpc hono
101
+ ```
102
+
103
+ ```bash
104
+ // npmjs registry
84
105
  pnpm add @thednp/rpc hono
85
106
  ```
86
107
 
@@ -89,6 +110,7 @@ pnpm add @thednp/rpc hono
89
110
  ```ts
90
111
  // server.ts
91
112
  import { Hono } from 'hono';
113
+ import { serveStatic } from '@hono/node-server/serve-static';
92
114
  import { attachRPC, attachVite } from '@thednp/rpc/hono';
93
115
  import { createServer } from 'vite';
94
116
 
@@ -111,6 +133,12 @@ export default app;
111
133
  ### Installation
112
134
 
113
135
  ```bash
136
+ // jsr registry
137
+ pnpm add jsr:@thednp/rpc koa koa-body
138
+ ```
139
+
140
+ ```bash
141
+ // npmjs registry
114
142
  pnpm add @thednp/rpc koa koa-body
115
143
  ```
116
144
 
@@ -122,6 +150,7 @@ Body parser must be registered before the RPC middleware:
122
150
  // server.ts
123
151
  import Koa from 'koa';
124
152
  import { koaBody } from 'koa-body';
153
+ import serve from 'koa-static';
125
154
  import { attachRPC, attachVite } from '@thednp/rpc/koa';
126
155
  import { createServer } from 'vite';
127
156
 
@@ -136,7 +165,7 @@ if (isDev) {
136
165
  attachVite(app, vite);
137
166
  } else {
138
167
  await attachRPC(app);
139
- app.use(require('koa-static')('dist'));
168
+ app.use(serve('dist'));
140
169
  }
141
170
 
142
171
  app.listen(3000);