@thednp/rpc 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/fastify/fastify.d.mts +21 -8
- package/dist/fastify/fastify.d.mts.map +1 -1
- package/dist/fastify/fastify.mjs +1 -6
- package/dist/fastify/fastify.mjs.map +1 -1
- package/dist/fastify/plugin/fastify/plugin.d.mts +8 -8
- package/dist/fastify/plugin/fastify/plugin.d.mts.map +1 -1
- package/dist/fastify/plugin/fastify/plugin.mjs +1 -6
- package/dist/fastify/plugin/fastify/plugin.mjs.map +1 -1
- package/dist/helpers/helpers.d.mts +11 -10
- package/dist/helpers/helpers.d.mts.map +1 -1
- package/dist/helpers/helpers.mjs.map +1 -1
- package/dist/hono/hono.d.mts +9 -1
- package/dist/hono/hono.d.mts.map +1 -1
- package/dist/hono/hono.mjs.map +1 -1
- package/dist/index.d.mts +29 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/server/server.d.mts +17 -16
- package/dist/server/server.d.mts.map +1 -1
- package/dist/server/server.mjs.map +1 -1
- package/package.json +3 -4
- package/wiki/adapters.md +0 -172
- package/wiki/best-practices.md +0 -364
- package/wiki/client-usage.md +0 -66
- package/wiki/configuration.md +0 -76
- package/wiki/getting-started.md +0 -105
- package/wiki/index.md +0 -14
- package/wiki/security.md +0 -76
- package/wiki/server-functions.md +0 -137
- package/wiki/setup.md +0 -81
package/README.md
CHANGED
|
@@ -18,6 +18,8 @@ The server functions run **isomorphically** within any Vite powered runtime.
|
|
|
18
18
|
|
|
19
19
|
Most RPC solutions ask you to adopt a new way of thinking, require learning a complex API, some are vendor locked, some even allow you to blend in with your client code (via `"use server"` directive), for sure they are powerful and work well, they provide excellent DX, but complexity always comes with its own drawbacks.
|
|
20
20
|
|
|
21
|
+
`@thednp/rpc` carves out the niche that wants to do RPC **without the weight of an entire framework**. If your app is a Vite site, a static SPA, or a small server powered by a single middleware — but you still want typed, cancellable, server-only functions callable from the client — you shouldn't have to adopt a full meta-framework, a full-stack router, or a build-time convention just to bridge the two. This plugin gives you that bridge alone: no framework to learn, no runtime to adopt, no vendor to sign up with.
|
|
22
|
+
|
|
21
23
|
### Simplicity is best
|
|
22
24
|
|
|
23
25
|
`@thednp/rpc` takes simplicity very seriously:
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fp from "fastify-plugin";
|
|
1
2
|
import { FastifyInstance, FastifyReply, FastifyRequest, HookHandlerDoneFunction } from "fastify";
|
|
2
3
|
import { MiddlewareOptions, RpcPluginOptions } from "@thednp/rpc";
|
|
3
4
|
import { ViteDevServer } from "vite";
|
|
@@ -7,6 +8,25 @@ import "@hono/node-server";
|
|
|
7
8
|
import "hono/factory";
|
|
8
9
|
import "koa";
|
|
9
10
|
//#region src/fastify/types.d.ts
|
|
11
|
+
/**
|
|
12
|
+
* Fastify RPC plugin signature: registers the middleware as a preHandler hook.
|
|
13
|
+
*/
|
|
14
|
+
type FastifyRPCPlugin = (fastify: FastifyInstance, initialOptions: Partial<MiddlewareOptions<"fastify">>, done: () => void) => void;
|
|
15
|
+
/**
|
|
16
|
+
* `fastify-plugin` function type, used to type the wrapped export.
|
|
17
|
+
*/
|
|
18
|
+
type FastifyPlugin = typeof fp;
|
|
19
|
+
/**
|
|
20
|
+
* Return type of `fastify-plugin` wrapping, matching the final plugin export.
|
|
21
|
+
*/
|
|
22
|
+
type RegisteredFastifyRPCPlugin = ReturnType<FastifyPlugin>;
|
|
23
|
+
/**
|
|
24
|
+
* Options accepted by the Fastify RPC plugin (`fp()`-wrapped registration).
|
|
25
|
+
*/
|
|
26
|
+
type RpcFastifyPluginOptions = MiddlewareOptions<"fastify"> & {
|
|
27
|
+
/** Whether this is an RPC plugin registration */
|
|
28
|
+
isRPC: boolean;
|
|
29
|
+
};
|
|
10
30
|
/**
|
|
11
31
|
* Fastify-specific middleware options, constrained to the `"fastify"` adapter.
|
|
12
32
|
*/
|
|
@@ -28,13 +48,6 @@ interface FastifyMiddlewareHooks {
|
|
|
28
48
|
*/
|
|
29
49
|
handler: (req: FastifyRequest, res: FastifyReply, done: HookHandlerDoneFunction) => Promise<void>;
|
|
30
50
|
}
|
|
31
|
-
/**
|
|
32
|
-
* Options accepted by the Fastify RPC plugin (`fp()`-wrapped registration).
|
|
33
|
-
*/
|
|
34
|
-
type RpcFastifyPluginOptions = MiddlewareOptions<"fastify"> & {
|
|
35
|
-
/** Whether this is an RPC plugin registration */
|
|
36
|
-
isRPC: boolean;
|
|
37
|
-
};
|
|
38
51
|
//#endregion
|
|
39
52
|
//#region src/fastify/createMiddleware.d.ts
|
|
40
53
|
/**
|
|
@@ -106,5 +119,5 @@ declare function attachVite(app: FastifyInstance, vite: ViteDevServer): void;
|
|
|
106
119
|
*/
|
|
107
120
|
declare const readBody: (req: FastifyRequest) => Promise<BodyResult>;
|
|
108
121
|
//#endregion
|
|
109
|
-
export { type FastifyMiddlewareFn, type FastifyMiddlewareHooks, type FastifyMiddlewareOptions, type RpcFastifyPluginOptions, attachRPC, attachVite, createMiddleware, createRPCMiddleware, readBody };
|
|
122
|
+
export { type FastifyMiddlewareFn, type FastifyMiddlewareHooks, type FastifyMiddlewareOptions, type FastifyPlugin, type FastifyRPCPlugin, type RegisteredFastifyRPCPlugin, type RpcFastifyPluginOptions, attachRPC, attachVite, createMiddleware, createRPCMiddleware, readBody };
|
|
110
123
|
//# sourceMappingURL=fastify.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fastify.d.mts","names":[],"sources":["../../src/fastify/types.d.ts","../../src/fastify/createMiddleware.ts","../../src/types.d.ts","../../src/fastify/helpers.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"fastify.d.mts","names":[],"sources":["../../src/fastify/types.d.ts","../../src/fastify/createMiddleware.ts","../../src/types.d.ts","../../src/fastify/helpers.ts"],"mappings":";;;;;;;;;;;;;KAYY,oBACV,SAAS,iBACT,gBAAgB,QAAQ,+BACxB;;;;KAMU,uBAAuB;;;;KAKvB,6BAA6B,WAAW;;;;KAKxC,0BAA0B;;EAEpC;;;;;KAMU,2BAA2B;;;;;KAM3B,uBACV,UAAU,yCAEV,iBAAiB,QAAQ,8BACtB;;;;UAKY;;;;;;;EAOf,UACE,KAAK,gBACL,KAAK,cACL,MAAM,4BACH;;;;;;;;;;cChCM,kBAAkB;;;;;;;;cAgFlB,qBAAqB;;;;;;KC5CtB;EACN;EAAiC,MAAM;;EACvC;EAA2B;;;;;;KA8BrB;;;;KAIA;GAAgB,cAAc,YAAY;;;;;KAI1C,YAAY;;;;KAIZ,YAAY,gBAAgB,YAAY;;;;;;;;iBCpG9B,UAAU,KAAK,kBAAe;;;;;;;iBAYpC,WAAW,KAAK,iBAAiB,MAAM;;;;;;;cAgB1C,WAAQ,KACd,mBACJ,QAAQ"}
|
package/dist/fastify/fastify.mjs
CHANGED
|
@@ -33,12 +33,7 @@ const CLIENT_DISCONNECTED = "client disconnected";
|
|
|
33
33
|
const MIDDLEWARE_NAME_USED = (name) => `The middleware name "${name}" is already used.`;
|
|
34
34
|
//#endregion
|
|
35
35
|
//#region src/fastify/plugin.ts
|
|
36
|
-
/**
|
|
37
|
-
* Fastify plugin factory that registers the RPC middleware as a preHandler hook.
|
|
38
|
-
* @param fastify - Fastify instance
|
|
39
|
-
* @param initialOptions - Middleware options including rpcPrefix
|
|
40
|
-
* @param done - Callback to signal plugin registration completion
|
|
41
|
-
*/
|
|
36
|
+
/** @module Fastify plugin. Exports the RPC plugin wrapped with `fastify-plugin` for lifecycle-compatible registration. */
|
|
42
37
|
const RpcPlugin = (fastify, initialOptions, done) => {
|
|
43
38
|
const rpcMiddleware = createRPCMiddleware(initialOptions);
|
|
44
39
|
fastify.addHook("preHandler", async (request, reply) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fastify.mjs","names":["fastifyRpcPlugin"],"sources":["../../src/options.ts","../../src/tools.ts","../../src/constants.ts","../../src/fastify/plugin.ts","../../src/fastify/helpers.ts","../../src/fastify/createMiddleware.ts"],"sourcesContent":["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","/**\n * Escapes special regex metacharacters in a string.\n * Used to safely embed user-configurable values (like rpcPrefix) into regular expressions,\n * preventing ReDoS and regex injection attacks.\n * @param s - The raw string to escape\n * @returns The escaped string safe for use in new RegExp()\n */\nexport function escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\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","/** @module Fastify plugin. Exports the RPC plugin wrapped with `fastify-plugin` for lifecycle-compatible registration. */\n// inspired by https://github.com/royalswe/vike-fastify-boilerplate/blob/main/server/index.ts\nimport type { FastifyInstance } from \"fastify\";\nimport fp from \"fastify-plugin\";\nimport type { MiddlewareOptions } from \"../types.d.ts\";\nimport { createRPCMiddleware } from \"./createMiddleware.ts\";\n\nexport type { MiddlewareOptions };\n\n/**\n * Fastify RPC plugin signature: registers the middleware as a preHandler hook.\n */\ntype FastifyRPCPlugin = (\n fastify: FastifyInstance,\n initialOptions: Partial<MiddlewareOptions<\"fastify\">>,\n done: () => void,\n) => void;\n\n/**\n * Fastify plugin factory that registers the RPC middleware as a preHandler hook.\n * @param fastify - Fastify instance\n * @param initialOptions - Middleware options including rpcPrefix\n * @param done - Callback to signal plugin registration completion\n */\nconst RpcPlugin: FastifyRPCPlugin = (\n fastify,\n initialOptions,\n done,\n) => {\n // Register RPC middleware as preHandler hook\n const rpcMiddleware = createRPCMiddleware(initialOptions);\n fastify.addHook(\"preHandler\", async (request, reply) => {\n const next = () =>\n new Promise((resolve) => {\n rpcMiddleware(request, reply, resolve);\n });\n await next();\n });\n\n done();\n};\n\n/**\n * `fastify-plugin` function type, used to type the wrapped export.\n */\ntype FastifyPlugin = typeof fp;\n\n/**\n * Return type of `fastify-plugin` wrapping, matching the final plugin export.\n */\ntype RegisteredFastifyRPCPlugin = ReturnType<FastifyPlugin>;\n\n// Export the plugin wrapped with fastify-plugin\nconst rpcPlugin = fp(RpcPlugin, {\n name: \"uni-rpc-fastify-plugin\",\n}) as RegisteredFastifyRPCPlugin;\n\nexport { rpcPlugin as default };\n","// @thednp/rpc/src/fastify/helpers.ts\nimport type { FastifyRequest } from \"fastify\";\nimport type { ViteDevServer } from \"vite\";\nimport type { FastifyInstance } from \"fastify\";\nimport type { Buffer } from \"node:buffer\";\nimport fastifyRpcPlugin from \"./plugin.ts\";\nimport type { BodyResult, JsonValue } from \"../types.d.ts\";\n\n/**\n * Convenience function to load RPC config and register the RPC plugin to a Fastify instance.\n * Dynamically imports loadRPCConfig and registers the fastify-rpc plugin.\n * @param app - Fastify instance\n */\nexport async function attachRPC(app: FastifyInstance) {\n const { loadRPCConfig } = await import(\"@thednp/rpc\");\n const { adapter: _adapter, ...options } = await loadRPCConfig();\n await app.register(fastifyRpcPlugin, options);\n}\n\n/**\n * Attaches Vite's dev server middlewares to a Fastify instance for development mode.\n * Uses an `onRequest` hook to delegate to Vite's connect-compatible middleware stack.\n * @param app - Fastify instance\n * @param vite - Running Vite dev server\n */\nexport function attachVite(app: FastifyInstance, vite: ViteDevServer) {\n app.addHook(\"onRequest\", async (request, reply) => {\n const next = () =>\n new Promise((resolve) => {\n vite.middlewares(request.raw, reply.raw, resolve);\n });\n await next();\n });\n}\n\n/**\n * Reads and parses the HTTP request body from a Fastify request.\n * If Fastify's body parser already consumed the stream, uses the pre-parsed body from `req.body`.\n * @param req - Fastify request object\n * @returns A promise resolving to the parsed body with its content type\n */\nexport const readBody = (\n req: FastifyRequest,\n): Promise<BodyResult> => {\n return new Promise((resolve, reject) => {\n const contentType = req.headers[\"content-type\"]?.toLowerCase() || \"\";\n const reqBody = req.body as JsonValue;\n\n if (reqBody !== undefined) {\n const isJSON = contentType.includes(\"json\");\n resolve({\n contentType: isJSON ? \"application/json\" : \"text/plain\",\n data: isJSON ? reqBody : String(reqBody),\n } as BodyResult);\n return;\n }\n\n const toggleListeners = (add?: boolean) => {\n const method = add ? \"on\" : \"off\";\n req.raw[method](\"data\", onData);\n req.raw[method](\"end\", onEnd);\n req.raw[method](\"error\", onError);\n };\n\n let body = \"\";\n\n const onData = (chunk: Buffer) => {\n body += chunk.toString();\n };\n\n const onEnd = () => {\n toggleListeners();\n const isJSON = contentType.includes(\"json\");\n try {\n const data = JSON.parse(body);\n resolve({\n contentType: isJSON ? \"application/json\" : \"text/plain\",\n data,\n });\n } catch (_e) {\n resolve({ contentType: \"text/plain\", data: String(body) });\n }\n };\n const onError = (err: Error) => {\n toggleListeners();\n reject(err);\n };\n\n toggleListeners(true);\n });\n};\n","// src/fastify/createMiddleware.ts\nimport type {\n FastifyReply,\n FastifyRequest,\n HookHandlerDoneFunction,\n} from \"fastify\";\nimport type {\n FastifyMiddlewareFn,\n FastifyMiddlewareOptions,\n} from \"./types.d.ts\";\nimport type { JsonValue } from \"../types.d.ts\";\nimport { scanForServerFiles, serverFunctionsMap } from \"@thednp/rpc/server\";\nimport { defaultMiddlewareOptions, defaultRPCOptions } from \"../options.ts\";\nimport { escapeRegExp } from \"../tools.ts\";\nimport {\n CLIENT_DISCONNECTED,\n FUNCTION_NOT_FOUND,\n INTERNAL_SERVER_ERROR,\n METHOD_NOT_ALLOWED,\n MIDDLEWARE_NAME_USED,\n REQUEST_FORBIDDEN,\n} from \"../constants.ts\";\nimport { readBody } from \"./helpers.ts\";\n\nlet middlewareCount = 0;\nconst middlewareStack = new Set<string>();\n\n/**\n * Creates a Fastify preHandler hook with optional path and rpcPrefix filtering.\n * Middleware names are deduplicated. Prefix and path regexes are compiled once at creation time.\n * @param initialOptions - Options for rpcPrefix, path matching, and the handler function\n * @returns A Fastify preHandler hook function\n */\nexport const createMiddleware: FastifyMiddlewareFn = (initialOptions = {}) => {\n const options = Object.assign(\n {},\n defaultMiddlewareOptions,\n initialOptions,\n ) as FastifyMiddlewareOptions;\n\n const middlewareName = options.name;\n const rpcPrefix = options.rpcPrefix;\n const path = options.path;\n const handler = options.handler;\n\n let name = middlewareName;\n if (!name) {\n name = \"viteRPCMiddleware-\" + middlewareCount;\n middlewareCount += 1;\n }\n if (middlewareStack.has(name)) {\n throw new Error(MIDDLEWARE_NAME_USED(name));\n }\n middlewareStack.add(name);\n\n // Hoist regex compilation out of per-request path. Escape the prefix to\n // prevent regex injection via metacharacters in the config string.\n const prefixRegex: RegExp | null = rpcPrefix\n ? new RegExp(`^/${escapeRegExp(rpcPrefix)}/`)\n : null;\n const pathMatcher: RegExp | null = path\n ? (typeof path === \"string\" ? new RegExp(path) : path)\n : null;\n\n const middlewareHandler = async (\n req: FastifyRequest,\n reply: FastifyReply,\n done: HookHandlerDoneFunction,\n ) => {\n const reqUrl = new URL(req.url, \"http://localhost\");\n const url = reqUrl.pathname;\n\n // When serving from production server, scan for server files\n if (serverFunctionsMap.size === 0) {\n await scanForServerFiles();\n }\n\n // No need to continue when no handler provided\n if (!handler) {\n done();\n return;\n }\n\n // Path matching\n if (pathMatcher && !pathMatcher.test(url)) {\n done();\n return;\n }\n\n // rpcPrefix matching (boundary-safe via escaped regex)\n if (prefixRegex && !prefixRegex.test(url)) {\n done();\n return;\n }\n\n // Execute handler\n await handler(req, reply, done);\n };\n\n Object.defineProperty(middlewareHandler, \"name\", {\n value: name,\n });\n\n return middlewareHandler;\n};\n\n/**\n * Creates the Fastify RPC middleware that routes incoming requests to registered server functions.\n * Wraps the generic createMiddleware with the RPC handler that reads the body, dispatches\n * to the matching function, and sends the JSON-serialized result.\n * @param initialOptions - Options including rpcPrefix for URL routing\n * @returns A Fastify preHandler hook function\n */\nexport const createRPCMiddleware: FastifyMiddlewareFn = (\n initialOptions = {},\n) => {\n const options = Object.assign(\n {},\n defaultMiddlewareOptions,\n { rpcPrefix: defaultRPCOptions.rpcPrefix },\n initialOptions,\n ) as FastifyMiddlewareOptions;\n\n // Hoist prefix regex (escaped) and the literal prefix-for-replace out of the\n // per-request handler to avoid regex injection and per-request compilation.\n const rpcPrefix = options.rpcPrefix;\n const prefixRegex = rpcPrefix\n ? new RegExp(`^/${escapeRegExp(rpcPrefix)}/`)\n : /* istanbul ignore next */ null;\n const prefixReplace = `/${rpcPrefix}/`;\n\n return createMiddleware({\n ...options,\n handler: async (\n req: FastifyRequest,\n reply: FastifyReply,\n _done: HookHandlerDoneFunction,\n ) => {\n const reqUrl = new URL(req.url, \"http://localhost\");\n const url = reqUrl.pathname;\n\n // Defense-in-depth: validate prefix match via escaped regex even though\n // the outer createMiddleware gates on the same prefix already.\n // istanbul ignore if\n if (prefixRegex && !prefixRegex.test(url)) {\n return;\n }\n\n // Optional origin check: reject requests whose Origin header does not\n // match the configured origin. Requests without an Origin header\n // (curl, native clients) pass through unchecked.\n const origin = options.origin;\n const requestOrigin = req.headers.origin;\n if (origin && requestOrigin && requestOrigin !== origin) {\n reply.status(403).send({ error: REQUEST_FORBIDDEN });\n return;\n }\n\n const functionName = url.replace(prefixReplace, \"\");\n const serverFunction = serverFunctionsMap.get(functionName);\n\n if (!serverFunction) {\n reply.status(404).send({\n error: FUNCTION_NOT_FOUND,\n });\n return;\n }\n\n try {\n const method = serverFunction.options?.method || \"POST\";\n if (req.method.toUpperCase() !== method) {\n reply.status(405).send({ error: METHOD_NOT_ALLOWED });\n return;\n }\n\n let args: JsonValue[] = [];\n if (method === \"GET\") {\n const raw = reqUrl.searchParams.get(\"args\");\n // istanbul ignore else\n if (raw) args = JSON.parse(raw);\n } else {\n const body = await readBody(req);\n args = Array.isArray(body.data) ? body.data : [body.data];\n }\n const { data: dataResult, cancel } = serverFunction.handler(...args);\n const onClose = () => cancel(CLIENT_DISCONNECTED);\n\n req.raw.on(\"close\", onClose);\n const data = await dataResult;\n req.raw.off(\"close\", onClose);\n\n // istanbul ignore else\n if (!reply.raw.headersSent) reply.status(200).send({ data });\n } catch (err) {\n console.error(String(err));\n reply.status(500).send({ error: INTERNAL_SERVER_ERROR });\n }\n },\n });\n};\n"],"mappings":";;;AAYA,MAAa,oBAAsC;CACjD,WAAW;CACX,SAAS;AACX;AAEA,MAAa,2BAA2B;CACtC,WAAW,KAAA;CACX,MAAM,KAAA;CACN,QAAQ,KAAA;AACV;;;;;;;;;;ACdA,SAAgB,aAAa,GAAmB;CAC9C,OAAO,EAAE,QAAQ,uBAAuB,MAAM;AAChD;;;ACCA,MAAa,qBAAqB;AAElC,MAAa,qBAAqB;AAElC,MAAa,oBAAoB;AAEjC,MAAa,wBAAwB;AAErC,MAAa,sBAAsB;;AAGnC,MAAa,wBAAwB,SACnC,wBAAwB,KAAK;;;;;;;;;ACE/B,MAAM,aACJ,SACA,gBACA,SACG;CAEH,MAAM,gBAAgB,oBAAoB,cAAc;CACxD,QAAQ,QAAQ,cAAc,OAAO,SAAS,UAAU;EACtD,MAAM,aACJ,IAAI,SAAS,YAAY;GACvB,cAAc,SAAS,OAAO,OAAO;EACvC,CAAC;EACH,MAAM,KAAK;CACb,CAAC;CAED,KAAK;AACP;AAaA,MAAM,YAAY,GAAG,WAAW,EAC9B,MAAM,yBACR,CAAC;;;;;;;;AC1CD,eAAsB,UAAU,KAAsB;CACpD,MAAM,EAAE,kBAAkB,MAAM,OAAO;CACvC,MAAM,EAAE,SAAS,UAAU,GAAG,YAAY,MAAM,cAAc;CAC9D,MAAM,IAAI,SAASA,WAAkB,OAAO;AAC9C;;;;;;;AAQA,SAAgB,WAAW,KAAsB,MAAqB;CACpE,IAAI,QAAQ,aAAa,OAAO,SAAS,UAAU;EACjD,MAAM,aACJ,IAAI,SAAS,YAAY;GACvB,KAAK,YAAY,QAAQ,KAAK,MAAM,KAAK,OAAO;EAClD,CAAC;EACH,MAAM,KAAK;CACb,CAAC;AACH;;;;;;;AAQA,MAAa,YACX,QACwB;CACxB,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,cAAc,IAAI,QAAQ,eAAe,EAAE,YAAY,KAAK;EAClE,MAAM,UAAU,IAAI;EAEpB,IAAI,YAAY,KAAA,GAAW;GACzB,MAAM,SAAS,YAAY,SAAS,MAAM;GAC1C,QAAQ;IACN,aAAa,SAAS,qBAAqB;IAC3C,MAAM,SAAS,UAAU,OAAO,OAAO;GACzC,CAAe;GACf;EACF;EAEA,MAAM,mBAAmB,QAAkB;GACzC,MAAM,SAAS,MAAM,OAAO;GAC5B,IAAI,IAAI,OAAO,CAAC,QAAQ,MAAM;GAC9B,IAAI,IAAI,OAAO,CAAC,OAAO,KAAK;GAC5B,IAAI,IAAI,OAAO,CAAC,SAAS,OAAO;EAClC;EAEA,IAAI,OAAO;EAEX,MAAM,UAAU,UAAkB;GAChC,QAAQ,MAAM,SAAS;EACzB;EAEA,MAAM,cAAc;GAClB,gBAAgB;GAChB,MAAM,SAAS,YAAY,SAAS,MAAM;GAC1C,IAAI;IACF,MAAM,OAAO,KAAK,MAAM,IAAI;IAC5B,QAAQ;KACN,aAAa,SAAS,qBAAqB;KAC3C;IACF,CAAC;GACH,SAAS,IAAI;IACX,QAAQ;KAAE,aAAa;KAAc,MAAM,OAAO,IAAI;IAAE,CAAC;GAC3D;EACF;EACA,MAAM,WAAW,QAAe;GAC9B,gBAAgB;GAChB,OAAO,GAAG;EACZ;EAEA,gBAAgB,IAAI;CACtB,CAAC;AACH;;;AClEA,IAAI,kBAAkB;AACtB,MAAM,kCAAkB,IAAI,IAAY;;;;;;;AAQxC,MAAa,oBAAyC,iBAAiB,CAAC,MAAM;CAC5E,MAAM,UAAU,OAAO,OACrB,CAAC,GACD,0BACA,cACF;CAEA,MAAM,iBAAiB,QAAQ;CAC/B,MAAM,YAAY,QAAQ;CAC1B,MAAM,OAAO,QAAQ;CACrB,MAAM,UAAU,QAAQ;CAExB,IAAI,OAAO;CACX,IAAI,CAAC,MAAM;EACT,OAAO,uBAAuB;EAC9B,mBAAmB;CACrB;CACA,IAAI,gBAAgB,IAAI,IAAI,GAC1B,MAAM,IAAI,MAAM,qBAAqB,IAAI,CAAC;CAE5C,gBAAgB,IAAI,IAAI;CAIxB,MAAM,cAA6B,YAC/B,IAAI,OAAO,KAAK,aAAa,SAAS,EAAE,EAAE,IAC1C;CACJ,MAAM,cAA6B,OAC9B,OAAO,SAAS,WAAW,IAAI,OAAO,IAAI,IAAI,OAC/C;CAEJ,MAAM,oBAAoB,OACxB,KACA,OACA,SACG;EAEH,MAAM,MAAM,IADO,IAAI,IAAI,KAAK,kBACf,CAAC,CAAC;EAGnB,IAAI,mBAAmB,SAAS,GAC9B,MAAM,mBAAmB;EAI3B,IAAI,CAAC,SAAS;GACZ,KAAK;GACL;EACF;EAGA,IAAI,eAAe,CAAC,YAAY,KAAK,GAAG,GAAG;GACzC,KAAK;GACL;EACF;EAGA,IAAI,eAAe,CAAC,YAAY,KAAK,GAAG,GAAG;GACzC,KAAK;GACL;EACF;EAGA,MAAM,QAAQ,KAAK,OAAO,IAAI;CAChC;CAEA,OAAO,eAAe,mBAAmB,QAAQ,EAC/C,OAAO,KACT,CAAC;CAED,OAAO;AACT;;;;;;;;AASA,MAAa,uBACX,iBAAiB,CAAC,MACf;CACH,MAAM,UAAU,OAAO,OACrB,CAAC,GACD,0BACA,EAAE,WAAW,kBAAkB,UAAU,GACzC,cACF;CAIA,MAAM,YAAY,QAAQ;CAC1B,MAAM,cAAc,YAChB,IAAI,OAAO,KAAK,aAAa,SAAS,EAAE,EAAE,IACzC;CACL,MAAM,gBAAgB,IAAI,UAAU;CAEpC,OAAO,iBAAiB;EACtB,GAAG;EACH,SAAS,OACP,KACA,OACA,UACG;GACH,MAAM,SAAS,IAAI,IAAI,IAAI,KAAK,kBAAkB;GAClD,MAAM,MAAM,OAAO;GAKnB,IAAI,eAAe,CAAC,YAAY,KAAK,GAAG,GACtC;GAMF,MAAM,SAAS,QAAQ;GACvB,MAAM,gBAAgB,IAAI,QAAQ;GAClC,IAAI,UAAU,iBAAiB,kBAAkB,QAAQ;IACvD,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,EAAE,OAAO,kBAAkB,CAAC;IACnD;GACF;GAEA,MAAM,eAAe,IAAI,QAAQ,eAAe,EAAE;GAClD,MAAM,iBAAiB,mBAAmB,IAAI,YAAY;GAE1D,IAAI,CAAC,gBAAgB;IACnB,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,EACrB,OAAO,mBACT,CAAC;IACD;GACF;GAEA,IAAI;IACF,MAAM,SAAS,eAAe,SAAS,UAAU;IACjD,IAAI,IAAI,OAAO,YAAY,MAAM,QAAQ;KACvC,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,EAAE,OAAO,mBAAmB,CAAC;KACpD;IACF;IAEA,IAAI,OAAoB,CAAC;IACzB,IAAI,WAAW,OAAO;KACpB,MAAM,MAAM,OAAO,aAAa,IAAI,MAAM;KAE1C,IAAI,KAAK,OAAO,KAAK,MAAM,GAAG;IAChC,OAAO;KACL,MAAM,OAAO,MAAM,SAAS,GAAG;KAC/B,OAAO,MAAM,QAAQ,KAAK,IAAI,IAAI,KAAK,OAAO,CAAC,KAAK,IAAI;IAC1D;IACA,MAAM,EAAE,MAAM,YAAY,WAAW,eAAe,QAAQ,GAAG,IAAI;IACnE,MAAM,gBAAgB,OAAO,mBAAmB;IAEhD,IAAI,IAAI,GAAG,SAAS,OAAO;IAC3B,MAAM,OAAO,MAAM;IACnB,IAAI,IAAI,IAAI,SAAS,OAAO;IAG5B,IAAI,CAAC,MAAM,IAAI,aAAa,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC;GAC7D,SAAS,KAAK;IACZ,QAAQ,MAAM,OAAO,GAAG,CAAC;IACzB,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,EAAE,OAAO,sBAAsB,CAAC;GACzD;EACF;CACF,CAAC;AACH"}
|
|
1
|
+
{"version":3,"file":"fastify.mjs","names":["fastifyRpcPlugin"],"sources":["../../src/options.ts","../../src/tools.ts","../../src/constants.ts","../../src/fastify/plugin.ts","../../src/fastify/helpers.ts","../../src/fastify/createMiddleware.ts"],"sourcesContent":["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","/**\n * Escapes special regex metacharacters in a string.\n * Used to safely embed user-configurable values (like rpcPrefix) into regular expressions,\n * preventing ReDoS and regex injection attacks.\n * @param s - The raw string to escape\n * @returns The escaped string safe for use in new RegExp()\n */\nexport function escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\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","/** @module Fastify plugin. Exports the RPC plugin wrapped with `fastify-plugin` for lifecycle-compatible registration. */\n// inspired by https://github.com/royalswe/vike-fastify-boilerplate/blob/main/server/index.ts\nimport fp from \"fastify-plugin\";\nimport type { MiddlewareOptions } from \"../types.d.ts\";\nimport type {\n FastifyRPCPlugin,\n RegisteredFastifyRPCPlugin,\n} from \"./types.d.ts\";\nimport { createRPCMiddleware } from \"./createMiddleware.ts\";\n\nexport type { MiddlewareOptions };\n\nconst RpcPlugin: FastifyRPCPlugin = (\n fastify,\n initialOptions,\n done,\n) => {\n // Register RPC middleware as preHandler hook\n const rpcMiddleware = createRPCMiddleware(initialOptions);\n fastify.addHook(\"preHandler\", async (request, reply) => {\n const next = () =>\n new Promise((resolve) => {\n rpcMiddleware(request, reply, resolve);\n });\n await next();\n });\n\n done();\n};\n\n// Export the plugin wrapped with fastify-plugin\nconst rpcPlugin = fp(RpcPlugin, {\n name: \"uni-rpc-fastify-plugin\",\n}) as RegisteredFastifyRPCPlugin;\n\nexport { rpcPlugin as default };\n","// @thednp/rpc/src/fastify/helpers.ts\nimport type { FastifyRequest } from \"fastify\";\nimport type { ViteDevServer } from \"vite\";\nimport type { FastifyInstance } from \"fastify\";\nimport type { Buffer } from \"node:buffer\";\nimport type { BodyResult, JsonValue } from \"../types.d.ts\";\nimport fastifyRpcPlugin from \"./plugin.ts\";\n\n/**\n * Convenience function to load RPC config and register the RPC plugin to a Fastify instance.\n * Dynamically imports loadRPCConfig and registers the fastify-rpc plugin.\n * @param app - Fastify instance\n */\nexport async function attachRPC(app: FastifyInstance) {\n const { loadRPCConfig } = await import(\"@thednp/rpc\");\n const { adapter: _adapter, ...options } = await loadRPCConfig();\n await app.register(fastifyRpcPlugin, options);\n}\n\n/**\n * Attaches Vite's dev server middlewares to a Fastify instance for development mode.\n * Uses an `onRequest` hook to delegate to Vite's connect-compatible middleware stack.\n * @param app - Fastify instance\n * @param vite - Running Vite dev server\n */\nexport function attachVite(app: FastifyInstance, vite: ViteDevServer) {\n app.addHook(\"onRequest\", async (request, reply) => {\n const next = () =>\n new Promise((resolve) => {\n vite.middlewares(request.raw, reply.raw, resolve);\n });\n await next();\n });\n}\n\n/**\n * Reads and parses the HTTP request body from a Fastify request.\n * If Fastify's body parser already consumed the stream, uses the pre-parsed body from `req.body`.\n * @param req - Fastify request object\n * @returns A promise resolving to the parsed body with its content type\n */\nexport const readBody = (\n req: FastifyRequest,\n): Promise<BodyResult> => {\n return new Promise((resolve, reject) => {\n const contentType = req.headers[\"content-type\"]?.toLowerCase() || \"\";\n const reqBody = req.body as JsonValue;\n\n if (reqBody !== undefined) {\n const isJSON = contentType.includes(\"json\");\n resolve({\n contentType: isJSON ? \"application/json\" : \"text/plain\",\n data: isJSON ? reqBody : String(reqBody),\n } as BodyResult);\n return;\n }\n\n const toggleListeners = (add?: boolean) => {\n const method = add ? \"on\" : \"off\";\n req.raw[method](\"data\", onData);\n req.raw[method](\"end\", onEnd);\n req.raw[method](\"error\", onError);\n };\n\n let body = \"\";\n\n const onData = (chunk: Buffer) => {\n body += chunk.toString();\n };\n\n const onEnd = () => {\n toggleListeners();\n const isJSON = contentType.includes(\"json\");\n try {\n const data = JSON.parse(body);\n resolve({\n contentType: isJSON ? \"application/json\" : \"text/plain\",\n data,\n });\n } catch (_e) {\n resolve({ contentType: \"text/plain\", data: String(body) });\n }\n };\n const onError = (err: Error) => {\n toggleListeners();\n reject(err);\n };\n\n toggleListeners(true);\n });\n};\n","// src/fastify/createMiddleware.ts\nimport type {\n FastifyReply,\n FastifyRequest,\n HookHandlerDoneFunction,\n} from \"fastify\";\nimport type {\n FastifyMiddlewareFn,\n FastifyMiddlewareOptions,\n} from \"./types.d.ts\";\nimport type { JsonValue } from \"../types.d.ts\";\nimport { scanForServerFiles, serverFunctionsMap } from \"@thednp/rpc/server\";\nimport { defaultMiddlewareOptions, defaultRPCOptions } from \"../options.ts\";\nimport { escapeRegExp } from \"../tools.ts\";\nimport {\n CLIENT_DISCONNECTED,\n FUNCTION_NOT_FOUND,\n INTERNAL_SERVER_ERROR,\n METHOD_NOT_ALLOWED,\n MIDDLEWARE_NAME_USED,\n REQUEST_FORBIDDEN,\n} from \"../constants.ts\";\nimport { readBody } from \"./helpers.ts\";\n\nlet middlewareCount = 0;\nconst middlewareStack = new Set<string>();\n\n/**\n * Creates a Fastify preHandler hook with optional path and rpcPrefix filtering.\n * Middleware names are deduplicated. Prefix and path regexes are compiled once at creation time.\n * @param initialOptions - Options for rpcPrefix, path matching, and the handler function\n * @returns A Fastify preHandler hook function\n */\nexport const createMiddleware: FastifyMiddlewareFn = (initialOptions = {}) => {\n const options = Object.assign(\n {},\n defaultMiddlewareOptions,\n initialOptions,\n ) as FastifyMiddlewareOptions;\n\n const middlewareName = options.name;\n const rpcPrefix = options.rpcPrefix;\n const path = options.path;\n const handler = options.handler;\n\n let name = middlewareName;\n if (!name) {\n name = \"viteRPCMiddleware-\" + middlewareCount;\n middlewareCount += 1;\n }\n if (middlewareStack.has(name)) {\n throw new Error(MIDDLEWARE_NAME_USED(name));\n }\n middlewareStack.add(name);\n\n // Hoist regex compilation out of per-request path. Escape the prefix to\n // prevent regex injection via metacharacters in the config string.\n const prefixRegex: RegExp | null = rpcPrefix\n ? new RegExp(`^/${escapeRegExp(rpcPrefix)}/`)\n : null;\n const pathMatcher: RegExp | null = path\n ? (typeof path === \"string\" ? new RegExp(path) : path)\n : null;\n\n const middlewareHandler = async (\n req: FastifyRequest,\n reply: FastifyReply,\n done: HookHandlerDoneFunction,\n ) => {\n const reqUrl = new URL(req.url, \"http://localhost\");\n const url = reqUrl.pathname;\n\n // When serving from production server, scan for server files\n if (serverFunctionsMap.size === 0) {\n await scanForServerFiles();\n }\n\n // No need to continue when no handler provided\n if (!handler) {\n done();\n return;\n }\n\n // Path matching\n if (pathMatcher && !pathMatcher.test(url)) {\n done();\n return;\n }\n\n // rpcPrefix matching (boundary-safe via escaped regex)\n if (prefixRegex && !prefixRegex.test(url)) {\n done();\n return;\n }\n\n // Execute handler\n await handler(req, reply, done);\n };\n\n Object.defineProperty(middlewareHandler, \"name\", {\n value: name,\n });\n\n return middlewareHandler;\n};\n\n/**\n * Creates the Fastify RPC middleware that routes incoming requests to registered server functions.\n * Wraps the generic createMiddleware with the RPC handler that reads the body, dispatches\n * to the matching function, and sends the JSON-serialized result.\n * @param initialOptions - Options including rpcPrefix for URL routing\n * @returns A Fastify preHandler hook function\n */\nexport const createRPCMiddleware: FastifyMiddlewareFn = (\n initialOptions = {},\n) => {\n const options = Object.assign(\n {},\n defaultMiddlewareOptions,\n { rpcPrefix: defaultRPCOptions.rpcPrefix },\n initialOptions,\n ) as FastifyMiddlewareOptions;\n\n // Hoist prefix regex (escaped) and the literal prefix-for-replace out of the\n // per-request handler to avoid regex injection and per-request compilation.\n const rpcPrefix = options.rpcPrefix;\n const prefixRegex = rpcPrefix\n ? new RegExp(`^/${escapeRegExp(rpcPrefix)}/`)\n : /* istanbul ignore next */ null;\n const prefixReplace = `/${rpcPrefix}/`;\n\n return createMiddleware({\n ...options,\n handler: async (\n req: FastifyRequest,\n reply: FastifyReply,\n _done: HookHandlerDoneFunction,\n ) => {\n const reqUrl = new URL(req.url, \"http://localhost\");\n const url = reqUrl.pathname;\n\n // Defense-in-depth: validate prefix match via escaped regex even though\n // the outer createMiddleware gates on the same prefix already.\n // istanbul ignore if\n if (prefixRegex && !prefixRegex.test(url)) {\n return;\n }\n\n // Optional origin check: reject requests whose Origin header does not\n // match the configured origin. Requests without an Origin header\n // (curl, native clients) pass through unchecked.\n const origin = options.origin;\n const requestOrigin = req.headers.origin;\n if (origin && requestOrigin && requestOrigin !== origin) {\n reply.status(403).send({ error: REQUEST_FORBIDDEN });\n return;\n }\n\n const functionName = url.replace(prefixReplace, \"\");\n const serverFunction = serverFunctionsMap.get(functionName);\n\n if (!serverFunction) {\n reply.status(404).send({\n error: FUNCTION_NOT_FOUND,\n });\n return;\n }\n\n try {\n const method = serverFunction.options?.method || \"POST\";\n if (req.method.toUpperCase() !== method) {\n reply.status(405).send({ error: METHOD_NOT_ALLOWED });\n return;\n }\n\n let args: JsonValue[] = [];\n if (method === \"GET\") {\n const raw = reqUrl.searchParams.get(\"args\");\n // istanbul ignore else\n if (raw) args = JSON.parse(raw);\n } else {\n const body = await readBody(req);\n args = Array.isArray(body.data) ? body.data : [body.data];\n }\n const { data: dataResult, cancel } = serverFunction.handler(...args);\n const onClose = () => cancel(CLIENT_DISCONNECTED);\n\n req.raw.on(\"close\", onClose);\n const data = await dataResult;\n req.raw.off(\"close\", onClose);\n\n // istanbul ignore else\n if (!reply.raw.headersSent) reply.status(200).send({ data });\n } catch (err) {\n console.error(String(err));\n reply.status(500).send({ error: INTERNAL_SERVER_ERROR });\n }\n },\n });\n};\n"],"mappings":"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"}
|
|
@@ -32,6 +32,14 @@ interface HonoMiddlewareHooks {
|
|
|
32
32
|
}
|
|
33
33
|
//#endregion
|
|
34
34
|
//#region src/fastify/types.d.ts
|
|
35
|
+
/**
|
|
36
|
+
* `fastify-plugin` function type, used to type the wrapped export.
|
|
37
|
+
*/
|
|
38
|
+
type FastifyPlugin = typeof fp;
|
|
39
|
+
/**
|
|
40
|
+
* Return type of `fastify-plugin` wrapping, matching the final plugin export.
|
|
41
|
+
*/
|
|
42
|
+
type RegisteredFastifyRPCPlugin = ReturnType<FastifyPlugin>;
|
|
35
43
|
/**
|
|
36
44
|
* Fastify middleware handler signature used by the RPC middleware.
|
|
37
45
|
*/
|
|
@@ -155,14 +163,6 @@ interface MiddlewareOptions<A extends RpcPluginOptions["adapter"] = "express"> {
|
|
|
155
163
|
}
|
|
156
164
|
//#endregion
|
|
157
165
|
//#region src/fastify/plugin.d.ts
|
|
158
|
-
/**
|
|
159
|
-
* `fastify-plugin` function type, used to type the wrapped export.
|
|
160
|
-
*/
|
|
161
|
-
type FastifyPlugin = typeof fp;
|
|
162
|
-
/**
|
|
163
|
-
* Return type of `fastify-plugin` wrapping, matching the final plugin export.
|
|
164
|
-
*/
|
|
165
|
-
type RegisteredFastifyRPCPlugin = ReturnType<FastifyPlugin>;
|
|
166
166
|
declare const rpcPlugin: RegisteredFastifyRPCPlugin;
|
|
167
167
|
//#endregion
|
|
168
168
|
export { type MiddlewareOptions, rpcPlugin as default };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.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/fastify/plugin.ts"],"mappings":";;;;;;;;;;;;;;UA+BiB;;;;;;;EAOf,UACE,KAAK,kBAAkB,SACvB,KAAK,iBAAiB,UACtB,MAAM,QAAQ,eAAe,iBAC1B;;;;;;;
|
|
1
|
+
{"version":3,"file":"plugin.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/fastify/plugin.ts"],"mappings":";;;;;;;;;;;;;;UA+BiB;;;;;;;EAOf,UACE,KAAK,kBAAkB,SACvB,KAAK,iBAAiB,UACtB,MAAM,QAAQ,eAAe,iBAC1B;;;;;;;UCzBU;;EAEf,SAAS;;;;;;;KCEC,uBAAuB;;;;KAKvB,6BAA6B,WAAW;;;;UA4BnC;;;;;;;EAOf,UACE,KAAK,gBACL,KAAK,cACL,MAAM,4BACH;;;;;;;UCzCU;;;;;;EAMf,UAAU,KAAK,SAAS,MAAM,SAAS;;;;;;;;UCTxB;;EAEf,SAAS;;EAET,MAAM;;EAEN,SAAS;;EAET,KAAK;;;;;;;;UAgMU;;;;;;;;;;;EAWf;;;;;;;EAQA;;UAGe,kBACf,UAAU;;;;EAKV;;;;;;;;;;;;EAaA,gBAAgB;;;;;;;;;;EAWhB;;;;;;;;EASA;;;;;;;;;;;;;;;;;;EAmBA,UAAU,eAAe;;;;cC9QrB,WAEA"}
|
|
@@ -188,12 +188,7 @@ const createRPCMiddleware = (initialOptions = {}) => {
|
|
|
188
188
|
};
|
|
189
189
|
//#endregion
|
|
190
190
|
//#region src/fastify/plugin.ts
|
|
191
|
-
/**
|
|
192
|
-
* Fastify plugin factory that registers the RPC middleware as a preHandler hook.
|
|
193
|
-
* @param fastify - Fastify instance
|
|
194
|
-
* @param initialOptions - Middleware options including rpcPrefix
|
|
195
|
-
* @param done - Callback to signal plugin registration completion
|
|
196
|
-
*/
|
|
191
|
+
/** @module Fastify plugin. Exports the RPC plugin wrapped with `fastify-plugin` for lifecycle-compatible registration. */
|
|
197
192
|
const RpcPlugin = (fastify, initialOptions, done) => {
|
|
198
193
|
const rpcMiddleware = createRPCMiddleware(initialOptions);
|
|
199
194
|
fastify.addHook("preHandler", async (request, reply) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.mjs","names":[],"sources":["../../../../src/options.ts","../../../../src/tools.ts","../../../../src/constants.ts","../../../../src/fastify/helpers.ts","../../../../src/fastify/createMiddleware.ts","../../../../src/fastify/plugin.ts"],"sourcesContent":["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","/**\n * Escapes special regex metacharacters in a string.\n * Used to safely embed user-configurable values (like rpcPrefix) into regular expressions,\n * preventing ReDoS and regex injection attacks.\n * @param s - The raw string to escape\n * @returns The escaped string safe for use in new RegExp()\n */\nexport function escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\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","// @thednp/rpc/src/fastify/helpers.ts\nimport type { FastifyRequest } from \"fastify\";\nimport type { ViteDevServer } from \"vite\";\nimport type { FastifyInstance } from \"fastify\";\nimport type { Buffer } from \"node:buffer\";\nimport fastifyRpcPlugin from \"./plugin.ts\";\nimport type { BodyResult, JsonValue } from \"../types.d.ts\";\n\n/**\n * Convenience function to load RPC config and register the RPC plugin to a Fastify instance.\n * Dynamically imports loadRPCConfig and registers the fastify-rpc plugin.\n * @param app - Fastify instance\n */\nexport async function attachRPC(app: FastifyInstance) {\n const { loadRPCConfig } = await import(\"@thednp/rpc\");\n const { adapter: _adapter, ...options } = await loadRPCConfig();\n await app.register(fastifyRpcPlugin, options);\n}\n\n/**\n * Attaches Vite's dev server middlewares to a Fastify instance for development mode.\n * Uses an `onRequest` hook to delegate to Vite's connect-compatible middleware stack.\n * @param app - Fastify instance\n * @param vite - Running Vite dev server\n */\nexport function attachVite(app: FastifyInstance, vite: ViteDevServer) {\n app.addHook(\"onRequest\", async (request, reply) => {\n const next = () =>\n new Promise((resolve) => {\n vite.middlewares(request.raw, reply.raw, resolve);\n });\n await next();\n });\n}\n\n/**\n * Reads and parses the HTTP request body from a Fastify request.\n * If Fastify's body parser already consumed the stream, uses the pre-parsed body from `req.body`.\n * @param req - Fastify request object\n * @returns A promise resolving to the parsed body with its content type\n */\nexport const readBody = (\n req: FastifyRequest,\n): Promise<BodyResult> => {\n return new Promise((resolve, reject) => {\n const contentType = req.headers[\"content-type\"]?.toLowerCase() || \"\";\n const reqBody = req.body as JsonValue;\n\n if (reqBody !== undefined) {\n const isJSON = contentType.includes(\"json\");\n resolve({\n contentType: isJSON ? \"application/json\" : \"text/plain\",\n data: isJSON ? reqBody : String(reqBody),\n } as BodyResult);\n return;\n }\n\n const toggleListeners = (add?: boolean) => {\n const method = add ? \"on\" : \"off\";\n req.raw[method](\"data\", onData);\n req.raw[method](\"end\", onEnd);\n req.raw[method](\"error\", onError);\n };\n\n let body = \"\";\n\n const onData = (chunk: Buffer) => {\n body += chunk.toString();\n };\n\n const onEnd = () => {\n toggleListeners();\n const isJSON = contentType.includes(\"json\");\n try {\n const data = JSON.parse(body);\n resolve({\n contentType: isJSON ? \"application/json\" : \"text/plain\",\n data,\n });\n } catch (_e) {\n resolve({ contentType: \"text/plain\", data: String(body) });\n }\n };\n const onError = (err: Error) => {\n toggleListeners();\n reject(err);\n };\n\n toggleListeners(true);\n });\n};\n","// src/fastify/createMiddleware.ts\nimport type {\n FastifyReply,\n FastifyRequest,\n HookHandlerDoneFunction,\n} from \"fastify\";\nimport type {\n FastifyMiddlewareFn,\n FastifyMiddlewareOptions,\n} from \"./types.d.ts\";\nimport type { JsonValue } from \"../types.d.ts\";\nimport { scanForServerFiles, serverFunctionsMap } from \"@thednp/rpc/server\";\nimport { defaultMiddlewareOptions, defaultRPCOptions } from \"../options.ts\";\nimport { escapeRegExp } from \"../tools.ts\";\nimport {\n CLIENT_DISCONNECTED,\n FUNCTION_NOT_FOUND,\n INTERNAL_SERVER_ERROR,\n METHOD_NOT_ALLOWED,\n MIDDLEWARE_NAME_USED,\n REQUEST_FORBIDDEN,\n} from \"../constants.ts\";\nimport { readBody } from \"./helpers.ts\";\n\nlet middlewareCount = 0;\nconst middlewareStack = new Set<string>();\n\n/**\n * Creates a Fastify preHandler hook with optional path and rpcPrefix filtering.\n * Middleware names are deduplicated. Prefix and path regexes are compiled once at creation time.\n * @param initialOptions - Options for rpcPrefix, path matching, and the handler function\n * @returns A Fastify preHandler hook function\n */\nexport const createMiddleware: FastifyMiddlewareFn = (initialOptions = {}) => {\n const options = Object.assign(\n {},\n defaultMiddlewareOptions,\n initialOptions,\n ) as FastifyMiddlewareOptions;\n\n const middlewareName = options.name;\n const rpcPrefix = options.rpcPrefix;\n const path = options.path;\n const handler = options.handler;\n\n let name = middlewareName;\n if (!name) {\n name = \"viteRPCMiddleware-\" + middlewareCount;\n middlewareCount += 1;\n }\n if (middlewareStack.has(name)) {\n throw new Error(MIDDLEWARE_NAME_USED(name));\n }\n middlewareStack.add(name);\n\n // Hoist regex compilation out of per-request path. Escape the prefix to\n // prevent regex injection via metacharacters in the config string.\n const prefixRegex: RegExp | null = rpcPrefix\n ? new RegExp(`^/${escapeRegExp(rpcPrefix)}/`)\n : null;\n const pathMatcher: RegExp | null = path\n ? (typeof path === \"string\" ? new RegExp(path) : path)\n : null;\n\n const middlewareHandler = async (\n req: FastifyRequest,\n reply: FastifyReply,\n done: HookHandlerDoneFunction,\n ) => {\n const reqUrl = new URL(req.url, \"http://localhost\");\n const url = reqUrl.pathname;\n\n // When serving from production server, scan for server files\n if (serverFunctionsMap.size === 0) {\n await scanForServerFiles();\n }\n\n // No need to continue when no handler provided\n if (!handler) {\n done();\n return;\n }\n\n // Path matching\n if (pathMatcher && !pathMatcher.test(url)) {\n done();\n return;\n }\n\n // rpcPrefix matching (boundary-safe via escaped regex)\n if (prefixRegex && !prefixRegex.test(url)) {\n done();\n return;\n }\n\n // Execute handler\n await handler(req, reply, done);\n };\n\n Object.defineProperty(middlewareHandler, \"name\", {\n value: name,\n });\n\n return middlewareHandler;\n};\n\n/**\n * Creates the Fastify RPC middleware that routes incoming requests to registered server functions.\n * Wraps the generic createMiddleware with the RPC handler that reads the body, dispatches\n * to the matching function, and sends the JSON-serialized result.\n * @param initialOptions - Options including rpcPrefix for URL routing\n * @returns A Fastify preHandler hook function\n */\nexport const createRPCMiddleware: FastifyMiddlewareFn = (\n initialOptions = {},\n) => {\n const options = Object.assign(\n {},\n defaultMiddlewareOptions,\n { rpcPrefix: defaultRPCOptions.rpcPrefix },\n initialOptions,\n ) as FastifyMiddlewareOptions;\n\n // Hoist prefix regex (escaped) and the literal prefix-for-replace out of the\n // per-request handler to avoid regex injection and per-request compilation.\n const rpcPrefix = options.rpcPrefix;\n const prefixRegex = rpcPrefix\n ? new RegExp(`^/${escapeRegExp(rpcPrefix)}/`)\n : /* istanbul ignore next */ null;\n const prefixReplace = `/${rpcPrefix}/`;\n\n return createMiddleware({\n ...options,\n handler: async (\n req: FastifyRequest,\n reply: FastifyReply,\n _done: HookHandlerDoneFunction,\n ) => {\n const reqUrl = new URL(req.url, \"http://localhost\");\n const url = reqUrl.pathname;\n\n // Defense-in-depth: validate prefix match via escaped regex even though\n // the outer createMiddleware gates on the same prefix already.\n // istanbul ignore if\n if (prefixRegex && !prefixRegex.test(url)) {\n return;\n }\n\n // Optional origin check: reject requests whose Origin header does not\n // match the configured origin. Requests without an Origin header\n // (curl, native clients) pass through unchecked.\n const origin = options.origin;\n const requestOrigin = req.headers.origin;\n if (origin && requestOrigin && requestOrigin !== origin) {\n reply.status(403).send({ error: REQUEST_FORBIDDEN });\n return;\n }\n\n const functionName = url.replace(prefixReplace, \"\");\n const serverFunction = serverFunctionsMap.get(functionName);\n\n if (!serverFunction) {\n reply.status(404).send({\n error: FUNCTION_NOT_FOUND,\n });\n return;\n }\n\n try {\n const method = serverFunction.options?.method || \"POST\";\n if (req.method.toUpperCase() !== method) {\n reply.status(405).send({ error: METHOD_NOT_ALLOWED });\n return;\n }\n\n let args: JsonValue[] = [];\n if (method === \"GET\") {\n const raw = reqUrl.searchParams.get(\"args\");\n // istanbul ignore else\n if (raw) args = JSON.parse(raw);\n } else {\n const body = await readBody(req);\n args = Array.isArray(body.data) ? body.data : [body.data];\n }\n const { data: dataResult, cancel } = serverFunction.handler(...args);\n const onClose = () => cancel(CLIENT_DISCONNECTED);\n\n req.raw.on(\"close\", onClose);\n const data = await dataResult;\n req.raw.off(\"close\", onClose);\n\n // istanbul ignore else\n if (!reply.raw.headersSent) reply.status(200).send({ data });\n } catch (err) {\n console.error(String(err));\n reply.status(500).send({ error: INTERNAL_SERVER_ERROR });\n }\n },\n });\n};\n","/** @module Fastify plugin. Exports the RPC plugin wrapped with `fastify-plugin` for lifecycle-compatible registration. */\n// inspired by https://github.com/royalswe/vike-fastify-boilerplate/blob/main/server/index.ts\nimport type { FastifyInstance } from \"fastify\";\nimport fp from \"fastify-plugin\";\nimport type { MiddlewareOptions } from \"../types.d.ts\";\nimport { createRPCMiddleware } from \"./createMiddleware.ts\";\n\nexport type { MiddlewareOptions };\n\n/**\n * Fastify RPC plugin signature: registers the middleware as a preHandler hook.\n */\ntype FastifyRPCPlugin = (\n fastify: FastifyInstance,\n initialOptions: Partial<MiddlewareOptions<\"fastify\">>,\n done: () => void,\n) => void;\n\n/**\n * Fastify plugin factory that registers the RPC middleware as a preHandler hook.\n * @param fastify - Fastify instance\n * @param initialOptions - Middleware options including rpcPrefix\n * @param done - Callback to signal plugin registration completion\n */\nconst RpcPlugin: FastifyRPCPlugin = (\n fastify,\n initialOptions,\n done,\n) => {\n // Register RPC middleware as preHandler hook\n const rpcMiddleware = createRPCMiddleware(initialOptions);\n fastify.addHook(\"preHandler\", async (request, reply) => {\n const next = () =>\n new Promise((resolve) => {\n rpcMiddleware(request, reply, resolve);\n });\n await next();\n });\n\n done();\n};\n\n/**\n * `fastify-plugin` function type, used to type the wrapped export.\n */\ntype FastifyPlugin = typeof fp;\n\n/**\n * Return type of `fastify-plugin` wrapping, matching the final plugin export.\n */\ntype RegisteredFastifyRPCPlugin = ReturnType<FastifyPlugin>;\n\n// Export the plugin wrapped with fastify-plugin\nconst rpcPlugin = fp(RpcPlugin, {\n name: \"uni-rpc-fastify-plugin\",\n}) as RegisteredFastifyRPCPlugin;\n\nexport { rpcPlugin as default };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"plugin.mjs","names":[],"sources":["../../../../src/options.ts","../../../../src/tools.ts","../../../../src/constants.ts","../../../../src/fastify/helpers.ts","../../../../src/fastify/createMiddleware.ts","../../../../src/fastify/plugin.ts"],"sourcesContent":["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","/**\n * Escapes special regex metacharacters in a string.\n * Used to safely embed user-configurable values (like rpcPrefix) into regular expressions,\n * preventing ReDoS and regex injection attacks.\n * @param s - The raw string to escape\n * @returns The escaped string safe for use in new RegExp()\n */\nexport function escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\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","// @thednp/rpc/src/fastify/helpers.ts\nimport type { FastifyRequest } from \"fastify\";\nimport type { ViteDevServer } from \"vite\";\nimport type { FastifyInstance } from \"fastify\";\nimport type { Buffer } from \"node:buffer\";\nimport type { BodyResult, JsonValue } from \"../types.d.ts\";\nimport fastifyRpcPlugin from \"./plugin.ts\";\n\n/**\n * Convenience function to load RPC config and register the RPC plugin to a Fastify instance.\n * Dynamically imports loadRPCConfig and registers the fastify-rpc plugin.\n * @param app - Fastify instance\n */\nexport async function attachRPC(app: FastifyInstance) {\n const { loadRPCConfig } = await import(\"@thednp/rpc\");\n const { adapter: _adapter, ...options } = await loadRPCConfig();\n await app.register(fastifyRpcPlugin, options);\n}\n\n/**\n * Attaches Vite's dev server middlewares to a Fastify instance for development mode.\n * Uses an `onRequest` hook to delegate to Vite's connect-compatible middleware stack.\n * @param app - Fastify instance\n * @param vite - Running Vite dev server\n */\nexport function attachVite(app: FastifyInstance, vite: ViteDevServer) {\n app.addHook(\"onRequest\", async (request, reply) => {\n const next = () =>\n new Promise((resolve) => {\n vite.middlewares(request.raw, reply.raw, resolve);\n });\n await next();\n });\n}\n\n/**\n * Reads and parses the HTTP request body from a Fastify request.\n * If Fastify's body parser already consumed the stream, uses the pre-parsed body from `req.body`.\n * @param req - Fastify request object\n * @returns A promise resolving to the parsed body with its content type\n */\nexport const readBody = (\n req: FastifyRequest,\n): Promise<BodyResult> => {\n return new Promise((resolve, reject) => {\n const contentType = req.headers[\"content-type\"]?.toLowerCase() || \"\";\n const reqBody = req.body as JsonValue;\n\n if (reqBody !== undefined) {\n const isJSON = contentType.includes(\"json\");\n resolve({\n contentType: isJSON ? \"application/json\" : \"text/plain\",\n data: isJSON ? reqBody : String(reqBody),\n } as BodyResult);\n return;\n }\n\n const toggleListeners = (add?: boolean) => {\n const method = add ? \"on\" : \"off\";\n req.raw[method](\"data\", onData);\n req.raw[method](\"end\", onEnd);\n req.raw[method](\"error\", onError);\n };\n\n let body = \"\";\n\n const onData = (chunk: Buffer) => {\n body += chunk.toString();\n };\n\n const onEnd = () => {\n toggleListeners();\n const isJSON = contentType.includes(\"json\");\n try {\n const data = JSON.parse(body);\n resolve({\n contentType: isJSON ? \"application/json\" : \"text/plain\",\n data,\n });\n } catch (_e) {\n resolve({ contentType: \"text/plain\", data: String(body) });\n }\n };\n const onError = (err: Error) => {\n toggleListeners();\n reject(err);\n };\n\n toggleListeners(true);\n });\n};\n","// src/fastify/createMiddleware.ts\nimport type {\n FastifyReply,\n FastifyRequest,\n HookHandlerDoneFunction,\n} from \"fastify\";\nimport type {\n FastifyMiddlewareFn,\n FastifyMiddlewareOptions,\n} from \"./types.d.ts\";\nimport type { JsonValue } from \"../types.d.ts\";\nimport { scanForServerFiles, serverFunctionsMap } from \"@thednp/rpc/server\";\nimport { defaultMiddlewareOptions, defaultRPCOptions } from \"../options.ts\";\nimport { escapeRegExp } from \"../tools.ts\";\nimport {\n CLIENT_DISCONNECTED,\n FUNCTION_NOT_FOUND,\n INTERNAL_SERVER_ERROR,\n METHOD_NOT_ALLOWED,\n MIDDLEWARE_NAME_USED,\n REQUEST_FORBIDDEN,\n} from \"../constants.ts\";\nimport { readBody } from \"./helpers.ts\";\n\nlet middlewareCount = 0;\nconst middlewareStack = new Set<string>();\n\n/**\n * Creates a Fastify preHandler hook with optional path and rpcPrefix filtering.\n * Middleware names are deduplicated. Prefix and path regexes are compiled once at creation time.\n * @param initialOptions - Options for rpcPrefix, path matching, and the handler function\n * @returns A Fastify preHandler hook function\n */\nexport const createMiddleware: FastifyMiddlewareFn = (initialOptions = {}) => {\n const options = Object.assign(\n {},\n defaultMiddlewareOptions,\n initialOptions,\n ) as FastifyMiddlewareOptions;\n\n const middlewareName = options.name;\n const rpcPrefix = options.rpcPrefix;\n const path = options.path;\n const handler = options.handler;\n\n let name = middlewareName;\n if (!name) {\n name = \"viteRPCMiddleware-\" + middlewareCount;\n middlewareCount += 1;\n }\n if (middlewareStack.has(name)) {\n throw new Error(MIDDLEWARE_NAME_USED(name));\n }\n middlewareStack.add(name);\n\n // Hoist regex compilation out of per-request path. Escape the prefix to\n // prevent regex injection via metacharacters in the config string.\n const prefixRegex: RegExp | null = rpcPrefix\n ? new RegExp(`^/${escapeRegExp(rpcPrefix)}/`)\n : null;\n const pathMatcher: RegExp | null = path\n ? (typeof path === \"string\" ? new RegExp(path) : path)\n : null;\n\n const middlewareHandler = async (\n req: FastifyRequest,\n reply: FastifyReply,\n done: HookHandlerDoneFunction,\n ) => {\n const reqUrl = new URL(req.url, \"http://localhost\");\n const url = reqUrl.pathname;\n\n // When serving from production server, scan for server files\n if (serverFunctionsMap.size === 0) {\n await scanForServerFiles();\n }\n\n // No need to continue when no handler provided\n if (!handler) {\n done();\n return;\n }\n\n // Path matching\n if (pathMatcher && !pathMatcher.test(url)) {\n done();\n return;\n }\n\n // rpcPrefix matching (boundary-safe via escaped regex)\n if (prefixRegex && !prefixRegex.test(url)) {\n done();\n return;\n }\n\n // Execute handler\n await handler(req, reply, done);\n };\n\n Object.defineProperty(middlewareHandler, \"name\", {\n value: name,\n });\n\n return middlewareHandler;\n};\n\n/**\n * Creates the Fastify RPC middleware that routes incoming requests to registered server functions.\n * Wraps the generic createMiddleware with the RPC handler that reads the body, dispatches\n * to the matching function, and sends the JSON-serialized result.\n * @param initialOptions - Options including rpcPrefix for URL routing\n * @returns A Fastify preHandler hook function\n */\nexport const createRPCMiddleware: FastifyMiddlewareFn = (\n initialOptions = {},\n) => {\n const options = Object.assign(\n {},\n defaultMiddlewareOptions,\n { rpcPrefix: defaultRPCOptions.rpcPrefix },\n initialOptions,\n ) as FastifyMiddlewareOptions;\n\n // Hoist prefix regex (escaped) and the literal prefix-for-replace out of the\n // per-request handler to avoid regex injection and per-request compilation.\n const rpcPrefix = options.rpcPrefix;\n const prefixRegex = rpcPrefix\n ? new RegExp(`^/${escapeRegExp(rpcPrefix)}/`)\n : /* istanbul ignore next */ null;\n const prefixReplace = `/${rpcPrefix}/`;\n\n return createMiddleware({\n ...options,\n handler: async (\n req: FastifyRequest,\n reply: FastifyReply,\n _done: HookHandlerDoneFunction,\n ) => {\n const reqUrl = new URL(req.url, \"http://localhost\");\n const url = reqUrl.pathname;\n\n // Defense-in-depth: validate prefix match via escaped regex even though\n // the outer createMiddleware gates on the same prefix already.\n // istanbul ignore if\n if (prefixRegex && !prefixRegex.test(url)) {\n return;\n }\n\n // Optional origin check: reject requests whose Origin header does not\n // match the configured origin. Requests without an Origin header\n // (curl, native clients) pass through unchecked.\n const origin = options.origin;\n const requestOrigin = req.headers.origin;\n if (origin && requestOrigin && requestOrigin !== origin) {\n reply.status(403).send({ error: REQUEST_FORBIDDEN });\n return;\n }\n\n const functionName = url.replace(prefixReplace, \"\");\n const serverFunction = serverFunctionsMap.get(functionName);\n\n if (!serverFunction) {\n reply.status(404).send({\n error: FUNCTION_NOT_FOUND,\n });\n return;\n }\n\n try {\n const method = serverFunction.options?.method || \"POST\";\n if (req.method.toUpperCase() !== method) {\n reply.status(405).send({ error: METHOD_NOT_ALLOWED });\n return;\n }\n\n let args: JsonValue[] = [];\n if (method === \"GET\") {\n const raw = reqUrl.searchParams.get(\"args\");\n // istanbul ignore else\n if (raw) args = JSON.parse(raw);\n } else {\n const body = await readBody(req);\n args = Array.isArray(body.data) ? body.data : [body.data];\n }\n const { data: dataResult, cancel } = serverFunction.handler(...args);\n const onClose = () => cancel(CLIENT_DISCONNECTED);\n\n req.raw.on(\"close\", onClose);\n const data = await dataResult;\n req.raw.off(\"close\", onClose);\n\n // istanbul ignore else\n if (!reply.raw.headersSent) reply.status(200).send({ data });\n } catch (err) {\n console.error(String(err));\n reply.status(500).send({ error: INTERNAL_SERVER_ERROR });\n }\n },\n });\n};\n","/** @module Fastify plugin. Exports the RPC plugin wrapped with `fastify-plugin` for lifecycle-compatible registration. */\n// inspired by https://github.com/royalswe/vike-fastify-boilerplate/blob/main/server/index.ts\nimport fp from \"fastify-plugin\";\nimport type { MiddlewareOptions } from \"../types.d.ts\";\nimport type {\n FastifyRPCPlugin,\n RegisteredFastifyRPCPlugin,\n} from \"./types.d.ts\";\nimport { createRPCMiddleware } from \"./createMiddleware.ts\";\n\nexport type { MiddlewareOptions };\n\nconst RpcPlugin: FastifyRPCPlugin = (\n fastify,\n initialOptions,\n done,\n) => {\n // Register RPC middleware as preHandler hook\n const rpcMiddleware = createRPCMiddleware(initialOptions);\n fastify.addHook(\"preHandler\", async (request, reply) => {\n const next = () =>\n new Promise((resolve) => {\n rpcMiddleware(request, reply, resolve);\n });\n await next();\n });\n\n done();\n};\n\n// Export the plugin wrapped with fastify-plugin\nconst rpcPlugin = fp(RpcPlugin, {\n name: \"uni-rpc-fastify-plugin\",\n}) as RegisteredFastifyRPCPlugin;\n\nexport { rpcPlugin as default };\n"],"mappings":"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"}
|
|
@@ -5,6 +5,7 @@ import "hono";
|
|
|
5
5
|
import "@hono/node-server";
|
|
6
6
|
import "hono/factory";
|
|
7
7
|
import "fastify";
|
|
8
|
+
import "fastify-plugin";
|
|
8
9
|
import "koa";
|
|
9
10
|
//#region src/types.d.ts
|
|
10
11
|
/**
|
|
@@ -30,6 +31,16 @@ type JsonArray = JsonValue[];
|
|
|
30
31
|
* Any JSON-serializable value: primitive, array, or object.
|
|
31
32
|
*/
|
|
32
33
|
type JsonValue = JsonPrimitive | JsonArray | JsonObject;
|
|
34
|
+
/**
|
|
35
|
+
* Return shape of `innerModule`: a promise of the response data plus
|
|
36
|
+
* a `cancel` function to abort the underlying fetch request.
|
|
37
|
+
*/
|
|
38
|
+
type InnerModReturn = {
|
|
39
|
+
/** Promise resolving to the server response data */
|
|
40
|
+
data: Promise<JsonValue | void>;
|
|
41
|
+
/** Aborts the in-flight request with the given reason */
|
|
42
|
+
cancel: (reason: string) => void;
|
|
43
|
+
};
|
|
33
44
|
//#endregion
|
|
34
45
|
//#region src/helpers.d.ts
|
|
35
46
|
/**
|
|
@@ -41,16 +52,6 @@ type JsonValue = JsonPrimitive | JsonArray | JsonObject;
|
|
|
41
52
|
* @returns The response data, or void on cancellation
|
|
42
53
|
*/
|
|
43
54
|
declare const handleResponse: (response: Response) => Promise<JsonValue | void>;
|
|
44
|
-
/**
|
|
45
|
-
* Return shape of `innerModule`: a promise of the response data plus
|
|
46
|
-
* a `cancel` function to abort the underlying fetch request.
|
|
47
|
-
*/
|
|
48
|
-
type InnerModReturn = {
|
|
49
|
-
/** Promise resolving to the server response data */
|
|
50
|
-
data: Promise<JsonValue | void>;
|
|
51
|
-
/** Aborts the in-flight request with the given reason */
|
|
52
|
-
cancel: (reason: string) => void;
|
|
53
|
-
};
|
|
54
55
|
/**
|
|
55
56
|
* Creates an AbortController-bound fetch call for a single RPC function.
|
|
56
57
|
* Used by the auto-generated client modules to issue HTTP requests with cancellation support.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.d.mts","names":[],"sources":["../../src/types.d.ts","../../src/helpers.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"helpers.d.mts","names":[],"sources":["../../src/types.d.ts","../../src/helpers.ts"],"mappings":";;;;;;;;;;;;;KAgEY;;;;;KAqCA;;;;KAIA;GAAgB,cAAc,YAAY;;;;;KAI1C,YAAY;;;;KAIZ,YAAY,gBAAgB,YAAY;;;;;KAmMxC;;EAEV,MAAM,QAAQ;;EAEd,SAAS;;;;;;;;;;;;cC5SE,iBAAc,UACf,aACT,QAAQ;;;;;;;;;;;;;;cAyBE,cAAW,MAChB,UAAQ,SACL,aAAW,aACP,aAAW,gBACV,cACF,4BAEX"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.mjs","names":[],"sources":["../../src/constants.ts","../../src/helpers.ts"],"sourcesContent":["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","/** @module Client-side helper utilities. Exports `handleResponse` for processing fetch responses and `innerModule` for creating AbortController-bound RPC fetch calls. */\nimport type { Credentials, JsonValue } from \"./types.d.ts\";\nimport { FETCH_ERROR_PREFIX, REQUEST_CANCELLED } from \"./constants.ts\";\n\n/**\n * Processes an HTTP fetch response from the RPC server.\n * On HTTP 499 or 408 (client cancellation), logs a warning and returns undefined.\n * On other error statuses, throws a Fetch error.\n * On success, parses JSON and returns `result.data` — or throws if `result.error` is set.\n * @param response - Fetch Response object from the RPC endpoint\n * @returns The response data, or void on cancellation\n */\nexport const handleResponse = async (\n response: Response,\n): Promise<JsonValue | void> => {\n if (!response.ok) {\n if (response.status === 499 || response.status === 408) {\n return console.warn(REQUEST_CANCELLED);\n }\n throw new Error(FETCH_ERROR_PREFIX + response.statusText);\n }\n const result = await response.json();\n if (result.error) throw new Error(result.error);\n return result.data;\n};\n\n/**\n *
|
|
1
|
+
{"version":3,"file":"helpers.mjs","names":[],"sources":["../../src/constants.ts","../../src/helpers.ts"],"sourcesContent":["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","/** @module Client-side helper utilities. Exports `handleResponse` for processing fetch responses and `innerModule` for creating AbortController-bound RPC fetch calls. */\nimport type { Credentials, InnerModReturn, JsonValue } from \"./types.d.ts\";\nimport { FETCH_ERROR_PREFIX, REQUEST_CANCELLED } from \"./constants.ts\";\n\n/**\n * Processes an HTTP fetch response from the RPC server.\n * On HTTP 499 or 408 (client cancellation), logs a warning and returns undefined.\n * On other error statuses, throws a Fetch error.\n * On success, parses JSON and returns `result.data` — or throws if `result.error` is set.\n * @param response - Fetch Response object from the RPC endpoint\n * @returns The response data, or void on cancellation\n */\nexport const handleResponse = async (\n response: Response,\n): Promise<JsonValue | void> => {\n if (!response.ok) {\n if (response.status === 499 || response.status === 408) {\n return console.warn(REQUEST_CANCELLED);\n }\n throw new Error(FETCH_ERROR_PREFIX + response.statusText);\n }\n const result = await response.json();\n if (result.error) throw new Error(result.error);\n return result.data;\n};\n\n/**\n * Creates an AbortController-bound fetch call for a single RPC function.\n * Used by the auto-generated client modules to issue HTTP requests with cancellation support.\n * GET requests carry arguments as an `?args=` JSON query parameter, since a fetch\n * request body is not allowed on GET.\n * @param body - Serialized request body (JSON string or raw text)\n * @param headers - HTTP headers (Content-Type, etc.)\n * @param credentials - Fetch credentials policy (\"same-origin\", \"include\", or \"omit\")\n * @param prefix - RPC endpoint prefix (e.g. \"__rpc\")\n * @param name - Registered server function name\n * @param method - HTTP method to use, \"POST\" by default\n * @returns An object with `data` (promise resolving to the server response) and `cancel` (abort function)\n */\nexport const innerModule = (\n body: BodyInit,\n headers: HeadersInit,\n credentials: Credentials,\n prefix: string,\n name: string,\n method?: \"GET\" | \"POST\",\n): InnerModReturn => {\n const controller = new AbortController();\n const cancel = (reason: string) => controller.abort(reason);\n\n const fetcher = async () => {\n try {\n const isGet = method === \"GET\";\n const url = isGet\n ? `/${prefix}/${name}?args=${encodeURIComponent(String(body))}`\n : `/${prefix}/${name}`;\n const response = await fetch(url, {\n method: isGet ? \"GET\" : \"POST\",\n headers,\n credentials,\n body: isGet ? undefined : body,\n signal: controller.signal,\n });\n return await handleResponse(response);\n } catch (err) {\n throw err;\n }\n };\n\n return {\n data: fetcher(),\n cancel,\n };\n};\n"],"mappings":";AAEA,MAAa,oBAAoB;AAEjC,MAAa,qBAAqB;;;;;;;;;;;ACQlC,MAAa,iBAAiB,OAC5B,aAC8B;CAC9B,IAAI,CAAC,SAAS,IAAI;EAChB,IAAI,SAAS,WAAW,OAAO,SAAS,WAAW,KACjD,OAAO,QAAQ,KAAK,iBAAiB;EAEvC,MAAM,IAAI,MAAM,qBAAqB,SAAS,UAAU;CAC1D;CACA,MAAM,SAAS,MAAM,SAAS,KAAK;CACnC,IAAI,OAAO,OAAO,MAAM,IAAI,MAAM,OAAO,KAAK;CAC9C,OAAO,OAAO;AAChB;;;;;;;;;;;;;;AAeA,MAAa,eACX,MACA,SACA,aACA,QACA,MACA,WACmB;CACnB,MAAM,aAAa,IAAI,gBAAgB;CACvC,MAAM,UAAU,WAAmB,WAAW,MAAM,MAAM;CAE1D,MAAM,UAAU,YAAY;EAC1B,IAAI;GACF,MAAM,QAAQ,WAAW;GACzB,MAAM,MAAM,QACR,IAAI,OAAO,GAAG,KAAK,QAAQ,mBAAmB,OAAO,IAAI,CAAC,MAC1D,IAAI,OAAO,GAAG;GAClB,MAAM,WAAW,MAAM,MAAM,KAAK;IAChC,QAAQ,QAAQ,QAAQ;IACxB;IACA;IACA,MAAM,QAAQ,KAAA,IAAY;IAC1B,QAAQ,WAAW;GACrB,CAAC;GACD,OAAO,MAAM,eAAe,QAAQ;EACtC,SAAS,KAAK;GACZ,MAAM;EACR;CACF;CAEA,OAAO;EACL,MAAM,QAAQ;EACd;CACF;AACF"}
|
package/dist/hono/hono.d.mts
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import { createMiddleware as createMiddleware$1 } from "hono/factory";
|
|
2
2
|
import { Context, Hono, MiddlewareHandler } from "hono";
|
|
3
|
+
import { IncomingMessage } from "node:http";
|
|
3
4
|
import { MiddlewareOptions, RpcPluginOptions } from "@thednp/rpc";
|
|
4
5
|
import { HttpBindings } from "@hono/node-server";
|
|
5
6
|
import { ViteDevServer } from "vite";
|
|
6
7
|
import "express";
|
|
7
8
|
import "fastify";
|
|
9
|
+
import "fastify-plugin";
|
|
8
10
|
import "koa";
|
|
9
11
|
//#region src/hono/types.d.ts
|
|
12
|
+
/**
|
|
13
|
+
* Node incoming message with an optional pre-parsed body.
|
|
14
|
+
*/
|
|
15
|
+
type IncomingWithBody = IncomingMessage & {
|
|
16
|
+
body?: unknown;
|
|
17
|
+
};
|
|
10
18
|
/**
|
|
11
19
|
* Hono-specific middleware options, constrained to the `"hono"` adapter.
|
|
12
20
|
*/
|
|
@@ -106,5 +114,5 @@ declare const viteMiddleware: (vite: ViteDevServer) => ReturnType<typeof createM
|
|
|
106
114
|
*/
|
|
107
115
|
declare const readBody: (c: Context) => Promise<BodyResult>;
|
|
108
116
|
//#endregion
|
|
109
|
-
export { type HonoMiddlewareFn, type HonoMiddlewareHooks, type HonoMiddlewareOptions, attachRPC, attachVite, createMiddleware, createRPCMiddleware, readBody, viteMiddleware };
|
|
117
|
+
export { type HonoMiddlewareFn, type HonoMiddlewareHooks, type HonoMiddlewareOptions, type IncomingWithBody, attachRPC, attachVite, createMiddleware, createRPCMiddleware, readBody, viteMiddleware };
|
|
110
118
|
//# sourceMappingURL=hono.d.mts.map
|
package/dist/hono/hono.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hono.d.mts","names":[],"sources":["../../src/hono/types.d.ts","../../src/hono/createMiddleware.ts","../../src/types.d.ts","../../src/hono/helpers.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"hono.d.mts","names":[],"sources":["../../src/hono/types.d.ts","../../src/hono/createMiddleware.ts","../../src/types.d.ts","../../src/hono/helpers.ts"],"mappings":";;;;;;;;;;;;;;KAOY,mBAAmB;EAAoB;;;;;KAKvC,wBAAwB;;;;UAKnB;;EAEf,SAAS;;;;;;KAOC,oBAAoB,UAAU,sCACxC,iBAAiB,QAAQ,kBAAkB,QACxC;;;;;;;;;;cCAQ,kBAAkB;;;;;;;;cA0ElB,qBAAqB;;;;;;KCjCtB;EACN;EAAiC,MAAM;;EACvC;EAA2B;;;;;;KA8BrB;;;;KAIA;GAAgB,cAAc,YAAY;;;;;KAI1C,YAAY;;;;KAIZ,YAAY,gBAAgB,YAAY;;;;;;;;iBCjG9B,UAAU,KAAK,OAAI;;;;;;;cAa5B,aAAU,KAAS,MAAI,MAAQ;;;;;;;;;cAY/B,iBAAc,MACnB,kBACL,kBAAkB;EAAmB,UAAU;;;;;;;;cAgDrC,WAAQ,GAChB,YACF,QAAQ"}
|
package/dist/hono/hono.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hono.mjs","names":["createMiddleware","createHonoMiddleware"],"sources":["../../src/options.ts","../../src/tools.ts","../../src/constants.ts","../../src/hono/helpers.ts","../../src/hono/createMiddleware.ts"],"sourcesContent":["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","/**\n * Escapes special regex metacharacters in a string.\n * Used to safely embed user-configurable values (like rpcPrefix) into regular expressions,\n * preventing ReDoS and regex injection attacks.\n * @param s - The raw string to escape\n * @returns The escaped string safe for use in new RegExp()\n */\nexport function escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\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","// src/hono/helpers.ts\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport type { HttpBindings } from \"@hono/node-server\";\nimport type { Context, Hono } from \"hono\";\nimport { createMiddleware } from \"hono/factory\";\nimport type { ViteDevServer } from \"vite\";\nimport type { ContentfulStatusCode } from \"hono/utils/http-status\";\nimport { createRPCMiddleware } from \"./createMiddleware.ts\";\nimport type { BodyResult } from \"../types.d.ts\";\n\n/**\n * Convenience function to load RPC config and attach the RPC middleware to a Hono app.\n * Dynamically imports loadRPCConfig and registers the middleware.\n * @param app - Hono application instance\n */\nexport async function attachRPC(app: Hono) {\n const { loadRPCConfig } = await import(\"@thednp/rpc\");\n const { adapter: _adapter, ...options } = await loadRPCConfig();\n\n app.use(createRPCMiddleware(options));\n}\n\n/**\n * Attaches Vite's dev server middlewares to a Hono app for development mode.\n * Uses the viteMiddleware wrapper to bridge Vite's Connect-compatible stack into Hono.\n * @param app - Hono application instance\n * @param vite - Running Vite dev server\n */\nexport const attachVite = (app: Hono, vite: ViteDevServer): void => {\n app.use(viteMiddleware(vite));\n};\n\n/**\n * Creates a Hono-compatible middleware from a Vite dev server middleware stack.\n * Bridges the Connect/Express middleware interface to Hono's context-based request/response model.\n * Supports both Node.js and Bun runtimes with separate polyfill paths.\n * @param vite - Running Vite dev server\n * @returns A Hono middleware function\n * @see https://github.com/honojs/hono/issues/3162#issuecomment-2331118049\n */\nexport const viteMiddleware = (\n vite: ViteDevServer,\n): ReturnType<typeof createMiddleware<{ Bindings: HttpBindings }>> => {\n return createMiddleware<{ Bindings: HttpBindings }>((c, next) => {\n return new Promise((resolve) => {\n // Node.js\n // @ts-expect-error - NodeJS is different\n // istanbul ignore if\n if (typeof Bun === \"undefined\") {\n vite.middlewares(c.env.incoming, c.env.outgoing, () => resolve(next()));\n return;\n }\n\n /* istanbul ignore next */ {\n // Bun\n let sent = false;\n const headers = new Headers();\n // Polyfill the node:http IncommingMessage and ServerResponse\n vite.middlewares(\n {\n url: new URL(c.req.path, \"http://localhost\").pathname,\n method: c.req.raw.method,\n headers: Object.fromEntries(c.req.raw.headers),\n } as IncomingMessage,\n {\n setHeader(name, value: string) {\n headers.set(name, value);\n return this;\n },\n end(body) {\n sent = true;\n resolve(\n // @ts-expect-error - weird\n c.body(body, c.res.status as ContentfulStatusCode, headers),\n );\n },\n } as ServerResponse,\n () => sent || resolve(next()),\n );\n }\n });\n });\n};\n\n/**\n * Reads and parses the HTTP request body from a Hono context.\n * Supports JSON and text content types, with pre-parsed body detection for server-side environments.\n * @param c - Hono request context\n * @returns A promise resolving to the parsed body with its content type\n */\nexport const readBody = async (\n c: Context,\n): Promise<BodyResult> => {\n const contentType = c.req.header(\"content-type\")?.toLowerCase() || \"\";\n const isJSON = contentType.includes(\"json\");\n /** Node incoming message with an optional pre-parsed body */\n type IncomingWithBody = IncomingMessage & { body?: unknown };\n const incoming = (c.env as HttpBindings).incoming as\n | IncomingWithBody\n | undefined;\n if (incoming?.body !== undefined) {\n const reqBody = incoming.body;\n return {\n contentType: isJSON ? \"application/json\" : \"text/plain\",\n data: isJSON ? reqBody : String(reqBody),\n } as BodyResult;\n }\n if (isJSON) {\n const data = await c.req.json();\n return {\n contentType: \"application/json\",\n data,\n };\n }\n\n const text = await c.req.text();\n return { contentType: \"text/plain\", data: String(text) };\n};\n","// src/hono/createMiddleware.ts\nimport type { Context, Next } from \"hono\";\nimport type { HonoMiddlewareFn, HonoMiddlewareOptions } from \"./types.d.ts\";\nimport type { JsonValue } from \"../types.d.ts\";\nimport { createMiddleware as createHonoMiddleware } from \"hono/factory\";\nimport { scanForServerFiles, serverFunctionsMap } from \"@thednp/rpc/server\";\nimport { defaultMiddlewareOptions, defaultRPCOptions } from \"../options.ts\";\nimport { escapeRegExp } from \"../tools.ts\";\nimport {\n CLIENT_DISCONNECTED,\n FUNCTION_NOT_FOUND,\n INTERNAL_SERVER_ERROR,\n METHOD_NOT_ALLOWED,\n MIDDLEWARE_NAME_USED,\n REQUEST_FORBIDDEN,\n} from \"../constants.ts\";\nimport { readBody } from \"./helpers.ts\";\n\nlet middlewareCount = 0;\nconst middlewareStack = new Set<string>();\n\n/**\n * Creates a Hono middleware with optional path and rpcPrefix filtering.\n * Middleware names are deduplicated. Prefix and path regexes are compiled once at creation time.\n * Uses Hono's factory `createMiddleware` to wrap the handler.\n * @param initialOptions - Options for rpcPrefix, path matching, and the handler function\n * @returns A Hono middleware function\n */\nexport const createMiddleware: HonoMiddlewareFn = (initialOptions = {}) => {\n const options = Object.assign(\n {},\n defaultMiddlewareOptions,\n initialOptions,\n ) as HonoMiddlewareOptions;\n\n const middlewareName = options.name;\n const rpcPrefix = options.rpcPrefix;\n const path = options.path;\n const handler = options.handler;\n\n let name = middlewareName;\n if (!name) {\n name = \"viteRPCMiddleware-\" + middlewareCount;\n middlewareCount += 1;\n }\n if (middlewareStack.has(name)) {\n throw new Error(MIDDLEWARE_NAME_USED(name));\n }\n middlewareStack.add(name);\n\n // Hoist regex compilation out of per-request path. Escape the prefix to\n // prevent regex injection via metacharacters in the config string.\n const prefixRegex: RegExp | null = rpcPrefix\n ? new RegExp(`^/${escapeRegExp(rpcPrefix)}/`)\n : null;\n const pathMatcher: RegExp | null = path\n ? (typeof path === \"string\" ? new RegExp(path) : path)\n : null;\n\n const middlewareHandler = createHonoMiddleware(\n async (c: Context, next: Next) => {\n const reqUrl = new URL(c.req.path, \"http://localhost\");\n const url = reqUrl.pathname;\n\n if (serverFunctionsMap.size === 0) {\n await scanForServerFiles();\n }\n\n // No need to continue when no handler provided\n if (!handler) {\n await next();\n return;\n }\n\n if (pathMatcher && !pathMatcher.test(url)) {\n await next();\n return;\n }\n\n if (prefixRegex && !prefixRegex.test(url)) {\n await next();\n return;\n }\n\n return await handler(c, next);\n },\n );\n\n Object.defineProperty(middlewareHandler, \"name\", {\n value: name,\n });\n\n return middlewareHandler;\n};\n\n/**\n * Creates the Hono RPC middleware that routes incoming requests to registered server functions.\n * Wraps the generic createMiddleware with the RPC handler that reads the body, dispatches\n * to the matching function, and returns the JSON-serialized result.\n * @param initialOptions - Options including rpcPrefix for URL routing\n * @returns A Hono middleware function\n */\nexport const createRPCMiddleware: HonoMiddlewareFn = (initialOptions = {}) => {\n const options = Object.assign(\n {},\n defaultMiddlewareOptions,\n { rpcPrefix: defaultRPCOptions.rpcPrefix },\n initialOptions,\n ) as HonoMiddlewareOptions;\n\n // Hoist prefix regex (escaped) and the literal prefix-for-replace out of the\n // per-request handler to avoid regex injection and per-request compilation.\n const rpcPrefix = options.rpcPrefix;\n const prefixRegex = rpcPrefix\n ? new RegExp(`^/${escapeRegExp(rpcPrefix)}/`)\n : /* istanbul ignore next */ null;\n const prefixReplace = `/${rpcPrefix}/`;\n\n return createMiddleware({\n ...options,\n handler: async (c: Context, _next: Next) => {\n const { path: reqPath } = c.req;\n // const { rpcPrefix: prefix } = options;\n\n // Defense-in-depth: validate prefix match via escaped regex even though\n // the outer createMiddleware gates on the same prefix already.\n // istanbul ignore if\n if (prefixRegex && !prefixRegex.test(reqPath)) {\n /* istanbul ignore next */\n return;\n }\n\n // Optional origin check: reject requests whose Origin header does not\n // match the configured origin. Requests without an Origin header\n // (curl, native clients) pass through unchecked.\n const origin = options.origin;\n const requestOrigin = c.req.header(\"origin\");\n if (origin && requestOrigin && requestOrigin !== origin) {\n return c.json({ error: REQUEST_FORBIDDEN }, 403);\n }\n\n const functionName = reqPath.replace(prefixReplace, \"\");\n const serverFunction = serverFunctionsMap.get(functionName);\n\n if (!serverFunction) {\n return c.json({ error: FUNCTION_NOT_FOUND }, 404);\n }\n\n try {\n const method = serverFunction.options?.method || \"POST\";\n if (c.req.method.toUpperCase() !== method) {\n return c.json({ error: METHOD_NOT_ALLOWED }, 405);\n }\n\n let args: JsonValue[] = [];\n if (method === \"GET\") {\n const raw = c.req.query(\"args\");\n // istanbul ignore else\n if (raw) args = JSON.parse(raw);\n } else {\n const body = await readBody(c);\n args = Array.isArray(body.data) ? body.data : [body.data];\n }\n const fnResult = serverFunction.handler(...args);\n const onAbort = () => fnResult.cancel(CLIENT_DISCONNECTED);\n // The runtime adapter may be absent in some Hono environments\n // (e.g. standalone serverless adapters), so guard the close hook.\n c.env.incoming?.on(\"close\", onAbort);\n const result = await fnResult.data;\n c.env.incoming?.off(\"close\", onAbort);\n\n return c.json({ data: result }, 200);\n } catch (err) {\n console.error(String(err));\n return c.json({ error: INTERNAL_SERVER_ERROR }, 500);\n }\n },\n });\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"hono.mjs","names":["createMiddleware","createHonoMiddleware"],"sources":["../../src/options.ts","../../src/tools.ts","../../src/constants.ts","../../src/hono/helpers.ts","../../src/hono/createMiddleware.ts"],"sourcesContent":["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","/**\n * Escapes special regex metacharacters in a string.\n * Used to safely embed user-configurable values (like rpcPrefix) into regular expressions,\n * preventing ReDoS and regex injection attacks.\n * @param s - The raw string to escape\n * @returns The escaped string safe for use in new RegExp()\n */\nexport function escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\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","// src/hono/helpers.ts\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport type { HttpBindings } from \"@hono/node-server\";\nimport type { Context, Hono } from \"hono\";\nimport type { ViteDevServer } from \"vite\";\nimport type { ContentfulStatusCode } from \"hono/utils/http-status\";\nimport type { BodyResult } from \"../types.d.ts\";\nimport type { IncomingWithBody } from \"./types.d.ts\";\nimport { createMiddleware } from \"hono/factory\";\nimport { createRPCMiddleware } from \"./createMiddleware.ts\";\n\n/**\n * Convenience function to load RPC config and attach the RPC middleware to a Hono app.\n * Dynamically imports loadRPCConfig and registers the middleware.\n * @param app - Hono application instance\n */\nexport async function attachRPC(app: Hono) {\n const { loadRPCConfig } = await import(\"@thednp/rpc\");\n const { adapter: _adapter, ...options } = await loadRPCConfig();\n\n app.use(createRPCMiddleware(options));\n}\n\n/**\n * Attaches Vite's dev server middlewares to a Hono app for development mode.\n * Uses the viteMiddleware wrapper to bridge Vite's Connect-compatible stack into Hono.\n * @param app - Hono application instance\n * @param vite - Running Vite dev server\n */\nexport const attachVite = (app: Hono, vite: ViteDevServer): void => {\n app.use(viteMiddleware(vite));\n};\n\n/**\n * Creates a Hono-compatible middleware from a Vite dev server middleware stack.\n * Bridges the Connect/Express middleware interface to Hono's context-based request/response model.\n * Supports both Node.js and Bun runtimes with separate polyfill paths.\n * @param vite - Running Vite dev server\n * @returns A Hono middleware function\n * @see https://github.com/honojs/hono/issues/3162#issuecomment-2331118049\n */\nexport const viteMiddleware = (\n vite: ViteDevServer,\n): ReturnType<typeof createMiddleware<{ Bindings: HttpBindings }>> => {\n return createMiddleware<{ Bindings: HttpBindings }>((c, next) => {\n return new Promise((resolve) => {\n // Node.js\n // @ts-expect-error - NodeJS is different\n // istanbul ignore if\n if (typeof Bun === \"undefined\") {\n vite.middlewares(c.env.incoming, c.env.outgoing, () => resolve(next()));\n return;\n }\n\n /* istanbul ignore next */ {\n // Bun\n let sent = false;\n const headers = new Headers();\n // Polyfill the node:http IncommingMessage and ServerResponse\n vite.middlewares(\n {\n url: new URL(c.req.path, \"http://localhost\").pathname,\n method: c.req.raw.method,\n headers: Object.fromEntries(c.req.raw.headers),\n } as IncomingMessage,\n {\n setHeader(name, value: string) {\n headers.set(name, value);\n return this;\n },\n end(body) {\n sent = true;\n resolve(\n // @ts-expect-error - weird\n c.body(body, c.res.status as ContentfulStatusCode, headers),\n );\n },\n } as ServerResponse,\n () => sent || resolve(next()),\n );\n }\n });\n });\n};\n\n/**\n * Reads and parses the HTTP request body from a Hono context.\n * Supports JSON and text content types, with pre-parsed body detection for server-side environments.\n * @param c - Hono request context\n * @returns A promise resolving to the parsed body with its content type\n */\nexport const readBody = async (\n c: Context,\n): Promise<BodyResult> => {\n const contentType = c.req.header(\"content-type\")?.toLowerCase() || \"\";\n const isJSON = contentType.includes(\"json\");\n const incoming = (c.env as HttpBindings).incoming as\n | IncomingWithBody\n | undefined;\n if (incoming?.body !== undefined) {\n const reqBody = incoming.body;\n return {\n contentType: isJSON ? \"application/json\" : \"text/plain\",\n data: isJSON ? reqBody : String(reqBody),\n } as BodyResult;\n }\n if (isJSON) {\n const data = await c.req.json();\n return {\n contentType: \"application/json\",\n data,\n };\n }\n\n const text = await c.req.text();\n return { contentType: \"text/plain\", data: String(text) };\n};\n","// src/hono/createMiddleware.ts\nimport type { Context, Next } from \"hono\";\nimport type { HonoMiddlewareFn, HonoMiddlewareOptions } from \"./types.d.ts\";\nimport type { JsonValue } from \"../types.d.ts\";\nimport { createMiddleware as createHonoMiddleware } from \"hono/factory\";\nimport { scanForServerFiles, serverFunctionsMap } from \"@thednp/rpc/server\";\nimport { defaultMiddlewareOptions, defaultRPCOptions } from \"../options.ts\";\nimport { escapeRegExp } from \"../tools.ts\";\nimport {\n CLIENT_DISCONNECTED,\n FUNCTION_NOT_FOUND,\n INTERNAL_SERVER_ERROR,\n METHOD_NOT_ALLOWED,\n MIDDLEWARE_NAME_USED,\n REQUEST_FORBIDDEN,\n} from \"../constants.ts\";\nimport { readBody } from \"./helpers.ts\";\n\nlet middlewareCount = 0;\nconst middlewareStack = new Set<string>();\n\n/**\n * Creates a Hono middleware with optional path and rpcPrefix filtering.\n * Middleware names are deduplicated. Prefix and path regexes are compiled once at creation time.\n * Uses Hono's factory `createMiddleware` to wrap the handler.\n * @param initialOptions - Options for rpcPrefix, path matching, and the handler function\n * @returns A Hono middleware function\n */\nexport const createMiddleware: HonoMiddlewareFn = (initialOptions = {}) => {\n const options = Object.assign(\n {},\n defaultMiddlewareOptions,\n initialOptions,\n ) as HonoMiddlewareOptions;\n\n const middlewareName = options.name;\n const rpcPrefix = options.rpcPrefix;\n const path = options.path;\n const handler = options.handler;\n\n let name = middlewareName;\n if (!name) {\n name = \"viteRPCMiddleware-\" + middlewareCount;\n middlewareCount += 1;\n }\n if (middlewareStack.has(name)) {\n throw new Error(MIDDLEWARE_NAME_USED(name));\n }\n middlewareStack.add(name);\n\n // Hoist regex compilation out of per-request path. Escape the prefix to\n // prevent regex injection via metacharacters in the config string.\n const prefixRegex: RegExp | null = rpcPrefix\n ? new RegExp(`^/${escapeRegExp(rpcPrefix)}/`)\n : null;\n const pathMatcher: RegExp | null = path\n ? (typeof path === \"string\" ? new RegExp(path) : path)\n : null;\n\n const middlewareHandler = createHonoMiddleware(\n async (c: Context, next: Next) => {\n const reqUrl = new URL(c.req.path, \"http://localhost\");\n const url = reqUrl.pathname;\n\n if (serverFunctionsMap.size === 0) {\n await scanForServerFiles();\n }\n\n // No need to continue when no handler provided\n if (!handler) {\n await next();\n return;\n }\n\n if (pathMatcher && !pathMatcher.test(url)) {\n await next();\n return;\n }\n\n if (prefixRegex && !prefixRegex.test(url)) {\n await next();\n return;\n }\n\n return await handler(c, next);\n },\n );\n\n Object.defineProperty(middlewareHandler, \"name\", {\n value: name,\n });\n\n return middlewareHandler;\n};\n\n/**\n * Creates the Hono RPC middleware that routes incoming requests to registered server functions.\n * Wraps the generic createMiddleware with the RPC handler that reads the body, dispatches\n * to the matching function, and returns the JSON-serialized result.\n * @param initialOptions - Options including rpcPrefix for URL routing\n * @returns A Hono middleware function\n */\nexport const createRPCMiddleware: HonoMiddlewareFn = (initialOptions = {}) => {\n const options = Object.assign(\n {},\n defaultMiddlewareOptions,\n { rpcPrefix: defaultRPCOptions.rpcPrefix },\n initialOptions,\n ) as HonoMiddlewareOptions;\n\n // Hoist prefix regex (escaped) and the literal prefix-for-replace out of the\n // per-request handler to avoid regex injection and per-request compilation.\n const rpcPrefix = options.rpcPrefix;\n const prefixRegex = rpcPrefix\n ? new RegExp(`^/${escapeRegExp(rpcPrefix)}/`)\n : /* istanbul ignore next */ null;\n const prefixReplace = `/${rpcPrefix}/`;\n\n return createMiddleware({\n ...options,\n handler: async (c: Context, _next: Next) => {\n const { path: reqPath } = c.req;\n // const { rpcPrefix: prefix } = options;\n\n // Defense-in-depth: validate prefix match via escaped regex even though\n // the outer createMiddleware gates on the same prefix already.\n // istanbul ignore if\n if (prefixRegex && !prefixRegex.test(reqPath)) {\n /* istanbul ignore next */\n return;\n }\n\n // Optional origin check: reject requests whose Origin header does not\n // match the configured origin. Requests without an Origin header\n // (curl, native clients) pass through unchecked.\n const origin = options.origin;\n const requestOrigin = c.req.header(\"origin\");\n if (origin && requestOrigin && requestOrigin !== origin) {\n return c.json({ error: REQUEST_FORBIDDEN }, 403);\n }\n\n const functionName = reqPath.replace(prefixReplace, \"\");\n const serverFunction = serverFunctionsMap.get(functionName);\n\n if (!serverFunction) {\n return c.json({ error: FUNCTION_NOT_FOUND }, 404);\n }\n\n try {\n const method = serverFunction.options?.method || \"POST\";\n if (c.req.method.toUpperCase() !== method) {\n return c.json({ error: METHOD_NOT_ALLOWED }, 405);\n }\n\n let args: JsonValue[] = [];\n if (method === \"GET\") {\n const raw = c.req.query(\"args\");\n // istanbul ignore else\n if (raw) args = JSON.parse(raw);\n } else {\n const body = await readBody(c);\n args = Array.isArray(body.data) ? body.data : [body.data];\n }\n const fnResult = serverFunction.handler(...args);\n const onAbort = () => fnResult.cancel(CLIENT_DISCONNECTED);\n // The runtime adapter may be absent in some Hono environments\n // (e.g. standalone serverless adapters), so guard the close hook.\n c.env.incoming?.on(\"close\", onAbort);\n const result = await fnResult.data;\n c.env.incoming?.off(\"close\", onAbort);\n\n return c.json({ data: result }, 200);\n } catch (err) {\n console.error(String(err));\n return c.json({ error: INTERNAL_SERVER_ERROR }, 500);\n }\n },\n });\n};\n"],"mappings":"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"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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
4
|
import { NextFunction, Request, Response } from "express";
|
|
@@ -6,6 +6,7 @@ import { MiddlewareHandler } from "hono";
|
|
|
6
6
|
import "@hono/node-server";
|
|
7
7
|
import "hono/factory";
|
|
8
8
|
import { FastifyReply, FastifyRequest, HookHandlerDoneFunction } from "fastify";
|
|
9
|
+
import "fastify-plugin";
|
|
9
10
|
import { Context, Next } from "koa";
|
|
10
11
|
//#region src/express/types.d.ts
|
|
11
12
|
/**
|
|
@@ -232,6 +233,22 @@ type ClientFunctionWithOptions = ClientFunction & {
|
|
|
232
233
|
/** Per-function content type and credentials options */
|
|
233
234
|
options?: ServerFunctionOptions;
|
|
234
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
|
+
};
|
|
235
252
|
/**
|
|
236
253
|
* Entry in the server functions map: registered name, client handler,
|
|
237
254
|
* optional per-function options, and the original export name.
|
|
@@ -326,6 +343,16 @@ interface MiddlewareOptions<A extends RpcPluginOptions["adapter"] = "express"> {
|
|
|
326
343
|
*/
|
|
327
344
|
handler?: FrameworkHooks[A]["handler"];
|
|
328
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
|
+
};
|
|
329
356
|
//#endregion
|
|
330
357
|
//#region src/index.d.ts
|
|
331
358
|
/**
|
|
@@ -352,5 +379,5 @@ declare const loadRPCConfig: (f?: string) => Promise<RpcPluginOptions>;
|
|
|
352
379
|
*/
|
|
353
380
|
declare function rpcPlugin(devOptions?: Partial<RpcPluginOptions>): Plugin<unknown>;
|
|
354
381
|
//#endregion
|
|
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 };
|
|
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 };
|
|
356
383
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -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":"
|
|
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"}
|